Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Create a View

In the previous lesson, you set up the routes that directed your request all the way to your file. You got an error telling you that this views doesn’t actually exist. Now, you need to create a view.

Go to, where you already have some imports as well as a helpful message to create your views here. Create your new function, project_list():

# Create your views here.
def project_list(request):

Now you’ve got a new error message that you can use to fix your function!

00:00 In the previous video, we set up the routes that directed our request all the way to our views file, but then we ended up getting an error that told us that “This view doesn’t actually exist.” So, what we’re going to need to do next, is we need to create a view.

00:17 Here is our error friend again. It’s telling us AttributeError […] 'projects.views' has no attribute 'project_list'.

00:25 This is where I directed it to. However, inside of views, there’s no such thing. So, I want to take this as a moment to just take a look over to the browser and see, is Django maybe giving us an error message there?

00:39 I’m going to go here and I reload this. Keep in mind one thing you can check if you get this: is your server still running?

00:49 This is where we have our server running, but it’s giving us an error message already here. So, there’s these two places in Django where you can see error messages.

00:59 They’re either presented in here, in the browser, with this nice yellow bar up top that we saw before, or—if you’re just getting a browser error here—you head over to the terminal tab where you’re running your development server, and then you’re going to have an error message sitting in here. In this case, that’s what we’re having. And as I said before, it’s telling us, “Okay. Inside of the views file, we’re directing to a function that does not exist.” So, let’s change that!

01:29 I’m heading over to views. Again, you see there’s already some imports done by Django, and it’s telling us with this helpful message here, Create your views here. Let’s go ahead and do that. We’re looking for a view called project_list, I’m going to say def project_list(),

01:51 and just one thing that you need to know is that we’re always passing the request here. The request is a Django object that gets passed around all the time.

02:00 Okay. So, I created this function here now. It currently doesn’t do anything, just passing in here, but it exists. So, let’s see what our server does. It automatically reloads, and it seems to be fine!

02:15 We’ve seen this message before and that looks like the server is working, it’s running, so that means we can head back over to our browser and do a reload on /projects.

02:26 And there it is! We’ve got a new error message! This one is called ValueError at /projects/. Let’s read what Django is telling us: The view projects.views.project_list didn't return an HttpResponse object. It returned None instead. So, good things about this: the first thing, it’s pointing to the right thing, like we know we’re going into projects.views, it’s accessing the function that we just created.

02:56 But now, to the error, or what’s been going wrong: it didn’t return a HttpResponse object, but instead it returned None. Cool, so there’s a lot of information that we need already in here.

03:08 If I head back over to my text editor, this is what’s happening. It’s going all the way to this project_list(), it’s finding this. However, the problem was—here we have it again—the view did not return an HttpResponse object, but None instead.

03:27 It returns None because that’s just what any Python function does. If you don’t define a return statement, it always returns None.

03:34 Since we’re not defining anything here, this is what’s getting returned. Instead of returning None, what we do want to do is what the error message here suggests us to do.

03:45 We want to return an HttpResponse object. Okay. So, let’s do that: HttpResponse.

03:56 Okay, so, “What is this HttpResponse object?” is what PyCharm is wondering. The problem is that we have not imported it yet. So I go ahead and—you can always look this up if you don’t know where to find it, but I’m going to tell you now it sits in django.httpimport HttpResponse.

04:19 There it is. Okay. So now we’ve imported this HttpResponse object,

04:26 and now I’m returning an empty one. Let’s see. The error seems to be gone. Our server is telling us, “Okay, it’s fine.” There’s no error sitting here, which always gives us a chance to look back

04:43 into our browser and do a reload. Okay, we’re not getting an error. We’re also not getting anything else. We’re just getting an empty page. So in this case, this means that everything’s going fine and we’re returning an HttpResponse object. That’s what we were asked to do.

05:01 Only, this doesn’t really tell us anything right now. So, what do we need to do? What an HttpResponse object in Django does is it just returns some HTML.

05:13 So, I can put some HTML in here, let’s say a heading…

05:24 and pass it directly to this object. The server is still not complaining, so let’s head back over to our browser and reload this.

05:36 And, there we are! Great. So, we’re returning the HttpResponse object that renders our HTML, and we’re getting a heading that is exactly what I’ve written in here, inside of <h1>.

05:48 So, that’s pretty cool, right? And just to show you that I can pass any sort of HTML in here, I’m going to sneak off to the internet for a moment and paste in something here for you to see. See you in a moment.

06:35 Okay, I’m back now, and let’s check out how our site looks, just with some other pasted HTML in there. Look at that. Essentially, there’s some styling that’s off, right?

06:48 Because we’re not using some Javascript and it’s not having the CSS files associated with it, but I have the whole HTML—huge HTML page just copy-pasted right inside of this.

07:03 I need to scroll for a while to get back up on top. There we are. All I’m doing is I’m returning still this one HttpResponse object, only now I’ve pasted a whole bunch of HTML in here. And that’s fine.

