Edited at

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ほどでここまでできたので、これを参考にしてもらたら半日でできる(と信じてます)

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