Django2.1 Tutotrial Lab

Hosting:

The main purpose of this project is to practice Django 2.1 Getting started effectively.

Like Yoga’s daily practice, I need a routine to awaken my body and mind. Same as programming, I need lab to maintain skills to invoke idea.

Simple is Power

Repeat practice and find out your weakness.

You have your right to be an individual thinker.

Terminal Prompt

Before activate virtual environment:

export PS1='$ '

After activate virtual environment:

export PS1='(venv)$ '

To determine which python:

which python

Table of Contents

Writing your first Django app, part 1

1-1. Virtual Envrionment

Lab:

$ python3.6 -v venv venv
$ source venv/bin/activate
(venv)$ pip install django
(venv)$ pip freeze

Note

$ To check installed packages.

_images/img1-1_01.png

1-2. Start Project

Lab:

(venv)$ django-admin startproject mysite
(venv)$ cd mysite
(venv)$ python manage.py runserver

Note

To see a rocket!

_images/img1-2_01.png

1-3. Start App

Lab:

(venv)$ python manage.py startapp polls
*** edit mysite/urls.py
*** add polls/urls.py
*** edit polls/views.py
*** add go.py
(venv)$ . go
  • mysite/urls.py:

    from django.contrib import admin
    from django.urls import path,include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('polls/', include('polls.urls')),
    ]
    
  • polls/urls.py:

    from django.urls import path
    from . import views
    
    urlpatterns = [
      path('', views.index, name='index'),
    ]
    
  • polls/views.py:

    from django.http import HttpResponse
    def index(request):
        return HttpResponse("Hello, world. You're at the polls index.")
    
  • go:

    python manage.py runserver
    

Note

To ensure http://127.0.0.1:8000/polls/ is working.

_images/img1-3_01.png

Warning

Be aware http://127.0.0.1:8000 is damaged!

_images/img1-3_02.png

Writing your first Django app, part 2

2-1. Admin

Lab:

(venv)$ python manage.py migrate
(venv)$ python manage.py createsuperuser
(venv)$ . go

Note

http://127.0.0.1:8000/admin/, login to maintain user/group.

_images/img2-1-1.png
_images/img2-1-2.png

2-2. Model

Lab:

