【Vue.js入門】DjangoのtemplateにVuetifyでビルドしたHtml、CSS、jsが使えるのか試してみた

この記事は約20分で読めます。
広告

Djangoをバックエンド、Vue.jsのVuetifyでビルドしてできた静的ファイルHtml、css、jsの組み合わせが使えるのか知りたかった。
フロントエンド開発はVuetifyで簡単に見た目のいいデザインができます。
それで出来上がったものとDjangoの間にデータの受け渡しをどうやるのか知りたかったので実験しました。
結果として動くことは確認できました。完成品
苦労した点は2つ

  • Cors対策→手間はかかるがビルドしてDjango側のテンプレートとstaticで扱うと同一オリジンになりCors対策不要になる。
  • CSRF対策→axiosの設定でCookieからcsrftalkenを取り出して送信する設定があるのでそれを使った。

今回Python Django チュートリアル SPA編(1)を参考にさせていただきました。
Djangoの公式チュートリアルをベースにしているようです。
私のブログでも扱っています。

パソコンに仮想環境は作らず当サイトでおなじみのPaizaCloudを使用していきます。(私のパソコンに開発環境構築できるストレージの余裕がないため)

広告

PaizaCloudで準備

Python Django チュートリアル SPA編(1)は自分のパソコンで仮想環境を作っていますが、PaizaCloudで開発していきます。PaizaCloudのやり方はこちらの記事を参考にしてください。
PaizaCloudで
新規サーバー作成→サーバー名入力
Web開発:はNode.jsとDjango選択
データベース:はPostgreSQLを選択
新規サーバー作成
まずDjangoのプロジェクトを作成します。

PaizaCloudの左にあるターミナルアイコンをクリックするとターミナルが立ち上がります。
django-admin startproject tutorial でEnterでプロジェクトが作成されます。(以下Enterは省略)
cd tutorial ディレクトリが移動されます

以降は参考サイト
Python Django チュートリアル SPA編(1)
「pollsアプリケーションの追加」からやっていきます。
PaizaCloud固有の方法や上記参考サイトの内容で現在はできないことがあったら個別に指摘していきます。

tutorial/settings.pyでPaizaCloudの場合、以下変更も必要です。
ALLOWED_HOSTS = []
→ALLOWED_HOSTS = ['*']にして保存

polls/admin.pyは元々あるfrom django.contrib import adminの行は必要ですので、この部分を消さないように気を付けましょう。

開発サーバーhttp://localhost:8000/はPaizaCloudではhttps://localhost-個人別の名前.paiza-user-free.cloud:8000/になります。
例えば管理画面に入るアドレスはhttps://localhost-個人別の名前.paiza-user-free.cloud:8000/adminになります。

ターミナルの実行状態を止めるにはCTRL+Cです。
「関連ライブラリの吐き出し」は不要です。
目次へ

Vutifyのインストール

参考サイトPython Django チュートリアル SPA編(2)の方法ではうまくいきませんでした。原因は参考サイトのようにパソコンに仮想環境を作っておらずPaizaCloudという別のサーバーを使用しているためです。
具体的につまづく部分は
動作する場所をhttp://localhost:8080/ではなくhttps://localhost-個人別の名前.paiza-user-free.cloud:8080/に変更することができません。
なのでPaizaCloudを使用する場合、別な方法でVutifyのインストールを完了させます。
その他いろいろうまくいかないので参考サイトに沿いながらも、ここの章は独自の方法でやってます。

まずPaizaCloudの場合、左にあるターミナルアイコンをクリックして、もう1個ターミナルを立ち上げます。

vue-cliのインストール
npm install -g @vue/cli
これはVue 3がインストールされてるのかな?。
今回Vue 2でないとVutifyが使えないようですが一旦このままで後でVue 2に変えられました。
(この辺はよくわかりませんがうまくvue-cliインストールできました。)

