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