10 分で終わる Django の外部キーのチュートリアル

2020-04-01 作成: windows10/Python-3.8.2-amd64/Django-3.0.4

Django の外部キーについての日本語の情報は、ネットでも豊富ではありません。
しかし普通は、「好きな食べ物」と「住所録」は、別のアプリにするはずで、
外部キーの使い方は基本必修項目なのだろうと思います。

同じデータベース内と、外部のデータベースへの外部キーについて、
簡単なチュートリアルを作成しました。
複数のデータベースを相互に関連付ける方法を手軽に知りたい、という人向きです。

- ねこカフェ用のねこ管理アプリ cafe
 - ねこの名前のテーブルと、好きな食べ物のテーブルをつくり、外部キーで関連付ける。
- ねこの住所録のアプリ negura
 - ねこのすみかのデータベースを作る。cafe アプリから外部キーで呼ばれる。


なお、Django を初めて使う人は、まず基本編のチュートリアルを読んでください。
[10 分で終わる Django の実用チュートリアル](https://qiita.com/taiyaki/items/352d36cda37af74eb826)


# 前準備

## プロジェクトの作成
ソースを置きたい場所で以下を実行しプロジェクトを新規作成。

```
django-admin startproject mysite
```

作成したディレクトリ mysite に入ってアプリケーションを 2 つ、新規作成。

```
cd mysite
python manage.py startapp cafe
python manage.py startapp sumika
```

ここまでのファイルの配置はこうなる

```
mysite/
    mysite/
        __pycashe__/    <- 気にしなくていい
        setting.py, urls.py など *.py が 5 個
    neko/
        migrations/     <- 気にしなくていい
        models.py, views.py など *.py が 6 個
    sumika/
        migrations/     <- 気にしなくていい
        models.py, views.py など *.py が 6 個
    manage.py  
```

## プロジェクトを構成するアプリの登録

cafe と negura のふたつのアプリを登録。

```py:mysite/mysite/settings.py
INSTALLED_APPS = [
    'cafe.apps.CafeConfig',
    'negura.apps.NeguraConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
```

```py:mysite/mysite/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('cafe/', include('cafe.urls')),
    path('negura/', include('negura.urls')),
    path('admin/', admin.site.urls),
]
```


# アプリの実装

## cafe アプリの作成

### モデル、ビュー、url の作成

neko_food は、cafe アプリ内の同一データベース内での外部キー
neko_negura は、negura アプリ、つまり隣のアプリの別データベースへの外部キー

`def __str__(self):`メソッドは、ForeignKey を使ったとき、django の汎用ビューでは普通は必要になる。
このメソッドをオーバーライドしておかないと、ブラウザに表示したときに、
`さんま` ではなく `food_name.object(1)` のようにインスタンスの名前が表示されてしまう。
このメソッドのおかげで、CreateView や FormView を使うときに、プルダウンリストに文字列が表示される。

neko_negura は、外のアプリからモデルを引っ張ってきているので、
`negura.NeguraModel` と、アプリ名を先頭につけてからモデルを呼び出す。


```py:mysite/cafe/models.py
from django.db import models
from negura.models import NeguraModel

class NekoModel(models.Model):
    neko_name = models.CharField(max_length = 20)
    neko_food = models.ForeignKey('FoodModel', null = True,on_delete = models.SET_NULL)
    neko_negura = models.ForeignKey('negura.NeguraModel', null = True,on_delete = models.SET_NULL)


class FoodModel(models.Model):
    food_name = models.CharField(max_length = 20)
    def __str__(self):
        return self.food_name
```

どのビューがどのテンプレートを使うのか、ややこしくならないように、
くどいファイル名を付けている。

```py:mysite/cafe/views.py
from django.views import generic
from .models import NekoModel, FoodModel

class NekoListView(generic.ListView):
    model = NekoModel
    context_object_name = 'nekolistview_context'
    template_name = 'cafe/nekolistview_template.html'

class NekoCreateView(generic.CreateView):
    model = NekoModel
    context_object_name = 'nekocreateview_context'
    template_name = 'cafe/nekocreateview_template.html'
    fields = ['neko_name', 'neko_food', 'neko_negura']
    success_url = '/cafe/nekolist_url'

class FoodListView(generic.ListView):
    model = FoodModel
    context_object_name = 'foodlistview_context'
    template_name = 'cafe/foodlistview_template.html'
```

urls.py はファイルを新設する
どのパス名がどのビューを指すのか混乱しないように、くどい名前を付けている。

```py:mysite/cafe/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('nekolist_url', views.NekoListView.as_view(), name = 'nekolistview_path'),
    path('nekocreate_url', views.NekoCreateView.as_view(), name = 'nekocreateview_path'),
    path('foodlist_url', views.FoodListView.as_view(), name = 'foodlistview_path'),
]
```

食べ物や住所の入力用ビューは、チュートリアルの手数が多くなるので作成しない。
代わりに admin 画面から入力するため、admin.py を編集し、有効化する。

```py:mysite/cafe/admin.py
from cafe.models import NekoModel, FoodModel
from django.contrib import admin

admin.site.register(NekoModel)
admin.site.register(FoodModel)
```

### テンプレートの作成

テンプレートを 3 つ作成。
置き場所はデフォルトのディレクトリにしたが、階層が深すぎて本当は好きじゃない。

```html:mysite/cafe/template/cafe/nekolistview_template.html
<h1>ねこのいちらん</h1>
 <table>
  {% for neko_param in nekolistview_context %}
    <tr>
      <td>{{ neko_param.neko_name }}</td>
      <td>{{ neko_param.neko_food.food_name }}</td>
      <td>{{ neko_param.neko_negura.negura_name }}</td>
    </tr>
  {% endfor %}
</table>
<p><a href = "{% url 'foodlistview_path' %}">ねこのたべもの</a></p>
<p><a href = "{% url 'neguralistview_path' %}">ねこのねぐら</a></p>
<p><a href = "{% url 'nekocreateview_path' %}">ねこのとうろく</a></p>
```

```html:mysite/cafe/template/cafe/foodlistview_template.html
<h1>ねこのたべもの</h1>
<table>
  {% for food_param in foodlistview_context %}
    <tr>
      <td>{{ food_param.food_name }}</td>
    </tr>
  {% endfor %}
</table>
<p><a href = "{% url 'nekolistview_path' %}">ねこのいちらん</a></p>
```

```html:mysite/cafe/template/cafe/nekocreateview.html
<h1>ねこのとうろく</h1>
<form method = "post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type = "submit" value = "とうろく" />
</form>
<p><a href = "{% url 'nekolistview_path' %}">ねこのいちらん</a></p>
```

## negura アプリの作成

### モデル、ビュー、url の作成

基本的にはさきほどの cafe でやったことの繰り返し。

```py:mysite/negura/models.py
from django.db import models

class NeguraModel(models.Model):
    negura_name = models.CharField(max_length = 20)
    def __str__(self):
        return self.negura_name
```

```py:mysite/negura/views.py
from django.views import generic
from .models import NeguraModel

class NeguraListView(generic.ListView):
    model = NeguraModel
    context_object_name = 'neguralistview_context'
    template_name = 'negura/neguralistview_template.html'
```

```py:mysite/negura/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('neguralist_url', views.NeguraListView.as_view(), name = 'neguralistview_path'),
]
```

```py:mysite/negura/admin.py
from cafe.models import NeguraModel
from django.contrib import admin

admin.site.register(NeguraModel)
```

### テンプレートの作成

テンプレートを 1 つ作成
置き場所はデフォルトの場所で。

```html:mysite/negura/template/negura/neguralistview_template.html
<h1>ねこのねぐら</h1>
<table>
  {% for negura_param in neguralistview_context %}
    <tr>
      <td>{{ negura_param.negura_name }}</td>
    </tr>
  {% endfor %}
</table>
<p><a href = "{% url 'nekolistview_path' %}">ねこのいちらん</a></p>
```

# 仕上げ

## マイグレーションと admin の設定

マイグレーションしたあとは、admin サイト用の管理者を作成し、
admin 画面にアクセスできるようにしておく。

```
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
```

## HTTP サーバーの起動と初期データの入力

開発用のサーバーを起動する。デフォルトで起動しているので、ポート番号は 8000。

```
python manage.py runserver
```

ブラウザで以下にアクセスして admin 画面を出す。
「たべもの」、「すみか」のふたつは、このアプリでは直接入力できないので、admin 画面から入力する。

```
http://localhost:8000/admin/
```

たとえば、たべものは、「さんま」「かりかり」、すみかは、「だんぼーる」「もうふ」など。

## 動作確認

以下を入力すると、ねこのリストが出る。
最初は登録されたねこがいないので、表にはないっていない。

```
http://localhost:8000/neko/
```

「ねこのとうろく」リンクから登録用画面に入ると、
フォームにねこの名前を入力し、
「たべもの」と「ねぐら」はドロップダウンリストから選べるようになっている。


おしまい