web-dev-qa-db-ja.com

Vue with django

私は最近、Djangoを使用していくつかのソーシャルメディアWebサイトを開始しました。デフォルトDjangoテンプレートエンジンを使用して、ページを埋めます。しかし、現時点では、javascriptを追加して、サイトをより動的にします。これの意味は:

  • ヘッダーとフッターは、各ページでと同じです。ヘッダーには、ドロップダウンメニュー、入力時に検索する検索フォームが必要です。
  • 私の現在のDjango=アプリケーションにはbase templateがあります。これはすべてのページにこれがあるはずなので、HTMLがあります。
  • このサイトは、複数のページで構成されており、インデックスページ、プロファイルページ、レジスタページを考えます。これらの各ページには、いくつかの共通コンポーネントがありますが、多くの異なる動的コンポーネントもあります。たとえば、登録ページにはフォーム検証をその場で行う必要がありますが、プロファイルページにはこれは必要ありません。プロファイルページには、無限スクロールのステータスフィードが必要です。

Vueを使用して動的コンポーネントを処理したいのですが、どのように始めるべきかわかりません。アプリケーションはSPAであってはなりません。

  • structureVueコード?
  • どのようにこれをバンドルする必要がありますか。 Gulpを使用していますか?または多分 Django-webpack-loader
  • Djangoテンプレートタグを使用できるようにする必要があります。たとえば、{% url 'index' %}ドロップダウンメニュー。
17
user6827

これは、opinionに基づく質問のように見えますが、明確な答えはありません。

notではなく、アプリをSingle Page Application(SPA)。もしそうなら、Vueを使用する動機は何ですか?ページ内のユーザーインタラクションを処理するには?

Vueは、非SPAコンテキストでも問題なく動作します。データをドロップダウン、フォームなどにバインドするなど、ページ内での豊富なインタラクションを処理するのに役立ちます。しかし、Vueの真の力は、SPAコンテキストで使用すると明らかになります。

あなたの場合、Vue.jsをstandaloneモードで使用することをお勧めします。このモードでは、Vue内でtemplateをすばやく定義できます。 _コンポーネントとすべてのコードを1つのjavascriptファイルに簡単に記述します。

必要なものは次のとおりです。 https://vuejs.org/guide/installation.html#Standalone

「Vue.jsスタンドアロンモード」では、webpackビルドシステムまたはvue-cliは必要ありません。 Djangoの既存の開発環境でアプリを直接ビルドできます。 gulpは、jQueryベースのアプリで行うように、オプションでjavascriptファイルを通常どおり縮小およびバンドルできます。

Vue.jsはテンプレートに二重中括弧{{..}}を使用するため、Djangoテンプレート文字列に干渉しません。

Vue.jsのすべてのjsFiddleの例は、スタンドアロンモードで実行されます。それがまさにこの瞬間に必要なものです。 vue.jsタグで最近の質問のいくつかを見て、サンプルのjsFiddleを見つけて、それがどのように行われるかを見ることができます。

複雑なSPAアプリの場合は、サーバー側とは別にVueコードをビルドし、ダミーAJAX呼び出しで徹底的にテストし、本番用にビルドしてから、最終的な本番ビルドをドロップする必要がありますエンドツーエンドのテスト用のサーバー。これは将来あなたができることです。

19
Mani

OPが求めていたことをほとんど実行しようとしながら、私はこの質問や他の質問をしばらく前に見ました。残念ながら、Vueに関するほとんどの情報はSPAコンテキストで提供されます。ただし、Evan Youが頻繁に繰り返しているように、Vueは意見を表明せず、SPA内で使用する必要はありません。

調査結果の一部を共有し、DjangoとVueを連携させるための可能なアプローチをスケッチしたいと思います。 SPAは必須ではありませんが、Vueの真の力はそのコンポーネントから得られるものであり、そのためにWebpackまたは同様のアプローチに向かわせると思います。html

また、明確にするために、私のコンテンツの90%以上Djangoビューコードとテンプレートは以前とまったく同じままでした。 Djangoは、webpack_loaderとrender_bundleを使用していることを特に気にしません。さらに、render_bundleがVueと関係があることはさらに少なくなります。 Django template blocksverbatimタグとVue slotsの間で、既存のコンテンツのほとんどを残すことができる柔軟性が得られます一人で。

最後に、アプリが安定したら、ポート3000でhotreloadサーバーを無効にし、npm run build-productionを実行し、collectstaticを使用して、通常の静的コンテンツと同様にnginx/ApacheでJSを提供できます。したがって、nodeは、サービスとしてではなく、臨時のバッチとして実行されます。 Djangoの設定を少しいじっていますが、当然のことです。

