【Django入門】Django+Rest frameworks+Vue.js(VueCLIを使わずに理解する)楽天APIを使ったアプリ作成

この記事は約28分で読めます。

JavaScript系になじみがない人はVueCLIが入ってくるとNode.jsやnpmなど
一気に未知な部分が入ってきていやになると思います。
まずはRest frameworksとVue.jsだけを使用して理解していくのがいいかと思い見つけました。
nMoMo’sさんのVue.jsとDjangoでサクッとCRUD
この記事で大体大枠は理解できました。
ということでこちらの記事にあるサンプルを利用させていただき
楽天Apiからデータを受け取りVue.jsへ出力するアプリを作りました。
追加した処理の内容は
index.htmlに入力formを追加して検索ワードをurls.py→views.pyと送り
views.pyで楽天apiのデータ取得→データベース→Vue.jsで表示という流れです。

完成品 楽天ブックスCD検索APIを利用したアプリ

まだ経験が浅いため、とりあえず動いてるという段階で
この方法を利用していくのはおすすめしません。
特にDjangoとVue.js間でAPI以外の使い方しちゃってます。
あくまで理解の手助けとなればと思います。
その辺はご了承ください。

PaizaCloudで動かす

PaizaCloudを使うとパソコンに開発環境を構築する必要がありません。
PaizaCloudのやり方はこちらの記事を参考にしてください。

nMoMo’sさんのVue.jsとDjangoでサクッとCRUDのコードをPaizaCloudで動かしてみます。
上記記事の1番下にGitHubへのリンクがあります。
それをクリックし緑色のCodeをクリック
https://github.com/~.gitというコードをコピーします。
PaizaCloudのターミナルで

git clone 今コピーしたhttps://github.com/~.git
cd vuejs-django-crud

settings.pyを変更
ALLOWED_HOSTS = []→ALLOWED_HOSTS = ['*']へ変更して保存

ターミナルで
pip install djangorestframework
python manage.py migrate
python manage.py runserver
左の8000をクリックするとサーバーが立ち上がります。
アドレスバーに
 https://localhost-人によって違う部分.paiza-user-free.cloud:8000/article
入力してEnterで動きます。

ターミナルでCTRL+Cでサーバーが停止します。

改造したコード

models.pyに1個モデルを追加します。

article_body2 = models.TextField()

viewsets.pyに今追加したモデルを追加します。
ここはデータベースのフィールド検索に必要なようで
今回は関係ないと思いますが一応入れておきます。

search_fields = ('article_id', 'article_heading', 'article_body')
       ↓↓
search_fields = ('article_id', 'article_heading', 'article_body', 'article_body2')

 vuejs_crud/urls.pyに1行追加

    path('article', TemplateView.as_view(template_name='index.html')),
    path('test',include("article.urls")),
]

articleディレクトリを右クリックし新規ファイル追加でurls.py追加
article/urls.pyの内容は以下

from django.urls import path
from . import views


app_name = 'test_app'
urlpatterns = [
    path('', views.AccessView.as_view(), name="test"),
]

article/views.pyを以下のように書き換えてください。
その際APP_ID=’個人別コード‘は個別に入力してください。

from django.shortcuts import render

from django.views import View
from .models import Article
from django.shortcuts import redirect
import requests
import numpy as np

class AccessView(View):
    
    def get(self, request):

        return redirect('/article')
    def post(self, request, *args, **kwargs):
        Article.objects.all().delete() 
        keyword=request.POST["artist"]
        test_text=''
        request_url='https://app.rakuten.co.jp/services/api/BooksCD/Search/20170404'
        APP_ID='個人別コード'
        params={'artistName':keyword,
                    'sort':'-releaseDate',
                   'applicationId':APP_ID}
        try:                
            res=requests.get(request_url,params)
            if res.status_code == 200:    
                result=res.json()
                dummy=[]
                if result['last'] !='':
                    suu=result['last']
                else:
                    suu=0
        
                for i in range(suu):
        
                    if (result['Items'][i]['Item']['smallImageUrl']!='' and result['Items'][i]['Item']['title']!='' and result['Items'][i]['Item']['itemUrl'] !=''):
                        dummy.append(result['Items'][i]['Item']['smallImageUrl'])
                        dummy.append(result['Items'][i]['Item']['title'])
                        dummy.append(result['Items'][i]['Item']['itemUrl'])
                
                #3つずつ二次元に変換
                num=int(len(dummy)/3)
                result1=np.array(dummy).reshape(num,3).tolist()
                if result1 == []:
                    print("空です")
                    test_text='該当するアーティストがいません'
                dic=result1
                if Article.objects.all().count()==0:
                    for i in range(suu):
                        Article.objects.create(article_id=i,article_heading=dic[i][0], article_body=dic[i][2], article_body2=dic[i][1])
            else:
                test_text='失敗'
        except requests.exceptions.RequestException as e:
            print("エラー : ",e)
            test_text='エラー'

        context = { "test_text"     : test_text
                    }
    
        return render(request,"index.html",context)