07:15 There’s no error, and we’re actually getting images displayed and text displayed. Everything’s here, just the styling is a bit off because we’re not linking to files that the pages are usually linking to.

07:27 So, you can see that this is a way that you could actually build a simple HTML page. However, it’s really not very sustainable, and pretty messy to have all this HTML in here. We want to have a separation of concerns.

07:42 We want to keep the HTML somewhere else than the Python code that we’re writing, so what we’re going to do in the next video is we’re gonna create—instead of pasting the HTML directly in here, we’re going to separate this functionality off into templates and create an HTML template for our HTML. See you in the next video!

Avatar image for Paul

Paul on Oct. 28, 2019

Something to note here: When I was building out this new view, I wasn’t getting the same error messages in my browser, I was seeing a PageNotFound error instead.

After getting to know this “friend” a little better I realized that I had the wrong url in my address bar. I was still at the home page of of where Martin was actually at, which was

Avatar image for Martin Breuss

Martin Breuss RP Team on Oct. 29, 2019

Awesome, great job figuring this out @Paul and thanks for posting here. There’s always something to learn when you get to know your friends better ;)

Avatar image for Kevin Lao

Kevin Lao on Nov. 19, 2019

Hello Martin,

On that note, is there a way for us to resolve the ‘Page not found’ friend when we just go to

Avatar image for Martin Breuss

Martin Breuss RP Team on Nov. 23, 2019

Yes, there’s a way to do that–you’ll have to create a urlpattern for the empty path / (without anything after) and write a view that handles that path and returns something.

As you don’t have anything defined for the empty path, it can’t find the page and tells you, quite literally: Page not found.

Avatar image for Tumise

Tumise on Dec. 13, 2019

hello martin would it be alright to create all both my apps and projects in my virtual enviroment folder?? i seem to have created it there and everything works fine for now

Avatar image for Martin Breuss

Martin Breuss RP Team on Dec. 14, 2019

@oladelealade I suggest to keep (or move) your apps and projects folders outside of the venv folder. One reason is that you might want to .gitignore your virtual environment folder before committing to version control, and then you couldn’t add any project code.

I suggest to aim for the following structure:

├── .env/
├── portfolio/
└── projects/
Avatar image for Ekundayo Blessing

Ekundayo Blessing on Jan. 5, 2020

I got the two files mixed up. Fixed now 😀

Avatar image for Martin Breuss

Martin Breuss RP Team on Jan. 7, 2020

Well done @Ekundayo Blessing! :)

Avatar image for Brandy Wright

Brandy Wright on March 3, 2020

For whatever reason, my “projects” app already had a so I didnt get the same errors. Here is what it contains currently:

from django.shortcuts import render
from projects.models import Project

# Create your views here.
def all_projects(request):
    # query the db to return all project objects
    projects = Project.objects.all()
    return render(request, 'projects/all_projects.html',
                  {'projects': projects})

def project_detail(request, pk):
    project = Project.objects.get(pk=pk)
    return render(request, 'projects/detail.html',
                  {'project': project})

I’m going to omit the steps and make no changes yet.

Avatar image for Martin Breuss

Martin Breuss RP Team on March 5, 2020

Hi @Brandy Wright. If you downloaded the .zip file you get the complete code of the finished project. If you want to build it alongside the course, which is what I suggest in order to get the maximum learning benefit, then you should build the project from scratch. Think of the .zip file just as a reference project you can look up stuff if you get stuck.

Avatar image for emalfiza

emalfiza on March 18, 2020

Absolutely loving your clear and well explanation of django URLs…anywhere else I was not sure of the logic, I am so grateful of you @Martin Breuss.

Regarding the UnresolvedReference PyCharm to give some hints, but is there any other way to find out more about!

Avatar image for npujari

npujari on May 8, 2020

Hi Martin, I am following along your steps. I am getting this error message.

django.core.exceptions.ImproperlyConfigured: WSGI application ‘portfolio.wsgi.application’ could not be loaded; Error importing module.

Avatar image for npujari

npujari on May 8, 2020

Disregard my previous question. I was able to fix it. Thanks.

Avatar image for Andrew

Andrew on May 26, 2020

Oaky, I did not see “Hello, everyone!” on your django.

Avatar image for Martin Breuss

Martin Breuss RP Team on May 28, 2020

Hi @Andrew. Check out the video around 5:30, which is where it shows how “Hello everyone!” gets displayed after passing the text to the HttpResponse() object.

npujari, I wish you had posted your solution. I’m hitting the same problem.

The cause appears to be the 'projects' line we added to MIDDLEWARE in The causing error is:

Traceback (most recent call last): File "/Users/sdg/Code/django-portfolio/.dp/lib/python3.7/site-packages/django/utils/", line 13, in import_string module_path, class_name = dotted_path.rsplit('.', 1) ValueError: not enough values to unpack (expected 2, got 1)

Following shortly thereafter is:

File "/Users/sdg/Code/django-portfolio/.dp/lib/python3.7/site-packages/django/utils/", line 15, in import_string raise ImportError("%s doesn't look like a module path" % dotted_path) from err ImportError: projects doesn't look like a module path