おpび申し上げますが、これは非常に大雑把です。実際にコードを削除するので、動作するコードを期待しないでください。しかし、うまくいけば、それがあなたにアイデアを与えるだろう。

mysite/__ full12_vue.html:

これは、既存のDjango基本テンプレート__ full12.htmlを拡張する私のVue-ify Djangoテンプレートです。 。

  • __ full12.htmlは、{%コンテンツのように、すべての一般的なDjangoブロックを定義すると仮定します%}など

    (実際には、ID bme-vueを持つ非常に重要なdivがあるため、このテンプレートも最後に追加しました。)

  • ユーザーメッセージを表示するVueコンポーネントを追加しました。

  • Vue + Bootstrapドロップダウンを使用するようにメニューテンプレートを再定義しました。

{% extends "mysite/__full12.html" %}
<!-- KEY: use this to hook up to https://github.com/ezhome/Django-webpack-loader -->
{% load render_bundle from webpack_loader %}


{% block nav %}
    <!-- uses Vue to setup Bootstrap dropdown menus -->
    {% include "mysite/menu_vue.html" %}
{% endblock nav %}


{% block user_msg %}
<div class="row">
    <div class="col-6">
        <!-- uses Vue to display user messages -->
        <bme-user-messages>
            <div>THIS SHOULDNT APPEAR ON SCREEN IF VUE WORKED</div>
        </bme-user-messages>
    </div>
</div>
{% endblock user_msg %}



{%block extra_js_body_end%}
    <!-- KEY  this points Django Webpack loader to appropriate Webpack entry point -->
    {% render_bundle bundle_name %}
{%endblock extra_js_body_end%}

webpack.config.development.js

これは、ビューで指定するbundle_nameにどのJSを提供するかをWebpackに指示する場所です。

Webpackの設定方法は私の投稿の範囲外ですが、それが本当のPIAであったことは保証できます。私はpipDjango-webpack-loaderで始め、次に https://github.com/NdagiStanley/vue-Django 次にBootstrap 4個。ただし、私の意見では、最終結果はスタンドアロンよりもはるかに強力です。

/*
webpack config dev for retest
*/

config.entry = {
  "main" : [
    'webpack-dev-server/client?http://localhost:3000','../../static_src/js/index'],

  // ....stuff..
  //KEY: ONE entrypoint for EACH bundlename that you use.  
  "mydjangoappname/some_Django_view" : ["../../static_src/js/mydjangoappname/some_Django_view"],
  "mydjangoappname/another_Django_view" : ["../../static_src/js/mydjangoappname/another_Django_view"],
  // ....stuff..

}

  // ....stuff left out...

mydjangoappname/some_Django_template.html

最後に、実際のコンテンツを表示する準備ができました。

bme-nav-itembme-tab-paneは、Boostrap 4タブのナビゲーションとコンテンツに使用する2つのカスタムVueコンポーネントです。

Djangoはvar settings= some-json-objectを使用して、ページ汎用ではなくインスタンス固有のデータをVueおよびJSに通信します

{% extends "mysite/__full12_vue.html" %}

<script>
// KEY: settings is provided by json.dumps(some_settings_dictionary) 
// which your views puts into your RequestContext.
// this is how Django tells Vue what changes with different data, on the same view
var settings = {{settings | safe}};
</script>

{% block content %}

    <!-- a button to run a Celery batch via a post command, url should probably come 
    from Django url reverse and be put into a Vue property...
     -->
    <button v-bind:data-url="url_batch" type="button" class="btn btn-block btn-outline-primary" @click.prevent="run_batch">

    <!-- lotsa stuff left out.... -->

    <ul id="tab-contents-nav" class="nav nav-tabs nav-pills">

    <!--  *label* is using a Vue Prop and because there is no {% verbatim %} guard around it, Django will
        inject the contents.  {% urlrev xxx %} could be used to write to an 'url' prop.  Ditto the conditional
        inclusion, by Django, of a template if it's in the RequestContext.
    -->
        {% if templatename_details %}
        <bme-nav-item link="details" 
            label="{{details_label}}" >         
        </bme-nav-item>
        {% endif %}

<!-- lotsa stuff left out.... -->

<bme-tab-pane link="details">
    <div slot="content">

        <!-- KEY: Vue slots are incredibly powerful with Django.  Basically this is saying
                  to Django : inject what you want in the slot, using your include, I'll tidy up afterwards.
                  In my case, this is a Bootstrap NavItem + NavTab 
        -->
        {% if templatename_details %}

            {% include templatename_details %}
        {% else %}
            <span class="text-warning">SHOULDNT APPEAR IF VUE WORKED </span>
        {% endif %}

    </div>
</bme-tab-pane>

{% endblock content %}

