Help us understand the problem. What is going on with this article?

1日で出来る!DjangoでCRUDを作る

More than 1 year has passed since last update.

はじめに

Vagrant+Python3+Django環境でHelloWorldの続編です。

Hello Worldを出したら、とりあえずデータ登録(C)、参照(R)、更新(U)、削除(D)をしたかったので、todoアプリ的なものを作りました。

ただただ、一覧表示、登録、更新・削除、をするアプリです。
うまくいけば、環境構築含め1日あれば作れる規模感です

基本的には、はじめての Django アプリ作成にそって進めます(多少アレンジが入ります)

アプリケーションをはじめよう

todoアプリケーションを作成する

※python3で実行しているので注意!!

[vagrant@localhost mysite]$ pwd
/home/vagrant/app/mysite
[vagrant@localhost mysite]$ python3 manage.py startapp todos
[vagrant@localhost mysite]$ ls -ltr todos/
total 20
-rw-r--r--. 1 vagrant vagrant  63 Jul 30 16:31 views.py
-rw-r--r--. 1 vagrant vagrant  60 Jul 30 16:31 tests.py
-rw-r--r--. 1 vagrant vagrant  57 Jul 30 16:31 models.py
drwxr-xr-x. 1 vagrant vagrant 102 Jul 30 16:31 migrations
-rw-r--r--. 1 vagrant vagrant   0 Jul 30 16:31 __init__.py
-rw-r--r--. 1 vagrant vagrant  85 Jul 30 16:31 apps.py
-rw-r--r--. 1 vagrant vagrant  63 Jul 30 16:31 admin.py

ちなみに、「プロジェクト(mysite)」と「アプリケーション(todos)」の違いはこんな感じ

プロジェクトとアプリケーション

プロジェクトとアプリケーションの違いとは何でしょうか?アプリケーションとは、実際に何らかの処理を行う Web アプリケーションを指します。例えばブログシステムや公開レコードのデータベース、単純な投票アプリといった具合です。プロジェクトとは、あるウェブサイト向けに設定とアプリケーションを集めたものです。一つのプロジェクトには複数のアプリケーションを入れられ ます。また、一つのアプリケーションは複数のプロジェクトで使えます。

アプリケーションができたところで、新しい画面をつくってみよう。

まずはtodos/views.pyを作成

todos/views.py
from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world! new page!")

続いて、todos/urls.pyを作成し、urlとviews.pyをマッピング

todos/urls.py
from django.urls import path
from . import views

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

最後に、mysite/urls.pyを修正して、プロジェクト(mysite)のurlマッピングに上記を追加する

mysite/urls.py
from django.contrib import admin
from django.urls import include, path ## ここに「include」を追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('todos/', include('todos.urls')), ## ここを追加
]

これで、動作確認する。

python3 manage.py runserver 0:8000を実行してから、http://localhost:18000/todos/を確認

寂しい画面でHello, world! new page!って出たと思います。

DataBaseを利用しよう

今回は楽をしてSQLiteを使います(デフォルト設定でSQLiteになっています)

え?ホント?って方はmysite/setting.pyを確認。

以下みたいに'ENGINE': 'django.db.backends.sqlite3',ってなっているはず。

mysite/setting.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

ちょっと試しにマイグレーションしてみる(デフォルトの設定が読み込まれる)

[vagrant@localhost mysite]$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying sessions.0001_initial... OK

migratemysite/setting.py INSTALLED_APPSの設定を読み込んで、プロジェクトで必要なデータベースを作ってくれます。

では、今回作りたいモデルを作成します

todos/models.py
from django.db import models

# Create your models here.

class Todo(models.Model):
    todo_id = models.CharField(unique=True, max_length=5)
    title = models.CharField(max_length=50)
    main_text = models.CharField(max_length=300)
    update_date = models.DateTimeField('date published')

※fieldのリファレンスはModel field referenceを参考ください

またまた、mysite/setting.pyを修正して、上記をプロジェクトに反映

