Использование Vue с django

Недавно я начал работу на каком-то веб-сайте в социальных сетях, используя Django. Я использую механизм шаблона django по умолчанию для заполнения моих страниц. Но в этот момент я хочу добавить javascript, чтобы сделать сайт более динамичным. Это означает:

  • Верхний и нижний колонтитулы одинаковы на каждой странице. Заголовок должен иметь раскрывающееся меню, форму поиска, которая выполняется при вводе текста.
  • В моем текущем приложении django есть базовый шаблон , который имеет HTML-код верхнего и нижнего колонтитула, поскольку каждая страница должна иметь это.
  • Сайт состоит из нескольких страниц, подумайте об индексной странице, странице профиля, странице регистрации. Каждая из этих страниц имеет некоторые общие, но также и множество различных динамических компонентов. Например, страница регистрации должна иметь форму проверки на лету, но страница профиля ей не нужна. Страница профиля должна иметь фид состояния с бесконечной прокруткой.

Я хочу использовать Vue для работы с динамическими компонентами, но я не знаю, как мне начать работу. Приложение не должно быть SPA.

  • Как я должен структурировать код Vue?
  • Как мне расслоить. Используя Gulp? Или возможно django-webpack-loader?
  • Я все равно могу использовать теги шаблона Django, например, я хочу иметь возможность использовать {% url 'index' %} в выпадающем меню.

Ответы

Ответ 1

Это выглядит как вопрос мнение, для которого нет четкого ответа.

Вы упомянули о том, что не хотите, чтобы приложение было единственным приложением страницы (SPA). Если да, то в чем мотивация использования Vue? Чтобы обрабатывать пользовательские взаимодействия внутри страницы?

Vue может работать отлично в контексте non-SPA. Это поможет вам справиться с богатыми взаимодействиями на странице, например, привязать ваши данные к раскрывающимся спискам, формам и т.д. Но реальная сила Vue появляется, когда вы используете ее в контексте SPA.

В вашем случае я бы рекомендовал использовать Vue.js в автономном режиме , где вы можете быстро определить template в компонентах Vue и легко записать весь свой код в одном файле javascript.

Вот что вам нужно: https://vuejs.org/guide/installation.html#Standalone

В "автономном режиме Vue.js" нет необходимости в какой-либо системе сборки webpack или vue-cli. Вы можете создать приложение непосредственно в существующей среде dev для django. gulp может произвольно минимизировать и связывать ваши файлы javascript, как и с вашими приложениями на основе jQuery.

Vue.js использует двойные фигурные скобки {{..}} для шаблонов, поэтому он не будет мешать вашим строкам шаблона django.

Все примеры jsFiddle для Vue.js выполняются в автономном режиме . Это именно то, что вам нужно в этот момент. Вы можете посмотреть некоторые из последних вопросов с тегом vue.js, найти образец jsFiddle и посмотреть, как это делается.

Для сложных SPA-приложений вам необходимо создать свой код Vue отдельно от серверной части, тщательно протестировать его с помощью фиктивных вызовов AJAX, построить его для производства и затем отбросить окончательную производственную сборку на свой сервер для сквозного тестирования. Это то, что вы можете сделать в будущем.

Ответ 2

Я смотрел на этот вопрос, а другие на некоторое время назад, глядя на то, чтобы делать то, что спрашивал OP. К сожалению, большая часть информации о Vue представлена ​​в контексте SPA. Однако, как часто повторял Эван, Vue не упрям ​​и не требует использования в SPA.

Я хотел бы поделиться некоторыми своими выводами и наметить возможный подход к совместной работе Django и Vue. В то время как SPA не требуется Я думаю, что реальная сила Vue исходит от своих Компонентов, и это подталкивает вас к Webpack или подобному подходу, а не простому автономному mode в html.

Также, чтобы быть предельно ясным: 90% + моего содержимого код и шаблоны Django остались точно такими же, как раньше. Django не особо заботится о том, чтобы он использовал webpack_loader и render_bundle. И тем более, что render_bundle имеет какое-то отношение к Vue. Между тегами Django template blocks, verbatim и Vue slots вы получаете большую гибкость, которая позволяет оставить только часть вашего существующего контента.

Наконец, мое понимание django-webpack заключается в том, что, когда вы будете готовы, вы можете collectstatic, и все ваши пакеты, созданные с помощью webpack, могут затем обслуживаться django/nginx/apache, как и другие статические ресурсы, без каких-либо node или webpack. Таким образом, вы не добавляете сложные движущиеся части js в свои производственные системы. (примечание: это последнее является предположением, пожалуйста, подтвердите или опроверьте комментарии, если вы знаете)

Извинения, это довольно отрывочно, не ожидайте рабочего кода, поскольку я так много снимаю. Но, надеюсь, это даст вам представление.

MySite/__ 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, какой JS должен служить для bundle_name, который вы указываете в представлении.

Как настроить Webpack выходит за рамки моего сообщения, но я могу заверить вас, что это настоящий PIA. Я начал с pip django-webpack-loader, затем https://github.com/NdagiStanley/vue-django, затем загрузив 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-item и bme-tab-pane - 2 пользовательских компонента Vue, которые я использую для вкладок и содержимого вкладки Boostrap 4.

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 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 not SPA-only
      // it 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 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 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 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 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 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.

MySite/__ full12.html

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

    <!--     KEY: the #bme-vue wrapper/css selector tells Vue what 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>