Last time Two times before First writing
Last time, I added the function of the polls application and it works as a voting application.
Finally, we tested and fixed the bug, and the last time was over.
This time, I'll take a look at the contents of Django Tutorials 5-7.
I fixed the was_published_recently bug in the test I created last time, but since this bug fix has no origin or child to affect other parts, I will test for it.
Add the following newly under the QuestionModelTests class in tests.py.
mysite/polls/tests.py
def test_was_published_recently_with_old_question(self):
#Represents yesterday
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
#Represents the last minute
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
was_published_recently () returns more than yesterday and less than now with True, sotest_was_published_recently_with_old_question ()should be False and test_was_published_recently_with_recent_question () should return with True.
This prediction is in self.assertIs, so I will check if it is true.
cmd
(Django) C:\User\mysite>python manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
Destroying test database for alias 'default'...
So, Ran 3 tests in 0.002s is displayed, and you can see that 3 tests were successful, including the ones I wrote last time.
According to the tutorial, this voting app still has bugs.
It overlaps with the problematic part of was_published_recently, but even questions that were pub_date at a future date and time will be displayed in the view.
This time, we will build a test environment using an interactive shell.
cmd
>>> from django.test import Client
>>> # create an instance of the client for our use
>>> client = Client()
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
This completes the installation of the test client.
In the tutorial, client.get ('/') is executed before that to detect the error, but it is omitted here.
And what's in the response ...
cmd
>>> response.status_code
200
>>> response.content </ul>\n'
b'\n <ul>\n \n <li><a href="/polls/1/">What's new?</a></li>\n \n </ul>\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's new?>]>
It is like this.
Where is the content of response.content drawn from?
I read the official documentation, but I didn't understand.
I know I'm pulling something that looks like html from somewhere, but conversely, I only know that.
Let's take a second look and improve the view. Improve not to display the questions registered in the future date and time.
mysite/polls/views.py
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
#Rewriting this function
def get_queryset(self):
#Difference below
return Question.objects.filter(
pub_date__lte=timezone.now()
).order_by('-pub_date')[:5]
pub_date__lte = timezone.now () points to a pub_date that is earlier than the current date and time.
__lte means less than equal, which means something likepub_date <= timezone.now ().
We will create a test code like the one we wrote at the time of was_published_recently and prepare it for easy testing.
mysite/polls/tests.py
def create_question(question_text, days):
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class QuestionIndexViewTests(TestCase):
def test_no_questions(self):
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):
create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
def test_future_question(self):
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):
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: Past question.>']
)
def test_two_past_questions(self):
create_question(question_text="Past question 1.", days=-30)
create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question 2.>', '<Question: Past question 1.>']
)
The code is longer, but it contains test_no_question () if there are no questions, test_past_question if the question was added in the past time, andif the question was added in the future time. Test the four patterns: test_future_question (), test_future_question_and_past_question when there are two questions added in the past and future, and test_two_past_questions () when both questions were added in the past. It is written in.
create_question defines the process of creating a question in a test as a function.
Hopefully it will succeed as follows: Once I got FAIL, even if I fixed the code after that, it didn't work in the same terminal. So, if you make a mistake and want to start over, it may be better to raise another terminal.
cmd
(Django) C:\User\mysite>python manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..........
----------------------------------------------------------------------
Ran 8 tests in 0.044s
OK
Destroying test database for alias 'default'...
The improvement in the previous test is that ʻIndexView will not display the questions posted in the future, but if you pull it by URL, DetailViewwill still be displayed. So I will solve this problem. First, add theget_queryset ()method to theDetailView class in the same way as adding ʻIndexView.
mysite/polls/views.py
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now())
After this, the tutorial is in English for some reason, but as it is written, I added a test to check that pub_date shows the past ones and not the future ones. It means that you have to.
mysite/polls/test.py
class QuestionDetailViewTests(TestCase):
def test_future_question(self):
future_question = create_question(question_text='Future question.', days=5)
url = reverse('polls:detail', args=(future_question.id,))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_past_question(self):
past_question = create_question(question_text='Past Question.', days=-5)
url = reverse('polls:detail', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)
This can be interpreted in the same way as for ʻIndexView. test_future_question checks if pub_datedoes not show future ones asDetailView, and test_past_questionchecks ifpub_date shows past ones in DetailView`.
This is also tested and looks like this:
Since the total number of tests so far is 10, the result of the test should be 10 tests, so the result should be Ran 10 tests.
cmd
(Django) C:\User\mysite>python manage.py test polls
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..........
----------------------------------------------------------------------
Ran 10 tests in 0.058s
OK
Destroying test database for alias 'default'...
Looking at it so far, it seems that you can make a test for ResultView as it is.
The tutorial was limited to suggestions, but let's write one here.
mysite/polls/test.py
class QuestionResultViewTests(TestCase):
def test_future_question(self):
future_question = create_question(question_text='Future question.', days=5)
url = reverse('polls:results', args=(future_question.id,))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_past_question(self):
past_question = create_question(question_text='Past Question.', days=-5)
url = reverse('polls:results', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)
Actually, it's almost the same as when it was DetailView. The only thing that has changed is ʻurl = reverse ('polls: results', args = ..) `.
Also, let's put get_queryset properly in ResultsView of views.py.
Like this
mysite/polls/views.py
class ResultsView(generic.DetailView):
model = Question
template_name = 'polls/results.html'
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now())
This is the end of the tutorial test, but it seems that Django also has the ability to test the screen behavior created by HTML. It's really sophisticated.
From here you can enter tutorial 6.
So far, I haven't used CSS or JavaScript that often appear in creating web pages, but Django can use them well. Django treats these files as static files.
It seems that django.contrib.staticfiles can centrally manage these static files for each application.
Create a static folder under polls for Django to work with static files. Create another polls folder under static as you did when you created the template folder.
Then I will write the following CSS.
mysite/polls/static/polls/style.css
li a {
color: green;
}
Then add a reference to CSS in ʻindex.html` as well.
mysite/polls/templates/polls/index.html
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
Write this statement at the top of the file. {% static%} is a Django template tag that indicates the location of static files.
When I check it, ...

It's definitely green, but the difference is too subtle (laughs)
We will add settings to style.css to add a background image.
mysite/polls/static/polls/style.css
body {
background: white url("images/background.png ") no-repeat;
}
As you can see from the link, it refers to the image background.png under themysite / polls / static / polls / image /folder, so put the corresponding file there in advance. The image will not be displayed without it.
So, here is the result of bringing in a suitable image from Irasutoya.
It was displayed nicely!
I confirmed that you can add Question from the administrator screen a few times ago (EP1-2), but in Django that screen You can even customize it.
From the tutorial, try the code to play with the question customization screen.
mysite/polls/admin.py
from django.contrib import admin
from .models import Question
# Register your models here
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date']}),
]
admin.site.register(Question, QuestionAdmin)
The changes are the addition of the QuestionAdmin class and the addition of QuestionAdmin to the argument of ʻadmin.site.register ()`.
Now when you log in as an administrator and enter the question change screen, the fields are separated as shown below.
Right now you can only see the question, but you can also see each option that comes with the question.
mysite/polls/admin.py
from .models import Choice, Question
admin.site.register(Choice)
The changes are that Choice is also ʻimport from models and ʻadmin.site.register (Choice) is added.
When I check it, first of all, you can select Choice at the beginning like this
Then select Choice to enter the selection screen. Then you will reach the following setting screen.
This is still good enough, but since Choice is tied to Question, wouldn't it be easier to use if you could edit it together?
Therefore, I will stop ʻadmin.site.register (Choice)` and modify it more.
mysite/polls/admin.py
class ChoiceInline(admin.StackedInline):
model = Choice
extra = 3
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
inlines = [ChoiceInline]
Add the ChoiceInline class and add the part that receives it inside the QuestionAdmin class. Delete ʻadmin.site.register (Choice) . Then you will be able to edit Choice on the Question` screen as shown below.