index.htmlを以下のように書き換え

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Vue.js & Django | CRUD</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <!-- bootstap -->
    <link
      rel="stylesheet"
      href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
      integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
      crossorigin="anonymous"
    />
    <!-- boostrap css -->
    <style>
      /* Absolute Center Spinner */
      .loading {
        position: fixed;
        z-index: 999;
        height: 2em;
        width: 2em;
        overflow: show;
        margin: auto;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
      }

      /* Transparent Overlay */
      .loading:before {
        content: "";
        display: block;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.3);
      }

      /* :not(:required) hides these rules from IE9 and below */
      .loading:not(:required) {
        /* hide "loading..." text */
        font: 0/0 a;
        color: transparent;
        text-shadow: none;
        background-color: transparent;
        border: 0;
      }

      .loading:not(:required):after {
        content: "";
        display: block;
        font-size: 10px;
        width: 1em;
        height: 1em;
        margin-top: -0.5em;
        -webkit-animation: spinner 1500ms infinite linear;
        -moz-animation: spinner 1500ms infinite linear;
        -ms-animation: spinner 1500ms infinite linear;
        -o-animation: spinner 1500ms infinite linear;
        animation: spinner 1500ms infinite linear;
        border-radius: 0.5em;
        -webkit-box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0,
          rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0,
          rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.5) -1.5em 0 0 0,
          rgba(0, 0, 0, 0.5) -1.1em -1.1em 0 0, rgba(0, 0, 0, 0.75) 0 -1.5em 0 0,
          rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
        box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0,
          rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0,
          rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) -1.5em 0 0 0,
          rgba(0, 0, 0, 0.75) -1.1em -1.1em 0 0,
          rgba(0, 0, 0, 0.75) 0 -1.5em 0 0, rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
      }

      /* Animation */
      @-webkit-keyframes spinner {
        0% {
          -webkit-transform: rotate(0deg);
          -moz-transform: rotate(0deg);
          -ms-transform: rotate(0deg);
          -o-transform: rotate(0deg);
          transform: rotate(0deg);
        }

        100% {
          -webkit-transform: rotate(360deg);
          -moz-transform: rotate(360deg);
          -ms-transform: rotate(360deg);
          -o-transform: rotate(360deg);
          transform: rotate(360deg);
        }
      }

      @-moz-keyframes spinner {
        0% {
          -webkit-transform: rotate(0deg);
          -moz-transform: rotate(0deg);
          -ms-transform: rotate(0deg);
          -o-transform: rotate(0deg);
          transform: rotate(0deg);
        }

        100% {
          -webkit-transform: rotate(360deg);
          -moz-transform: rotate(360deg);
          -ms-transform: rotate(360deg);
          -o-transform: rotate(360deg);
          transform: rotate(360deg);
        }
      }

      @-o-keyframes spinner {
        0% {
          -webkit-transform: rotate(0deg);
          -moz-transform: rotate(0deg);
          -ms-transform: rotate(0deg);
          -o-transform: rotate(0deg);
          transform: rotate(0deg);
        }

        100% {
          -webkit-transform: rotate(360deg);
          -moz-transform: rotate(360deg);
          -ms-transform: rotate(360deg);
          -o-transform: rotate(360deg);
          transform: rotate(360deg);
        }
      }

      @keyframes spinner {
        0% {
          -webkit-transform: rotate(0deg);
          -moz-transform: rotate(0deg);
          -ms-transform: rotate(0deg);
          -o-transform: rotate(0deg);
          transform: rotate(0deg);
        }

        100% {
          -webkit-transform: rotate(360deg);
          -moz-transform: rotate(360deg);
          -ms-transform: rotate(360deg);
          -o-transform: rotate(360deg);
          transform: rotate(360deg);
        }
      }
    </style>
  </head>

  <body>
    <div id="starting">
      <div class="container">
        <div class="row">
          <h1>
            楽天ブックスCD検索APIを利用したアプリ

            <form action="test" method="post">
                {% csrf_token %}
                <input type="text" class="form-control" id="artist" name="artist" placeholder="アーティスト名 例Babymetal">
                <input class="btn btn-info" type="submit" value="検索" name="name">
            </form>

          </h1>
          &emsp;

          <table class="table">
            <thead>
              <tr>
                <th scope="col">新しい順</th>
                <th scope="col">ジャケット</th>
                <th scope="col">タイトル</th>
                <th scope="col">削除</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="article in articles">
                <th scope="row">${article.article_id}</th>
                <!-- <td>${article.article_heading}</td> -->
                <td><img v-bind:src="article.article_heading"></td>
                <td><a v-bind:href="article.article_body">${article.article_body2}</a></td>
                <!-- <td>${article.article_body}</td> -->
                 <td>

                   <button
                     class="btn btn-danger"
                     v-on:click="deleteArticle(article.article_id)"
                   >
                     削除
                   </button>
                 </td> 
              </tr>
            </tbody>
          </table>

        <p> {{ test_text }}</p>
        <br>
        <p> このアプリについては <a href="kikuichige.com">イチゲブログ参照</a></p>
    </div>
    
    <!-- bootrtap js files -->
    <script
      src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
      integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
      integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
      integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
      crossorigin="anonymous"
    ></script>

    <!-- vue.js files -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue-resource@1.3.5"></script>
    <script type="text/javascript">
      Vue.http.headers.common["X-CSRFToken"] = "{{ csrf_token }}";
      new Vue({
        el: "#starting",
        delimiters: ["${", "}"],
        data: {
          articles: [],
          loading: true,
          currentArticle: {},
          message: null,
          newArticle: { article_heading: null, article_body: null, article_body2: null },
          search_term: "",
        },
        mounted: function() {
          this.getArticles();
        },
        methods: {
          home(){
              this.$router.push('/test')
          },
          getArticles: function() {
            let api_url = "/api/article/";
            if (this.search_term !== "" || this.search_term !== null) {
              api_url = `/api/article/?search=${this.search_term}`;
            }
            this.loading = true;
            this.$http
              .get(api_url)
              .then(response => {
                this.articles = response.data;
                this.loading = false;
              })
              .catch(err => {
                this.loading = false;
                console.log(err);
              });
          },
          getArticle: function(id) {
            this.loading = true;
            this.$http
              .get(`/api/article/${id}/`)
              .then(response => {
                this.currentArticle = response.data;
                $("#editArticleModal").modal("show");
                this.loading = false;
              })
              .catch(err => {
                this.loading = false;
                console.log(err);
              });
          },
          addArticle: function() {
            this.loading = true;
            this.$http
              .post("/api/article/", this.newArticle)
              .then(response => {
                this.loading = true;
                this.getArticles();
              })
              .catch(err => {
                this.loading = true;
                console.log(err);
              });
          },
          updateArticle: function() {
            this.loading = true;
            this.$http
              .put(
                `/api/article/${this.currentArticle.article_id}/`,
                this.currentArticle
              )
              .then(response => {
                this.loading = false;
                this.currentArticle = response.data;
                this.getArticles();
              })
              .catch(err => {
                this.loading = false;
                console.log(err);
              });
          },
          deleteArticle: function(id) {
            this.loading = true;
            this.$http
              .delete(`/api/article/${id}/`)
              .then(response => {
                this.loading = false;
                this.getArticles();
              })
              .catch(err => {
                this.loading = false;
                console.log(err);
              });
          },
          setArticle: function() {
            this.loading = true;
            this.currentArticle.article_heading='1';
            this.$http
              .get(
                `/test/`,
                this.currentArticle
              )
              .then(response => {
                this.loading = false;
                this.currentArticle = response.data;
                this.getArticles();
              })
              .catch(err => {
                this.loading = false;
                console.log(err);
              });
          },
        }
      });
    </script>
  </body>
