django as your backbone
DESCRIPTION
Using (Python) Django and Backbone together, in order to "move to the frontend" with your web applications. Working API driven. Also benefits from plugins like require.js. Presentation given at Python Meetup.TRANSCRIPT
"DJANGO AS YOURBACKBONE"
AN INTRODUCTION
By Roderick Schaefer (We handle IT)
0
I'm a 31 year old freelance developer, currently working forSchuberg Philis.
My focus is development in Python and PHP accompanied bygoodies like Backbone and supporting tools.
WHERE ARE YOUSchuberg PhilisMission Critical OutsourcingDevOps<3 Python and Django
WHY ARE WE HEREPythonDjangoBackbone ?
Moving to the frontend
OLD SCHOOL WEB APP DEVELOPMENTDjango with the MTV ("MVC") pattern
(M) models.pyclass CandidatePhase(models.Model): created = models.DateTimeField(auto_now_add=True) candidate = models.ForeignKey(Candidate)
def save(self, *args, **kwargs): super(CandidatePhase, self).save(*args, **kwargs)
self.candidate.name = 'Django'
def __unicode__(self): return unicode(self.candidate.name)
(T) index.html<html> <head> <title>My very own website</title> </head> <body> <div id="content"> <ul> {% for candidate in candidates %} <li>{{ candidate.name }}</li> {% endfor %} </ul> </div> </body></html>
(V) views.py@render_to('myapp/index.html')def list_candidates(request): candidates = Candidate.objects.all() phases = Phase.objects.filter(active=True)
return { candidates: candidates, phases: phases, }
NEW: API DRIVEN, FRONTEND MVCPythonDjangojQueryTastyPieBackboneRequire.jsUnderscoreHandlebarsBackbone-tastypie ?
Wait.. Why ?
BECAUSE WE CAN!It is not only awesome to work API driven...
IT JUST MAKES SENSE
API: Securely expose databases
Frontend(s): Consume, process, present
Separation of concerns
Single Page Apps
TASTYPIE: AN INTRODUCTIONAPI webservice for DjangoAllows RESTful implementationORM exposureNon-ORMAuthenticationAuthorizationSerialization (JSON!)Pagination
BACKBONE: AN INTRODUCTIONFrontend MVCLeverages Underscore.js and friendsRouterModelsCollectionsViewsEventsTemplates (using plugins)
I CAN HAZ CODEBasic frontend app, powered by Django and Backbone
your_app/urls.pyfrom django.conf.urls import patterns, url
from apps.appraisal import views
urlpatterns = patterns( '', url(r'̂$', views.index, name='index'),)
No need to touch this anymore from now on.
your_app/views.pyfrom annoying.decorators import render_to
@render_to('appraisal/index.html')def index(request): return {}
No need to touch this anymore from now on.
your_app/templates/index.html{% extends "base.html" %}
{% block content %} <script data-main="appraisal/bootstrap" src="js/require.js"></script>
<div id="content"></div>{% endblock %}
No need to touch this anymore from now on.
your_app/api.pyclass FeedbackResource(PatchableModelResource): review = ToOneField( 'apps.appraisal.api.ReviewResource', 'review' )
class Meta: resource_name = 'appraisal/feedback' authentication = ConnectAuthentication() authorization = FeedbackAuthorization() queryset = Feedback.objects.prefetch_related('review') serializer = Serializer(formats=('json', ))
def hydrate(self, bundle): bundle.data.get('content') = awesome(bundle.data.get('content'))
return bundle
This is where you do the heavy lifting in Django.
your_app/static/bootstrap.jsrequire.config({ baseUrl: '/static', paths: { 'backbone' : 'plugins/backbone-min', 'handlebars': 'plugins/handlebars-1.0.0-rc.3.min' }, shim: { 'backbone': { deps : ['jquery', 'underscore'], exports: 'Backbone' }, 'handlebars': { exports: 'Handlebars' } }});require(['appraisal/app'], function(App) { App.initialize(); });
Convention > configuration, only specify for non-standard pathsor shim (Non-AMD).
your_app/static/app.jsdefine([ 'appraisal/router'], function(menu, Router) { var initialize = function() { Router.initialize(); };
return { initialize: initialize };});
Small example, just load router..
your_app/static/router.jsdefine([ 'backbone', 'views/dashboard'], function(Backbone, DashboardView) { var AppRouter = Backbone.Router.extend({ routes: { 'dashboard': 'dashboard' // many more routes.. },
dashboard: function() { var dashboardView = new DashboardView();
dashboardView.render(); }, });
var initialize = function(){ var app_router = new AppRouter();
Backbone.history.start(); };
return { initialize: initialize };});
Try not to do anything besides routing.
your_app/static/views/peers.jsdefine([ 'jquery', 'backbone', 'handlebars', 'collections/persons', 'text!templates/peers.html',], function($, Backbone, Handlebars, PersonCollection, peerTemplate) { var ReviewListingView = Backbone.View.extend({ el: '#content',
initialize: function() { this.personCollection = new PersonCollection();
this.personCollection.on('reset', this.updatePerson, this); this.personCollection.fetch();
_.bindAll(this, 'render', 'remove', 'help'); },
events: { 'click .help': 'help' },
template: Handlebars.compile(peerTemplate),
render: function() { this.$el.html(this.template({ forUrl: forUrl }));
your_app/static/models/SomeModel.jsdefine([ 'backbone'], function(Backbone) { var PersonModel = Backbone.Model.extend({ urlRoot: '/api/v1/team/person/' });
return PersonModel;});
your_app/static/templates/SomeTemplate.html<div class="btn-group"> <a href="#peers/add{{forUrl}}" class="btn">Add Peer</a> <button id="peers_done" class="btn">I am done</button></div>
<div> <table id="appraisal_reviews_grid"></table></div>
Event binding in your Views. {{forUrl}} is a variable given toHandlebars template compiler.
BACKBONE <3 TASTYPIEAdd PATCH support to ModelResource
class PatchableModelResource(ModelResource): def patch_detail(self, request, **kwargs): ### ---snip--- ###
# This is where the magic happens. request._read_started = False body = request.body # This is where it stops.
### ---snip--- ###
BACKBONE <3 TASTYPIEPATCH part 2
class FormValidation(TastyPieFormValidation): def uri_to_pk(self, uri): if uri is None: return None
# This is where the magic happens. if isinstance(uri, Bundle): uri = uri.data['resource_uri'] elif isinstance(uri, list) and uri: if isinstance(uri[0], Bundle): for i in xrange(len(uri)): uri[i] = uri[i].data['resource_uri'] # This is where it stops.
### ---snip--- ###
BACKBONE <3 TASTYPIEAdd File Upload support to ModelResource
class MultipartResource(object): def deserialize(self, request, data, format=None): if not format: format = request.META.get('CONTENT_TYPE', 'application/json')
if 'application/x-www-form-urlencoded' == format: return request.POST
if format.startswith('multipart'): data = request.POST.copy() data.update(request.FILES)
return data
return super(MultipartResource, self).deserialize(request, data, format)
class CandidateResource(MultipartResource, PatchableModelResource): # Implementation of API endpoint supporting PATCH and File uploads.
BACKBONE <3 TASTYPIEGlue: Backbone-Tastypie.js
<script src="{{ STATIC_URL }}js/plugins/underscore.min.js"></script><script src="{{ STATIC_URL }}js/plugins/backbone-min.js"></script>
<script src="{{ STATIC_URL }}js/plugins/backbone-tastypie.js"></script>
BACKBONE <3 TASTYPIEForm generation: Backbone-Forms.js
You could map a Django ModelForm to the frontendvar User = Backbone.Model.extend({ schema: { title: { type: 'Select', options: ['Mr', 'Mrs', 'Ms'] }, name: 'Text', email: { validators: ['required', 'email'] }, birthday: 'Date', password: 'Password', address: { type: 'NestedModel', model: Address }, notes: { type: 'List', itemType: 'Text' } }});
var user = new User();
var form = new Backbone.Form({ model: user}).render();
TRANSITIONING TO FRONTEND MVCTHIS IS CONNECT
TRANSITIONING TO FRONTEND MVCExisting applicationA collection of Apps, reallyShared libs, code, authentication layers etc
"Can I haz Backbone coolness?!"
Product is already live, actively in use.
TRANSITIONING TO FRONTEND MVC
Yes you can!
Wrap your base.html's javascripts in a block. Empty that block inyour Backbone powered apps.
Include some "legacy-scripts" template from your backboneapp's templates, for compatibility with generic stuff like the main
navigation.
TRANSITIONING TO FRONTEND MVCyour_app/templates/index.html
{% extends "page.base.page.html" %}{% block content %} {% block "javascripts" %}{% endblock %} {% include "legacy-scripts.html" %}
<script data-main="YourApp/bootstrap" src="js/require.js"></script>
<div id="content"></div>{% endblock %}
This is all you need for awesome.
CHALLENGES
URL reversal (API endpoints in Backbone Model)Session state awareness in the frontend (like Permissions,Request object)
ALTERNATIVESTastyPie vs Django REST Framework.
Backbone vs Angular vs Ember vs .. Check out TodoMVC.com!
WHAT'S NEXT?Currently looking into javascript package management, sockets,
..
1. Grunt2. Bower3. NPM4. Node JS5. So much to do.....
QUESTIONS?See you around, enjoy your evening at Schuberg Philis!
Don't forget to grab a couple of beers whileyou're at it...