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