</html>

データベースを以下のようにしてリセットします。

db.sqlite3 ファイルを削除
migrations フォルダーを削除
python manage.py makemigrations article
python manage.py migrate

先ほどと同じようにpython manage.py runserver
8000をクリックしてarticleに移動すると動きます。

楽天APIの使い方は、Pythonで楽天トラベルAPIを使用した宿泊先リスト作成方法をわかりやすく解説!が分かりやすいです。

懸念点

Djangoのテンプレート構文とVueのバインディングが完全に被っているため、テンプレート内で何も対策せずにいずれかを使用するとバグを抱えてしまいます。

nMoMo’s
とあります。
今回私が改造したプログラムでまずい部分は
index.htmlの<p> {{ test_text }}</p>
これはdjangoからエラーメッセージを表示させるために付けました。
しかし{{}}の表記法はJavaScriptでも使われている場合があります。
これはDjangoとJavaScriptで被ってしまいます。
今回は一応動いている。

また入力ホームの出力先に(action)に新たに追加したtestではなくarticleに
以下のようにすると
<form action="article" method="post">
これはMethod Not Allowed (POST): /article
とエラーになります。

なのでindex.htmlにDjangoから書き込みたい場合は
データベース→Rest frameworks→Vue.jsが無難かもしれません。

フロントエンド(Vue.js、画面)とバックエンド(Django、サーバー)とのやり取りは
Rest frameworks経由でAPI(Jsonでのデータのやり取り)だけにするのが本来のやり方かも。
他にも気を付けるところがあるのかもしれないが
今のところ私にはわからないです。

MENTAやってます(ichige)
イチゲをOFUSEで応援する

コメント

タイトルとURLをコピーしました