mydjangoappname/some_Django_view.js

  import Vue from 'vue';
  import Vuex from 'vuex';
  //now Vue is using Vuex, which injects $store centralized state variables as needed
  Vue.use(Vuex);



  //KEY: re-using components defined once.
  import {base_messages, base_components} from '../mysite/commonbase.js';

  var local_components = {
    //nothing, but I could have imported some other components to mix-n-match
    //in any case, bme-nav-item, bme-tab-pane and bme-user-messages need to 
    //coming from somewhere for this page! 
  };

  const components = Object.assign({}, base_components, local_components);

  //we're going to put together a Vue on the fly...

  export function dovue(config) {

      //KEY:  The store is a Vuex object - don't be fooled, it's not SPA-only
      // it's the easiest way to coherently share data across Vue Components, so use it.
      store.commit('initialize', config);

      //here I am telling the store which html IDs need hiding
      var li_tohide = settings.li_tohide || [];
      li_tohide.forEach(function(hidden) {
          store.commit('add_hidden', hidden);
      });

      /* eslint-disable no-new */
      var vm = new Vue({

        //KEY:  This tells the Vue instance what parts of your html are in its scope.
        el: '#bme-vue'

        //KEY: each bme-xxx and bme-yyy special tag needs to be found in components below
        //otherwise you'll see my SHOULDNT APPEAR IF VUE WORKED text in your page
        ,components: components

        ,data: {
          li_rowcount: window.settings.li_rowcount || []
          ,csrf_token: window.csrf_token
          ,url_batch: "some url"
        }
        ,mounted: function () {
           // a Vue lifecycle hook.  You could use to set up Vue Event listening for example
           console.log("data.js.lifecycle.mounted");
        }
        ,methods : {
          ,run_batch: function(e) {
              //hook this up to a button 
              console.assert(this.$data, COMPONENTNAME + ".run_batch.this.$data missing. check object types");
              var url = e.target.dataset.url

              //this is defined elsewhere 
              post_url_message(this, url, this.csrf_token);
          }
        }
        //the Vuex instance to use for this Vue.
        ,store: store
      });

      //did Django provide any user messages that need displaying?
      var li_user_message = config.li_user_message || [];

      li_user_message.forEach(function(user_message, i) {
        //the bme-user-messages Component?  It's listening for this event 
        //and will display Bootstrap Alerts for them.
        vm.$emit(base_messages.EV_USER_MESSAGE, user_message);
      });
      return vm;
  }

  //various app and page specific settings...
  import {app_config, LOCALNAV_LINK, TOPNAV_LINK_OTHERS} from "./constants";
  var page_config = {
    //used to show which navigation items are .active
    localnav_link : LOCALNAV_LINK.data
    , topnav_link: TOPNAV_LINK_OTHERS.system_edit_currentdb
  };

  //this is coming from Django's RequestContext.
  var generated_config = window.settings;

  //ok, now we are merging together a Django app level config, the page config and finally
  //what the Django view has put into settings.  This will be passed to the Vuex store
  //individual Vue Components will then grab what they need from their $store attribute
  //which will point to that Vuex instance.
  var local_config = Object.assign({}, app_config, page_config, generated_config);
  var vm = dovue(local_config);

vuex/generic.js:

そして、単純な、ほとんど読み取り専用のストア実装:

//you can add your page's extra state, but this is a shared baseline
//for the site
const state = {
  active_tab: ""
  ,topnav_link: ""
  ,localnav_link: ""
  ,li_user_message: []
  ,s_visible_tabid: new Set()
  ,urls: {} 
};
const mutations = {
    //this is what your page-specific JS is putting into the state.
    initialize(state, config){
      //initialize the store to a given configuration
      //KEY: attributes that did not exist on the state in the first place wont be reactive.
        // console.log("store.initialize");
        Object.assign(state, config);
    },
    //a sample mutation
    set_active_tab(state, tabid){
        //which bme-tab-nav is active?
        if (! state.s_visible_tab.has(tabid)){
          return;
        }
        state.active_tab = tabid;
    },
};

export {state as generic_state, mutations};

また、一般的なファイル階層の概念を示すために:

.
./manage.py
./package.json  //keep this under version control
./

