[PYTHON] I want to create a lunch database [EP1-4] Django study for the first time

This article is a continuation.

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.

Comprehensive testing

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.

View test

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&#x27;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.

View improvements

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 ().

Write view test code

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'...

DetailView test

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.

Static file management

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.

Customize the app structure

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, ... image.png

It's definitely green, but the difference is too subtle (laughs)

Add background image

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. image.png It was displayed nicely!

customize admin form

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. image.png 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 image.png Then select Choice to enter the selection screen. Then you will reach the following setting screen. image.png 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. image.png

Next, we will make this screen, which is the previous point of the Question setting screen, easier to use. image.png 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) image.png

Further customize the administrator screen

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. image.png 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.

Recommended Posts

I want to create a lunch database [EP1] Django study for the first time
I want to create a lunch database [EP1-4] Django study for the first time
I want to create a Dockerfile for the time being.
[Hi Py (Part 1)] I want to make something for the time being, so first set a goal.
I want to move selenium for the time being [for mac]
Start Django for the first time
I want to record the execution time and keep a log.
For the time being, I want to convert files with ffmpeg !!
I tried tensorflow for the first time
I tried to create serverless batch processing for the first time with DynamoDB and Step Functions
For the first time in Numpy, I will update it from time to time
Python: I want to measure the processing time of a function neatly
I tried using scrapy for the first time
How to use MkDocs for the first time
I want to easily create a Noise Model
I tried python programming for the first time.
I tried Mind Meld for the first time
I want to create a plug-in type implementation
I want to upload a Django app to heroku
Try posting to Qiita for the first time
I want to create a nice Python development environment for my new Mac
I want to make a music player and file music at the same time
I want to add silence to the beginning of a wav file for 1 second
Let's display a simple template that is ideal for Django for the first time
GTUG Girls + PyLadiesTokyo Meetup I went to machine learning for the first time
I want to create an API that returns a model with a recursive relationship in the Django REST Framework
What I got into Python for the first time
I tried Python on Mac for the first time.
I want to scroll the Django shift table, but ...
Register a task in cron for the first time
I tried python on heroku for the first time
For the first time, I learned about Unix (Linux).
I want to manually create a legend with matplotlib
AI Gaming I tried it for the first time
I made a command to wait for Django to start until the DB is ready
It's okay to participate for the first time! A hackathon starter kit that you want to prepare "before" participating in the hackathon!
Kaggle for the first time (kaggle ①)
Kaguru for the first time
[TensorFlow] I want to master the indexing for Ragged Tensor
I want to make a blog editor with django admin
A study method for beginners to learn time series analysis
Summary of stumbling blocks in Django for the first time
I tried the Google Cloud Vision API for the first time
I tried to create a bot for PES event notification
I want to use the Ubuntu desktop environment on Android for the time being (Termux version)
I want to use Ubuntu's desktop environment on Android for the time being (UserLAnd version)
Steps to create a Django project
Qiskit: I want to create a circuit that creates arbitrary states! !!
[For self-learning] Go2 for the first time
I want to create a histogram and overlay the normal distribution curve on it. matplotlib edition
I made a function to check if the webhook is received in Lambda for the time being
I want to create a pipfile and reflect it in docker
See python for the first time
Raspberry Pi --1 --First time (Connect a temperature sensor to display the temperature)
I want to create a machine learning service without programming! WebAPI
Introduction to Deep Learning for the first time (Chainer) Japanese character recognition Chapter 3 [Character recognition using a model]
The story of returning to the front line for the first time in 5 years and refactoring Python Django
Create a function to get the contents of the database in Go
Create a REST API to operate dynamodb with the Django REST Framework
What I learned by writing a Python Pull Request for the first time in my life
[For beginners] I want to get the index of an element that satisfies a certain conditional expression