end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

django for python 入門

djangoを殆ど触ったことがない為、参考urlの内容を写経してみました

目次

参考url

python仮想環境作成と、djangoのインストール

$ cat /etc/redhat-release 
Red Hat Enterprise Linux release 9.1 (Plow)

$ which python
~/.pyenv/shims/python

$ python --version
Python 3.9.14

$ pwd
/home/end0tknr/proj

$ python -m venv django
$ source ./bin/activate

$ pip install django
  :
Successfully installed asgiref-3.6.0 django-4.1.7 sqlparse-0.4.3

django プロジェクトの作成

設定dir名がプロジェクト名となる為、一旦、 config という名でプロジェクト作成し、その後、名称変更

$ cd /home/end0tknr/proj/django

$ django-admin startproject config
$ mv config myproj
$ cd myproj

$ tree
├ config
│ ├ asgi.py
│ ├ settings.py
│ ├ urls.py
│ └ wsgi.py
└ manage.py

付属サーバによる接続確認

vi config/settings.py

ALLOWED_HOSTS = ['*']    # old) ALLOWED_HOSTS = []

TIME_ZONE = 'Asia/Tokyo' # old) TIME_ZONE = 'UTC'

付属サーバ起動と、接続確認

$ python3 manage.py runserver 0.0.0.0:8080

上記を実行後、ブラウザでアクセスすると、以下が表示

簡易アプリ作成 - その1. VIEW

manage.py startapp $app_name

$ cd /home/end0tknr/proj/django/myproj
$ python3 manage.py startapp books

$ tree
├ books          ## NEW
│ ├ admin.py    ## 〃
│ ├ apps.py     ## 〃
│ ├ migrations/ ## 〃
│ ├ models.py   ## 〃
│ ├ tests.py    ## 〃
│ └ views.py    ## 〃
├ config
│ ├ asgi.py
│ ├ settings.py
│ ├ urls.py
│ └ wsgi.py
├ db.sqlite3
└ manage.py

config/settings.py 修正

