tenant view, WIP
[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         console.log(modelName);
402         console.log(modelAttrs);
403
404 //        if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
405 //            modelAttrs["defaults"] = xosdefaults[modelName];
406 //        }
407
408         if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
409             modelAttrs["validators"] = xosvalidators[modelName];
410         }
411
412         lib[modelName] = XOSModel.extend(modelAttrs);
413
414         collectionAttrs["model"] = lib[modelName];
415
416         lib[collectionClassName] = XOSCollection.extend(collectionAttrs);
417         lib[collectionName] = new lib[collectionClassName]();
418
419         lib.allCollectionNames.push(collectionName);
420         lib.allCollections.push(lib[collectionName]);
421     };
422
423     function xoslib() {
424         this.allCollectionNames = [];
425         this.allCollections = [];
426
427         /* Give an id, the name of a collection, and the name of a field for models
428            within that collection, lookup the id and return the value of the field.
429         */
430
431         this.idToName = function(id, collectionName, fieldName) {
432             linkedObject = xos[collectionName].get(id);
433             if (linkedObject == undefined) {
434                 return "#" + id;
435             } else {
436                 return linkedObject.attributes[fieldName];
437             }
438         };
439
440         /* defining the models
441
442            modelName          - name of the model.
443
444            relatedCollections - collections which should be drawn as an inline
445                                 list when the detail view is displayed.
446                                 Format: <collection>:<collectionFieldName> where
447                                 <collectionFieldName> is the name of the field
448                                 in the collection that points back to the
449                                 collection in the detail view.
450
451            foreignCollections - collections which are used in idToName() calls
452                                 when presenting the data to the user. Used to
453                                 create a listento event. Somewhat
454                                 redundant with foreignFields.
455
456            foreignFields -      <localFieldName>:<collection>. Used to
457                                 automatically map ids into humanReadableNames
458                                 when presenting data to the user.
459
460            m2mfields -          <localFieldName>:<colleciton>. Used to
461                                 populate choices in picker lists. Simalar to
462                                 foreignFields.
463
464            listFields -         fields to display in lists
465
466            detailFields -       fields to display in detail views
467
468            addFields -          fields to display in popup add windows
469
470            inputType -          by default, "detailFields" will be displayed
471                                 as text input controls. This will let you display
472                                 a checkbox or a picker instead.
473         */
474
475         define_model(this, {urlRoot: SLIVER_API,
476                             relatedCollections: {"networkSlivers": "sliver"},
477                             foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
478                             foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
479                             modelName: "sliver",
480                             listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
481                             addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
482                             detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
483                             preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
484                             });
485
486         define_model(this, {urlRoot: SLICE_API,
487                            relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
488                            foreignCollections: ["services", "sites"],
489                            foreignFields: {"service": "services", "site": "sites"},
490                            listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
491                            detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
492                            inputType: {"enabled": "checkbox"},
493                            modelName: "slice",
494                            xosValidate: function(attrs, options) {
495                                errors = XOSModel.prototype.xosValidate(this, attrs, options);
496                                // validate that slice.name starts with site.login_base
497                                site = attrs.site || this.site;
498                                if ((site!=undefined) && (attrs.name!=undefined)) {
499                                    site = xos.sites.get(site);
500                                    if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
501                                         errors = errors || {};
502                                         errors["name"] = "must start with " + site.attributes.login_base + "_";
503                                    }
504                                }
505                                return errors;
506                              },
507                            });
508
509         define_model(this, {urlRoot: SLICEPRIVILEGE_API,
510                             foreignCollections: ["slices", "users", "sliceRoles"],
511                             modelName: "slicePrivilege",
512                             foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
513                             listFields: ["backend_status", "id", "user", "slice", "role"],
514                             detailFields: ["backend_status", "user", "slice", "role"],
515                             });
516
517         define_model(this, {urlRoot: SLICEROLE_API,
518                             modelName: "sliceRole",
519                             listFields: ["backend_status", "id", "role"],
520                             detailFields: ["backend_status", "role"],
521                             });
522
523         define_model(this, {urlRoot: NODE_API,
524                             foreignCollections: ["sites", "deployments"],
525                             modelName: "node",
526                             foreignFields: {"site": "sites", "deployment": "deployments"},
527                             listFields: ["backend_status", "id", "name", "site", "deployment"],
528                             detailFields: ["backend_status", "name", "site", "deployment"],
529                             });
530
531         define_model(this, {urlRoot: SITE_API,
532                             relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
533                             modelName: "site",
534                             listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
535                             detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
536                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
537                             });
538
539         define_model(this, {urlRoot: SITEDEPLOYMENT_API,
540                             foreignCollections: ["sites", "deployments", "controllers"],
541                             foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
542                             modelName: "siteDeployment",
543                             listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
544                             detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
545                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
546                             });
547
548         define_model(this, {urlRoot: USER_API,
549                             relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
550                             foreignCollections: ["sites"],
551                             modelName: "user",
552                             foreignFields: {"site": "sites"},
553                             listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
554                             detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
555                             });
556
557         define_model(this, { urlRoot: DEPLOYMENT_API,
558                              relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
559                              m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
560                              modelName: "deployment",
561                              listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
562                              detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
563                              inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
564                              });
565
566         define_model(this, {urlRoot: IMAGE_API,
567                             model: this.image,
568                             modelName: "image",
569                             listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
570                             detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
571                             });
572
573         define_model(this, {urlRoot: NETWORKTEMPLATE_API,
574                             modelName: "networkTemplate",
575                             listFields: ["backend_status", "id", "name", "visibility", "translation", "shared_network_name", "shared_network_id"],
576                             detailFields: ["backend_status", "name", "description", "visibility", "translation", "shared_network_name", "shared_network_id"],
577                             });
578
579         define_model(this, {urlRoot: NETWORK_API,
580                             relatedCollections: {"networkSlivers": "network"},
581                             foreignCollections: ["slices", "networkTemplates"],
582                             modelName: "network",
583                             foreignFields: {"template": "networkTemplates", "owner": "slices"},
584                             listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
585                             detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
586                             });
587
588         define_model(this, {urlRoot: NETWORKSLIVER_API,
589                             modelName: "networkSliver",
590                             foreignFields: {"network": "networks", "sliver": "slivers"},
591                             listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
592                             detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
593                             });
594
595         define_model(this, {urlRoot: SERVICE_API,
596                             modelName: "service",
597                             listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
598                             detailFields: ["backend_status", "name", "description", "versionNumber"],
599                             });
600
601         define_model(this, {urlRoot: FLAVOR_API,
602                             modelName: "flavor",
603                             m2mFields: {"deployments": "deployments"},
604                             listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
605                             detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
606                             inputType: {"default": "checkbox", "deployments": "picker"},
607                             });
608
609         define_model(this, {urlRoot: CONTROLLER_API,
610                             modelName: "controller",
611                             listFields: ["backend_status", "id", "name", "version", "backend_type"],
612                             detailFields: ["backend_status", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
613                             });
614
615         /* removed
616         define_model(this, {urlRoot: CONTROLLERSITEDEPLOYMENT_API,
617                             modelName: "controllerSiteDeployment",
618                             foreignCollections: ["site_deployments", "controllers"],
619                             foreignFields: {"site_deployment": "siteDeployments", "controller": "controllers"},
620                             listFields: ["backend_status", "id", "site_deployment", "controller", "tenant_id"],
621                             detailFields: ["backend_status", "site_deployment", "controller", "tenant_id"],
622                             });
623         */
624
625         /* DELETED in site-controller branch
626
627         define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
628                             modelName: "networkDeployment",
629                             foreignFields: {"network": "networks", "deployment": "deployments"},
630                             listFields: ["backend_status", "id", "network", "deployment", "net_id"],
631                             detailFields: ["backend_status", "network", "deployment", "net_id"],
632                             });
633
634         define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
635                            foreignCollections: ["slices", "deployments"],
636                            modelName: "sliceDeployment",
637                            foreignFields: {"slice": "slices", "deployment": "deployments"},
638                            listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
639                            detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
640                            });
641
642         define_model(this, {urlRoot: USERDEPLOYMENT_API,
643                             foreignCollections: ["users","deployments"],
644                             modelName: "userDeployment",
645                             foreignFields: {"deployment": "deployments", "user": "users"},
646                             listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
647                             detailFields: ["backend_status", "user", "deployment", "kuser_id"],
648                             });
649
650         END stuff deleted in site-controller branch */
651
652         /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
653
654         define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
655                             modelName: "imageDeployment",
656                             foreignCollections: ["images", "deployments"],
657                             listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
658                             detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
659                             });
660
661         */
662
663         // enhanced REST
664         // XXX this really needs to somehow be combined with Slice, to avoid duplication
665         define_model(this, {urlRoot: SLICEPLUS_API,
666                            relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
667                            foreignCollections: ["services", "sites"],
668                            foreignFields: {"service": "services", "site": "sites"},
669                            listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
670                            detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
671                            inputType: {"enabled": "checkbox"},
672                            modelName: "slicePlus",
673                            collectionName: "slicesPlus",
674                            defaults: extend_defaults("slice", {"network_ports": "", "site_allocation": []}),
675                            xosValidate: function(attrs, options) {
676                                errors = XOSModel.prototype.xosValidate(this, attrs, options);
677                                // validate that slice.name starts with site.login_base
678                                site = attrs.site || this.site;
679                                if ((site!=undefined) && (attrs.name!=undefined)) {
680                                    site = xos.sites.get(site);
681                                    if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
682                                         errors = errors || {};
683                                         errors["name"] = "must start with " + site.attributes.login_base + "_";
684                                    }
685                                }
686                                return errors;
687                              },
688                            });
689
690         define_model(this, {urlRoot: TENANTVIEW_API,
691                             modelName: "tenantview",
692                             collectionName: "tenantview",
693                             listFields: [],
694                             detailFields: [],
695                             });
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 }