web-dev-qa-db-fra.com

Utilisation de Vue avec django

J'ai récemment commencé sur un site Web de médias sociaux à l'aide de Django. J'utilise le moteur de modèle par défaut Django pour remplir mes pages. Mais en ce moment, je veux ajouter javascript pour rendre le site plus dynamique. Ça signifie:

  • L'en-tête et le pied de page sont les identiques sur chaque page. L'en-tête doit avoir un menu déroulant, un formulaire de recherche qui recherche lorsque vous tapez.
  • Mon Django a un modèle de base qui a l'en-tête et le pied de page HTML, puisque chaque page devrait en avoir.
  • Le site se compose de plusieurs pages, pensez à la page d'index, la page de profil, la page d'inscription. Chacune de ces pages a des composants dynamiques communs mais aussi beaucoup de différents. Par exemple, la page d'inscription devrait avoir une validation de formulaire à la volée, mais la page de profil n'en a pas besoin. La page de profil doit avoir un flux d'état avec défilement infini.

Je veux utiliser Vue pour gérer les composants dynamiques, mais je ne sais pas par où commencer. L'application ne doit pas être un SPA.

  • Comment dois-je structure le code Vue?
  • Comment dois-je bundle cela. Vous utilisez Gulp? Ou peut-être Django-webpack-loader ?
  • Je devrais toujours pouvoir utiliser les balises de modèle Django, par exemple, je veux pouvoir utiliser {% url 'index' %} dans le menu déroulant.
17
user6827

Cela ressemble à une question basée sur une opinion pour laquelle il n'y a pas de réponse claire.

Vous avez mentionné que vous ne souhaitez pas que l'application soit une application monopage (SPA) . Si oui, quelle est la motivation pour utiliser Vue? Pour gérer les interactions des utilisateurs dans la page?

Vue peut parfaitement fonctionner dans un contexte non SPA. Il vous aidera à gérer les interactions riches au sein de la page, comme la liaison de vos données à des listes déroulantes, des formulaires, etc. Mais la véritable puissance de Vue apparaît lorsque vous l'utilisez dans le contexte SPA.

Pour votre cas, je recommanderais d'utiliser Vue.js en mode autonome , où vous pouvez rapidement définir template dans Vue composants et écrivez tout votre code facilement dans un seul fichier javascript.

Voici ce dont vous avez besoin: https://vuejs.org/guide/installation.html#Standalone

En "mode autonome Vue.js", aucun système de construction de webpack ou vue-cli. Vous pouvez créer l'application directement dans votre environnement de développement existant pour Django. gulp peut éventuellement réduire et regrouper vos fichiers javascript normalement, tout comme vous le faites avec vos applications basées sur jQuery.

Vue.js utilise des accolades doubles bouclées {{..}} pour les modèles, de sorte qu'il n'interfère pas avec vos chaînes de modèles Django.

Tous les exemples jsFiddle pour Vue.js fonctionnent en mode autonome . C'est précisément ce dont vous avez besoin en ce moment. Vous pouvez consulter certaines des questions récentes avec vue.js, trouvez un exemple de jsFiddle et voyez comment cela se fait.

Pour les applications SPA complexes, vous devez créer votre code Vue séparément du côté serveur, testez-le soigneusement avec des appels factices AJAX, construisez-le pour la production, puis supprimez-le). la production finale intégrée à votre serveur pour des tests de bout en bout. C'est quelque chose que vous pourrez faire à l'avenir.

19
Mani

J'ai regardé cette question et d'autres tout à l'heure tout en cherchant à faire à peu près ce que le PO demandait. Malheureusement, la plupart des informations sur Vue est donnée dans un contexte SPA. Cependant, comme Evan You l'a souvent répété, Vue n'est pas d'opinion et ne nécessite pas utilisation au sein d'un SPA.

Je voudrais partager certaines de mes conclusions et esquisser une approche possible pour faire fonctionner ensemble Django et Vue. Bien qu'un SPA ne soit pas requis, je pense que la vraie puissance de Vue vient de ses composants et cela vous pousse un peu vers un Webpack ou une approche similaire, pas un simple mode autonome en html.

Aussi, juste pour être très clair: 90% + de mon contenu Django voir le code et les modèles sont restés exactement les mêmes qu'avant. Django ne fait pas particulièrement attention qu'il utilise webpack_loader et render_bundle. Et encore moins que render_bundle a quelque chose à voir avec Vue. Entre Django template blocks, verbatim tags et Vue slots, vous bénéficiez d'une grande flexibilité qui vous permet de laisser la plupart de votre contenu existant seul.

