Django not serving text data points for template (home page)

Hello Pythonistas!

I’m building a CMS with Django. When my site serves the homepage template, the two “title” (CharField) and “body” (TextField) contents are not parsing. I’m not sure why. I’ve compared my views, urls, and models configuration to other Django projects I have on file and I can’t identify where the error is in my new codebase.

Here is my home.html template:

{% extends 'base.html' %}

{% block content %}

   {% if contents %}

       <h1>{{ contents.title }}</h1>

       <p>{{ contents.body }}</p>

   {% endif %}

{% endblock %}

When Django serves the homepage, the partial template loads, along with the navbar which is a part of that extends clause. So that works. The rest of the body content is blank. When I remove or comment out the Jinja conditional logic, Django then serves the heading and paragraph elements, but they are empty. It just shows this:

<h1></h1>
<p></p>

Here is the relevant section to my views.py for this web app:

from django.shortcuts import render
from django.views.generic import TemplateView
from .models import Home, 
 
class HomeView(TemplateView):
   model = Home
   # fields = ['title','body']
   context_object_name = 'contents'   
   template_name = "landings/home.html"

Here is my urls.py:

from django.urls import path
from .views import HomeView
 
app_name= 'landings'
 
urlpatterns = [
   path('', HomeView.as_view(),name='home'),
]

Here is my app’s models.py:

from django.db import models
 
class Home(models.Model):
   title = models.CharField(max_length=200)
   body = models.TextField(null=True, blank=True)
  
   def __str__(self):
       return f'{self.title}'

Here is my Admin Dashboard showing that placeholder content has been added to these data points:

Any idea what I could try next?

contents is probably referring to a list of Home objects.
And since contents is a list and not a Home instance, it will not have the attributes you’re trying to access.

You either should:

  • write a for to access all items on contents;
  • override the context that is passed to the template so just the first item goes to the context.

A TemplateView does not, by default, display any specific instance of an object. For that you would need to use a DetailView.

Setting class variables on a TemplateView does not inject their usage within the view. It’s up to you when using a TemplateView to load any data necessary.

As Ken points out:

So I can’t use a TemplateView.

The DetailView CBV requires a string or int like as a slug or pk in the url pattern. But for my use-case, I am just trying to create a home (landing) page for my site. The location in my web address bar needs to be: https://localhost:8000/. There must not be a slug or any string appended to the end or after the forward slash because the home page should be the ‘top level’ parent location of the whole site.

Here is the relevant section to my urls.py:

urlpatterns = [
    path('', HomeView.as_view(),name='home'),
    path('<str:slug>', AboutView.as_view(),name='about'),
    ...
]

As you can see above in my use case, the first parameter in the urlpatterns path function is an empty string. There is no place for a slug. How do I satisfy Django’s requirement for a DetailView to pass a slug / pk variable into the url pattern when the top level home (langing) page shouldn’t have one?

My DetailView CBV to handle the “about” page on the site (which outlines my organization’s mission / purpose) is similar but slightly different. The address location needs to be: https://localhost:8000/about. Since a slug is required for the DetailView, and since the address location is a string, I can use: ’<str:slug>’ in the url pattern this time. You can see that in the second path() function call above. But Django still throws a 404 when I navigate to https://localhost:8000/about:

image

I have been mindful by carefully adding about into the slug CharField inside the entry in the Admin Dashboard for this app. But I still get a 404. I continue to mishandle this slug properly.

TemplateView is the wrong way to go. I need a DetailView which requires a proper usage of the slug / pk variable. But I am doing something wrong.

In fact I already previously tried using a DetailView with some confusion: Resolving `AttributeError` for home / landing page routing with CBVs - #5 by Drone4four
The takeaway from that thread was that it doesn’t make sense to create a link on a page referring to itself using a slug and that it doesn’t make sense to use a slug for a home page. So I resolved to using a TemplateView which I am walking back by working with DetailViews again. Now I am back to my original problem with the way I am mishandling slugs.

As per ccbv.co.uk, and as you can see in the code snippets below, I leveraged this section of the doc: DeleteView -- Classy CBV

Someone else recommended the Django Class Based Views Diagrams website. Here is the specific location on that site for the DetailView Diagram.