Next, we will make this screen, which is the previous point of the Question setting screen, easier to use.
I didn't find it difficult to use, but I'll add new code to ʻadmin.pyandmodels.py` to make it even more convenient.
mysite/polls/admin.py
class QuestionAdmin(admin.ModelAdmin):
fieldsets = [
(None, {'fields': ['question_text']}),
('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
]
inlines = [ChoiceInline]
#The following additional sentence
list_display = ('question_text', 'pub_date', 'was_published_recently')
list_filter = ['pub_date']
search_fields = ['question_text']
mysite/polls/models.py
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self) -> str:
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
#The following additional sentence
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
The content displayed by list_display has been expanded, and the sidebar filter works with list_filter, and you can also search with search_fields. (The main changes are in the box)

The administrator screen can also be customized using the html file as in the case of ʻIndexView. To be honest, the default is sufficient, but it may be convenient to use it someday. First, tell setting.py the path of the template` that this project will reference.
mysite/mysite/setting.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], #This line! !! !!
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Actually, copying and pasting the code in this tutorial failed.
In the tutorial, it is 'DIRS': [BASE_DIR /'templates'],, but this is regarded as a string type calculation, and of course the string cannot be divided, so an error will occur.
So I specified the path by setting ʻos.path.join (BASE_DIR,'templates')], `to avoid the error.
Reading the tutorial in English didn't give me an answer here. Is there something wrong with the tutorial? I think it's my oversight, but ...
Then pull the admin screen Template from the Django source file. There's also a Template example in the Django package. friendly. You can find out where the Django source files are in the code below.
cmd
(Django) C:\Users\mysite> python -c "import django; print(django.__path__)"
Once you know the location, open it with Explorer and put base_site.html under mysite / templates / admin.
Create a new templates folder and the ʻadmin folder under it here. (Base_site.html is in django / contrib / admin / templates / adminin the source file) Then editbase_site.html` a little.
mysite/templates/admin/base_site.html
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">Administrator screen</a></h1>
{% endblock %}
If you extract only the changed part, it will be here. If you put a character in the <a> <\ a> tag, it will appear at the top of the page.
By default, it contains {{site_header | default: _ ('Django administration')}}.
And let's check if the change is reflected as follows.
It's changed properly! !!
This concludes the Django tutorial. There was quite a lot. To be honest, even if you come here, it's still impossible to let go (laughs) However, if you understand the basics, I think that your studies will make great progress. It was such a chewy content. There is an advanced tutorial after this, but I haven't done it yet (laughs) When you need it ...
Next time, I will study Git while turning Django a little more, so thank you.