Enfin, une fois que votre application est stable, vous pouvez désactiver le serveur hotreload sur le port 3000, exécutez npm run build-production et utilisez collectstatic pour que votre JS soit servi via nginx/Apache comme tout contenu statique normal. Ainsi, node s'exécute en tant que lot occasionnel plutôt qu'en tant que service. Prend un peu de tripoter la configuration de Django, mais tout à fait dans les limites du raisonnable.

Toutes mes excuses, c'est assez sommaire, ne vous attendez pas à du code de travail car je me déshabille tellement. Mais j'espère que cela vous donnera une idée.

mysite/__ full12_vue.html:

Ceci est mon modèle Vue-ify de base Django modèle qui étend mon modèle de base Django base, __ full12.html .

  • supposons que __ full12.html définit tous les blocs généraux Django, comme { % contenu de bloc%} et similaires

    (en fait, il y a un div très important avec ID bme-vue j'ai donc ajouté ce modèle à la fin également. )

  • J'ai ajouté un composant Vue pour afficher les messages des utilisateurs.

  • Et redéfini le modèle de menu pour utiliser Vue + Bootstrap dropdowns.

{% 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 :

C'est là que vous indiquez à Webpack le JS à servir pour le bundle_name que vous spécifiez dans la vue.

Comment configurer Webpack est hors de portée de mon message, mais je peux vous assurer que c'était un vrai PIA. J'ai commencé avec pip Django-webpack-loader , puis https://github.com/NdagiStanley/vue-Django puis Bootstrap 4 trucs. Cependant, le résultat final est bien plus puissant que autonome, à mon avis.

/*
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

Enfin, nous sommes prêts à afficher certains contenus réels:

bme-nav-item et bme-tab-pane sont 2 composants personnalisés Vue que j'utilise pour la navigation et le contenu de l'onglet Boostrap 4).

Django utilise var settings= some-json-object pour communiquer des données spécifiques à l'instance, plutôt que génériques, à Vue et 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:

Et une implémentation de magasin naïve, principalement en lecture seule:

//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};

et pour vous donner une idée de la hiérarchie générale des fichiers:

.
./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, j'ai dû modifier le modèle de base du site pour m'assurer que div # bme-vue est toujours disponible.

Probablement un peu de refactorisation nécessaire entre cela et 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

Voici comment j'intègre Vue à un projet Django:

La première approche consiste à créer des applications Django et Vue distinctes. Django sera responsable de servir l'API construite en utilisant le cadre Django REST et Vue consommera ces API en utilisant le client Axios ou l'API de récupération du navigateur. Vous aurez besoin d'avoir deux serveurs, à la fois en développement et en production, un pour Django (API REST) ​​et l'autre pour Vue (pour servir des fichiers statiques).

La deuxième approche est différente, les applications frontales et back-end seront couplées. Fondamentalement, vous utiliserez Django pour servir à la fois le frontend Vue et pour exposer l'API REST. Vous devrez donc intégrer Vue et Webpack à Django, voici les étapes que vous pouvez suivre pour le faire

Générez d'abord votre projet Django puis à l'intérieur de ce répertoire de projet, générez votre application Vue en utilisant la CLI Vue

Pour le projet Django, installez Django-webpack-loader Avec pip:

pip install Django-webpack-loader

Ajoutez ensuite l'application aux applications installées et configurez-la dans settings.py en ajoutant l'objet suivant

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

Ajoutez ensuite un modèle Django qui sera utilisé pour monter l'application Vue et sera servi par Django

{ % 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>

Ajoutez ensuite une URL dans urls.py pour diffuser ce modèle

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")),

]

Si vous démarrez les serveurs Django et Vue à ce stade, vous obtiendrez une erreur Django indiquant que le webpack-stats.json n'existe pas. Donc, vous devez ensuite rendre votre application Vue capable de générer le fichier de statistiques.

Allez-y et naviguez dans votre application Vue puis installez webpack-bundle-tracker

npm install webpack-bundle-tracker --save

Accédez à build/webpack.base.conf.js Puis ajoutez

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

Cela ajoute le plugin BundleTracker à Webpack et lui dit de générer le fichier webpack-stats.json Dans le dossier parent où vivent les fichiers Django .

Maintenant, si vous réexécutez votre serveur Vue le webpack-stats.json sera généré et Django pourra le consommer pour trouver des informations sur les bundles Webpack générés par le serveur de développement Vue .

Vous pouvez trouver plus d'informations dans ce tutoriel .

3
Ahmed Bouchefra