プロジェクトを作成
vue create frontend
以下のように出たとき[Vue 2]にカーソルキーで変更してEnterします。(これがVue 2に変わるところ?)
Vue CLI v5.0.8
? Please pick a preset:
  Default ([Vue 3] babel, eslint)
❯ Default ([Vue 2] babel, eslint)
  Manually select features

この次もuse yeanではなくuse npmにしてEnter

package.jsonでバージョン確認するとこのようになっています。
  "dependencies": {
    "axios": "^0.27.2",
    "core-js": "^3.8.3",
    "moment": "^2.29.4",
    "vue": "^2.6.14",
    "vuetify": "^2.6.0"
  },

frontend/vue.config.jsというファイルがあります。
PaizaCloudの場合、このファイルを以下のように変更しないと立ち上げたときInvalid Host headerと出ます。(~allowedHosts: “all”,~の部分、注意、ビルド前には消した方がいい
下の赤だけでいいです。
publicPathの設定も後で楽なのでしておきます。
ビルドしたときjsやcssディレクトリが相対パスになるように./にしています。

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true
})
module.exports = {
  devServer: {
    allowedHosts: "all",
  },
  publicPath: './',
}
Vutifyをインストールすると自動的にVutify用のコードtranspileDependencies: ['vuetify']などが追加されます。なのでVutifyインストール後に、vue.config.jsを見ると記述が変わります。

vuetifyをインストール

cd frontend
vue add vuetify

 Still proceed? (y/N)yでEnter

? Choose a preset: Default (recommended)はそのままEnter

開発サーバーの起動はnpm run serveです。
npm run devではないので注意してください。
8080アイコンクリックでVutifyの画面が確認できます。

目次へ

axios使う場合のCORS対策

Python Django チュートリアル SPA編(2)Vutifyの設定以降は参考にしつつ完全に違うコードになりましたので、ここからはポイントのみ記していきます。

Python Django チュートリアル SPA編(2)vuetifyの設定
main.jsそのまま触らない。
public/index.htmlにCDN追加

<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet"><!-- font, iconのimport -->
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet"><!-- cssのimport -->
App.vueそのまま最終的な形は後でコード載せています。

Python Django チュートリアル SPA編(3)
routerは使わない。
<v-app-barをコメントアウトして以下に入れ替え(上と左に空白ができるため最終的には消しました。)
    <v-navigation-drawer app></v-navigation-drawer>
    <v-toolbar app></v-toolbar>
    <v-content>
      <v-container fluid>
        <!-- <router-view></router-view> -->
      </v-container>
    </v-content>
    <v-footer app></v-footer>
components/Poll/index.vueを作成
App.vueで以下の赤い部分変更(下にコード全部あります)    
    <v-main>
        <PollIndex/>
      <!-- <HelloWorld/> -->
    </v-main>
  </v-app>
</template>

<script>
//import HelloWorld from './components/HelloWorld';
import PollIndex from './components/Poll/index';
export default {
  name: 'App',

  components: {
    //HelloWorld,
    PollIndex,
  },
<template>
  <v-app>
      <div style="text-align: center" >
   <h1 style="color:#ff0000">Django+Vuetifyで作ったPollsアプリ</h1>
   <p>このサイトの詳細は<a href="https://kikuichige.com" target="_blank">イチゲブログ</a>参照</p>
   </div>
<!-- <v-navigation-drawer app></v-navigation-drawer>
    <v-toolbar app></v-toolbar>
    <v-content>
      <v-container fluid> -->
        <!-- <router-view></router-view> -->
      <!-- </v-container>
          </v-content>
          <v-footer app></v-footer> -->
    <v-main>
        <PollIndex/>
    </v-main>
  </v-app>
</template>

<script>
//import HelloWorld from './components/HelloWorld';
import PollIndex from './components/Poll/index';
export default {
  name: 'App',

  components: {
    //HelloWorld,
    PollIndex,
  },

  data: () => ({
    //
  }),
};
</script>
<template>
  <div>
    polls
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'PollIndex',
  methods: {
    fetchData () {
      axios.get('https://localhost-人によって違うサーバー名.paiza-user-free.cloud:8000/api/1.0/questions/').then(res => {
        console.log(res)
      })
    },
  },
  mounted () {
    this.fetchData()
  },
}
axios.defaults.withCredentials = true; 
</script>
axiosをインストールします。
npm install axios --save
日付フォーマットの修正
npm install moment --save
npm run serveでPollsという文字が出ます。(h1タグのタイトルは個人的に付けました)
tutorial/settings.pyに以下追加。これ入れるとAPIで送られてくるデータの形が、どう変わるか確認すると後々、役に立つと思います。
# =========================
# django-restframework 設定
# =========================
REST_FRAMEWORK = {
    'PAGE_SIZE': 100,
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
}

