Django REST Framework を使ってAPIを作ります。Djangoについては 別記事 を参照してください。テスト環境は Apple Silicon Mac です。次のサイトに倣ってテストしました。ただし、多少アレンジしています。

テスト環境とプロジェクトの作成
まず virtualenv で専用環境を作って、必要なライブラリをインストールします。Djangoと同じ手順ですがインストールするものが少し違います。
1 2 3 4 5 6 7 |
$ mkdir drf $cd drf $ virtualenv django $ source django/bin/activate (django) $ pip3 install django (django) $ pip3 install django-rest-framework (django) $ pip3 install django-filter |
続いてプロジェクトとアプリを作ります。ここも Django の手順と同じです。”project” という名前でプロジェクトを作成します。
1 |
(django) $ django-admin startproject project |
出来たディレクトリに降りると manage.py というファイルがあります。”api” という名前でアプリを作ります。api の下に templates/api/index.html というテンプレートファイルを作ります。参考にしたブログ記事では project 直下に templates/index.html を作っていますが、最終的には他のアプリと統合するつもりなのでこのようにします。index.html の中身は後で作りますので空っぽでかまいません。そうすると次のようなファイル構成になります。tree コマンドは “brew install tree” で予めインストールしてあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
(django) $ cd project ← manage.pyの階層に降りる (django) $ python3 manage.py startapp api ← アプリを作成 (django) $ mkdir api/templates (django) $ mkdir api/templates/api (django) $ touch api/templates/api/index.html ← 空ファイルを作成 (django) $ tree ../project project ├── api │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── templates │ │ └── api │ │ └── index.html ← 作ったファイル │ ├── tests.py │ └── views.py ├── manage.py └── project ├── __init__.py ├── __pycache__ ← この下は省略 ├── asgi.py ├── settings.py ├── urls.py └── wsgi.py |
project/settings.py
project/settings.py を編集していきます。
INSTALL_APPS に3行追加します。インストールのときは django_filter でしたが、ここでは django_filters と s が付きます。
1 2 3 4 5 6 7 8 9 10 11 |
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', # 追加 'django_filters', # 追加 'api', # 追加 ] |
TEMPLATES のファイルパスを書く。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR / 'templates'], # この行変更 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, |
タイムゾーンを指定します。
1 2 3 4 5 |
#LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'ja' #TIME_ZONE = 'UTC' TIME_ZONE = 'Asia/Tokyo' |
ファイルの最後に次の内容を追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# 以下、追加 # rest_framework 設定 REST_FRAMEWORK = { 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.SessionAuthentication', ) } # 管理画面のログインを通常ログインとして流用する LOGIN_URL = '/admin/login/' LOGOUT_REDIRECT_URL = '/' |
api/models.py
api/models.py を次のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
from django.db import models # Create your models here. class Log(models.Model): created_at = models.DateTimeField( verbose_name='登録時間', auto_now_add=True, ) temperature = models.FloatField( verbose_name='気温', blank=True ) humidity = models.FloatField( verbose_name='湿度', blank=True ) # 以下は管理サイト上の表示設定 def __str__(self): return self.created_at class Meta: verbose_name = '採取データ' verbose_name_plural = '採取データ' |
データベースの初期化
データベースを初期化します。
1 2 |
(django) $ python3 manage.py makemigrations (django) $ python3 manage.py migrate |
管理画面
api/admin.py を次のようにします。
1 2 3 4 5 6 7 |
from django.contrib import admin from .models import Log @admin.register(Log) class LogAdmin(admin.ModelAdmin): pass |
管理者を作成します。
1 |
(django) $ python3 manage.py createsuperuser |
動かしてみます。
1 |
(django) $ python3 manage.py runserver |
ブラウザで http://localhost:8000/admin/ をアクセスすると管理画面が表示されます。createsuperuser で設定した ID と Password でログインします。(ここでは ID: admin、Password: oit12345)ログインできることを確認したら、とりあえずそのままにしておきます。
api/views.py
api/views.py を次のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import django_filters from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import TemplateView from rest_framework import serializers from rest_framework import viewsets from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.permissions import IsAuthenticated from .models import Log class LogSerializer(serializers.ModelSerializer): class Meta: model = Log fields = '__all__' class LogFilter(django_filters.FilterSet): class Meta: model = Log fields = {'created_at': ['gte', ], } class LogViewSet(viewsets.ModelViewSet): queryset = Log.objects.all().order_by("created_at") serializer_class = LogSerializer filter_class = LogFilter authentication_classes = (SessionAuthentication, BasicAuthentication) permission_classes = (IsAuthenticated,) class MainView(LoginRequiredMixin, TemplateView): template_name = 'api/index.html' |
project/urls.py
project/urls.py を次のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from django.contrib import admin from django.urls import path, include from rest_framework import routers from api.views import LogViewSet from api.views import MainView router = routers.DefaultRouter() router.register(r'logs', LogViewSet) urlpatterns = [ path('admin/', admin.site.urls), path('api/', include(router.urls)), path('', MainView.as_view()), ] # ログイン画面のタイトルを変更 admin.site.site_header = '気温と湿度の変化' |
ここまで来るとログ機能が使えます。runserver で動かした状態で http://localhost:8000/api/logs/ にアクセスしてみてください。
テンプレート
api/templates/api/index.html を次のようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>気温・湿度の変化</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> </head> <body> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="#">気温・湿度の変化</a> <button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#Navber" aria-controls="Navber" aria-expanded="false" aria-label="ナビゲーションの切替"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="Navber"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="{% url 'admin:index' %}">管理サイト</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'admin:logout' %}">ログアウト</a> </li> </ul> </div> <!-- /.navbar-collapse --> </nav> <div class="container"> <div class="container"> <canvas id="myChart"></canvas> </div> <div class="row mt-3"> <div class="col-12"> <form> <fieldset class="form-group"> <legend class="text-center">表示期間</legend> <div class="text-center"> <div class="form-check form-check-inline"> <label class="form-check-label"> <input type="radio" class="form-check-input" name="days" id="term_4" value="7"> 1週間分 </label> </div> <div class="form-check form-check-inline"> <label class="form-check-label"> <input type="radio" class="form-check-input" name="days" id="term_3" value="3"> 3日分 </label> </div> <div class="form-check form-check-inline"> <label class="form-check-label"> <input type="radio" class="form-check-input" name="days" id="term_1" value="1" checked> 1日分 </label> </div> </div> </fieldset> </form> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/locale/ja.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.0/underscore-min.js" charset="utf-8"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js" charset="utf-8"></script> <script> var chart; // グラフ描画 function drawChart(data) { var ctx = document.getElementById("myChart").getContext('2d'); if (chart) { chart.destroy(); } chart = new Chart(ctx, { type: 'line', data: { labels: data[0], datasets: [{ label: '気温', data: data[1], yAxisID: "y-axis-1", fill: false, backgroundColor: 'rgb(255, 99, 132)', borderColor: 'rgb(255, 99, 132)', }, { label: '湿度', data: data[2], yAxisID: "y-axis-2", fill: false, backgroundColor: "rgb(54, 162, 235)", borderColor: "rgb(54, 162, 235)", },] }, options: { scales: { yAxes: [ { // 気温軸(左) id: "y-axis-1", type: "linear", position: "left", ticks: { max: 50, min: -30, stepSize: 10 }, }, { // 湿度軸(右) id: "y-axis-2", type: "linear", position: "right", ticks: { max: 100, min: 0, stepSize: 25 }, }], xAxes: [{ type: 'time', time: { displayFormats: { hour: 'D日H時' } } }] }, tooltips: { callbacks: { title: function (tooltipItem, data) { return moment(tooltipItem[0].xLabel).format('YYYY-MM-DD HH:mm') } } } } }) } // データ読込 function reload() { // 指定時間を取得 var days = $('input[name=days]:checked').val(); // 1日、3日、7日前の時間を設定 var param = moment().subtract(days, 'days').format('YYYY-MM-DD HH:mm:ss') $.getJSON('/api/logs?created_at__gte=' + param) .done(function (source) { let data = new Array(3); data[0] = _.pluck(source, 'created_at'); data[1] = _.pluck(source, 'temperature'); data[2] = _.pluck(source, 'humidity'); drawChart(data); }) }; $(document).ready(function () { // ラジオボタンのイベントリスナー $('input[name="days"]:radio').on('change', function () { reload(); }); // 初期化 reload(); }); </script> </body> </html> |
テスト
runserver を状態で http://localhost:8000/ にアクセスすれば動いているはずです。ただし、データが何も入っていないのでグラフの形式だけが表示されます。

