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 the
get_queryset ()method to the
DetailView 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 as
DetailView, and
test_past_questionchecks if
pub_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.pyand
models.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 edit
base_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.