将 Vue 与 Django 一起使用

新手上路,请多包涵

我最近开始使用 Django 开发一些社交媒体网站。我使用默认的 django 模板引擎来填充我的页面。但此时此刻,我想添加 javascript ,让网站更具活力。这表示:

  • 每页的页眉和页脚都相同。标题应该有一个下拉菜单,一个在您输入时搜索的搜索表单。
  • 我当前的 Django 应用程序有一个包含页眉和页脚 HTML 的 基本模板,因为每个页面都应该有这个。
  • 该站点由 多个页面 组成,例如索引页面、个人资料页面、注册页面。这些页面中的每一个都有一些共同但也有很多不同的动态组件。例如,注册页面应该有动态的表单验证,但是个人资料页面不需要这个。个人资料页面应该有一个无限滚动的状态提要。

想用Vue来处理动态组件,但不知如何入手。该应用程序不应是 SPA。

  • 我应该如何 构建 Vue 代码?
  • 我应该如何 捆绑 这个。使用吞咽?或者 django-webpack-loader
  • 我应该仍然能够使用 Django 模板标签,例如我希望能够在下拉菜单中使用 {% url 'index' %}

原文由 user6827 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 636
2 个回答

这看起来像是一个基于 意见 的问题,没有明确的答案。

您提到您 希望该应用程序成为 单页应用程序 (SPA) 。如果是这样,使用 Vue 的动机是什么?处理页面内的用户交互?

Vue 可以在非 SPA 上下文中完美运行。它将帮助您处理页面内的丰富交互,例如将您的数据绑定到下拉菜单、表单等。但是当您在 SPA 上下文中使用它时,Vue 的真正威力就会显现出来。

对于您的情况,我建议在 独立 模式下使用 Vue.js,您可以在 Vue 组件中快速定义 template 并将所有代码轻松写入一个 javascript 文件中。

这是您需要的: https ://vuejs.org/guide/installation.html#Standalone

在“Vue.js 独立模式”下,不需要任何 webpack 构建系统或 vue-cli 。您可以直接在现有的 Django 开发环境中构建应用程序。 gulp 可以选择正常缩小和捆绑您的 javascript 文件,就像您使用基于 jQuery 的应用程序一样。

Vue.js 使用双花括号 {{..}} 作为模板,所以它不会干扰你的 django 模板字符串。

Vue.js 的所有 jsFiddle 示例都以 独立模式 运行。这正是您此刻所需要的。您可以查看带有 vue.js 标签的一些最近的问题,找到一个示例 jsFiddle 并查看它是如何完成的。

对于复杂的 SPA 应用程序,您需要从服务器端单独构建您的 Vue 代码,使用虚拟 AJAX 调用对其进行彻底测试,为生产构建它,然后将最终生产构建放入您的服务器以进行端到端测试。这是你将来可以做的事情。

原文由 Mani 发布,翻译遵循 CC BY-SA 3.0 许可协议

前阵子我看了这个问题和其他问题,同时希望做几乎 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 让您的 JS 像任何普通静态内容一样通过 nginx/apache 提供服务。所以 node 作为偶尔的批处理而不是作为服务运行。需要对 Django 的配置进行一些调整,但在合理范围内。

抱歉,这很粗略,不要指望工作代码,因为我正在剥离这么多。但希望它会给你一个想法。

我的网站/__full12_vue.html:

这是我的基本 Vue-ify Django 模板,它扩展了我现有的 Django 基本模板 __full12.html

  • 假设 __full12.html 定义了所有通用的 Django 块,如 {% block content %}

(实际上,有一个非常重要的 div ID bme-vue 所以我也在最后添加了这个模板。)

  • 我添加了一个 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

这是您告诉 Webpack 为您在视图中指定的 bundle_name 服务哪个 JS 的地方。

如何配置 Webpack 超出了我的帖子范围,但我可以向你保证这是一个真正的 PIA。我从 pip django-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

好的,我确实必须修改站点的基本模板以确保 div#bme-vue 始终可用。

可能需要在这个和 mysite/__full12_vue.html 之间进行一些重构。

我的网站/__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>

原文由 JL Peyret 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Stack Overflow 翻译
子站问答
访问
宣传栏