├── .gitignore ├── README.md └── backbone_example ├── __init__.py ├── local_settings.py ├── manage.py ├── requirements.txt ├── settings.py ├── static ├── css │ └── style.css └── js │ ├── ICanHaz.min.js │ ├── app.js │ ├── backbone-min.js │ ├── backbone-tastypie.js │ └── underscore-min.js ├── templates ├── detailApp.mustache ├── index.html ├── listApp.mustache └── tweetTemplate.mustache ├── tweets ├── __init__.py ├── api │ ├── __init__.py │ ├── api.py │ └── resources.py ├── models.py ├── templatetags │ ├── __init__.py │ ├── mustache.py │ ├── straight_include.py │ └── verbatim.py ├── tests │ ├── __init__.py │ └── gravatar.py ├── urls.py └── views.py └── urls.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *.db 4 | pip-log.txt 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Django Backbone Example 2 | ----------------------- 3 | 4 | This is an example application using Django, with the help of [django-tastypie](https://github.com/toastdriven/django-tastypie), and [backbone.js](https://github.com/documentcloud/backbone). Because everyone needs to write a Twitter clone, it is a Twitter clone. 5 | 6 | 7 | Running locally 8 | --------------- 9 | 10 | Preferably in a virtualenv, run the following commands: 11 | 12 | git clone https://joshbohde@github.com/joshbohde/django-backbone-example.git 13 | cd django-backbone-example/backbone_example 14 | pip install -r requirements.txt 15 | ./manage.py syncdb --noinput 16 | ./manage.py runserver 17 | -------------------------------------------------------------------------------- /backbone_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bohde/django-backbone-example/92d0e644feebab4e49081cbf632f9f0f1c4643d5/backbone_example/__init__.py -------------------------------------------------------------------------------- /backbone_example/local_settings.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /backbone_example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /backbone_example/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.3.2 2 | mimeparse>=0.1.3 3 | python-dateutil==1 4 | django-tastypie==0.9.11 5 | pystache==0.3.1 6 | -------------------------------------------------------------------------------- /backbone_example/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for backbone_example project. 2 | import os 3 | 4 | PROJECT_ROOT = os.path.dirname(__file__) 5 | 6 | DEBUG = True 7 | TEMPLATE_DEBUG = DEBUG 8 | 9 | ADMINS = ( 10 | # ('Your Name', 'your_email@domain.com'), 11 | ) 12 | 13 | MANAGERS = ADMINS 14 | 15 | DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. 16 | DATABASE_NAME = '/tmp/backbone_example.db' # Or path to database file if using sqlite3. 17 | DATABASE_USER = '' # Not used with sqlite3. 18 | DATABASE_PASSWORD = '' # Not used with sqlite3. 19 | DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. 20 | DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. 21 | 22 | # Local time zone for this installation. Choices can be found here: 23 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 24 | # although not all choices may be available on all operating systems. 25 | # If running in a Windows environment this must be set to the same as your 26 | # system time zone. 27 | TIME_ZONE = 'America/Chicago' 28 | 29 | # Language code for this installation. All choices can be found here: 30 | # http://www.i18nguy.com/unicode/language-identifiers.html 31 | LANGUAGE_CODE = 'en-us' 32 | 33 | SITE_ID = 1 34 | 35 | # If you set this to False, Django will make some optimizations so as not 36 | # to load the internationalization machinery. 37 | USE_I18N = True 38 | 39 | # Absolute path to the directory that holds media. 40 | # Example: "/home/media/media.lawrence.com/" 41 | MEDIA_ROOT = os.path.join(PROJECT_ROOT,'media') 42 | STATIC_ROOT = os.path.join(PROJECT_ROOT, 'collected_static') 43 | STATICFILES_DIRS = ( 44 | os.path.join(PROJECT_ROOT, 'static'), 45 | ) 46 | 47 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 48 | # trailing slash if there is a path component (optional in other cases). 49 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 50 | MEDIA_URL = '/media/' 51 | 52 | STATIC_URL = "/static/" 53 | 54 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 55 | # trailing slash. 56 | # Examples: "http://foo.com/media/", "/media/". 57 | ADMIN_MEDIA_PREFIX = '/media/admin/' 58 | 59 | # Make this unique, and don't share it with anybody. 60 | SECRET_KEY = '637i_o@27q89j^-gm+i!5g4#&pwo%)^^m&2g@4o^a8f92l#klq' 61 | 62 | # List of callables that know how to import templates from various sources. 63 | TEMPLATE_LOADERS = ( 64 | 'django.template.loaders.filesystem.load_template_source', 65 | 'django.template.loaders.app_directories.load_template_source', 66 | # 'django.template.loaders.eggs.load_template_source', 67 | ) 68 | 69 | MIDDLEWARE_CLASSES = ( 70 | 'django.middleware.common.CommonMiddleware', 71 | 'django.contrib.sessions.middleware.SessionMiddleware', 72 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 73 | ) 74 | 75 | ROOT_URLCONF = 'backbone_example.urls' 76 | 77 | TEMPLATE_DIRS = ( 78 | os.path.join(PROJECT_ROOT, "templates"), 79 | ) 80 | 81 | INSTALLED_APPS = ( 82 | 'django.contrib.auth', 83 | 'django.contrib.contenttypes', 84 | 'django.contrib.sessions', 85 | 'django.contrib.sites', 86 | 'django.contrib.staticfiles', 87 | 88 | 'tweets', 89 | ) 90 | -------------------------------------------------------------------------------- /backbone_example/static/css/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | background-color: #eee; 3 | font-family: "Helvetica Neue", Arial, sans-serif; 4 | } 5 | 6 | body { 7 | width: 720px; 8 | margin: auto; 9 | color: #777; 10 | } 11 | 12 | #input { 13 | padding: 1em 2em; 14 | } 15 | 16 | #input strong { 17 | display: block; 18 | font-size: 1.5em; 19 | font-weight: bold; 20 | } 21 | 22 | #input #message { 23 | margin-top: .5em; 24 | width: 100%; 25 | display: block; 26 | border: #ccc 1px solid; 27 | } 28 | 29 | #input .tweet { 30 | display: block; 31 | font-size: 1.25em; 32 | color: #333; 33 | background-color: #eee; 34 | padding: .25em; 35 | margin: .25em 0; 36 | } 37 | 38 | h2 { 39 | font-size: 1.5em; 40 | font-weight: bold; 41 | margin: 1em 1.33em; 42 | } 43 | 44 | #tweets { 45 | margin: 0 2em; 46 | border-top: 1px solid #ccc; 47 | } 48 | 49 | .tweet { 50 | font-size: 1.25em; 51 | color: #333; 52 | border: 1px solid #ccc; 53 | border-top: 1px solid #fff; 54 | padding: .5em; 55 | } 56 | 57 | .username { 58 | font-weight: bold; 59 | } 60 | 61 | .timestamp { 62 | font-size: .75em; 63 | } -------------------------------------------------------------------------------- /backbone_example/static/js/ICanHaz.min.js: -------------------------------------------------------------------------------- 1 | (function(){var m=function(){var f=function(){};f.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":true},context:{},render:function(a,b,c,d){if(!d){this.context=b;this.buffer=[]}if(!this.includes("",a))if(d)return a;else{this.send(a);return}a=this.render_pragmas(a);a=this.render_section(a,b,c);if(d)return this.render_tags(a,b,c,d);this.render_tags(a,b,c,d)},send:function(a){a!=""&&this.buffer.push(a)},render_pragmas:function(a){if(!this.includes("%",a))return a; 2 | var b=this;return a.replace(RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag),function(c,d,e){if(!b.pragmas_implemented[d])throw{message:"This implementation of mustache doesn't understand the '"+d+"' pragma"};b.pragmas[d]={};if(e){c=e.split("=");b.pragmas[d][c[0]]=c[1]}return""})},render_partial:function(a,b,c){a=this.trim(a);if(!c||c[a]===undefined)throw{message:"unknown_partial '"+a+"'"};if(typeof b[a]!="object")return this.render(c[a],b,c,true);return this.render(c[a],b[a],c,true)},render_section:function(a, 3 | b,c){if(!this.includes("#",a)&&!this.includes("^",a))return a;var d=this;return a.replace(RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg"),function(e,i,j,h){e=d.find(j,b);if(i=="^")return!e||d.is_array(e)&&e.length===0?d.render(h,b,c,true):"";else if(i=="#")return d.is_array(e)?d.map(e,function(g){return d.render(h,d.create_context(g),c,true)}).join(""):d.is_object(e)?d.render(h,d.create_context(e),c,true):typeof e==="function"? 4 | e.call(b,h,function(g){return d.render(g,b,c,true)}):e?d.render(h,b,c,true):""})},render_tags:function(a,b,c,d){var e=this,i=function(){return RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")},j=i(),h=function(n,l,k){switch(l){case "!":return"";case "=":e.set_delimiters(k);j=i();return"";case ">":return e.render_partial(k,b,c);case "{":return e.find(k,b);default:return e.escape(e.find(k,b))}};a=a.split("\n");for(var g=0;g\\]/g,function(b){switch(b){case "&":return"&";case "\\":return"\\\\";case '"':return'"';case "<":return"<";case ">":return">";default:return b}})},create_context:function(a){if(this.is_object(a))return a;else{var b=".";if(this.pragmas["IMPLICIT-ITERATOR"])b=this.pragmas["IMPLICIT-ITERATOR"].iterator;var c={};c[b]=a;return c}}, 7 | is_object:function(a){return a&&typeof a=="object"},is_array:function(a){return Object.prototype.toString.call(a)==="[object Array]"},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},map:function(a,b){if(typeof a.map=="function")return a.map(b);else{for(var c=[],d=a.length,e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")},has:function(a){return this.attributes[a]!=null},set:function(a,b){b||(b={});if(!a)return this;if(a.attributes)a=a.attributes;var c=this.attributes,d=this._escapedAttributes;if(!b.silent&&this.validate&&!this._performValidation(a,b))return!1;if(this.idAttribute in a)this.id=a[this.idAttribute]; 10 | var e=this._changing;this._changing=!0;for(var g in a){var h=a[g];if(!f.isEqual(c[g],h))c[g]=h,delete d[g],this._changed=!0,b.silent||this.trigger("change:"+g,this,h,b)}!e&&!b.silent&&this._changed&&this.change(b);this._changing=!1;return this},unset:function(a,b){if(!(a in this.attributes))return this;b||(b={});var c={};c[a]=void 0;if(!b.silent&&this.validate&&!this._performValidation(c,b))return!1;delete this.attributes[a];delete this._escapedAttributes[a];a==this.idAttribute&&delete this.id;this._changed= 11 | !0;b.silent||(this.trigger("change:"+a,this,void 0,b),this.change(b));return this},clear:function(a){a||(a={});var b,c=this.attributes,d={};for(b in c)d[b]=void 0;if(!a.silent&&this.validate&&!this._performValidation(d,a))return!1;this.attributes={};this._escapedAttributes={};this._changed=!0;if(!a.silent){for(b in c)this.trigger("change:"+b,this,void 0,a);this.change(a)}return this},fetch:function(a){a||(a={});var b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&& 12 | c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"read",this,a)},save:function(a,b){b||(b={});if(a&&!this.set(a,b))return!1;var c=this,d=b.success;b.success=function(a,e,f){if(!c.set(c.parse(a,f),b))return!1;d&&d(c,a,f)};b.error=i(b.error,c,b);var f=this.isNew()?"create":"update";return(this.sync||e.sync).call(this,f,this,b)},destroy:function(a){a||(a={});if(this.isNew())return this.trigger("destroy",this,this.collection,a);var b=this,c=a.success;a.success=function(d){b.trigger("destroy", 13 | b,b.collection,a);c&&c(b,d)};a.error=i(a.error,b,a);return(this.sync||e.sync).call(this,"delete",this,a)},url:function(){var a=k(this.collection)||this.urlRoot||l();if(this.isNew())return a;return a+(a.charAt(a.length-1)=="/"?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this)},isNew:function(){return this.id==null},change:function(a){this.trigger("change",this,a);this._previousAttributes=f.clone(this.attributes);this._changed=!1},hasChanged:function(a){if(a)return this._previousAttributes[a]!= 14 | this.attributes[a];return this._changed},changedAttributes:function(a){a||(a=this.attributes);var b=this._previousAttributes,c=!1,d;for(d in a)f.isEqual(b[d],a[d])||(c=c||{},c[d]=a[d]);return c},previous:function(a){if(!a||!this._previousAttributes)return null;return this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},_performValidation:function(a,b){var c=this.validate(a);if(c)return b.error?b.error(this,c,b):this.trigger("error",this,c,b),!1;return!0}}); 15 | e.Collection=function(a,b){b||(b={});if(b.comparator)this.comparator=b.comparator;f.bindAll(this,"_onModelEvent","_removeReference");this._reset();a&&this.reset(a,{silent:!0});this.initialize.apply(this,arguments)};f.extend(e.Collection.prototype,e.Events,{model:e.Model,initialize:function(){},toJSON:function(){return this.map(function(a){return a.toJSON()})},add:function(a,b){if(f.isArray(a))for(var c=0,d=a.length;c').hide().appendTo("body")[0].contentWindow,this.navigate(a); 25 | this._hasPushState?g(window).bind("popstate",this.checkUrl):"onhashchange"in window&&!b?g(window).bind("hashchange",this.checkUrl):setInterval(this.checkUrl,this.interval);this.fragment=a;m=!0;a=window.location;b=a.pathname==this.options.root;if(this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;else if(this._wantsPushState&&this._hasPushState&&b&&a.hash)this.fragment=a.hash.replace(j,""),window.history.replaceState({}, 26 | document.title,a.protocol+"//"+a.host+this.options.root+this.fragment);if(!this.options.silent)return this.loadUrl()},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.iframe.location.hash));if(a==this.fragment||a==decodeURIComponent(this.fragment))return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(window.location.hash)},loadUrl:function(a){var b=this.fragment=this.getFragment(a); 27 | return f.any(this.handlers,function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){var c=(a||"").replace(j,"");if(!(this.fragment==c||this.fragment==decodeURIComponent(c))){if(this._hasPushState){var d=window.location;c.indexOf(this.options.root)!=0&&(c=this.options.root+c);this.fragment=c;window.history.pushState({},document.title,d.protocol+"//"+d.host+c)}else if(window.location.hash=this.fragment=c,this.iframe&&c!=this.getFragment(this.iframe.location.hash))this.iframe.document.open().close(), 28 | this.iframe.location.hash=c;b&&this.loadUrl(a)}}});e.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.delegateEvents();this.initialize.apply(this,arguments)};var u=/^(\S+)\s*(.*)$/,n=["model","collection","el","id","attributes","className","tagName"];f.extend(e.View.prototype,e.Events,{tagName:"div",$:function(a){return g(a,this.el)},initialize:function(){},render:function(){return this},remove:function(){g(this.el).remove();return this},make:function(a, 29 | b,c){a=document.createElement(a);b&&g(a).attr(b);c&&g(a).html(c);return a},delegateEvents:function(a){if(a||(a=this.events))for(var b in f.isFunction(a)&&(a=a.call(this)),g(this.el).unbind(".delegateEvents"+this.cid),a){var c=this[a[b]];if(!c)throw Error('Event "'+a[b]+'" does not exist');var d=b.match(u),e=d[1];d=d[2];c=f.bind(c,this);e+=".delegateEvents"+this.cid;d===""?g(this.el).bind(e,c):g(this.el).delegate(d,e,c)}},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b= 30 | 0,c=n.length;b 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' ); 64 | 65 | return url; 66 | }; 67 | 68 | /** 69 | * Return 'data.objects' if it exists and is an array, or else just plain 'data'. 70 | */ 71 | Backbone.Model.prototype.parse = function( data ) { 72 | return data && data.objects && ( _.isArray( data.objects ) ? data.objects[ 0 ] : data.objects ) || data; 73 | }; 74 | 75 | Backbone.Collection.prototype.parse = function( data ) { 76 | return data && data.objects; 77 | }; 78 | 79 | Backbone.Collection.prototype.url = function( models ) { 80 | var url = this.urlRoot || ( models && models.length && models[0].urlRoot ); 81 | url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' ); 82 | 83 | // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute 84 | // (set to 'resource_uri') contains the model's id. 85 | if ( models && models.length ) { 86 | var ids = _.map( models, function( model ) { 87 | var parts = _.compact( model.id.split('/') ); 88 | return parts[ parts.length - 1 ]; 89 | }); 90 | url += 'set/' + ids.join(';') + '/'; 91 | } 92 | 93 | return url; 94 | }; 95 | })(); 96 | -------------------------------------------------------------------------------- /backbone_example/static/js/underscore-min.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.2.2 2 | // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(b.isFunction(a.isEqual))return a.isEqual(c);if(b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return String(a)==String(c);case "[object Number]":return a=+a,c=+c,a!=a?c!=c:a==0?1/a==1/c:a==c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,H=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&define.amd? 11 | define("underscore",function(){return b}):s._=b;b.VERSION="1.2.2";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,c){var b=e(a,c);(d[b]||(d[b]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e< 17 | f;){var g=e+f>>1;d(a[g])=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=H||function(a){if(a!== 24 | Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)? 25 | a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=l.call(arguments)=="[object Arguments]"?function(a){return l.call(a)=="[object Arguments]"}: 26 | function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null}; 27 | b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),function(c){I(c,b[c]=a[c])})};var J=0;b.uniqueId=function(a){var b=J++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g, 28 | interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g, 29 | "\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e(a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},I=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);G.call(a,this._wrapped);return u(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped, 30 | arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this); 31 | -------------------------------------------------------------------------------- /backbone_example/templates/detailApp.mustache: -------------------------------------------------------------------------------- 1 |

2 | Home 3 |

4 |
    5 |
  • tweetTemplate}} 7 |
  • 8 |