mysite/setting.py
INSTALLED_APPS = [
    'todos.apps.TodosConfig', ## ここを追加
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

テーブルをつくる(モデルの変更分を反映)

[vagrant@localhost mysite]$ python3 manage.py makemigrations todos
Migrations for 'todos':
  todos/migrations/0001_initial.py
    - Create model Todo

これでテーブルができた!!

CRUDを作ろう

まずはAPIを利用して、いろいろDBを操作してみよう。

python3 manage.py shellでPython 対話シェルを起動。

[vagrant@localhost mysite]$ python3 manage.py shell
Python 3.7.0 (default, Jul 26 2018, 15:06:09) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from todos.models import Todo
>>> from django.utils import timezone
>>> 
>>> Todo.objects.all()
<QuerySet []>
>>> 
>>> t = Todo(todo_id=1, title="hoge", main_text="fuga", update_date=timezone.now())
>>> t.save()
>>> Todo.objects.all()
<QuerySet [<Todo: Todo object (1)>]>
>>> 
>>> t1 = Todo.objects.get(todo_id=1)
>>> t1.title = 'hogehoge'
>>> t1.save()
>>>   
>>> t2 = Todo.objects.get(todo_id=1)
>>> t2.title
'hogehoge'
>>> 
>>> t2.delete()
(1, {'todos.Todo': 1})
>>> 
>>> Todo.objects.all()
<QuerySet []>
>>> 

これで参照、登録、更新、削除が一式できた

  1. Todo.objects.all()で全件取得、Todo.objects.get(todo_id=1)でtodo_id=1と一致するものを取得。
  2. Todo(todo_id=1, title="hoge", main_text="fuga", update_date=timezone.now()).save()で登録。重複していたら更新になる。(上記例では、変数においてからsave()している)
  3. Todo.objects.get(todo_id=1).delete()で削除

これをアプリケーションへ反映

todos/views.pyでCRUD用の画面を作成

todos/views.py
from django.http import HttpResponse
from django.utils import timezone
from todos.models import Todo

def index(request):
    return HttpResponse("Hello, world! new page!")

def create(request):
    t = Todo(todo_id=1, title="hoge", main_text="fuga", update_date=timezone.now())
    t.save()
    return HttpResponse("insert!")

def read(request):
    t = Todo.objects.get(todo_id=1)
    return HttpResponse('todo_id:%s,  title:%s, main_text:%s.' % (t.todo_id, t.title, t.main_text))

def update(request):
    t = Todo.objects.get(todo_id=1)
    t.title = 'changed!'
    t.save()
    return HttpResponse('update!')

def delete(request):
    t = Todo.objects.get(todo_id=1)
    t.delete()
    return HttpResponse('delete!')

もちろん、todos/urls.pyも修正

todos/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('create', views.create, name='create'),
    path('read', views.read, name='read'),
    path('update', views.update, name='update'),
    path('delete', views.delete, name='delete'),
]

これでアプリケーションを起動(python3 manage.py runserver 0:8000

以下でそれぞれがちゃんと動いていることを確認

  1. http://localhost:18000/todos/createinsert!と寂しく表示(裏でデータが登録されてる)
  2. http://localhost:18000/todos/readtodo_id:1, title:hoge, main_text:fuga.と出力。さっき登録したデータが表示できた
  3. http://localhost:18000/todos/updateupdate!と寂しく表示(裏でデータが更新されてる)
  4. http://localhost:18000/todos/readtodo_id:1, title:changed!, main_text:fuga.と出力。更新されていることを確認
  5. http://localhost:18000/todos/deletedelete!と寂しく表示(裏でデータが削除されてる)
  6. http://localhost:18000/todos/read → 対象のデータがなくてエラー
  7. http://localhost:18000/todos/create → 次の内容のためにInsertしておく

Templatesを利用しよう

さすがに雑すぎるので、このCRUDを(cssあてたら)ちゃんとした画面にする

todos/views.py
from django.http import HttpResponse
from django.utils import timezone
from django.shortcuts import render
from todos.models import Todo

def index(request):
    todo_list = Todo.objects.all()
    context = {'todo_list': todo_list}
    return render(request, 'todos/index.html', context)

これで、全件データ取得して、そのデータをtodo_listというキー値でテンプレートに渡す感じ

では、テンプレート側を作成する。

新規でtodos/templates/todos/index.htmlを作る

todos/templates/todos/index.html
<html>
<head></head>
<body>
  <a href="/todos/new/">登録</a>
  {% if todo_list %}
    <table>
      <tr>
        <td>id</td>
        <td>title</td>
        <td></td>
      </tr>
      {% for todo in todo_list %}
      <tr>
        <td><a href="/todos/{{ todo.todo_id }}/">{{ todo.todo_id }}</a></td>
        <td>{{ todo.title }}</td>
        <td><a href="/todos/delete/{{ todo.todo_id }}/">削除</a></td>
      </tr>
      {% endfor %}
    </table>
  {% else %}
      <p>No todos are available.</p>
  {% endif %}
  </body>
</html>

こんな感じ

  1. {% if todo_list %}todo_listが空じゃなければ中身の処理を実施
  2. {% for todo in todo_list %}これでfor文を実行(1つ1つをtodoという変数においている)
  3. {{ todo.todo_id }}で値の出力

テンプレートとの紐付けもできた!

CRUDを完成させよう

今まで使ったもの+αでCRUDを完成させる

まずはtodos/urls.pytodos/views.pyで一式画面紐付けさせる

todos/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('new/', views.new, name='new'),
    path('add/', views.add, name='add'),
    path('<int:todo_id>/', views.detail, name='detail'),
    path('add/<int:todo_id>/', views.update, name='update'),
    path('delete/<int:todo_id>/', views.delete, name='delete'),
]