ではAPIを使ってデータを送り込んでみましょう。POSTメソッドを使います。次に例示したコマンドで user:password は登録したユーザー名とパスワードに変更してください。JSON記述の中のダブルクォートをエスケープしなければならないことに注意してください。実際に送信した時刻が記録されてグラフなどにも反映しますので数分の感覚をあけて送信するとわかりやすいでしょう。
1 |
$ curl -v -u user:password -H "Accept: application/json" -H "Content-type: application/json" -X POST -d "{\"temperature\":30, \"humidity\":54}" http://localhost:8000/api/logs/ |
念の為、もう幾つか値を変化させてデータを送信します。実際に送信した時刻が記録されてグラフなどにも反映しますので数分の感覚をあけて送信するとわかりやすいでしょう。
1 2 |
$ curl -v -u user:password -H "Accept: application/json" -H "Content-type: application/json" -X POST -d "{\"temperature\":32, \"humidity\":50}" http://localhost:8000/api/logs/ $ curl -v -u user:password -H "Accept: application/json" -H "Content-type: application/json" -X POST -d "{\"temperature\":33, \"humidity\":52}" http://localhost:8000/api/logs/ |
ここで http://localhost:8000/api/logs/ を見てください。データが受信されていることがわかります。

グラフ表示も確認しておきましょう。http://localhost:8000/ です。