9 | -------------------------------------------------------------------------------- /backbone_example/templates/index.html: -------------------------------------------------------------------------------- 1 | {% load mustache %} 2 | {% load straight_include %} 3 | 4 | 5 | 6 | 7 | Django + Backbone.js 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | 39 | 40 |
41 | {% if data %} 42 | {% mustache "detailApp" data %} 43 | {% endif %} 44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /backbone_example/templates/listApp.mustache: -------------------------------------------------------------------------------- 1 |
2 | What's going on? 3 | 4 | 5 |
6 |

Timeline

7 |
    8 |
9 | -------------------------------------------------------------------------------- /backbone_example/templates/tweetTemplate.mustache: -------------------------------------------------------------------------------- 1 |

{{ message }}

2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /backbone_example/tweets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bohde/django-backbone-example/92d0e644feebab4e49081cbf632f9f0f1c4643d5/backbone_example/tweets/__init__.py -------------------------------------------------------------------------------- /backbone_example/tweets/api/__init__.py: -------------------------------------------------------------------------------- 1 | from api import v1 2 | -------------------------------------------------------------------------------- /backbone_example/tweets/api/api.py: -------------------------------------------------------------------------------- 1 | from tastypie.api import Api 2 | from resources import TweetResource 3 | 4 | v1 = Api("v1") 5 | v1.register(TweetResource()) 6 | -------------------------------------------------------------------------------- /backbone_example/tweets/api/resources.py: -------------------------------------------------------------------------------- 1 | from tastypie.resources import ModelResource 2 | from tastypie.authorization import Authorization 3 | 4 | from tweets.models import Tweet 5 | 6 | class TweetResource(ModelResource): 7 | class Meta: 8 | queryset = Tweet.objects.all() 9 | authorization = Authorization() 10 | list_allowed_methods = ['get', 'post'] 11 | detail_allowed_methods = ['get'] 12 | -------------------------------------------------------------------------------- /backbone_example/tweets/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class Tweet(models.Model): 4 | message = models.CharField(max_length=140) 5 | timestamp = models.DateTimeField(auto_now_add=True) 6 | -------------------------------------------------------------------------------- /backbone_example/tweets/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bohde/django-backbone-example/92d0e644feebab4e49081cbf632f9f0f1c4643d5/backbone_example/tweets/templatetags/__init__.py -------------------------------------------------------------------------------- /backbone_example/tweets/templatetags/mustache.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | import pystache 4 | 5 | register = template.Library() 6 | 7 | class View(pystache.View): 8 | template_path = settings.TEMPLATE_DIRS[0] 9 | 10 | def __init__(self, template_name, context): 11 | self.template_name = template_name 12 | return super(View, self).__init__(context=context) 13 | 14 | class MustacheNode(template.Node): 15 | def __init__(self, template_path, attr=None): 16 | self.template = template_path 17 | self.attr = attr 18 | 19 | def render(self, context): 20 | mcontext = context[self.attr] if self.attr else {} 21 | view = View(self.template, context=mcontext) 22 | return view.render() 23 | 24 | def do_mustache(parser, token): 25 | """ 26 | Loads a mustache template and render it inline 27 | 28 | Example:: 29 | 30 | {% mustache "foo/bar" data %} 31 | 32 | """ 33 | bits = token.split_contents() 34 | if len(bits) not in [2,3]: 35 | raise template.TemplateSyntaxError("%r tag takes two arguments: the location of the template file, and the template context" % bits[0]) 36 | path = bits[1] 37 | path = path[1:-1] 38 | attrs = bits[2:] 39 | return MustacheNode(path, *attrs) 40 | 41 | 42 | register.tag("mustache", do_mustache) 43 | -------------------------------------------------------------------------------- /backbone_example/tweets/templatetags/straight_include.py: -------------------------------------------------------------------------------- 1 | """ 2 | Straight Include template tag by @HenrikJoreteg 3 | 4 | Django templates don't give us any way to escape template tags. 5 | 6 | So if you ever need to include client side templates for ICanHaz.js (or anything else that 7 | may confuse django's templating engine) You can is this little snippet. 8 | 9 | Just use it as you would a normal {% include %} tag. It just won't process the included text. 10 | 11 | It assumes your included templates are in you django templates directory. 12 | 13 | Usage: 14 | 15 | {% load straight_include %} 16 | 17 | {% straight_include "my_icanhaz_templates.html" %} 18 | 19 | """ 20 | 21 | from django import template 22 | from django.conf import settings 23 | 24 | 25 | register = template.Library() 26 | 27 | 28 | class StraightIncludeNode(template.Node): 29 | def __init__(self, template_path): 30 | self.filepath = '%s/%s' % (settings.TEMPLATE_DIRS[0], template_path) 31 | 32 | def render(self, context): 33 | fp = open(self.filepath, 'r') 34 | output = fp.read() 35 | fp.close() 36 | return output 37 | 38 | 39 | def do_straight_include(parser, token): 40 | """ 41 | Loads a template and includes it without processing it 42 | 43 | Example:: 44 | 45 | {% straight_include "foo/some_include" %} 46 | 47 | """ 48 | bits = token.split_contents() 49 | if len(bits) != 2: 50 | raise template.TemplateSyntaxError("%r tag takes one argument: the location of the file within the template folder" % bits[0]) 51 | path = bits[1][1:-1] 52 | 53 | return StraightIncludeNode(path) 54 | 55 | 56 | register.tag("straight_include", do_straight_include) -------------------------------------------------------------------------------- /backbone_example/tweets/templatetags/verbatim.py: -------------------------------------------------------------------------------- 1 | """ 2 | From ericflo (https://gist.github.com/629508) 3 | 4 | jQuery templates use constructs like: 5 | 6 | {{if condition}} print something{{/if}} 7 | 8 | This, of course, completely screws up Django templates, 9 | because Django thinks {{ and }} mean something. 10 | 11 | Wrap {% verbatim %} and {% endverbatim %} around those 12 | blocks of jQuery templates and this will try its best 13 | to output the contents with no changes. 14 | """ 15 | 16 | from django import template 17 | 18 | register = template.Library() 19 | 20 | 21 | class VerbatimNode(template.Node): 22 | 23 | def __init__(self, text): 24 | self.text = text 25 | 26 | def render(self, context): 27 | return self.text 28 | 29 | 30 | @register.tag 31 | def verbatim(parser, token): 32 | text = [] 33 | while 1: 34 | token = parser.tokens.pop(0) 35 | if token.contents == 'endverbatim': 36 | break 37 | if token.token_type == template.TOKEN_VAR: 38 | text.append('{{') 39 | elif token.token_type == template.TOKEN_BLOCK: 40 | text.append('{%') 41 | text.append(token.contents) 42 | if token.token_type == template.TOKEN_VAR: 43 | text.append('}}') 44 | elif token.token_type == template.TOKEN_BLOCK: 45 | text.append('%}') 46 | return VerbatimNode(''.join(text)) 47 | -------------------------------------------------------------------------------- /backbone_example/tweets/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from gravatar import * 2 | -------------------------------------------------------------------------------- /backbone_example/tweets/tests/gravatar.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from tweets.utils import gravatar 4 | 5 | class TestGravatar(TestCase): 6 | def testSampleData(self): 7 | """Basic sanity check from http://en.gravatar.com/site/implement/hash/""" 8 | 9 | self.assertEqual(gravatar('MyEmailAddress@example.com '), 10 | '0bc83cb571cd1c50ba6f3e8a78ef1346') 11 | 12 | -------------------------------------------------------------------------------- /backbone_example/tweets/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, url, include 2 | from tweets.api import v1 3 | 4 | from .views import IndexView, DetailView 5 | 6 | urlpatterns = patterns('', 7 | url(r'^$', 8 | IndexView.as_view(), 9 | name='index'), 10 | 11 | url(r'^(?P\d+)/$', 12 | DetailView.as_view(), 13 | name="detail"), 14 | 15 | url(r'^api/', include(v1.urls)), 16 | ) 17 | 18 | 19 | -------------------------------------------------------------------------------- /backbone_example/tweets/views.py: -------------------------------------------------------------------------------- 1 | from django.views.generic.base import TemplateView 2 | from django.http import Http404 3 | 4 | from api import v1 5 | from .models import Tweet 6 | 7 | class IndexView(TemplateView): 8 | template_name = 'index.html' 9 | 10 | 11 | class DetailView(TemplateView): 12 | template_name = 'index.html' 13 | 14 | def get_detail(self, pk): 15 | tr = v1.canonical_resource_for('tweet') 16 | 17 | try: 18 | tweet = tr.cached_obj_get(pk=pk) 19 | except Tweet.DoesNotExist: 20 | raise Http404 21 | 22 | bundle = tr.full_dehydrate(tr.build_bundle(obj=tweet)) 23 | data = bundle.data 24 | return data 25 | 26 | def get_context_data(self, **kwargs): 27 | base = super(DetailView, self).get_context_data(**kwargs) 28 | base['data'] = self.get_detail(base['params']['pk']) 29 | return base 30 | -------------------------------------------------------------------------------- /backbone_example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import * 2 | 3 | # Uncomment the next two lines to enable the admin: 4 | # from django.contrib import admin 5 | # admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | (r'', include('tweets.urls')), 9 | 10 | # Uncomment the admin/doc line below and add 'django.contrib.admindocs' 11 | # to INSTALLED_APPS to enable admin documentation: 12 | # (r'^admin/doc/', include('django.contrib.admindocs.urls')), 13 | 14 | # Uncomment the next line to enable the admin: 15 | # (r'^admin/', include(admin.site.urls)), 16 | ) 17 | 18 | 19 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 20 | urlpatterns += staticfiles_urlpatterns() 21 | --------------------------------------------------------------------------------