INSTALLED_APPS = [
    'books',  ## ADD
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

アプリ用ルーティング設定

config/urls.py 修正

from django.contrib import admin
from django.urls    import path, include ## ADD

urlpatterns = [
    path('admin/', admin.site.urls),
    path('books/', include('books.urls')), ## ADD
]

books/urls.py 追加

from django.urls import path
from .           import views

# 名前空間:reverseメソッドやurlタグから
#          「<名前空間名>:<名前空間内のname>」でurlで逆引きできる
app_name = 'books'

urlpatterns = [
    path('', views.list_books, name='list_books' ),
]

books/views.py 変更

from django.shortcuts import render
from django.http import HttpResponse

def list_books(request):
    return HttpResponse("Hello world!")

付属サーバ起動と、動作確認

$ python3 manage.py runserver 0.0.0.0:8080

上記を実行後、http://localhost:8080/books/ へ、 ブラウザでアクセスすると、「Hello world!」と表示されれば、OK

簡易アプリ作成 - その2. MODEL

MODELクラス作成 - books/models.py の変更

from django.db import models

class Book(models.Model):
    book_id = models.CharField(max_length=32)
    title   = models.CharField(max_length=256)
    author  = models.CharField(max_length=256)

    def __str__(self):
        return self.title

DBマイグレーション ファイル作成

$ python3 manage.py makemigrations
Migrations for 'books':
  books/migrations/0001_initial.py
    - Create model Book

$ less books/migrations/0001_initial.py

from django.db import migrations, models

class Migration(migrations.Migration):
    initial = True
    dependencies = []
    operations = [
        migrations.CreateModel(
            name='Book',
            fields=[
                ('id', models.BigAutoField(auto_created=True,
                                           primary_key=True,
                                           serialize=False,
                                           verbose_name='ID')),
                ('book_id',models.CharField(max_length=32)),
                ('title',  models.CharField(max_length=256)),
                ('author', models.CharField(max_length=256)),
            ],
        ),
    ]

DBマイグレート実行

$ python3 manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, books, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  【略】
  Applying books.0001_initial... OK
  Applying sessions.0001_initial... OK

DBスキーマ確認

$ python3 manage.py dbshell
SQLite version 3.34.1 2021-01-20 14:10:07

sqlite> .tables
auth_group                  books_book                
auth_group_permissions      django_admin_log          
auth_permission             django_content_type       
auth_user                   django_migrations         
auth_user_groups            django_session            
auth_user_user_permissions

sqlite> .schema books_book
CREATE TABLE IF NOT EXISTS "books_book" (
  "id"      integer      NOT NULL PRIMARY KEY AUTOINCREMENT,
  "book_id" varchar(32)  NOT NULL,
  "title"   varchar(256) NOT NULL,
  "author"  varchar(256) NOT NULL );

付属する管理ツール

管理USER作成

$ python3 manage.py createsuperuser
Username (leave blank to use 'end0tknr'): 
Email address: 【空でもOK】
Password: 
Password (again): 
Superuser created successfully.

管理対象MODEL追加 - books/admin.py の修正

from django.contrib import admin
from .models import Book

admin.site.register(Book)

管理ツールによるDB編集

$ python3 manage.py runserver 0.0.0.0:8080

上記を実行し、http://localhost:8080/admin/ へ、アクセスすると、 次のような画面が表示され、ログイン後、dbを編集できます

HTMLテンプレート

テンプレート用DIRの追加 - config/settings.py の修正

import os                       ## ADD

TEMPLATES = [
    {'BACKEND': 'django.template.backends.django.DjangoTemplates',
     'DIRS': [os.path.join(BASE_DIR, 'templates')],  ## CHANGE
     }]

テンプレート用DIRの追加 - mkdir templates

$ cd /home/end0tknr/proj/django/myproj

mkdir -p templates
mkdir -p templates/books

$ tree
├ books
│ ├ admin.py
│ ├ apps.py
│ ├ migrations
│ │ └ 0001_initial.py
│ ├ models.py
│ ├ tests.py
│ ├ urls.py
│ └ views.py
├ config
│ ├ asgi.py
│ ├ settings.py
│ ├ urls.py
│ └ wsgi.py
├ db.sqlite3
├ manage.py
└ templates    ## NEW
   └ books     ## 〃

テンプレート ファイル作成

$ vi templates/layout.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
</head>
<body>
  <h1>{{ title }}</h1>
  {% block content %}{% endblock %}
</body>
</html>

$ vi templates/books/list_books.html

{% extends 'layout.html' %}
{% block content %}

<table>
  <thead>
    <tr>
      <th>Book ID</th>
      <th>Title</th>
      <th>Author</th>
      <th>Action</th>
    </tr>
  </thead>
  <tbody>
    {% if books %}
      {% for book in books %}
        <tr>
          <td><a href="/books/{{ book.book_id }}">{{ book.book_id }}</a></td>
          <td>{{ book.title }}</td>
          <td>{{ book.author }}</td>
          <td><a href="/books/{{ book.book_id }}/edit">[Edit]</a></td>
        </tr>
      {% endfor %}
    {% else %}
      <tr>
        <td colspan=4>No books.</td>
      </tr>
    {% endif %}
  </tbody>
</table>

{% endblock %}

VIEWメソッドの追加 - books/views.py の変更

from django.shortcuts import render
from django.http      import HttpResponse
from django.template  import loader
from .models          import Book

# def list_books(request):
#    return HttpResponse("Hello world!")

def list_books(request):
    books = Book.objects.all()
    context = {
        'title': 'List Books',
        'books': books,
    }
    template = loader.get_template('books/list_books.html')
    return HttpResponse(template.render(context, request))

付属サーバによる動作確認

$ python3 manage.py runserver 0.0.0.0:8080

上記を実行後、http://localhost:8080/books/ へ、 ブラウザでアクセスすると、先程、管理ツールで登録したものが表示されます

DB変更 - CRUD

編集用URL追加 - books/urls.py

from django.urls import path
from .           import views

# 名前空間:reverseメソッドやurlタグから
#          「<名前空間名>:<名前空間内のname>」でurlで逆引きできる
app_name = 'books'

urlpatterns = [
    path('',                  views.list_books, name='list_books' ),
    path('<str:book_id>',     views.detail_book,name='detail_book'),
    path('<str:book_id>/edit',views.edit_book,  name='edit_book'  ), ## ADD
]

編集用テンプレート作成 - templates/books/edit_book.html

※ 以下にある「{% url 'books:edit_book' book.book_id %}」は、 「/books/1/edit」のように実体化されます。

{% extends 'layout.html' %}
{% block content %}
<form method="POST" action="{% url 'books:edit_book' book.book_id %}">
  {% csrf_token %}
  <input type="hidden" name="mode" value="{{ mode }}">
  <table>
    <tr>
      <th>Book ID</th>
      <td><input type="text" name="book_id" readonly value="{{ book.book_id }}"></td>
    </tr>
    <tr>
      <th>Title</th>
      <td><input type="text" name="title"
          {% if mode != 'input' %}readonly{% endif %} value="{{ book.title }}"></td>
    </tr>
    <tr>
      <th>Author</th>
      <td><input type="text" name="author"
          {% if mode != 'input' %}readonly{% endif %} value="{{ book.author }}"></td>
    </tr>
  </table>
  <div class="basic-block">
    {% if mode == 'input' %}
      <button type="button" onclick="location.href='{% url 'books:list_books' %}'">Return</button>
      <button type="submit">OK</button>
    {% elif mode == 'confirm' %}
      <button type="button" onclick="history.back()">Back</button>
      <button type="submit">OK</button>
    {% elif mode == 'result' %}
      <button type="button" onclick="location.href='{% url 'books:list_books' %}'">Return</button>
    {% endif %}
  </div>
</form>
{% endblock %}

CRUD処理追加 - books/views.py

def edit_book(request, book_id):
    if request.method == 'GET':
        return edit_book_input(request, book_id)
    
    if request.method == 'POST':
        if request.POST['mode'] == 'input':
            return edit_book_confirm(request, book_id)
        if request.POST['mode'] == 'confirm':
            return edit_book_result(request, book_id)

def edit_book_input(request, book_id):
    try:
        book = Book.objects.get(book_id=book_id)
    except Book.DoesNotExist:
        book = None

    context = {
        'title': 'Edit Book(input)',
        'mode': 'input',
        'book': book,
    }
    template = loader.get_template('books/edit_book.html')
    return HttpResponse(template.render(context, request))

def edit_book_confirm(request, book_id):
    book = Book()
    book.book_id = request.POST['book_id']
    book.title = request.POST['title']
    book.author = request.POST['author']

    context = {
        'title': 'Edit Book(confirm)',
        'mode': 'confirm',
        'warning_message': 'Are you sure you want to save?',
        'book': book,
    }
    template = loader.get_template('books/edit_book.html')
    return HttpResponse(template.render(context, request))

def edit_book_result(request, book_id):
    try:
        book = Book.objects.get(book_id=book_id)
        book.book_id = request.POST['book_id']
        book.title   = request.POST['title']
        book.author  = request.POST['author']
        book.save() ## 保存
    except Book.DoesNotExist:
        book = None

    context = {
        'title': 'Edit Book(result)',
        'mode': 'result',
        'success_message': 'Success!',
        'book': book,
    }
    template = loader.get_template('books/edit_book.html')
    return HttpResponse(template.render(context, request))

付属サーバによる動作確認

$ python3 manage.py runserver 0.0.0.0:8080

上記を実行後、http://localhost:8080/books/ へ、 アクセスすると、以下の画面で、DB変更を確認できます

静的ファイル

静的ファイル用DIR追加 - config/settings.py の変更

STATIC_URL = 'static/'
STATICFILES_DIRS = ( os.path.join(BASE_DIR, 'static'), )

静的ファイル用DIR追加 - mkdir static

$ cd /home/end0tknr/proj/django/myproj

$ mkdir -p static/js
$ mkdir -p static/css
$ mkdir -p static/images

$ tree
├ books
│ ├ admin.py
│ ├ apps.py
│ ├ migrations
│ │ └ 0001_initial.py
│ ├ models.py
│ ├ tests.py
│ ├ urls.py
│ └ views.py
├ config
│ ├ asgi.py
│ ├ settings.py
│ ├ urls.py
│ └ wsgi.py
├ db.sqlite3
├ manage.py
├ static       ## NEW
│ ├ css       ## 〃
│ ├ images    ## 〃 
│ └ js        ## 〃
└ templates
    ├ books
    │ └ list_books.html
    └ layout.html

静的ファイルの作成

$ vi static/css/style.css

table    {      border-collapse: collapse; margin-bottom: .5rem; }
table th,
table td {      border: 1px solid #999; padding: .1rem .3rem; }
table th {      background-color: #ddd; }
button   {      line-height: 1.2rem; min-width: 6rem; }

$ vi templates/layout.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ title }}</title>
<link rel="stylesheet" href="/static/css/style.css"> <!--ADD-->
</head>
<body>
  <h1>{{ title }}</h1>
  {% block content %}{% endblock %}
</body>
</html>

付属サーバによる動作確認

$ python3 manage.py runserver 0.0.0.0:8080

上記を実行後、http://localhost:8080/books/ へ、 アクセスすると、以下が表示