├── mydjangoappname
│   ├── migrations
│   └── static
│       └── mydjangoappname
├── node_modules
├        //lots of JavaScript packages here, deposited/managed via npm && package.json
├── static
│   └── js
├── static_src
│   ├── assets
│   ├── bundles
│   │   // this is where Django-webpack-loader's default config deposits generated bundles...
│   │   // probably belonged somewhere else than under static_src ...
│   │   ├── mydjangoappname
│   ├── components
│   │   ├── mydjangoappname
│   ├── css
│   ├── js
│   │   ├── mydjangoappname
│   │   └── mysite
│   └── vuex
│       ├── mydjangoappname
├── staticfiles
│   //  for Production, collectstatic should grab Django-webpack-loader's bundles, I think...
├── templates
│   ├── admin
│   │   └── pssystem
│   ├── mydjangoappname
│   └── mysite
└── mysite
    ├── config
    ├       // where you configure webpack and the entry points.
    ├       webpack.config.development.js 
    ├── sql
    │   └── sysdata
    ├── static
    │   └── mysite
    └── templatetags

OK、サイトのベーステンプレートを変更して、div#bme-vueが常に利用できるようにする必要がありました。

おそらくこれとmysite/__ full12_vue.htmlの間に少しのリファクタリングが必要です。

mysite/__ full12.html

<!-- lots of stuff left out -->
<body>

    <!--     KEY: the #bme-vue wrapper/css selector tells Vue what's in scope.  
    it needs to span as much of the <body> as possible, but 
    also end right BEFORE the render_bundle tag.  I set that css
    selector in mydjangoappname/some_Django_view.js and I'd want to
    use the same selector everywhere throughout my app.
    -->

    <div id="bme-vue">
        <!-- anything that ends up here
, including through multiple nested/overridden Django content blocks
, can be processed by Vue
, but only when have Vue-relevant markup 
such as custom component tags or v-for directives.
-->

    ...more blocks...
    {% block search %}
    {% endblock search %}

    <div id="main" role="main">
        <div> <!-- class="container"> -->
            {% block content %}
            {% endblock %}
        </div>
    </div>
    ...more blocks...


    </div>    <!-- bme-vue -->
    {%block extra_js_body_end%}
    {%endblock extra_js_body_end%}
</body>
</html>
18
JL Peyret

VueをDjangoプロジェクトと統合する方法は次のとおりです。

最初のアプローチは、個別のDjangoアプリとVueアプリを構築することです。 Djangoは、Django RESTフレームワークを使用して構築されたAPIを提供し、VueはAxiosクライアントまたはブラウザのAPIを取得します。開発用と本番用の2つのサーバーが必要です。1つはDjango(REST API)用で、もう1つはVue(静的ファイルを提供するため)です。

2番目のアプローチは、フロントエンドアプリとバックエンドアプリを結合する方法が異なります。基本的に、Djangoを使用してVueフロントエンドを提供し、REST AP​​Iを公開します。したがって、VueおよびWebpackをDjangoと統合する必要があります。これらは、そのために実行できる手順です。

最初にDjangoプロジェクトを生成し、次にこのプロジェクトディレクトリ内でVue CLIを使用してVueアプリケーションを生成します

Djangoプロジェクトの場合Django-webpack-loaderをpipでインストールします。

pip install Django-webpack-loader

次に、インストールしたアプリにアプリを追加し、次のオブジェクトを追加してsettings.pyで構成します

WEBPACK_LOADER = {
    'DEFAULT': {
            'BUNDLE_DIR_NAME': '',
            'STATS_FILE': os.path.join(BASE_DIR, 'webpack-stats.json'),
        }
}

次に、Djangoアプリケーションをマウントするために使用され、Djangoによって提供されるVueテンプレートを追加します

{ % load render_bundle from webpack_loader % }

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Django + Vue</title>
  </head>
  <body>
    <div id="root">
     This is where Vue will be mounted
    </div>
    { % render_bundle 'app' % }
  </body>
</html>

次に、urls.pyにURLを追加して、このテンプレートを提供します

from Django.conf.urls import url
from Django.contrib import admin
from Django.views.generic import TemplateView

urlpatterns = [

    url(r'^', TemplateView.as_view(template_name="main.html")),

]

この時点でDjangoサーバーとVueサーバーの両方を起動すると、webpack-stats.jsonが存在しないというDjangoエラーが発生します。そのため、次にVueアプリケーションで統計ファイルを生成できるようにする必要があります。

先に進み、Vueアプリ内をナビゲートし、webpack-bundle-trackerをインストールします

npm install webpack-bundle-tracker --save

build/webpack.base.conf.jsに移動してから追加します

let BundleTracker = require('webpack-bundle-tracker')
module.exports = {
  // ...
  plugins: [
    new BundleTracker({filename: '../webpack-stats.json'}),
  ],
}

これは、BundleTrackerプラグインをWebpackに追加し、Djangoファイルがある親フォルダーにwebpack-stats.jsonファイルを生成するように指示しますライブ。

Vueサーバーを再実行すると、webpack-stats.jsonが生成され、Djangoはそれを消費して、Vue devサーバー。

詳細については、この tutorial を参照してください。

3
Ahmed Bouchefra