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

Unlock This Lesson

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

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

Create a View

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

Go to views.py, 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):
    pass

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!

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 http://127.0.0.1:8000/instead of where Martin was actually at, which was http://127.0.0.1:8000/projects/

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 ;)

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 127.0.0.1:8000/?

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.

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

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:

rp-portfolio
├── .env/
├── manage.py
├── portfolio/
└── projects/

Ekundayo Blessing on Jan. 5, 2020

I got the two urls.py files mixed up. Fixed now 😀

Martin Breuss RP Team on Jan. 7, 2020

Well done @Ekundayo Blessing! :)

Brandy Wright on March 3, 2020

For whatever reason, my “projects” app already had a views.py 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.

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.

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!

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.

npujari on May 8, 2020

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

Andrew on May 26, 2020

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

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 settings.py. The causing error is:

Traceback (most recent call last): File "/Users/sdg/Code/django-portfolio/.dp/lib/python3.7/site-packages/django/utils/module_loading.py", 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/module_loading.py", 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 foo.bar 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 settings.py, not the MIDDLEWARE list!

(Is there a facepalm emoji…?)

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!!

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),
]

to

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

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),
    path('admin/', admin.site.urls),
]

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

Page not found (404)
Request Method: GET
Request URL:    http://127.0.0.1:8000/

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

/
admin/

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 http://127.0.0.1:8000/, 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 http://127.0.0.1:8000/, 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 :)

iwatts on Sept. 23, 2020

Yeah - just tried it and you need:

127.0.0.1:8000/projects//

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

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)

Become a Member to join the conversation.