*** edit mysite/settings.py
*** edit poll/models.py
*** edit poll/admin.py
*** edit go
. go
  • mysite/settings.py:

    INSTALLED_APPS = [
       'polls',
       'django.contrib.admin',
       ...
    
  • polls/models.py:

    from django.db import models
    
    
    class Question(models.Model):
        question_text = models.CharField(max_length=200)
        pub_date = models.DateTimeField('date published')
        def __str__(self):
            return self.question_text
    
    class Choice(models.Model):
        question = models.ForeignKey(Question, on_delete=models.CASCADE)
        choice_text = models.CharField(max_length=200)
        votes = models.IntegerField(default=0)
        def __str__(self):
            return self.choice_text
    
  • polls/admin.py:

    from django.contrib import admin
    from .models import Question,Choice
    admin.site.register(Question)
    admin.site.register(Choice)
    
  • go.py:

    python manage.py makemigrations
    python manage.py migrate
    python manage.py runserver
    

Note

Able to maintain Question and Choice.

_images/img2-2-0.png

2-3. Command

Lab:

*** add polls/management/commands/initpolls.py
python manage.py
python manage.py initpolls
. go
  • polls/management/commands/initpolls.py:

    from django.core.management.base import BaseCommand, CommandError
    from polls.models import Question,Choice
    from django.utils import timezone
    
    class Command(BaseCommand):
        help = 'Create sample questions and choices.'
    
        def add_arguments(self, parser):
            parser.add_argument('question_num',type=int)
    
        def handle(self, *args, **options):
            q = Question.objects.all()
            q.delete()
    
            cnt = 0
            question_num = options['question_num']
            while (cnt < question_num):
                cnt += 1
                if cnt > 12:
                    self.stdout.write(self.style.WARNING('Max number was set to 12'))
                    break
                q = Question(question_text="Question #"+str(cnt),pub_date=timezone.now())
                q.save()
                q.choice_set.create(choice_text='Choice A for Question #'+str(cnt),votes=0)
                q.choice_set.create(choice_text='Choice B for Question #'+str(cnt),votes=0)
                q.choice_set.create(choice_text='Choice C for Question #'+str(cnt),votes=0)
    
            q = Question.objects.all()
            c = Choice.objects.all()
    
            self.stdout.write(self.style.SUCCESS('Questions "%s"' % q))
            self.stdout.write(self.style.SUCCESS('Choices "%s"' % c))
    
_images/img2-3-1.png
_images/img2-3-2.png
_images/img2-3-3.png
_images/img2-3-4.png

Writing your first Django app, part 3

References:

Hosting:

3-1. Polls Index

Lab:

*** edit polls/views.py
*** add polls/templates/polls/index.html
  • polls/views.py:

    from django.shortcuts import render
    from .models import Question
    
    def index(request):
        latest_question_list = Question.objects.order_by('-pub_date')[:5]
        context = {'latest_question_list': latest_question_list}
        return render(request, 'polls/index.html', context)
    
  • polls/templates/polls/index.html:

    {% if latest_question_list %}
      <ul>
      {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
      {% endfor %}
      </ul>
    {% else %}
      <p>No polls are available.</p>
    {% endif %}
    
_images/img3-1-1.png

Note

Show questions on our polls page.

3-2. Polls Detail

Lab:

*** edit polls/urls.py
*** edit polls/models.py
*** edit polls/views.py
*** edit polls/templates/polls/index.html
*** add polls/templates/polls/detail.html
. go
  • polls/urls.py:

    from django.urls import path
    from . import views
    app_name = 'polls'
    
    urlpatterns = [
      path('', views.index, name='index'),
      path('<int:question_id>/', views.detail, name='detail')
    ]
    
  • polls/models.py:

    from django.db import models
    
    class Question(models.Model):
        question_text = models.CharField(max_length=200)
        pub_date = models.DateTimeField('date published')
        def __str__(self):
            return self.question_text
    
    class Choice(models.Model):
        question = models.ForeignKey(Question, on_delete=models.CASCADE)
        choice_text = models.CharField(max_length=200)
        votes = models.IntegerField(default=0)
        def __str__(self):
            return self.question.question_text+" "+self.choice_text
    
  • polls/views.py:

    from django.shortcuts import render,get_object_or_404
    from .models import Question
    
    def index(request):
        latest_question_list = Question.objects.order_by('-pub_date')[:5]
        context = {'latest_question_list': latest_question_list}
        return render(request, 'polls/index.html', context)
    
    def detail(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        return render(request, 'polls/detail.html', {'question': question})
    
  • polls/templates/polls/index.html:

    {% if latest_question_list %}
      <ul>
      {% for question in latest_question_list %}
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
      {% endfor %}
      </ul>
    {% else %}
      <p>No polls are available.</p>
    {% endif %}
    
  • polls/templates/polls/detail.html:

    <h1>{{ question.question_text }}</h1>
    <ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }}</li>
    {% endfor %}
    </ul>
    
_images/img3-2-1.png

3-3. Django import / export

Lab:

pip install django-import-export
pip freeze
*** edit mysite/setting.py
*** edit polls/admin.py
. go
  • mysite/setting.py:

    INSTALLED_APPS = (
      'import_export',
      'polls',
      ...
    
    ...
    STATIC_URL = '/static/'
    # https://tutorial.djangogirls.org/en/django_start_project/
    STATIC_ROOT = os.path.join(BASE_DIR, 'static')
    
  • polls/admin.py:

    from django.contrib import admin
    
    from import_export import resources
    from import_export.admin import ImportExportModelAdmin
    
    from .models import Question,Choice
    
    
    class QuestionResource(resources.ModelResource):
        class Meta:
            model = Question
    
    class QuestionAdmin(ImportExportModelAdmin):
        resource_class = QuestionResource
    
    admin.site.register(Question,QuestionAdmin)
    
    class ChoiceResource(resources.ModelResource):
        class Meta:
            model = Choice
    
    class ChoiceAdmin(ImportExportModelAdmin):
        resource_class = ChoiceResource
    
    admin.site.register(Choice,ChoiceAdmin)
    
_images/img3-3-1.png
_images/img3-3-2-v2.png

Writing your first Django app, part 4

4-1. Forms

Lab:

*** edit polls/urls.py
*** edit polls/views.py
*** edit polls/templates/polls/detail.html
*** add polls/templates/polls/vote.html
*** add polls/templates/polls/result.html
. go
  • polls/urls.py:

    from django.urls import path
    from . import views
    app_name = 'polls'
    
    urlpatterns = [
      path('', views.index, name='index'),
      path('<int:question_id>/', views.detail, name='detail'),
      path('<int:question_id>/vote/', views.vote, name='vote'),
      path('<int:question_id>/results/', views.results, name='results'),
    ]
    
  • polls/views.py:

    from django.http import HttpResponse, HttpResponseRedirect
    from django.shortcuts import get_object_or_404, render
    from django.urls import reverse
    
    from .models import Choice, Question
    
    def index(request):
        latest_question_list = Question.objects.order_by('-pub_date')[:5]
        context = {'latest_question_list': latest_question_list}
        return render(request, 'polls/index.html', context)
    
    def detail(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        return render(request, 'polls/detail.html', {'question': question})
    
    def vote(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        try:
            selected_choice = question.choice_set.get(pk=request.POST['choice'])
        except (KeyError, Choice.DoesNotExist):
            # Redisplay the question voting form.
            return render(request, 'polls/detail.html', {
                'question': question,
                'error_message': "You didn't select a choice.",
            })
        else:
            selected_choice.votes += 1
            selected_choice.save()
            # Always return an HttpResponseRedirect after successfully dealing
            # with POST data. This prevents data from being posted twice if a
            # user hits the Back button.
            return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
    
    def results(request, question_id):
        question = get_object_or_404(Question, pk=question_id)
        return render(request, 'polls/results.html', {'question': question})
    
  • polls/templates/polls/detail.html:

    <h1>{{ question.question_text }}</h1>
    
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    
    <form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
    <input type="submit" value="Vote">
    </form>
    
  • polls/templates/polls/results.html:

    <h1>{{ question.question_text }}</h1>
    
    <ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
    {% endfor %}
    </ul>
    
    <a href="{% url 'polls:detail' question.id %}">Vote again?</a>
    
_images/img4-1-1-v2.png
_images/img4-1-2-v2.png

Note

Able to vote

Writing your first Django app, part 5

5-1. Test

Lab:

*** edit mysite/urls.py
*** edit polls/tests.py
*** add polls/templates/index.html
(venv)$ python manage.py test
  • polls/urls.py:

    from django.contrib import admin
    from django.urls import path,include
    from django.views.generic import TemplateView
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('polls/', include('polls.urls')),
        path('', TemplateView.as_view(template_name="index.html"),name='index'),
    ]
    
  • polls/tests.py:

    from django.test import TestCase
    from django.utils import timezone
    from django.urls import reverse
    import datetime
    
    from .models import Question
    
    class QuestionModelTests(TestCase):
    
        def test_was_published_recently_with_future_question(self):
            """
            was_published_recently() returns False for questions whose pub_date
            is in the future.
            """
            time = timezone.now() + datetime.timedelta(days=30)
            future_question = Question(pub_date=time)
            self.assertIs(future_question.was_published_recently(), False)
    
        def test_hello_world(self):
            response = self.client.get(reverse('index'))
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, "Hello World!")
    
  • polls/templates/index.html:

    Hello World!
    
_images/img5-1-1.png
_images/img5-1-2.png

Note

Fix / with ‘Hello World!’ using CBV.

Writing your first Django app, part 6

6-1. Template Extending

Lab:

*** edit polls/static/polls/style.css
*** add polls/templates/base.html
*** edit polls/templates/index.html
*** edit polls/templates/detail.html
*** edit polls/templates/results.html
(venv)$ python manage.py test
  • polls/static/polls/style.css:

    body{
      margin-top: 12px;
    }
    
    li a {
        color: black;
    }
    
  • polls/templates/base.html:

    {% load static %}
    <!doctype html>
    <html lang="en">
      <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
        <link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
        <title>Lab</title>
      </head>
      <body>
        <div class="container">
          <h1><a href='/polls/'>Django2.1 Tutotrial Lab</a></h1>
              <div class="row">
                <div class="col-md-8">
                {% block content %}
                {% endblock %}
                </div>
            </div>
        </div>
        <!-- Optional JavaScript -->
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
      </body>
    </html>
    
  • polls/templates/index.html:

    {% extends 'polls/base.html' %}
    {% block content %}
    
    {% if latest_question_list %}
      <h3><ul>
      {% for question in latest_question_list %}
        <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
      {% endfor %}
    </ul></h3>
    {% else %}
      <p>No polls are available.</p>
    {% endif %}
    {% endblock %}
    
  • polls/templates/detail.html:

    {% extends 'polls/base.html' %}
    {% block content %}
    <h3>{{ question.question_text }}</h3>
    
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    
    <form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
    <input class='btn btn-success' type="submit" value="Vote">
    </form>
    {% endblock %}
    
  • polls/templates/results.html:

    {% extends 'polls/base.html' %}
    {% block content %}
    <h2>{{ question.question_text }}</h2>
    
    <ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
    {% endfor %}
    </ul>
    
    <a class='btn btn-success' href="{% url 'polls:detail' question.id %}">Vote again?</a>
    {% endblock %}
    
_images/img6-1-1.png
_images/img6-1-2.png
_images/img6-1-3.png

Warning

You might need to ‘Clear Browsing Data’ to let css working during development.

Want List

Hosting:

Why?

From Django tutorial, we learned

python manage.py startapp polls

Later on we still need to create urls.py for route. And, to be production ready, we also need to have templates/polls/base.html, as well as static/polls/style.css. Why not to provide a much ready templates for startapp?

Learn lower level python

Something we need to guess to assume first. Then to verify and to adjust.

My desired startapp

References:

python manage.py startapp

根據個人的開發經驗,urls.py, templates/{{app_name}}/base.html, 總是會用到的,為什麼不預設就有呢?

從文檔和代碼看目前做法

模版

_images/want001-01.png
_images/want001-02.png
What’s standard startapp doing?
111

create the folder

222

copy files

333

replace variable with app name

To understand Django

Prepare envrionment to study Django:

$ pwd
/Users/pinglingchen/ksndjs/django001/django
$ ls
django
$ cd django
$ git remote -v
origin      https://github.com/twoutlook/django.git (fetch)
origin      https://github.com/twoutlook/django.git (push)
$ cd ..
$ ls
django
$ python3.6 -m venv venv
$ . venv/bin/activate
(venv) $ pip freeze
(venv) $ pip install -e django
Obtaining file:///Users/pinglingchen/ksndjs/django001/django/django
Collecting pytz (from Django==2.2.dev20190102231945)
  Using cached https://files.pythonhosted.org/packages/f8/0e/2365ddc010afb3d79147f1dd544e5ee24bf4ece58ab99b16fbb465ce6dc0/pytz-2018.7-py2.py3-none-any.whl
Collecting sqlparse (from Django==2.2.dev20190102231945)
  Using cached https://files.pythonhosted.org/packages/65/85/20bdd72f4537cf2c4d5d005368d502b2f464ede22982e724a82c86268eda/sqlparse-0.2.4-py2.py3-none-any.whl
Installing collected packages: pytz, sqlparse, Django
  Running setup.py develop for Django
Successfully installed Django pytz-2018.7 sqlparse-0.2.4
You are using pip version 10.0.1, however version 18.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
(venv) $ pip freeze
-e git+https://github.com/twoutlook/django.git@b5fe97a34ea527d4254b58c2e828450e7c32157f#egg=Django
pytz==2018.7
sqlparse==0.2.4
You are using pip version 10.0.1, however version 18.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
(venv) $
_images/want002-01.png
want/_static/want002-02.png
python manage.py startapp having urls.py
_images/want003-01.png
_images/want003-02.png
_images/want003-03.png
want/_static/want003-04.png