The issues I have with this site are twofold:

  1. The diagrams show all the variables and attributes and functionality which is great, but there is no free-flowing prose connecting the dots. I guess that is what the official Django Docs are for and the two sites should be used together.
  2. The diagrams show source code as black font on a white background. Without syntax highlighting, it makes it very hard to follow along. It makes me queasy.

Here are the relevant snippets from my codebase that I am currently working with:

views.py:

from django.shortcuts import render, reverse
from django.views.generic import DetailView # TemplateView
from .models import Home, Podcast, About, Contact

class HomeView(DetailView):
   model = Home
   # fields = ['title','body']
   context_object_name = 'contents'   
   template_name = "landings/home.html"
   # def get_slug_field(self):
   #     self.objects.title

class AboutView(DetailView):
   model = About
   context_object_name = 'contents' 
   template_name = "landings/about.html"


   def get_slug_field(self):
       """
       Get the name of a slug field to be used to look up by slug as per: https://ccbv.co.uk/projects/Django/4.1/django.views.generic.edit/DeleteView/#get_slug_field      
       """
       return self.slug 

models.py:

from django.shortcuts import reverse
from django.db import models
from ckeditor.fields import RichTextField
from django.template.defaultfilters import slugify

class Home(models.Model):
   title = models.CharField(max_length=200)
   slug = models.SlugField(null=False, unique=True, blank=True)
   body = RichTextField(config_name='default',max_length=300000,blank=True, null=True)

   def __str__(self):
       return f'{self.title}'
 
class About(models.Model):
   title = models.CharField(max_length=200)
   slug = models.SlugField(null=False, unique=True, blank=True)
   body = RichTextField(config_name='default',max_length=300000,blank=True, null=True)

The issue with a DetailView is that it needs to know what object to display.

This is not an accurate statement.

In the general case, that object is identified by a url parameter.

However, there is no requirement that that be the source for that paramter. If you have a fixed or defined entity that you want to display - or can programmatically identify which object to display, it can be done that way too.

You can override the get_object function to provide any instance of an object you want. But Django isn’t just going to arbitrarily select an object for you.

Actually, you navigated to about/, not about. However, your url definition:

Does not allow for a trailing slash.

Is this the location in the official Django docs explaining how to use and override the get_object() class method we are referring to?

It’s the right location for the description of the existing get_object method. It shows what parameters might be passed to it, and what it needs to return.

You override this as a method in your view in the same way you override any other CBV method. You need to accept whatever parameters it may supply and you must return the right object. You can examine the source for it to get an idea of how the current version works and what you might want to account for in your version. (If nothing else, the source for it will show you what the def statement should look like.)

Is this the right place to examine and learn from the source? DetailView -- Classy CBV (#get_object)

That is one such place, yes.

Or, you could see it in the original location in the repo at django/detail.py at 8acc433e415cd771f69dfe84e57878a83641e78b · django/django · GitHub.

Or, you could open django.views.generic.detail in the editor of your choice for whatever version of Django you may have installed.

(I use VSCode, and one of the things I do is add my site-packages directory for my virtual environment into my workspace to easily allow me to browse through the sources of the packages I’m using.)

Good catch. I appended a trailing / to the parameter inside the url path() function call. The 404 error went away. Then Django presented a different traceback: # AttributeError at /about/ - 'AboutView' object has no attribute 'slug'. The Django dev server pointed to a malformed class method. This one here:

def get_slug_field(self):
        """
        Get the name of a slug field to be used to look up by slug as per: https://ccbv.co.uk/projects/Django/4.1/django.views.generic.edit/DeleteView/#get_slug_field       
        """
        return self.slug 

I commented out those lines. The AttributeError went away for AboutView.

Now when I navigate to localhost:8000/about Django shows this:

File "/home/<user>/.local/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/home/<user>/.local/lib/python3.10/site-packages/django/template/defaulttags.py", line 472, in render
    url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app)
  File "/home/<user>/.local/lib/python3.10/site-packages/django/urls/base.py", line 88, in reverse
    return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
  File "/home/<user>/.local/lib/python3.10/site-packages/django/urls/resolvers.py", line 828, in _reverse_with_prefix
    raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'about' with no arguments not found. 1 pattern(s) tried: ['(?P<slug>[^/]+)/\\Z']
[29/Jan/2023 03:52:58] "GET /about/ HTTP/1.1" 500 168934

