Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Django + Vue, JavaScript de 3ª generación para modernizar Django

1,361 views

Published on

Slides de la charla que di en la PyConEs 2017 en Cáceres, el 24 de Septiembre.

Explicaba cómo montar un entorno de desarrollo ágil con Django en el back, Vue en el front y webpack para empaquetar el front y proporcionar Hot Module Reloading

Published in: Technology

Django + Vue, JavaScript de 3ª generación para modernizar Django

  1. 1. Django + Vue.JS JavaScript de 3ª generación para modernizar Django @javierabadia PyConES 2017 Cáceres
  2. 2. SPAs - Single Page Applications initial request HTML GET url HTML POST form HTML initial request HTML GET url (AJAX) JSON POST url (AJAX) JSON page reload Ciclo de Vida Tradicional de una Página Web Ciclo de Vida de una App SPA page reload
  3. 3. GET url (AJAX) JSON POST url (AJAX) JSON El problema fundamental en el ‘front’
  4. 4. Frameworks de JS 1ª generación 2ª generación 3ª generación
  5. 5. Un poco de Vue.js
  6. 6. ¡ Se dice /vjuː/ ! ¿ Sabes que estoy aprendiendo vue ?
  7. 7. Lo simple es fácil var app = new Vue({ el: '#app', data: { msg: ’Hi there!', frameworks: ['React', 'Vue.js', 'Angular.io'], }, methods: { isCool(framework) { return framework === 'Vue.js'; }, }, }); <div id="app"> {{msg}} <ul> <li v-for="framework in frameworks"> {{framework}} <span v-if="isCool(framework)"> , so cool! </span> </li> </ul> </div> HTML JavaScript Declarative Rendering Condicionales y Bucles
  8. 8. Eventos var app = new Vue({ ... methods: { isCool(framework) { return this.cool.indexOf(framework) !== -1; }, toggleCool(framework) { if( this.isCool(framework) ) this.cool.splice(this.cool.indexOf(framework),1); else this.cool.push(framework); } }, }); <ul> <li v-for="framework in frameworks" @click="toggleCool(framework)"> {{framework}} <span v-if="isCool(framework)"> , so cool! </span> </li> </ul> HTML JavaScript
  9. 9. Enlazando (binding) atributos <ul> <li class="framework-item" :class="{cool: isCool(framework)}" v-for="framework in frameworks" @click="toggleCool(framework)"> {{framework}}<span v-if="isCool(framework)">, so cool!</span> </li> </ul>
  10. 10. Vue.js
  11. 11. Componentes vue instances
  12. 12. Single-File components // MyComponent.vue <template> <div></div> </template> <script> export default { data() { return {}; }, }; </script> <style lang="less"> </style> js css webpack (o browserify)
  13. 13. Single-File components // MyComponent.vue <template> <div></div> </template> <script> export default { data() { return {}; }, }; </script> <style lang="less"> </style> js css webpack (o browserify)
  14. 14. Arquitectura de una SPA de Vue.js $ vue init webpack-simple frontend $ cd frontend $ yarn $ yarn run dev localhost:8080
  15. 15. DEMO 1
  16. 16. Ejemplo: Catálogo de GIFs para IoT http://localhost:8000/ http://localhost:8000/detail/323
  17. 17. La estructura clásica de Django Vistas Templates Modelos ORM Autenticación Middleware Formularios Administración HTML
  18. 18. DEMO 2
  19. 19. Django :8000 Webpack :8080
  20. 20. La experiencia ‘óptima’ de desarrollo • Trabajar de forma unificada (mismo IDE) • Desarrollo en el backend • debugging • breakpoints, etc • Desarrollo en el frontend • con agilidad • usando Hot Module Replacement (HMR)
  21. 21. El puente entre Django y Webpack $ cd frontend $ npm install --save-dev webpack-bundle-tracker # (en un virtualenv, por supuesto) $ pip install django-webpack-loader
  22. 22. Todo junto var path = require('path') var webpack = require('webpack') var BundleTracker = require('webpack-bundle-tracker'); module.exports = { … plugins: [ new BundleTracker({filename: './webpack-stats.json'}) ] } // frontend/webpack.conf.js {% extends 'base.html' %} {% load render_bundle from webpack_loader %} {% block content %} <div id="app"></div> {% render_bundle 'main' %} {% endblock %} {# backend/templates/index.html #} { "status": "done", "publicPath": "http://localhost:8080/dist/", "chunks": { "main": [ { "name": "build.js", "publicPath": "http://localhost:8080/dist/build.js", "path": "/Users/jami/…/gif_catalog/frontend/dist/build.js" } ] } } // frontend/webpack-stats.json … WEBPACK_LOADER = { 'DEFAULT': { 'BUNDLE_DIR_NAME': 'dist/', 'STATS_FILE': os.path.join(BASE_DIR, 'frontend/webpack-stats.json'), } } # settings.py
  23. 23. def index(request): return render(request, 'index.html', {}) # backend/views.py {% extends 'base.html' %} {% load render_bundle from webpack_loader %} {% block content %} <div id="app"></div> {% render_bundle 'main' %} {% endblock %} {# backend/templates/index.html #} Django :8000 Webpack :8080 urlpatterns = [ url(r'^', views.index), ] # backend/urls.py App.vue main.js *.vue localhost:8000/ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> … </head> <body> <div id="app"></div> <script type="text/javascript" src="http://localhost:8080/dist/build.js"> </script> </body </html> HMRbuild.js
  24. 24. DEMO 3
  25. 25. Webpack: detalles de configuración var path = require('path') var webpack = require('webpack') var BundleTracker = require('webpack-bundle-tracker'); module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, './dist'), publicPath: 'http://localhost:8080/dist/', filename: 'build.js' }, module: { ... }, devServer: { historyApiFallback: true, noInfo: true, headers: { 'Access-Control-Allow-Origin': '*' } }, plugins: [ new BundleTracker({filename: './webpack-stats.json'}) ] } url absoluta incluyendo puerto activar CORS para que el cliente HMR pueda hacer peticiones al devServer de webpack // webpack.config.js
  26. 26. Django :8000 localhost:8000/ HTML localhost:8000/api/* JSON Webpack :8080 HMR
  27. 27. Implementar una API ¿REST? urlpatterns = [ url(r'^api/pics', api.pics), url(r'^', views.index), ] # backend/urls.py def pics(request): count = GifPicture.objects.all().count() all_ids = range(count) random.shuffle(all_ids) picked_ids = all_ids[:18] gif_pictures = GifPicture.objects .filter(id__in=picked_ids) .order_by('-upload_date') result = { 'pics': gif_pictures, } return JsonResponse(result) # backend/api.py import axios from 'axios'; export default { getRandomPics() { return axios.get('/api/pics') .then(response => { return response.data.pics; }); }, } // gifPicsApi.js Django :8000 … <script> import gifPicsApi from '../services/gifPicsApi.js'; export default { … mounted() { gifPicsApi.getRandomPics().then(pics => { this.pics = pics; }); }, }; </script> // GifHome.vue
  28. 28. DEMO 4
  29. 29. Autenticación Una posible implementación: 2PA http://localhost:8000/login http://localhost:8000/* create session redirect set cookie vue-routing
  30. 30. Django + auth + sessions <script> export default { name: 'app', data() { return { msg: 'Welcome to Your Vue.js App!', user: {}, } }, created() { this.user = window.user; }, } </script> // App.vue @login_required def index(request): context = { 'user': request.user, } return render(request, 'index.html', context) # backend/views.py {% extends 'base.html' %} {% load render_bundle from webpack_loader %} {% block content %} <div id="app"></div> <script> var user = { username: "{{ user.username }}", email: "{{ user.email }}", }; </script> {% render_bundle 'main' %} {% endblock %} # backend/templates/index.html
  31. 31. DEMO 5
  32. 32. Routing
  33. 33. DEMO 6
  34. 34. Rutas urlpatterns = [ url(r'^api/suggestions/$', api.suggestions), url(r'^api/search/$', api.search), url(r'^api/pics/(?P<id>[0-9]+)$', api.pic_details), url(r'^api/pics/$', api.pics), url(r'^', views.index), ] # backend/urls.py Vue.use(Router); const router = new Router({ mode: 'history', routes: [ { path: '/', name: 'home', component: GifHome },{ path: '/detail/:id', name: 'detail', component: GifDetail, props:true },{ path: '*', component: Error404 }, // Not found ], }); # router.js urlpatterns = [ url(r'^admin/', admin.site.urls), url('^', include('django.contrib.auth.urls')), url(r'^', include('backend.urls')) ] # urls.py /login /logout
  35. 35. ¿Es Django el mejor backend? código isomórfico server-side rendering async I/O
  36. 36. Comentarios Finales • APIs • REST? • ‘a pelo’ • django-tastypie • django-rest-framework • GraphQL • graphene (django) • apollo (vue) • (no lo he probado) • Server Side Rendering • nope • seeding • sip • SEO • pre-render • inyectar contenido en Django
  37. 37. Conclusión
  38. 38. Referencias • Doc de Vue: https://vuejs.org/v2/guide/single-file-components.html • Doc de Webpack: https://webpack.js.org/ • SurviveJS: https://survivejs.com/webpack/ • webpack-bundle-tracker: https://github.com/ezhome/webpack-bundle-tracker • django-webpack-loader: https://github.com/ezhome/django-webpack-loader • hello-vue + Django project: https://github.com/rokups/hello-vue-django • modernize Django frontend: http://owaislone.org/blog/modern-frontends-with-django/ • Django + REACT with HMR: http://owaislone.org/blog/webpack-plus-reactjs-and-django/ • Django-angular: http://django-angular.readthedocs.io/en/latest/index.html
  39. 39. Gracias! @javierabadia

×