add controller to xoslib
[plstackapi.git] / planetstack / core / xoslib / static / js / xoslib / xos-backbone.js
1 if (! window.XOSLIB_LOADED ) {
2     window.XOSLIB_LOADED=true;
3
4     SLIVER_API = "/plstackapi/slivers/";
5     SLICE_API = "/plstackapi/slices/";
6     SLICEROLE_API = "/plstackapi/slice_roles/";
7     NODE_API = "/plstackapi/nodes/";
8     SITE_API = "/plstackapi/sites/";
9     USER_API = "/plstackapi/users/";
10     USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
11     DEPLOYMENT_API = "/plstackapi/deployments/";
12     IMAGE_API = "/plstackapi/images/";
13     IMAGEDEPLOYMENTS_API = "/plstackapi/imagedeployments/";
14     NETWORKTEMPLATE_API = "/plstackapi/networktemplates/";
15     NETWORK_API = "/plstackapi/networks/";
16     NETWORKSLIVER_API = "/plstackapi/networkslivers/";
17     SERVICE_API = "/plstackapi/services/";
18     SLICEPRIVILEGE_API = "/plstackapi/slice_privileges/";
19     NETWORKDEPLOYMENT_API = "/plstackapi/networkdeployments/";
20     FLAVOR_API = "/plstackapi/flavors/";
21     CONTROLLER_API = "/plstackapi/controllers/";
22
23     /* changed as a side effect of the big rename
24     SLICEDEPLOYMENT_API = "/plstackapi/slice_deployments/";
25     USERDEPLOYMENT_API = "/plstackapi/user_deployments/";
26     */
27
28     SLICEDEPLOYMENT_API = "/plstackapi/slicedeployments/";
29     USERDEPLOYMENT_API = "/plstackapi/userdeployments/";
30
31     SLICEPLUS_API = "/xoslib/slicesplus/";
32
33     XOSModel = Backbone.Model.extend({
34         /* from backbone-tastypie.js */
35         //idAttribute: 'resource_uri',
36
37         /* from backbone-tastypie.js */
38         url: function() {
39                     var url = this.attributes.resource_uri;
40
41                     if (!url) {
42                         if (this.id) {
43                             url = this.urlRoot + this.id;
44                         } else {
45                             // this happens when creating a new model.
46                             url = this.urlRoot;
47                         }
48                     }
49
50                     if (!url) {
51                         // XXX I'm not sure this does anything useful
52                         url = ( _.isFunction( this.collection.url ) ? this.collection.url() : this.collection.url );
53                         url = url || this.urlRoot;
54                     }
55
56                     // remove any existing query parameters
57                     url && ( url.indexOf("?") > -1 ) && ( url = url.split("?")[0] );
58
59                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
60
61                     url && ( url += "?no_hyperlinks=1" );
62
63                     return url;
64             },
65
66             listMethods: function() {
67                 var res = [];\r
68                 for(var m in this) {\r
69                     if(typeof this[m] == "function") {\r
70                         res.push(m)\r
71                     }\r
72                 }\r
73                 return res;\r
74             },
75
76             save: function(attributes, options) {
77                 if (this.preSave) {
78                     this.preSave();
79                 }
80                 return Backbone.Model.prototype.save.call(this, attributes, options);
81             },
82
83             getChoices: function(fieldName, excludeChosen) {
84                 choices=[];
85                 if (fieldName in this.m2mFields) {
86                     for (index in xos[this.m2mFields[fieldName]].models) {
87                         candidate = xos[this.m2mFields[fieldName]].models[index];
88                         if (excludeChosen && idInArray(candidate.id, this.attributes[fieldName])) {
89                             continue;
90                         }
91                         choices.push(candidate.id);
92                     }
93                 }
94                 return choices;
95             },
96
97             /* If a 'validate' method is supplied, then it will be called
98                automatically on save. Unfortunately, save calls neither the
99                'error' nor the 'success' callback if the validator fails.
100
101                For now, we're calling our validator 'xosValidate' so this
102                autoamtic validation doesn't occur.
103             */
104
105             xosValidate: function(attrs, options) {
106                 errors = {};
107                 foundErrors = false;
108                 _.each(this.validators, function(validatorList, fieldName) {
109                     _.each(validatorList, function(validator) {
110                         if (fieldName in attrs) {
111                             validatorResult = validateField(validator, attrs[fieldName], this)
112                             if (validatorResult != true) {
113                                 errors[fieldName] = validatorResult;
114                                 foundErrors = true;
115                             }
116                         }
117                     });
118                 });
119                 if (foundErrors) {
120                     return errors;
121                 }
122                 // backbone.js semantics -- on successful validate, return nothing
123             },
124
125             /* uncommenting this would make validate() call xosValidate()
126             validate: function(attrs, options) {
127                 r = this.xosValidate(attrs, options);
128                 console.log("validate");
129                 console.log(r);
130                 return r;
131             }, */
132     });
133
134     XOSCollection = Backbone.Collection.extend({
135         objects: function() {
136                     return this.models.map(function(element) { return element.attributes; });
137                  },
138
139         initialize: function(){
140           this.isLoaded = false;
141           this.failedLoad = false;
142           this.startedLoad = false;
143           this.sortVar = 'name';\r
144           this.sortOrder = 'asc';\r
145           this.on( "sort", this.sorted );\r
146         },\r
147 \r
148         relatedCollections: [],\r
149         foreignCollections: [],\r
150 \r
151         sorted: function() {\r
152             //console.log("sorted " + this.modelName);\r
153         },\r
154 \r
155         simpleComparator: function( model ){\r
156           parts=this.sortVar.split(".");\r
157           result = model.get(parts[0]);\r
158           for (index=1; index<parts.length; ++index) {\r
159               result=result[parts[index]];\r
160           }\r
161           return result;\r
162         },\r
163 \r
164         comparator: function (left, right) {\r
165             var l = this.simpleComparator(left);\r
166             var r = this.simpleComparator(right);\r
167 \r
168             if (l === void 0) return -1;\r
169             if (r === void 0) return 1;\r
170 \r
171             if (this.sortOrder=="desc") {\r
172                 return l < r ? 1 : l > r ? -1 : 0;\r
173             } else {\r
174                 return l < r ? -1 : l > r ? 1 : 0;\r
175             }\r
176         },\r
177 \r
178         fetchSuccess: function(collection, response, options) {\r
179             //console.log("fetch succeeded " + collection.modelName);\r
180             this.failedLoad = false;\r
181             this.fetching = false;\r
182             if (!this.isLoaded) {\r
183                 this.isLoaded = true;\r
184                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
185             }\r
186             this.trigger("fetchStateChange");\r
187             if (options["orig_success"]) {\r
188                 options["orig_success"](collection, response, options);\r
189             }\r
190         },\r
191 \r
192         fetchFailure: function(collection, response, options) {\r
193             //console.log("fetch failed " + collection.modelName);\r
194             this.fetching = false;\r
195             if ((!this.isLoaded) && (!this.failedLoad)) {\r
196                 this.failedLoad=true;\r
197                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
198             }\r
199             this.trigger("fetchStateChange");\r
200             if (options["orig_failure"]) {\r
201                 options["orig_failure"](collection, response, options);\r
202             }\r
203         },\r
204 \r
205         fetch: function(options) {\r
206             var self=this;\r
207             this.fetching=true;\r
208             //console.log("fetch " + this.modelName);\r
209             if (!this.startedLoad) {\r
210                 this.startedLoad=true;\r
211                 Backbone.trigger("xoslib:collectionLoadChange", this);\r
212             }\r
213             this.trigger("fetchStateChange");\r
214             if (options == undefined) {\r
215                 options = {};\r
216             }\r
217             options["orig_success"] = options["success"];\r
218             options["orig_failure"] = options["failure"];\r
219             options["success"] = function(collection, response, options) { self.fetchSuccess.call(self, collection, response, options); };\r
220             options["failure"] = this.fetchFailure;\r
221             Backbone.Collection.prototype.fetch.call(this, options);\r
222         },\r
223 \r
224         startPolling: function() {\r
225             if (!this._polling) {\r
226                 var collection=this;
227                 setInterval(function() { collection.fetch(); }, 10000);
228                 this._polling=true;
229                 this.fetch();
230             }
231         },
232
233         refresh: function(refreshRelated) {
234             if (!this.fetching) {
235                 this.fetch();
236             }
237             if (refreshRelated) {
238                 for (related in this.relatedCollections) {
239                     related = xos[related];
240                     if (!related.fetching) {
241                         related.fetch();
242                     }
243                 }
244             }
245         },
246
247         maybeFetch: function(options){
248                 // Helper function to fetch only if this collection has not been fetched before.
249             if(this._fetched){
250                     // If this has already been fetched, call the success, if it exists
251                 options.success && options.success();
252                 console.log("alreadyFetched");
253                 return;
254             }
255
256                 // when the original success function completes mark this collection as fetched
257             var self = this,
258             successWrapper = function(success){
259                 return function(){
260                     self._fetched = true;
261                     success && success.apply(this, arguments);
262                 };
263             };
264             options.success = successWrapper(options.success);
265             console.log("call fetch");
266             this.fetch(options);
267         },
268
269         getOrFetch: function(id, options){
270                 // Helper function to use this collection as a cache for models on the server
271             var model = this.get(id);
272
273             if(model){
274                 options.success && options.success(model);
275                 return;
276             }
277
278             model = new this.model({
279                 resource_uri: id
280             });
281
282             model.fetch(options);
283         },
284
285         /* filterBy: note that this yields a new collection. If you pass that
286               collection to a CompositeView, then the CompositeView won't get
287               any events that trigger on the original collection.
288
289               Using this function is probably wrong, and I wrote
290               FilteredCompositeView() to replace it.
291         */
292
293         filterBy: function(fieldName, value) {
294              filtered = this.filter(function(obj) {
295                  return obj.get(fieldName) == value;
296                  });
297              return new this.constructor(filtered);
298         },
299
300         /* from backbone-tastypie.js */
301         url: function( models ) {
302                     var url = this.urlRoot || ( models && models.length && models[0].urlRoot );
303                     url && ( url += ( url.length > 0 && url.charAt( url.length - 1 ) === '/' ) ? '' : '/' );
304
305                     // Build a url to retrieve a set of models. This assume the last part of each model's idAttribute
306                     // (set to 'resource_uri') contains the model's id.
307                     if ( models && models.length ) {
308                             var ids = _.map( models, function( model ) {
309                                             var parts = _.compact( model.id.split('/') );
310                                             return parts[ parts.length - 1 ];
311                                     });
312                             url += 'set/' + ids.join(';') + '/';
313                     }
314
315                     url && ( url += "?no_hyperlinks=1" );
316
317                     return url;
318             },
319
320         listMethods: function() {
321                 var res = [];\r
322                 for(var m in this) {\r
323                     if(typeof this[m] == "function") {\r
324                         res.push(m)\r
325                     }\r
326                 }\r
327                 return res;\r
328             },
329     });
330
331     function define_model(lib, attrs) {
332         modelName = attrs.modelName;
333         modelClassName = modelName;
334         collectionClassName = modelName + "Collection";
335
336         if (!attrs.addFields) {
337             attrs.addFields = attrs.detailFields;
338         }
339
340         attrs.inputType = attrs.inputType || {};
341         attrs.foreignFields = attrs.foreignFields || {};
342         attrs.m2mFields = attrs.m2mFields || {};
343         attrs.readOnlyFields = attrs.readOnlyFields || [];
344         attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
345
346         if (!attrs.collectionName) {
347             attrs.collectionName = modelName + "s";
348         }
349         collectionName = attrs.collectionName;
350
351         modelAttrs = {}
352         collectionAttrs = {}
353
354         for (key in attrs) {
355             value = attrs[key];
356             if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections"])>=0) {
357                 modelAttrs[key] = value;
358                 collectionAttrs[key] = value;
359             }
360             if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
361                 modelAttrs[key] = value;
362             }
363         }
364
365         if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
366             modelAttrs["defaults"] = xosdefaults[modelName];
367         }
368
369         if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
370             modelAttrs["validators"] = xosvalidators[modelName];
371         }
372
373         lib[modelName] = XOSModel.extend(modelAttrs);
374
375         collectionAttrs["model"] = lib[modelName];
376
377         lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
378         lib[collectionName] = new lib[collectionClassName]();
379
380         lib.allCollectionNames.push(collectionName);
381         lib.allCollections.push(lib[collectionName]);
382     };
383
384     function xoslib() {
385         this.allCollectionNames = [];
386         this.allCollections = [];
387
388         /* Give an id, the name of a collection, and the name of a field for models
389            within that collection, lookup the id and return the value of the field.
390         */
391
392         this.idToName = function(id, collectionName, fieldName) {
393             linkedObject = xos[collectionName].get(id);
394             if (linkedObject == undefined) {
395                 return "#" + id;
396             } else {
397                 return linkedObject.attributes[fieldName];
398             }
399         };
400
401         /* defining the models
402
403            modelName          - name of the model.
404
405            relatedCollections - collections which should be drawn as an inline
406                                 list when the detail view is displayed.
407                                 Format: <collection>:<collectionFieldName> where
408                                 <collectionFieldName> is the name of the field
409                                 in the collection that points back to the
410                                 collection in the detail view.
411
412            foreignCollections - collections which are used in idToName() calls
413                                 when presenting the data to the user. Used to
414                                 create a listento event. Somewhat
415                                 redundant with foreignFields.
416
417            foreignFields -      <localFieldName>:<collection>. Used to
418                                 automatically map ids into humanReadableNames
419                                 when presenting data to the user.
420
421            m2mfields -          <localFieldName>:<colleciton>. Used to
422                                 populate choices in picker lists. Simalar to
423                                 foreignFields.
424
425            listFields -         fields to display in lists
426
427            detailFields -       fields to display in detail views
428
429            addFields -          fields to display in popup add windows
430
431            inputType -          by default, "detailFields" will be displayed
432                                 as text input controls. This will let you display
433                                 a checkbox or a picker instead.
434         */
435
436         define_model(this, {urlRoot: SLIVER_API,
437                             relatedCollections: {"networkSlivers": "sliver"},
438                             foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
439                             foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
440                             modelName: "sliver",
441                             listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
442                             addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
443                             detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
444                             preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
445                             });
446
447         define_model(this, {urlRoot: SLICE_API,
448                            relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
449                            foreignCollections: ["services", "sites"],
450                            foreignFields: {"service": "services", "site": "sites"},
451                            listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
452                            detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
453                            inputType: {"enabled": "checkbox"},
454                            modelName: "slice",
455                            xosValidate: function(attrs, options) {
456                                errors = XOSModel.prototype.xosValidate(this, attrs, options);
457                                // validate that slice.name starts with site.login_base
458                                site = attrs.site || this.site;
459                                if ((site!=undefined) && (attrs.name!=undefined)) {
460                                    site = xos.sites.get(site);
461                                    if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
462                                         errors = errors || {};
463                                         errors["name"] = "must start with " + site.attributes.login_base + "_";
464                                    }
465                                }
466                                return errors;
467                              },
468                            });
469
470         define_model(this, {urlRoot: SLICEPRIVILEGE_API,
471                             foreignCollections: ["slices", "users", "sliceRoles"],
472                             modelName: "slicePrivilege",
473                             foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
474                             listFields: ["backend_status", "id", "user", "slice", "role"],
475                             detailFields: ["backend_status", "user", "slice", "role"],
476                             });
477
478         define_model(this, {urlRoot: SLICEROLE_API,
479                             modelName: "sliceRole",
480                             listFields: ["backend_status", "id", "role"],
481                             detailFields: ["backend_status", "role"],
482                             });
483
484         define_model(this, {urlRoot: NODE_API,
485                             foreignCollections: ["sites", "deployments"],
486                             modelName: "node",
487                             foreignFields: {"site": "sites", "deployment": "deployments"},
488                             listFields: ["backend_status", "id", "name", "site", "deployment"],
489                             detailFields: ["backend_status", "name", "site", "deployment"],
490                             });
491
492         define_model(this, {urlRoot: SITE_API,
493                             relatedCollections: {"users": "site", "slices": "site", "nodes": "site"},
494                             modelName: "site",
495                             listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
496                             detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
497                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
498                             });
499
500         define_model(this, {urlRoot: USER_API,
501                             relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
502                             foreignCollections: ["sites"],
503                             modelName: "user",
504                             foreignFields: {"site": "sites"},
505                             listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
506                             detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
507                             });
508
509         define_model(this, { urlRoot: DEPLOYMENT_API,
510                              relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
511                              m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
512                              modelName: "deployment",
513                              listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
514                              detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
515                              inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
516                              });
517
518         define_model(this, {urlRoot: IMAGE_API,
519                             model: this.image,
520                             modelName: "image",
521                             listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
522                             detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
523                             });
524
525         define_model(this, {urlRoot: NETWORKTEMPLATE_API,
526                             modelName: "networkTemplate",
527                             listFields: ["backend_status", "id", "name", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
528                             detailFields: ["backend_status", "name", "description", "visibility", "translation", "sharedNetworkName", "sharedNetworkId"],
529                             });
530
531         define_model(this, {urlRoot: NETWORK_API,
532                             relatedCollections: {"networkSlivers": "network"},
533                             foreignCollections: ["slices", "networkTemplates"],
534                             modelName: "network",
535                             foreignFields: {"template": "networkTemplates", "owner": "slices"},
536                             listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
537                             detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
538                             });
539
540         define_model(this, {urlRoot: NETWORKSLIVER_API,
541                             modelName: "networkSliver",
542                             foreignFields: {"network": "networks", "sliver": "slivers"},
543                             listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
544                             detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
545                             });
546
547         define_model(this, {urlRoot: SERVICE_API,
548                             modelName: "service",
549                             listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
550                             detailFields: ["backend_status", "name", "description", "versionNumber"],
551                             });
552
553         define_model(this, {urlRoot: FLAVOR_API,
554                             modelName: "flavor",
555                             m2mFields: {"deployments": "deployments"},
556                             listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
557                             detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
558                             inputType: {"default": "checkbox", "deployments": "picker"},
559                             });
560
561         define_model(this, {urlRoot: CONTROLLER_API,
562                             modelName: "controller",
563                             listFields: ["backend_status", "id", "name", "version", "backend_type"],
564                             detailFields: ["backend_status", "id", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
565                             });
566
567         /* DELETED in site-controller branch
568
569         define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
570                             modelName: "networkDeployment",
571                             foreignFields: {"network": "networks", "deployment": "deployments"},
572                             listFields: ["backend_status", "id", "network", "deployment", "net_id"],
573                             detailFields: ["backend_status", "network", "deployment", "net_id"],
574                             });
575
576         define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
577                            foreignCollections: ["slices", "deployments"],
578                            modelName: "sliceDeployment",
579                            foreignFields: {"slice": "slices", "deployment": "deployments"},
580                            listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
581                            detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
582                            });
583
584         define_model(this, {urlRoot: USERDEPLOYMENT_API,
585                             foreignCollections: ["users","deployments"],
586                             modelName: "userDeployment",
587                             foreignFields: {"deployment": "deployments", "user": "users"},
588                             listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
589                             detailFields: ["backend_status", "user", "deployment", "kuser_id"],
590                             });
591
592         END stuff deleted in site-controller branch */
593
594         /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
595
596         define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
597                             modelName: "imageDeployment",
598                             foreignCollections: ["images", "deployments"],
599                             listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
600                             detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
601                             });
602
603         */
604
605         // enhanced REST
606         // XXX this really needs to somehow be combined with Slice, to avoid duplication
607         define_model(this, {urlRoot: SLICEPLUS_API,
608                             relatedCollections: {'slivers': "slice"},
609                             modelName: "slicePlus",
610                             collectionName: "slicesPlus"});
611
612         this.listObjects = function() { return this.allCollectionNames; };
613
614         this.getCollectionStatus = function() {
615             stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
616             for (index in this.allCollections) {
617                 collection = this.allCollections[index];
618                 if (collection.isLoaded) {
619                     stats["isLoaded"] = stats["isLoaded"] + 1;
620                 }
621                 if (collection.failedLoad) {
622                     stats["failedLoad"] = stats["failedLoad"] + 1;
623                 }
624                 if (collection.startedLoad) {
625                     stats["startedLoad"] = stats["startedLoad"] + 1;
626                 }
627             }
628             stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
629             return stats;
630         };
631     };
632
633     xos = new xoslib();
634
635     function getCookie(name) {
636         var cookieValue = null;\r
637         if (document.cookie && document.cookie != '') {\r
638             var cookies = document.cookie.split(';');\r
639             for (var i = 0; i < cookies.length; i++) {\r
640                 var cookie = jQuery.trim(cookies[i]);\r
641                 // Does this cookie string begin with the name we want?\r
642                 if (cookie.substring(0, name.length + 1) == (name + '=')) {\r
643                     cookieValue = decodeURIComponent(cookie.substring(name.length + 1));\r
644                     break;\r
645                 }\r
646             }\r
647         }\r
648         return cookieValue;\r
649     }
650
651     (function() {
652       var _sync = Backbone.sync;\r
653       Backbone.sync = function(method, model, options){\r
654         options.beforeSend = function(xhr){\r
655           var token = getCookie("csrftoken");\r
656           xhr.setRequestHeader('X-CSRFToken', token);\r
657         };\r
658         return _sync(method, model, options);\r
659       };\r
660     })();
661 }