アプリの概要
お勧めのお店を投稿できる「ランチマップ」アプリの機能について整理します。
ルーティング
ルート | ビュー | 表示ページ |
---|---|---|
(ドメイン名)/lunchmap | IndexView.as_view() | 一覧 |
(ドメイン名)/lunchmap/(id) | DetailView.as_view() | 詳細 |
(ドメイン名)/lunchmap/create | CreateView.as_view() | 新規 |
(ドメイン名)/lunchmap/(id)/update | UpdateView.as_view() | 更新 |
(ドメイン名)/lunchmap/(id)/delete | DeleteView.as_view() | 削除 |
(ドメイン名)/lunchmap/admin | admin.site.urls | 管理サイト |
プロジェクトの準備
Djangoでは、機能別にアプリケーションを用意して、それを1つのプロジェクトにまとめていきます。
Djangoのプロジェクト
Djangoのプロジェクトは、アプリケーションをまとめる単位で、あるウェブサイト向けに設定とアプリケーションを集めたものです。Djangoでは、機能別にアプリケーションを用意して、それを1つのプロジェクトにまとめていきます。
プロジェクトの作成
$ python --version # Pythonのバージョン確認 Python 3.6.7 $ python -m django --version # Djangoのバージョン確認 2.1.3 $ django-admin startproject myapp # myappプロジェクトの作成
paiza cloud用にsettings.pyを修正
# myapp/myapp/settings.py # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ['*'] # どのホストからもアクセス可能に設定
DjangoのWebページにアクセス
$ cd myapp $ python manage.py runserver # Webサーバを起動
https://localhost:8000/で、DjangoのWebサーバーにアクセスできます。
DjangoのWebサーバを停止
「ctrl+c」キーでWebサーバを停止できます。
アプリケーションの作成
$ python manage.py startapp lunchmap
アプリケーションの登録
# myapp/myapp/settings.py INSTALLED_APPS = [ 'lunchmap.apps.LunchmapConfig', # apps.pyのLunchmapConfigを登録 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]
データベースのモデルの作成
ShopとCategoryのモデルを作成(カテゴリidを紐付け)して、マイグレーションでデータベースに適用していきます。
ショップid | id |
---|---|
店名 | name |
住所 | address |
カテゴリid | category |
投稿者id | author |
作成日時 | created_at |
更新日時 | updated_at |
カテゴリid | id |
---|---|
カテゴリ名 | name |
投稿者id | author |
作成日時 | created_at |
更新日時 | updated_at |
投稿者id | id |
---|---|
カテゴリ名 | user |
settings.pyでデータベースを設定
phpMyAdminで、lunchmapdbを作成しておきます。
※ MySQLのインストールがまだの場合、下記を参考にインストール
【Ubuntu 18.04 LTS】MySQLサーバを動かす
# myapp/myapp/settings.py # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases # Python3以降はmysqlclientを使用する #import pymysql # 追加 #pymysql.install_as_MySQLdb() # 追加 # プロジェクトからDBへの接続設定 DATABASES = { 'default': { # 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 'ENGINE': 'django.db.backends.mysql', 'NAME': 'lunchmapdb', 'USER': 'root', 'PASSWORD': 'XXXX', 'HOST': '127.0.0.1', 'PORT': '3306', } }
※ mysqlclientのインストールは、mysqlclient Python3でMySQLに接続 を参照
models.pyを記述
# myapp/lunchmap/models.py from django.db import models class Category(models.Model): # Categoryモデルの定義 name = models.CharField(max_length=255) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.name class Shop(models.Model): # Shopモデルの定義 name = models.CharField(max_length=255) address = models.CharField(max_length=255) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) # CategoryモデルとShopモデルの関連付け category = models.ForeignKey( Category, on_delete=models.PROTECT, ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.name
マイグレーションファイルの作成と実行
$ python manage.py makemigrations lunchmap # マイグレーションファイルの作成 Migrations for 'lunchmap': lunchmap/migrations/0001_initial.py - Create model Category - Create model Shop $ python manage.py migrate # マイグレーションの実行 Operations to perform: Apply all migrations: admin, auth, contenttypes, lunchmap, sessions Running migrations: Applying contenttypes.0001_initial... OK :
Djangoの管理サイトの設定
管理サイトを使用して、データベースやユーザー情報を簡単に設定することができます。
admin.pyにモデルを登録
# myapp/lunchmap/admin.py from django.contrib import admin from .models import Category, Shop admin.site.register(Category) admin.site.register(Shop)
管理サイトにアクセスする
$ python manage.py runserver # Webサーバを起動
https://localhost:8000/adminで、管理サイトにアクセスできます。
管理ユーザをコマンドで登録
$ cd myapp $ python manage.py createsuperuser Username: admin Email address: admin@XXXXX.jp Password: ********** Password (again): ********* Superuser created successfully.
お店一覧ページの作成
テンプレートにデータを供給するのはViewです。手短にコーディングできるクラスベース汎用ビューを使用して、登録しておいたサンプルデータを一覧表示していきます。
プロジェクトのurls.pyを記述
# myapp/myapp/urls.py from django.contrib import admin from django.urls import path, include from django.views.generic import RedirectView urlpatterns = [ path('admin/', admin.site.urls), # lunchmapのルートにアクセスした場合、lunchmapディレクトリのurls.pyを呼び出す path('lunchmap/', include('lunchmap.urls')), path('', RedirectView.as_view(url='/lunchmap/')), ]
lunchmapのurls.pyを作成
# myapp/lunchmap/urls.py from django.urls import path from . import views app_name = 'lunchmap' urlpatterns = [ # lunchmapの後に何も追加されてなかったら、views.IndexView.as_view()を呼び出す path('', views.IndexView.as_view(), name='index'), ]
views.pyにindexViewクラスを記述
# myapp/lunchmap/views.py from django.urls import reverse_lazy from django.views import generic # クラスベース汎用ビューを使用 from .models import Category, Shop # IndexViewクラスを作成 class IndexView(generic.ListView): # モデルで取り出したデータで一覧ページを作成 model = Shop
テンプレートを作成
<!-- myapp/lunchmap/templates/lunchmap/shop_list.html --> < !DOCTYPE html> <html> <head> <meta charset='utf-8'/> <title>Lunchmap</title> <style>body {padding: 10px;}</style> </head> <body> <h1>お店一覧</h1> {% for shop in object_list %} <p> {{ shop.category.name }}, {{ shop.name }}</p> {% endfor %} </body> </html>
共通テンプレートにBootstrapの導入
CSSフレームワークのBootstrapを導入して、ナビゲーションバーを追加していきます。
base.html(共通テンプレート)を作成
<!-- myapp/lunchmap/templates/lunchmap/base.html --> < !DOCTYPE html> <html> <head> <meta charset='utf-8'/> <!-- bootstrapはインタネット上にライブラリとして公開されているので、直接読み込む --> <meta name='viewport' content='width=device-width, initial-scale=1, shrink-to-fit=no'/> <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css'/> <style>body {padding-top: 80px;}</style> <title>Lunchmap</title> </head> <body> <!-- ナビゲーションバーを設定 --> <nav class='navbar navbar-expand-sm navbar-dark bg-dark fixed-top'> Lunchmap </nav> <div class='container'> {% block content %} <!-- contentというブロックを指定 --> {% endblock %} </div> </body> </html>
お店一覧テンプレートを修正
<!-- myapp/lunchmap/templates/lunchmap/shop_list.html --> {% extends './base.html' %} <!-- 以下のブロックの内容をbase.htmlに当てはめる --> {% block content %} <h1>お店一覧</h1> {% for shop in object_list %} <p>{{ shop.name }}, {{ shop.category.name }}</p> {% endfor %} {% endblock %}
お店の詳細ページの作成
詳細ページも、クラスベース汎用ビューを使うことで、少ないコードで記述できます。
lunchmapのurls.pyに追記
# myapp/lunchmap/urls.py urlpatterns = [ path('', views.IndexView.as_view(), name='index'), # lunchmapの後にidを指定すると、DetailViewクラスを呼び出してお店の個別ページを表示します path('<int:pk>/', views.DetailView.as_view(), name='detail'), ]
views.pyにDetailViewクラスを記述
# myapp/lunchmap/views.py from django.urls import reverse_lazy from django.views import generic from .models import Shop class IndexView(generic.ListView): model = Shop # 以下を追加 class DetailView(generic.DetailView): model = Shop
詳細ページのテンプレートを作成
<!-- myapp/lunchmap/templates/lunchmap/shop_detail.html --> {% extends './base.html' %} {% block content %} <h1>{{ shop.name }}</h1> <div> <p>{{ shop.category.name }}</p> <p>{{ shop.address }}</p> </div> <div> 一覧 </div> {% endblock %}
お店一覧ページをテーブルにして、詳細ページにリンクする
<!-- myapp/lunchmap/templates/lunchmap/shop_list.html --> {% extends './base.html' %} {% block content %} <h1>お店一覧</h1> <table class='table table-striped table-hover'> <tr> <th>カテゴリ</th><th>店名</th><th>住所</th> </tr> {% for shop in object_list %} <tr> <td>{{ shop.category.name }}</td> <td><a href='{% url "lunchmap:detail" shop.pk %}'>{{ shop.name }}</a></td> <td>{{ shop.address }}</td> </tr> {% endfor %} </table> {% endblock %}
新規と編集のフォームの作成
Djangoでは、クラスベースの汎用ビューを使うことで、新規と編集のフォームを共通のテンプレートで作ることができます。
shopsのurls.pyに追記
# myapp/lunchmap/urls.py urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('</int:pk><int:pk>/', views.DetailView.as_view(), name='detail'), # CreateViewクラスを呼び出して、新規の投稿フォームを表示 path('create/', views.CreateView.as_view(), name='create'), # UpdateViewクラスを呼び出して、既存データを編集するフォームを表示 path('</int:pk><int:pk>/update/', views.UpdateView.as_view(), name='update'), ]
※ プラグインを使用しているせいか、どうしても<\int:pk>等余分なコードが入り込んでしまいますので、適宜読み替えてください。
views.pyにCreateViewを記述
# myapp/lunchmap/views.py from django.urls import reverse_lazy from django.views import generic from .models import Shop class IndexView(generic.ListView): model = Shop class DetailView(generic.DetailView): model = Shop # 新規作成ビューの追加 class CreateView(generic.edit.CreateView): model = Shop fields = '__all__' # 全てのカラムを入力できるフォームを指定 # 編集ビューの追加 class UpdateView(generic.edit.UpdateView): model = Shop fields = '__all__'
テンプレートとしてshop_form.htmlを作成
&amp;amp;lt;!-- myapp/lunchmap/templates/lunchmap/shop_form.html --&amp;amp;gt; {% extends './base.html' %} {% block content %} &amp;amp;lt;!-- 新規と編集で共通のテンプレート --&amp;amp;gt; &amp;amp;lt;h1&amp;amp;gt;お店情報の{{ object|yesno:'更新,作成'}}&amp;amp;lt;/h1&amp;amp;gt; &amp;amp;lt;form action='' method='post'&amp;amp;gt;{% csrf_token %} &amp;amp;lt;!-- このフォームが正式であることを証明する --&amp;amp;gt; {{ form.as_p }} &amp;amp;lt;!-- 全ての項目を表示 --&amp;amp;gt; &amp;amp;lt;button type='submit' class='submit'&amp;amp;gt;{{ object|yesno:'更新,作成'}}&amp;amp;lt;/button&amp;amp;gt; &amp;amp;lt;/form&amp;amp;gt; &amp;amp;lt;div&amp;amp;gt; &amp;amp;lt;!-- JavaScriptで戻る機能を実装 --&amp;amp;gt; &amp;amp;lt;a href='JavaScript:history.back()'&amp;amp;gt;&amp;amp;lt; 戻る&amp;amp;lt;/a&amp;amp;gt; &amp;amp;lt;/a&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt; {% endblock %}
投稿フォームの保存機能の作成
一覧ページと詳細ページから、投稿フォームを呼び出すようにして、データの保存機能を完成させます。
reverse関数
reverse関数は、ビューの名前から、リダイレクト先のURLを調べる関数です。モデルやビューの中でルートを直接記述せずに済みます。
一覧ページから新規ページにリンク
&amp;amp;lt;!-- myapp/lunchmap/templates/lunchmap/shop_list.html --&amp;amp;gt; &amp;amp;lt;div&amp;amp;gt; &amp;amp;lt;a href='{% url &amp;amp;quot;lunchmap:create&amp;amp;quot; %}'&amp;amp;gt;新しいお店&amp;amp;lt;/a&amp;amp;gt; &amp;amp;lt;/div&amp;amp;gt;
詳細ページから更新ページにリンク
&amp;amp;lt;!-- myapp/lunchmap/templates/lunchmap/shop_detail.html --&amp;amp;gt; &amp;amp;lt;div&amp;amp;gt; 一覧 | &amp;amp;lt;a href='{% url &amp;amp;quot;lunchmap:update&amp;amp;quot; shop.pk %}'&amp;amp;gt;編集&amp;amp;lt;/a&amp;amp;gt; | &amp;amp;lt;/div&amp;amp;gt;
更新できるようにmodels.pyを修正
# myapp/lunchmap/models.py from django.db import models from django.urls import reverse # 追加 class Category(models.Model): name = models.CharField(max_length=255) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.name class Shop(models.Model): name = models.CharField(max_length=255) address = models.TextField(blank=True) author = models.ForeignKey( 'auth.User', on_delete=models.CASCADE, ) category = models.ForeignKey( Category, on_delete=models.PROTECT) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return self.name def get_absolute_url(self): # reverse関数を使用して、viewの名前からリダイレクト先を取得 return reverse('lunchmap:detail', kwargs={'pk': self.pk})
削除ページの作成
ルートで「delete」にアクセスした時、該当のお店情報を削除するように設定していきます。
shopsのurls.pyに追記
# myapp/lunchmap/urls.py urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('&amp;amp;lt;/int:pk&amp;amp;gt;&amp;amp;lt;int:pk&amp;amp;gt;/', views.DetailView.as_view(), name='detail'), path('create/', views.CreateView.as_view(), name='create'), path('&amp;amp;lt;/int:pk&amp;amp;gt;&amp;amp;lt;int:pk&amp;amp;gt;/update/', views.UpdateView.as_view(), name='update'), # (pk)/delete/を指定した場合、DeleteViewクラスを呼び出して削除の確認ページを表示 path('&amp;amp;lt;/int:pk&amp;amp;gt;&amp;amp;lt;int:pk&amp;amp;gt;/delete/', views.DeleteView.as_view(), name='delete'), ]
※ プラグインを使用しているせいか、どうしても<\int:pk>等余分なコードが入り込んでしまいますので、適宜読み替えてください。
views.pyにDeleteViewクラスを記述
# myapp/lunchmap/views.py from django.views import generic from .models import Category, Shop from django.urls import reverse_lazy # 追加 class IndexView(generic.ListView): model = Shop class DetailView(generic.DetailView): model = Shop class CreateView(generic.edit.CreateView): model = Shop fields = '__all__' class UpdateView(generic.edit.UpdateView): model = Shop fields = '__all__' class DeleteView(generic.edit.DeleteView): model = Shop # reverse_lazy関数は、viewの名前からリダイレクト先のURLを取得 success_url = reverse_lazy('lunchmap:index')
テンプレートとしてshop_confirm_delete.htmlを作成
&amp;amp;lt;!-- myapp/lunchmap/templates/lunchmap/shop_confirm_delete.html --&amp;amp;gt; {% extends './base.html' %} {% block content %} &amp;amp;lt;h1&amp;amp;gt;お店情報の削除&amp;amp;lt;/h1&amp;amp;gt; &amp;amp;lt;p&amp;amp;gt;'{{ object.name }}'を削除しますか?&amp;amp;lt;/p&amp;amp;gt; &amp;amp;lt;form action='' method='post'&amp;amp;gt;{% csrf_token %} &amp;amp;lt;button type='submit' class='submit delete'&amp;amp;gt;削除する&amp;amp;lt;/button&amp;amp;gt; &amp;amp;lt;/form&amp;amp;gt; &amp;amp;lt;div&amp;amp;gt; &amp;amp;lt;a href='JavaScript:history.back()'&amp;amp;gt;&amp;amp;lt; 戻る&amp;amp;lt;/a&amp;amp;gt; &amp;amp;lt;/a&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt; {% endblock %}
詳細ページから削除ページにリンク
&amp;amp;lt;!-- myapp/lunchmap/templates/lunchmap/shop_detail.html --&amp;amp;gt; &amp;amp;lt;div&amp;amp;gt; 一覧 | &amp;amp;lt;a href='{% url &amp;amp;quot;lunchmap:update&amp;amp;quot; shop.pk %}'&amp;amp;gt;編集&amp;amp;lt;/a&amp;amp;gt; | &amp;amp;lt;a href='{% url &amp;amp;quot;lunchmap:delete&amp;amp;quot; shop.pk %}'&amp;amp;gt;削除&amp;amp;lt;/a&amp;amp;gt; &amp;amp;lt;!-- 追加 --&amp;amp;gt; &amp;amp;lt;/div&amp;amp;gt;
Googleマップの表示
このアプリケーションにGoogleマップを組み込んでいきます。
APIとは
APIとは、Application Programming Interfaceの略で、プログラムから別のプログラムの機能を呼び出すために用意された命令や関数のことです。
Google Maps API
Google Maps Platform – Geo-location API
Developer Guide | Maps Embed API | Google Developers
APIキーの取得手順
- Google Developers Consoleにアクセス
- プロジェクトを作成を選択
- Google APIが表示されたら、Google Maps APIから「Google Maps Embed API」を選択
- 「有効にする」をクリック
- 「認証情報を作成」をクリックして、「必要な認証情報」ボタンをクリック
- 表示されたAPIキーを記録する
※ 特定のWebサービスだけから利用できるよう、「API利用制限」を設定することをお勧めします
※ この手順や利用範囲はGoogle側で変更される場合があります
shop_detail.htmlにマップを追加
&amp;amp;lt;!-- myapp/lunchmap/templates/lunchmap/shop_detail.html --&amp;amp;gt; &amp;amp;lt;iframe id='map' src='https://www.google.com/maps/embed/v1/place?key=XXXXXXXXXXXXXXXXXXXX&amp;amp;amp;q={{ shop.address }}' width='100%' height='320' frameborder='0'&amp;amp;gt; &amp;amp;lt;/iframe&amp;amp;gt;