<int:todo_id>と書けば、パラメータを渡せる。views.pyを見ればイメージつくはず。

続いて

todos/views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.utils import timezone
from django.urls import reverse
from django.shortcuts import render
from todos.models import Todo, TodoForm

def index(request):
    todo_list = Todo.objects.all()
    context = {'todo_list': todo_list}
    return render(request, 'todos/index.html', context)

def new(request):
    return render(request, 'todos/new.html')

def add(request):
    t1 = Todo()
    t1.todo_id = len(Todo.objects.order_by('-todo_id'))+1
    t1.update_date = timezone.now()
    t = TodoForm(request.POST, instance=t1)
    t.save()
    return HttpResponseRedirect(reverse('index'))

def detail(request, todo_id):
    todo = Todo.objects.get(todo_id=todo_id)
    context = {'todo': todo}
    return render(request, 'todos/new.html', context)

def update(request, todo_id):
    t1 = Todo.objects.get(todo_id=todo_id)
    t = TodoForm(request.POST, instance=t1)
    t.save()
    return HttpResponseRedirect(reverse('index'))

def delete(request, todo_id):
    t = Todo.objects.get(todo_id=todo_id)
    t.delete()
    return HttpResponseRedirect(reverse('index'))

新しい内容をつらつらと書いていくと、

  1. HttpResponseRedirect → 登録完了後にリダイレクトする(reverseを使ってurls.pyのnameと紐づけている)
  2. TodoFormformmodelの結びつける。(使ってみたものの、使いこなせなかった。。。)
  3. def detail(request, todo_id):urls.py<int:todo_id>と結びつける。urlをパラメータとして利用できる
  4. request.POST → POSTデータを利用できる。request.POST['key値']とすればパラメータを取得できるが、そのkey値がないとErrorになるので注意

ということで、新しく出てきたtodos/models.pytodos/templates/todos/new.htmlを実装して終了

todos/models.py
from django.db import models
from django.forms import ModelForm

# Create your models here.

class Todo(models.Model):
    todo_id = models.CharField(primary_key=True, max_length=5)
    title = models.CharField(max_length=50)
    main_text = models.CharField(max_length=300)
    update_date = models.DateTimeField('date published')

class TodoForm(ModelForm):
    class Meta:
        model = Todo
        fields = ['todo_id', 'title', 'main_text', 'update_date']
        exclude = ['todo_id', 'update_date']

exclude = ['todo_id', 'update_date']を指定することで画面に表示されないデータを表現できる、チェックを除外できる
とあるが、よくわかっていない。。。

最後にnew.htmlを作る

todos/templates/todos/new.html
<html>
<head></head>
<body>
  <form action="/todos/add/{% if todo %}{{ todo.todo_id }}/{% endif %}" method="post">
    {% csrf_token %}
    title: <input type="text" name="title" value="{{ todo.title }}"/>
    main: <input type="text" name="main_text" value="{{ todo.main_text }}"/>
    <input type="hidden" name="todo_id" value="{{ todo.todo_id }}"/>
    <input type="submit" value="登録"/>
  </form>
</body>
</html>

これで一式できたので、ポチポチしてみてください!

おわりに

最初にも書いた通り以下に沿って作っています。

なので、今これを見るとすんなり入ってくるのでは?と思います。

  1. はじめての Django アプリ作成、その 1
  2. はじめての Django アプリ作成、その 2
  3. はじめての Django アプリ作成、その 3
  4. はじめての Django アプリ作成、その 4

※まだ、一部(ModelFormなど)使いこなせていないので、この辺りは別途整理しようと思っています。

結構大変だったな、と思いつつも3〜5hほどでここまでできたので、これを参考にしてもらたら半日でできる(と信じてます)

まだまだ、勉強中なので、誤りとか、要望とかあればご指摘いただけると嬉しいです!

192_60_33_2
最近はプロジェクト管理がメイン。開発したいなと思ってる今日この頃
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away