It points to this line inside my _navbar.html partial:

 <li class="header__menuItem"><a href="{% url 'landings:podcasts' %}">Podcasts</a></li>

So that url is malformed. What else could I try to resolve that malformed anchor tag?

Here is the full _navbar.html:

{% load static %}

<!-- Header Section -->
<header class="header">
    <div class="container container--narrow">
        <a href="{% url 'landings:home' %}" class="header__logo">
            <img src="project_static_image_needed_here" alt="DevSearch Logo" />
        </a>

        <nav class="header__nav">
            <input type="checkbox" id="responsive-menu" />
            <label for="responsive-menu" class="toggle-menu">
                <span>Menu</span>
                <div class="toggle-menu__lines"></div>
            </label>
            <ul class="header__menu">
                <li class="header__menuItem"><a href="/">Home</a></li>
                <li class="header__menuItem"><a href="{% url 'landings:about' %}">About</a></li>  
                <li class="header__menuItem"><a href="{% url 'landings:podcasts' %}">Podcasts</a></li>
                <li class="header__menuItem"><a href="{% url 'articles:article_listing' %}">Articles</a></li>           
                <li class="header__menuItem"><a href="{% url 'landings:contact' %}">Contact</a></li>        
            </ul>
        </nav>
    </div>
</header>

Here is my urls.py for this particular web app:

from django.contrib import admin
from django.urls import path, include
from .views import HomeView, AboutView, PodcastView, ContactView

app_name= 'landings'

urlpatterns = [
    path('', HomeView.as_view(),name='home'),
    path('<str:slug>/', AboutView.as_view(),name='about'),
    path('<str:slug>/', PodcastView.as_view(),name='podcasts'),
    path('<str:slug>/', ContactView.as_view(),name='contact'),
]

Although there is still an AttributeError when I navigate to localhost:8000/:

File "/home/<user>/.local/lib/python3.10/site-packages/django/views/generic/detail.py", line 108, in get
    self.object = self.get_object()
  File "/home/<user>/.local/lib/python3.10/site-packages/django/views/generic/detail.py", line 46, in get_object
    raise AttributeError(
AttributeError: Generic detail view HomeView must be called with either an object pk or a slug in the URLconf.

No, actually the line with that error is this one:

However, all three of those lines are in error.

But I’ll come back to this in a moment.

This:

Is not going to do what you want it to do. URLs are matched sequentially - the first one that matches is going to be called. You will never call PodcastView or ContactView with these URLs.

Now, coming back to the error that I side-stepped - those urls named about, podcast and contact each requires a parameter. That means to reverse it, you need to pass the parameter in the url tag to be filled into the slug field.

However, as mentioned above, you don’t want to do this. You need to change your url patterns so that you’re not using the slug field to try and reference those views.

As for the last error:

This is what we’ve been talking about this whole time. You’re trying to use a DetailView without identifying an object to display.

Something else has struck me as I review this entire thread.

I’m getting the impression that you’ve got the idea that “Home” is one “thing”. It’s not. Home is a model capable of storing many instances of that type of object. The same with About.

So, think about what’s happening. You’re trying to use a view to show a “Home”, but you haven’t provided any information to identify which Home to display - that is the purpose of the pk or slug field as a parameter.

My understanding of Django’s CBV’s and the architecture of Django-based websites in general (prior to writing this thread originally), was that ListViews present web visitors with a list of multiple pages, like multiple blog posts formed with DetailViews. A ListView page in the context of a blog CMS might enumerate the title, date, and first 200 characters of the body text of multiple DetailView blog posts. Dynamic slugs and pk’s allow web visitors to navigate from one unique blog post to the next. I get that. This makes sense to me.

I learned about this in Jose Portilla’s Udemy course, Django 4 and Python Full-Stack Developer Masterclass that I finished watching end-to-end in August 2022. It’s 20 hours of content, of which 1 hour and 30 minutes is dedicated to CBVs. But I watched the full course.

The challenge with this Django project I am working on in this thread, I need a way to enable admin staff users to edit and update the ‘home’ page of which there is only one. This is why originally I figured the appropriate CBV for this task would be a TemplateView.

You can see my first iteration of the TemplateView solution in the very first comment at the top of this thread. The problem at the time was that my data points were not populating when Django was serving the template. It wasn’t working. So I reached out here for support.

Then you responded:

My (perhaps mis-)reading of this comment was that TemplateView web content can’t be added or edited through the Admin Dashboard and that I’d need to use a DetailView for my task. Ever since I have been fiddling and struggling to get my one and only ‘home’ page to work with a DetailView which now seems more clear than ever that this is not the right solution I need for the task.

My apologies for not clarifying my original intent/specs out of the gate. I will try to be more careful when writing future forum threads by describing my intention up front.

If DetailViews and TemplateViews aren’t the solution I need, then what is the best CBV option to build a singular ‘home’ page with designated Admin Dashboard staff members the ability to add/edit its content?

Side note #1: I won’t comment on any tutorials other than the Official Django Tutorial and the Django Girls Tutorial. There are too many out there and I can’t speak to what they address or ignore, or how they present their material - or more importantly, how they define the concepts being used.

It’s not the DetailView that is the issue. It’s your data models that is causing you these difficulties.

First, I think you should work on a change in your “mental model” of what’s going on here. You should stop thinking in terms of “pages”, and more in terms of “views rendering data as content within pages”. It’s a fairly crucial distinction when working in Django.

What does that mean here? Specifically, what you don’t have is a “home” page. Also, you don’t have an “about” page. You have one or more pages that are going to display content. (That “content” is to come from the data stored in your models.)

At one URL (e.g. /home/), you’re going to display the content from an instance of a model.

At a different URL (e.g. /about/), you’re going to display the content from an instance of a model.

They might be the same model, They might be different models. They might use the same template, the might use different template. Those distinctions don’t matter here yet.

Let’s look at the models you posted:

class Home(models.Model):
   title = models.CharField(max_length=200)
   slug = models.SlugField(null=False, unique=True, blank=True)
   body = RichTextField(config_name='default',max_length=300000,blank=True, null=True)

class About(models.Model):
   title = models.CharField(max_length=200)
   slug = models.SlugField(null=False, unique=True, blank=True)
   body = RichTextField(config_name='default',max_length=300000,blank=True, null=True)

What you have defined here is a model named Home. That means that this is going to create a table in your database called something like myapp_home. That table is capable of storing some number of instances of Home.

But, you’re saying you only have one bit of content for Home - therefore, your data model may be incorrect for the project you’re trying to build.

Repeat this logic for About. Same thoughts, same conclusion.

So the root issue at this point is that you haven’t appropriately defined your models to support the application you’re trying to build.

The next thing I see is that these two model definitions are identical. From what you’ve identified so far, I see no reason to make these two separate models.

So let’s define a new model:

class PageContent(models.Model):
   title = models.CharField(max_length=200)
   slug = models.SlugField(null=False, unique=True, blank=True)
   body = RichTextField(config_name='default',max_length=300000,blank=True, null=True)

and to this, I’m going to add a new field:
page_type = models.CharField(max_length=20)

What this means is that now, I can store both my Home and About content in this model, perhaps using page_type of home and about, respectively.

This allows me to retrieve the content for /home/ by PageContent.objects.get(page_type='home'). And this provides you with the information you need to proceed with your DetailView to display this content.

Going this route does require some additional work along the way - you want to ensure that your initial creation of these pages set the proper page_type You probably want to ensure that you never end up with multiple rows for either page_type home or about.

As a blog, you may even want to think about unifying this data with the rest of your blog’s data structures - that’s something only you can address.

But you do want to change your perspective on how you think about this.

  • You’re not creating pages in your database.
  • You’re adding data to your database that will be used by your views to create pages.

So with that in mind:

Conceptually, this is incorrect.

A ListView lists data. Some of that data are links to other URLs. Those URLs will (generally) read other data to produce pages.

Again, don’t think in terms of “pages”. Think in terms of URLs causing views to be called - which can read data to produce pages. (A view is not a “page”. A view is python code that produces something as a response. That response may/may not be HTML and may/may not be a complete page.)

Eureka! Thank you for your patience and for taking the time to write such detailed feedback and guidance so far, Ken. I got it working! (Partially). Django is serving the template with content, but I am still not quite there yet. We are noless making progress.

Leveraging DetailView -- Classy CBV again, as well as based on your suggested PageContent objects dbase call, I commented out all the repetitive class declarations in models.py and in views.py. I scrapped all 4 templates and am just using 1 now. It’s essentially a whole new web app.

For my use case, so far we’ve discussed a ‘home’ page and ‘about’ page but originally I also planned a page for ‘contact’ and ‘podcasts’. Previously, I had 4 separate models and 4 separate DetailViews in total to account for these pages. I have since got rid of all of that and have a single class model and single CBV.

Even though it still doesn’t fully work as intended, I already can identify the problem. I’ve tried a few different possibilities by swapping different variables and parameters but I still can’t quite get it right. After I share my latest source code below, I will provide an analysis of the problem as I see it as well as the things I’ve tried, albeit unsuccessfully so far.

Here we go.

Here is my (new) single model declaration:

class PageContent(models.Model):
   # title = models.CharField(max_length=200)
   slug = models.SlugField(null=False, unique=True, blank=True)
   page_type = models.CharField(max_length=20)
   body = RichTextField(config_name='default',max_length=300000,blank=True, null=True)


   def __str__(self):
       return f'{self.page_type}'

The main difference there is the page_type attribute that Ken recommended as well as the fact that I have cut out all the other class models.

Here is my views.py:

from django.views.generic import DetailView # TemplateView
from .models import PageContent # Home, Podcast, About, Contact


class PageView(DetailView):
   model = PageContent
   template_name = "landings/page_detail.html"
   context_object_name = 'contents' 
      
   def get_object(self): # queryset=None # page_type
       obj = PageContent.objects.get(page_type='home')
       return obj

In the PageView, I have this: PageContent.objects.get(page_type='home') dbase call which is what Ken recommended I try as well. Now I figure that the page_type parameter’s string assignment with ‘home’ was supposed to be merely a temporary stop gap or placeholder because there is going to be more than one page_types. Ken also indicated I’ll need to check to make sure there are no duplicates. One way to understand this clearly is when I change ‘home’ to ‘about’ or to ‘contact’, Django begins serving that corresponding data that I have entered into the Admin Dashboard. So what I am trying to figure out next is how to make the page_type call dynamic. One way I can think of, or at least if I was working with an id, would be to pass in a pk variable which would be an integer. But in my case, I am working with a slug which is a string. To this end, I’ve tried to make my string parameter dynamic by adding pk to the get_object method call and then changing page_type='home' to page_type=pk. That didn’t work. Next I tried changing page_type='home' by integrating an f-string like this: page_type=f’{page_type}’. That didn’t work either. I tried many other alternatives without success.

So a little more guidance here on how to make this page_type variable dynamic, I think I will have reached a full resolution and can mark this thread as solved.

For what it is worth, here is my single custom template:

{% extends 'base.html' %}
{% block content %}
   {% if contents %}
      {{ contents.body|safe }}
   {% endif %}
{% endblock %}

That’s a big change because in the past I had 4 templates which were basically all the same. Now I just have this one. I am no longer repeating myself here.

Finally, here is my urls.py which is also imperfect:

from django.contrib import admin
from django.urls import path, include
from .views import PageView #HomeView, AboutView, PodcastView, ContactView

app_name= 'landings'

urlpatterns = [
   path('<str:slug>/', PageView.as_view(),name='home'),
]

I noticed that when I change the name path parameter to one string or another, Django still serves the template with the same data, whether the data be for the ‘contact’ or ‘about’. I tried to make this ‘name’ parameter dynamic by using an f-string. No dice.

The only way to get Django to serve different data is if I change the page_type parameter in the get_object() DetailView class method so I believe that is my main knowledge gap as described earlier.

Thanks again @KenWhitesell for your help and patience. :slight_smile:

The reason you are currently overriding get_object is because you weren’t previously supplying a parameter.

Now, you’re defining a parameter in your url:

This parameter, by default, is going to compare to your slug field.

You could (depending upon the structure of the rest of your system), remove the get_object function, and instead of storing home, contact, about in the page_type field, store it in the slug field. (Which then also makes the page_type field superfluous.)

And you will have now refactored yourself into the position of using the DetailView in the manner originally intended.

1 Like

Side note: This

is potentially a very bad idea for any user-entered content. It’s not just that it’s a security hole large enough to drive an aircraft carrier through, but even a simple mistake in terms of something like mis-matched html tags can render that page display non-functional.

1 Like