1. Code
I wrote /polls/tests.py, which is based on Writing your first Django app, part 5.
Here is part of the code:
class QuestionIndexViewTests(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='testpass')
self.client.login(username='testuser', password='testpass') # ✅ log in the test user
def test_no_questions(self):
"""
If no questions exist, an appropriate message is displayed.
"""
response = self.client.get(reverse("polls:index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerySetEqual(response.context["latest_question_list"], [])
def test_past_question(self):
"""
Questions with a pub_date in the past are displayed on the
index page.
"""
question = create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse("polls:index"))
self.assertQuerySetEqual(
response.context["latest_question_list"],
[question],
)
def test_future_question(self):
Questions with a pub_date in the future aren't displayed on
"""
Questions with a pub_date in the future aren't displayed on
the index page.
"""
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse("polls:index"))
self.assertContains(response, "No polls are available.")
self.assertQuerySetEqual(response.context["latest_question_list"], [])
def test_future_question_and_past_question(self):
"""
Even if both past and future questions exist, only past questions
are displayed.
"""
question = create_question(question_text="Past question.", days=-30)
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse("polls:index"))
self.assertQuerySetEqual(
response.context["latest_question_list"],
[question],
)
def test_two_past_questions(self):
"""
The questions index page may display multiple questions.
"""
question1 = create_question(question_text="Past question 1.", days=-30)
question2 = create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse("polls:index"))
self.assertQuerySetEqual(
response.context["latest_question_list"],
[question2, question1],
)
2. Conversation with ChatGPT
The following is part of polls/tests.py. How can I use this function? What outcome will be created by this?
def create_question(question_text, days):
"""
Create a question with the given question_text and published the
given number of days offset to now (negative for questions published
in the past, positive for questions that have yet to be published).
"""
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
The create_question() function is a helper function designed to simplify creating Question objects for testing. Here's a full explanation and example of how to use it.
✅ What the function does:
-
It takes two arguments:
-
question_text: the text of the question. -
days: how many days to offset the publish date from now.-
Negative = past (e.g.,
-30means 30 days ago). -
Positive = future (e.g.,
+5means 5 days from now).
-
-
-
It creates and saves a
Questionobject with that text and publish date.
✅ How to use it in a test or in the Django shell:
In a unit test:
In the Django shell:
Then:
✅ What outcome will be created?
If you run:
Then it will:
-
Create a question titled
"What is your name?". -
Set
pub_dateto 7 days ago from the current time. -
Save it to the database.
-
Return the
Questionobject.
Let me know if you'd like to also create associated Choice entries for the question!
3. Another conversation
Do I need to delete the created Question object after testing?
Great question!
✅ Short Answer:
No, you usually do not need to delete the created Question object manually after testing — if you're
using Django's TestCase.
🔍 Why?
When you're writing tests using django.test.TestCase, Django automatically:
-
Uses a temporary test database.
-
Rolls back all database changes after each test method.
-
So every test starts with a clean database.
✅ Example:
This Question object is only available during this test. Once the test finishes, it’s automatically
deleted when the test database is reset.
❗Exception: If You're in the Django Shell or Real App
If you use create_question() in the Django shell or in the actual app, the Question object is saved in your real database. In that case:
-
It won’t be automatically deleted.
-
You can delete it manually if needed:
Let me know if you want a quick cleanup script for the shell too!
4. Another Conversation(2)
Does "a temporary test database" mean there will be a new database separate from the original sqlite database I am using?
Yes, exactly!
✅ In Django TestCase, a temporary test database means:
Django creates a separate, temporary database just for running tests — completely separate from your real database like db.sqlite3.
🔍 How it works:
-
Before running your tests, Django creates a new empty database (in memory or a temp file).
-
It applies all migrations to this temp database.
-
Tests run using this test database only.
-
After the tests finish, Django deletes the test database automatically.
🔒 Why this is safe and useful:
-
Your real data in
db.sqlite3stays untouched. -
Tests can freely create, modify, or delete records.
-
You don’t have to worry about cleaning up test data.
🛠️ Example log when running tests:
You might see something like this:
That shows the test database is being created and destroyed automatically.
Let me know if you want to actually see this behavior or try a custom test to verify it!
5. Another Conversation (3)
Can you explain the 3rd line of this code? Does "response" get an HTML file?
def test_future_question_is_not_displayed(self):
create_question("Future question", 30)
response = self.client.get(reverse("polls:index"))
self.assertNotContains(response, "Future question")
Yes, let's break it down line by line — especially the 3rd line as you asked:
🔍 Line 3 explanation:
What it does:
-
self.client.get(...)simulates a browser sending a GET request to the URL for thepolls:indexview. -
reverse("polls:index")returns the actual URL path for the named URL (polls:index) — typically something like"/polls/". -
responsecaptures the full HTTP response from that request, including:-
response.status_code(e.g., 200 OK) -
response.content(the actual HTML as bytes) -
response.context(data sent to the template)
-
✅ So yes — the variable response will contain the HTML page returned by the view at
polls:index.
Let me know if you want to inspect that HTML (response.content) or test more conditions!
6. Another Conversation (4)
What does this code do?
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
"""
If no questions exist, an appropriate message is displayed.
"""
response = self.client.get(reverse("polls:index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerySetEqual(response.context["latest_question_list"], [])
This Django test case checks the behavior of the polls index page when there are no questions in the database. Let’s go through the code line by line:
🔍 Code Breakdown:
This defines a test class that inherits from Django’s TestCase, which sets up a temporary test database for each test method.
This is a test method. Django will automatically run it because its name starts with test_.
This is a docstring describing what the test is supposed to check.
-
Uses Django's test client to simulate a GET request to the index page of the polls app.
-
reverse("polls:index")resolves the URL for theindexview in thepollsnamespace.
-
Asserts that the HTTP response status is
200 OK, meaning the page loaded successfully.
-
Checks that the phrase
"No polls are available."appears somewhere in the HTML response. -
This confirms that the proper message is shown when there are no poll questions.
-
Checks that the
latest_question_listvariable passed to the template is an empty list. -
This ensures that the view logic correctly returns an empty queryset when there are no questions in the database.
✅ Summary:
This test confirms that when there are no questions in the database, the polls index page:
-
Loads correctly,
-
Shows the message
"No polls are available.", -
Passes an empty list to the template context.
Would you like to see how to add a test when there is one question?
7. Another Question (5)
Where is "context_object" defined?
class IndexView(LoginRequiredMixin, generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
#Return the last five published questions (not including future ones).
return Question.objects.filter(pub_date__lte=timezone.now()).order_by("-pub_date")[:5]
Great question!
In your code:
🔍 What is context_object_name?
-
It is not a variable you're defining from scratch — it is a predefined attribute of Django’s
ListViewclass (which you are inheriting from viageneric.ListView). -
Setting it changes the name of the context variable that the view passes to the template.
🔧 By default:
If you don’t specify context_object_name, Django uses the default name:
So this would be your context in the template:
But by writing:
You're telling Django:
"When rendering the template, pass the list of questions using the context variable
latest_question_listinstead of the defaultobject_list."
✅ Where is context_object_name defined?
It is defined in Django's ListView class here:
Source: django.views.generic.list.ListView
🔁 So in your template:
...will work correctly because of context_object_name = "latest_question_list".
Let me know if you'd like to trace the full inheritance path of ListView or see what else you can customize.
8. Another Conversation
Where can Django put questions into latest_question_list?
Excellent follow-up!
Django puts questions into latest_question_list (or any context variable you define with context_object_name) automatically by:
✅ 1. Calling Your View’s get_queryset() Method
In your class-based view:
-
Django calls
get_queryset()internally when the view is accessed. -
This returns a QuerySet of
Questionobjects (e.g. 5 most recent ones).
✅ 2. Assigning the QuerySet to the Context
Because you wrote:
Django puts the result of get_queryset() into the context like this (internally):
This is how the list of questions becomes available to your template as {{ latest_question_list }}.
🧠 Summary:
-
get_queryset()provides the data. -
context_object_nametells Django what variable name to use in the template. -
Django automatically builds the context and injects it when rendering the template.
Let me know if you'd like to see what happens if you don’t specify context_object_name, or want
to customize the context further with get_context_data().
9. Another Conversation
How can I run this test?
To run your Django test (such as the one in polls/tests.py), follow these steps:
✅ 1. Make Sure You're in the Django Project Directory
You should see manage.py in the current folder.
✅ 2. Run the Tests Using manage.py
▶ Run all tests for your project:
▶ Run only the tests in the polls app:
▶ Run a specific test case or method:
If you want to run a specific test method like test_no_questions() inside QuestionIndexViewTests, run:
💡 Replace the method/class name with the one you want to test.
✅ 3. Read the Output
If the test passes, you'll see something like:
If the test fails, Django will print details showing:
-
What failed
-
What was expected
-
What was actually received
Would you like help interpreting any test output?
10. Another Conversation
If I ran python manage.py test polls.tests.QuestionIndexViewTests.test_no_questions, is a "no-question" case created?
Yes — when you run this test:
a temporary, isolated test database is created automatically, and in that database, no questions exist unless you add them inside the test. So in your case:
Here's what happens:
🧠 Explanation:
-
Since you don’t create any
Questionin this test, the test database will have zero questions. -
This simulates the "no questions" scenario.
-
The test checks that:
-
The HTTP response is 200.
-
The page contains
"No polls are available." -
The list of latest questions is empty:
[].
-
✅ Summary:
Yes, when you run that test, it creates a clean test environment (no data), and no questions exist unless explicitly created in the test code. That’s why this test correctly verifies how the app behaves when there are no questions.
Let me know if you want to see how to create questions in the test or simulate other cases!
11. Another Conversation
I ran it and I got this error. Why?
Traceback (most recent call last):
File "/home/mtag79/django_projects/mysite/polls/tests.py", line 53, in test_no_questions
self.assertEqual(response.status_code, 200)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 302 != 200
Great question — this error:
means your view redirected the request instead of returning a normal response.
🔍 What does 302 mean?
-
302is an HTTP status code for Redirect. -
This usually happens when a view is protected by
LoginRequiredMixin, but the test client is not logged in.
✅ What’s likely happening in your case
Your IndexView looks like this:
This means users must be logged in to see this view.
But in your test:
You're sending a request without logging in. So Django redirects the user to the login page →
302.
💡 How to fix it
You need to log in a test user before calling the view.
Add this to the beginning of your test:
from django.contrib.auth.models import User
class QuestionIndexViewTests(TestCase):
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='testpass')
def test_no_questions(self):
self.client.login(username='testuser', password='testpass') # ✅ log in
response = self.client.get(reverse("polls:index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerySetEqual(response.context["latest_question_list"], [])
✅ Summary
-
302means your test user is being redirected to login. -
Fix it by using
self.client.login(...)in the test.
Let me know if you'd like help creating more test users or testing logged-out behavior!
12. Another Conversation
Am I right to understand that testuser is created temporarily and destroyed after the test?
Yes, you're absolutely right!
In Django’s test framework, everything inside a test class that inherits from TestCase (like QuestionIndexViewTests) is executed within a temporary test database that is:
✅ Automatically Created and Destroyed
-
Created before your test runs
-
Destroyed immediately after your test finishes
🧪 What this means for your testuser
When you run this in your test:
-
The
testuseris created in a temporary database. -
It will not affect your real database.
-
After the test run, the user (and any test data) is completely deleted.
🧼 No Manual Cleanup Required
You do not need to delete the user or data manually — Django handles this for you.
Let me know if you want to confirm this behavior or explore how Django manages test databases behind the scenes!
13. Another Conversation
I ran:
python manage.py test polls.tests.QuestionIndexViewTests
, but I still got a 302 error. I put
def setUp(self):
self.user = User.objects.create_user(username='testuser', password='testpass')
You're almost there! The reason you're still getting a 302 (redirect) instead of the expected 200 status
code is because the test is hitting a login-required view without logging in.
Even though you've created the user in setUp(), Django doesn't automatically log the test client in.
✅ Fix: Log in the test user in setUp()
You need to explicitly log in the test client. Here's how to fix it:
🔁 Why 302 Happens
-
A
302redirect usually means: "This page requires login; please go to/accounts/login/". -
Since you're using
LoginRequiredMixinin yourIndexView, the test must simulate a logged-in session.
Let me know if you’d like help mocking login for views, bypassing login in tests, or inspecting redirects.
14. Another Conversation
It worked. I also ran:
python manage.py test polls.tests.QuestionIndexViewTests.test_no_questions
and I got it correctly. Is that mean the setUp() function is executed even if I don't specify it?
Yes, exactly! ✅
When you run:
Django still executes the full lifecycle of the test class QuestionIndexViewTests, which includes:
-
Calling
setUp()before each test method (in this case, justtest_no_questions). -
Running the test method itself.
-
Cleaning up after the test.
🔁 How Django's unittest.TestCase lifecycle works
For each test method:
-
setUp()is automatically called before the test method runs. -
tearDown()(if defined) is automatically called after the test method runs.
This happens even when running a single test, as in your command.
🔍 Bonus tip
If you ever want to verify the order, you can add something like this:
You’ll see the message printed when the test runs — confirming that Django calls it.
Let me know if you'd like to explore setUpTestData() too — it's a class-level setup that runs only once for all tests in the class.
15. Another Conversation
There are three types of assert...(). Can you explain the difference?
def test_no_questions(self):
"""
If no questions exist, an appropriate message is displayed.
"""
response = self.client.get(reverse("polls:index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerySetEqual(response.context["latest_question_list"], [])
Great question! In your test method, you're using three different assert...() methods from Django's test framework (inherited from Python's
unittest). Here's what each of them does:
1. self.assertEqual(response.status_code, 200)
✅ Purpose: Verifies that the HTTP response status code is exactly 200 (OK).
-
assertEqual(a, b)checks thata == b. -
If the values are not equal, the test fails.
📌 In your case:
-
You expect the index page to load normally (200 OK), not redirect or error.
2. self.assertContains(response, "No polls are available.")
✅ Purpose: Checks that a specific string exists in the response.content.
-
It looks for
"No polls are available."in the HTML content returned from the view. -
It also checks that the HTTP status is
200by default unless you passstatus_code=....
📌 This is a Django-specific enhancement over assertIn(...).
3. self.assertQuerySetEqual(response.context["latest_question_list"], [])
✅ Purpose: Compares a Django QuerySet with a Python list (or another QuerySet).
-
It checks if the objects in both match exactly in order and content.
-
response.context["latest_question_list"]should return an empty list if no questions exist.
📌 This is especially useful when testing results returned from the database.
Summary Table
| Assertion | Checks That... | Typical Use |
|---|---|---|
assertEqual(a, b)
|
a == b
|
Numbers, strings, counts |
assertContains(response, text)
|
text in response.content
|
HTML/text rendering |
assertQuerySetEqual(qs, list)
|
QuerySet matches expected values | DB-related results |
Let me know if you'd like examples or if you'd like to explore assertIn, assertTrue, or
assertRaises.