It seems to want a dotted value like instead of simply projects. I can take projects out of MIDDLEWARE and get the previous expected error, no problem. Any clues?

Perhaps I missed something. I’ll go back and review the previous two videos.

Many thanks for any help!

Ha! Posting publicly always makes the solution jump out! :-)

For future reference for anyone else who hits this error: put the line 'projects', in the INSTALLED_APPS list in, not the MIDDLEWARE list!

(Is there a facepalm emoji…?)

Avatar image for Martin Breuss

Martin Breuss RP Team on Sept. 19, 2020

There is! 🤦‍♀️ 🤦‍♂️ 😁

Thanks so much for posting your solution, as you noticed and mentioned, it can really help other learners down the road 👍

And great job figuring it out. It also helps me a lot when I write out a question for a post, often the solution comes along the way.

Well done!!

Avatar image for iwatts

iwatts on Sept. 20, 2020

I don’t know whether it is a change from Django2 to Django3 but I had to swap the line:

urlpatterns = [
    path('/', views.project_list),


urlpatterns = [
    path('', views.project_list),
Avatar image for Martin Breuss

Martin Breuss RP Team on Sept. 23, 2020

Hi @iwatts and thanks for noting this. I just went to double-check and having the / in your empty path does prevent it from loading correctly, both on Django 3 as well as Django 2.2.16, so there’s no change regarding the Django version, but it just doesn’t work.

So let’s explore this a bit more :)

Reproducing the Error

I gave it a spin with a small contrived example project, where I set the urlpatterns like so:

urlpatterns = [
    path('/', views.index),

And here’s the error message that Django sent me as a response when trying to access the base URL at as well as

Page not found (404)
Request Method: GET
Request URL:

Using the URLconf defined in mysite.urls, Django tried
these URL patterns, in this order:


The empty path didn't match any of these.

Django’s feedback in this case is maybe slightly confusing, but it’s really all in there. :) Django is telling us that it looked at the two existing path configurations, / and admin/, but that The empty path didn’t match any of these.

The Empty Path

When you go to a URL, such as, the / at the end isn’t a text part of the URL. Instead it has the meaning that it separates URL path components.

Django knows that and strips these off, and doesn’t consider them as part of a path component that you are defining in your path() calls.

Which means that if you type, Django receives as a string component to process further only the stuff that comes after the final /. In this case, that is nothing, or rather, an empty string ''.

A Hacky Experiment

So what would you need to do in order to match a path configuration that looks like this:

path('/', views.index)

What URL would you need to type into your browser to make Django match it? Give it a try and report back here :)

Avatar image for iwatts

iwatts on Sept. 23, 2020

Yeah - just tried it and you need:

I just presumed it was all a string - good to know :-)

Avatar image for Martin Breuss

Martin Breuss RP Team on Sept. 24, 2020

Nice! Good work! 🙌 :)

And just as a conclusion for anyone reading this down the road: Don’t put the slash character into your empty path, just leave it as an empty string instead:

path('', views.index)
Avatar image for KatMac

KatMac on May 24, 2021

I made it to this part, but am getting an error not sure how to fix.

When I enter:

python runserver

I get this error:

 File "C:\PYTHON-TRAIN\zoo\django-portfolio\portfolio\", line 21, in <module>
    path('projects/', include(projects.urls)),
NameError: name 'projects' is not defined

Here is the code I have in the portfolio\ file:

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('projects/', include(projects.urls)),
Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on May 25, 2021

@KatMac What’s your directory tree? Is there a projects folder in your django-portfolio/ parent directory? Perhaps, you misspelled the name project without the s at the end?

Avatar image for KatMac

KatMac on May 25, 2021

It was a typo. Sorry to bother you on that.

Avatar image for gerlachtatras

gerlachtatras on July 13, 2022

The file in django 4.0.6 uses from pathlib import Path, not import os like in the tutorial. How can I resolve this issue?

Avatar image for Bartosz Zaczyński

Bartosz Zaczyński RP Team on July 13, 2022

@gerlachtatras Django 4.0.6 seems to be working just fine when the path in your settings file is defined the old way with the os module. However, if it bothers you, then you can manually edit the file in your manaement app by replacing the BASE_DIR constant and updating all of its references, for example:


from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# ...

        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'projects/templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
Avatar image for apps89

apps89 on July 22, 2023

i am creating a new app called booking, i have checked the management urls and app urls, and still getting this error -

Page not found (404) Request Method: GET Request URL: Using the URLconf defined in djangoProject.urls, Django tried these URL patterns, in this order:

admin/ booking/ The current path, booking, didn’t match any of these.

code from management (djangoProject)

from django.contrib import admin from django.urls import include, path

urlpatterns = [ path(‘admin/’,, path(‘booking/’, include(‘booking.urls’)), ]

code from app, booking

from django.urls import path from booking import views

urlpatterns = [ path(’ ‘, views.booking_view), ]

i dont know what i am doing wring, i have checked and checked, cant see why i still have htis error

Become a Member to join the conversation.