目次へ

axios使う場合のCORS、CSRF対策

frontend側(Vue.Js)

frontend側はどこかに以下入れないとAccess to XMLHttpRequest at 'https://localhost-略:8000/api/1.0/questions/' from origin 'https://略:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.のエラーがでます。参照https://qiita.com/att55/items/2154a8aad8bf1409db2b

axios.defaults.withCredentials = true;
</script>
これは同一オリジンにすれば不要なので今回最終的には削除しました。

CSRFに関しては以下参照
知識の枝 Axiosを使い非同期通信を行う
非同期通信方法はAjax以外にもAxiosという方法があります。Vue.jsで推奨されている方法ですので、今回はこのAxiosをDjangoで使用する前提で解説したいと思います。
Django + axiosでCSRFエラーが発生する場合の対処方法 - Qiita
1.解決したい問題 Django + axiosでPOST通信を行った時、 console.log Forbidden (CSRF token missing or incorrect.): /top/telling [10...
具体的には以下2行追加 axios.defaults.xsrfCookieName = 'csrftoken' // ←ココと axios.defaults.xsrfHeaderName = "X-CSRFTOKEN" // ←ココに追加しました しかし8080を使っている状態だとうまくいきません。Cookieが設定されないので値が拾えません。 ビルドしてDjangoのテンプレートとstaticから配信する形にするとうまくいきます。次の章で説明します。
<template>
  <div>
    <v-card>
      <v-container grid-list-lg>
        <v-layout row wrap>
          <v-flex xs6 v-for="data in questions" :key="data.id">
            <v-card color="primary" class="white--text">
              <v-card-title primary-title>
                <div>{{ data.question_text }}</div>
              </v-card-title>
              
              <v-card-text v-if="data.choices.length">
                   <v-radio-group v-model="vote">
                       <!-- color="indigo"付けると消えない -->
                     <v-radio
                       v-for="choice in data.choices"
                       color="indigo" 
                       :key="choice.id"
                       :label="choice.choice_text + ' 投票数: ' + choice.votes"
                       :value="choice.id">
                     </v-radio>
                   </v-radio-group>
                   <v-btn @click="doVote" color="success" >投票</v-btn> <!-- @click="doVote" 追加 -->
                </v-card-text>
              
              <v-card-text>
                <div>{{ data.pub_date|printDate }}</div>
              </v-card-text>
            </v-card>
          </v-flex>
        </v-layout>
      </v-container>
    </v-card>
  </div>
</template>

<script>
import axios from 'axios'
import moment from 'moment'
export default {
  name: 'PollIndex',
  data () {
    return {
      vote: null,// <--- 追加
      questions: [],
    }
  },
  methods: {
    fetchData () {
      axios.get('https://localhost-人によって違うサーバー名.paiza-user-free.cloud:8000/api/1.0/questions/').then(res => {
        this.questions = res.data.results
      })
    },
    doVote () {  // 投票用メソッド追加
    console.log('押された')
      if (!this.vote) {
          console.log('押されたreturn')
        return
      }
      axios.defaults.xsrfCookieName = 'csrftoken' // ←ココと
           axios.defaults.xsrfHeaderName = "X-CSRFTOKEN" // ←ココに追加しました
      axios.post(`https://localhost-人によって違うサーバー名.paiza-user-free.cloud:8000/api/1.0/choices/${this.vote}/vote/`).then(res => {
        this.fetchData(),
        console.log(res)
      })
    },
    
    
  },
  mounted () {
    this.fetchData()
  },
  filters: {
    printDate (val) {
      return moment(val).locale('ja').format('YYYY年MM月DD日(ddd) HH時mm分ss秒')
    },
  },
}
axios.defaults.withCredentials = true; 
</script>

サーバー側(Django)

ここに書いてあります。【Django入門】Djangoのセキュリティ実験してみた!XSS、CSRF、CORSなど | イチゲ ブログ (kikuichige.com)

参考サイトに載っているCors対策のほかに以下が必要だった。
CORS_ALLOW_CREDENTIALS = True

データは参考サイトをみてadminまたはapi画面から入力してください。

Django側をpython manage.py runserver
Vue側をnpm run serveで動かすと投票画面が表示されます。
ただし投票ボタンは機能しません。(下の章で動くようにします)

実際動かしてedgeの場合、どこかで右クリックして「開発者ツールで調査する」で通信の様子が見れます。
下の図は誤解を生むかもしれません。CookieとCookieの設定の欄は、Cookie欄はその通信で要求ヘッダにCookieがあれば0ではなくなります。Cookieの設定欄は応答ヘッダにset-cookieがあるとき0でなくなります。なので0、0になっていても上の通信(時間的に前の段階)でset-cookieされていればCookieは設定されています。csrftalkenがいつset-cookieされるか確認してません。次の章でうごかしているときはsetされていました。
目次へ

ビルドしてindex.htmlと静的ファイルをDjango側へ

ビルドしてdistフォルダに出来上がったindex.html、js、cssはDjangoのテンプレートとstaticで普通に使えるファイルになってます。
ここからは同一オリジンになるのでCors対策は不要。(8080は使わない8000の1本になる)なのでsettings.pyのCors対策部分は削除しても大丈夫です。

VueをCTRL+Cで停止し
npm run build
ビルドするとfrontend/distディレクトリにhtmlとjs、cssファイルができています。mapは不要。jsとcssのファイル名はビルドするたびに変更されるので都度以下の作業で注意してください。
dist/index.htmlをDjangoのpolls/templatesに移動
dist/cssとjsにあるそれぞれのファイルをDjango側にpolls/static/polls/cssとjsディレクトリを作りそれぞれ移動*.mapは不要。

index.htmlを以下のように変更(メモ帳にコピペして編集するとやりやすいです)

冒頭に{% load static %}
jsファイルやcssファイルを参照しているところを{% static 'jsファイル' %}で囲む。

静的ファイル、staticに関しては、こちらで簡単な例で説明してます。

Djangoでindex.htmlを表示するように変更
tutorial/urls.pyに追加

    path('',include("polls.urls")),

tutorial/polls/urls.py追加
from django.urls import path
from . import views

app_name    = "polls"
urlpatterns = [ 
    path('', views.PollsView.as_view(), name="index"),
]

tutorial/polls/views.py追加
from django.shortcuts import render
from django.views import View

class PollsView(View):
    def get(self, request, *args, **kwargs):

        return render(request,"index.html")
python manage.py runserverで動作確認
完成品

あとがき

csrftalkenについてはいまいちスッキリしてません。
私の記事でもそうですが、プログラミングはバージョンアップ等によりすぐ情報が古くなります。そのため色々な知識が必要になってきます。静的ファイルやCookie、CSRF、CORSなどVutify以外の必要な項目も当ブログで扱っていますでご覧ください。

この記事を書いたイチゲを応援する
Vプリカでのお支払いがおすすめです。

MENTAやってます(ichige)

目次へ

コメント

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