django as your backbone

Post on 18-Jan-2015

2.553 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

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...

top related