tenant view only shows sites the user should be able to see
[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                     url && ( url += "?no_hyperlinks=1" );
323
324                     if (this.currentUserCanSee) {
325                         url && ( url += "&current_user_can_see=1" );
326                     }
327
328                     return url;
329             },
330
331         listMethods: function() {
332                 var res = [];\r
333                 for(var m in this) {\r
334                     if(typeof this[m] == "function") {\r
335                         res.push(m)\r
336                     }\r
337                 }\r
338                 return res;\r
339             },
340     });
341
342     function get_defaults(modelName) {
343         if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
344             return xosdefaults[modelName];
345         }
346         return undefined;
347     }
348
349     function extend_defaults(modelName, stuff) {
350         defaults = get_defaults(modelName);
351         if (defaults) {
352             return $.extend({}, defaults, stuff);
353         } else {
354             return stuff;
355         }
356     }
357
358     function define_model(lib, attrs) {
359         modelName = attrs.modelName;
360         modelClassName = modelName;
361         collectionClass = attrs.collectionClass || XOSCollection;
362         collectionClassName = modelName + "Collection";
363
364         if (!attrs.addFields) {
365             attrs.addFields = attrs.detailFields;
366         }
367
368         attrs.inputType = attrs.inputType || {};
369         attrs.foreignFields = attrs.foreignFields || {};
370         attrs.m2mFields = attrs.m2mFields || {};
371         attrs.readOnlyFields = attrs.readOnlyFields || [];
372         attrs.detailLinkFields = attrs.detailLinkFields || ["id","name"];
373
374         if (!attrs.collectionName) {
375             attrs.collectionName = modelName + "s";
376         }
377         collectionName = attrs.collectionName;
378
379         modelAttrs = {}
380         collectionAttrs = {}
381
382         for (key in attrs) {
383             value = attrs[key];
384             if ($.inArray(key, ["urlRoot", "modelName", "collectionName", "listFields", "addFields", "detailFields", "detailLinkFields", "foreignFields", "inputType", "relatedCollections", "foreignCollections", "defaults"])>=0) {
385                 modelAttrs[key] = value;
386                 collectionAttrs[key] = value;
387             }
388             if ($.inArray(key, ["validate", "preSave", "readOnlyFields"])) {
389                 modelAttrs[key] = value;
390             }
391         }
392
393         if (!modelAttrs.defaults) {
394             modelAttrs.defaults = get_defaults(modelName);
395         }
396
397 //        if ((typeof xosdefaults !== "undefined") && xosdefaults[modelName]) {
398 //            modelAttrs["defaults"] = xosdefaults[modelName];
399 //        }
400
401         if ((typeof xosvalidators !== "undefined") && xosvalidators[modelName]) {
402             modelAttrs["validators"] = xosvalidators[modelName];
403         }
404
405         lib[modelName] = XOSModel.extend(modelAttrs);
406
407         collectionAttrs["model"] = lib[modelName];
408
409         lib[collectionClassName] = collectionClass.extend(collectionAttrs);
410         lib[collectionName] = new lib[collectionClassName]();
411
412         lib.allCollectionNames.push(collectionName);
413         lib.allCollections.push(lib[collectionName]);
414     };
415
416     function xoslib() {
417         this.allCollectionNames = [];
418         this.allCollections = [];
419
420         /* Give an id, the name of a collection, and the name of a field for models
421            within that collection, lookup the id and return the value of the field.
422         */
423
424         this.idToName = function(id, collectionName, fieldName) {
425             linkedObject = xos[collectionName].get(id);
426             if (linkedObject == undefined) {
427                 return "#" + id;
428             } else {
429                 return linkedObject.attributes[fieldName];
430             }
431         };
432
433         /* defining the models
434
435            modelName          - name of the model.
436
437            relatedCollections - collections which should be drawn as an inline
438                                 list when the detail view is displayed.
439                                 Format: <collection>:<collectionFieldName> where
440                                 <collectionFieldName> is the name of the field
441                                 in the collection that points back to the
442                                 collection in the detail view.
443
444            foreignCollections - collections which are used in idToName() calls
445                                 when presenting the data to the user. Used to
446                                 create a listento event. Somewhat
447                                 redundant with foreignFields.
448
449            foreignFields -      <localFieldName>:<collection>. Used to
450                                 automatically map ids into humanReadableNames
451                                 when presenting data to the user.
452
453            m2mfields -          <localFieldName>:<colleciton>. Used to
454                                 populate choices in picker lists. Simalar to
455                                 foreignFields.
456
457            listFields -         fields to display in lists
458
459            detailFields -       fields to display in detail views
460
461            addFields -          fields to display in popup add windows
462
463            inputType -          by default, "detailFields" will be displayed
464                                 as text input controls. This will let you display
465                                 a checkbox or a picker instead.
466         */
467
468         define_model(this, {urlRoot: SLIVER_API,
469                             relatedCollections: {"networkSlivers": "sliver"},
470                             foreignCollections: ["slices", "deployments", "images", "nodes", "users", "flavors"],
471                             foreignFields: {"creator": "users", "image": "images", "node": "nodes", "deploymentNetwork": "deployments", "slice": "slices", "flavor": "flavors"},
472                             modelName: "sliver",
473                             listFields: ["backend_status", "id", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "image", "node", "flavor"],
474                             addFields: ["slice", "deploymentNetwork", "flavor", "image", "node"],
475                             detailFields: ["backend_status", "name", "instance_id", "instance_name", "slice", "deploymentNetwork", "flavor", "image", "node", "creator"],
476                             preSave: function() { if (!this.attributes.name && this.attributes.slice) { this.attributes.name = xos.idToName(this.attributes.slice, "slices", "name"); } },
477                             });
478
479         define_model(this, {urlRoot: SLICE_API,
480                            relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
481                            foreignCollections: ["services", "sites"],
482                            foreignFields: {"service": "services", "site": "sites"},
483                            listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
484                            detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
485                            inputType: {"enabled": "checkbox"},
486                            modelName: "slice",
487                            xosValidate: function(attrs, options) {
488                                errors = XOSModel.prototype.xosValidate(this, attrs, options);
489                                // validate that slice.name starts with site.login_base
490                                site = attrs.site || this.site;
491                                if ((site!=undefined) && (attrs.name!=undefined)) {
492                                    site = xos.sites.get(site);
493                                    if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
494                                         errors = errors || {};
495                                         errors["name"] = "must start with " + site.attributes.login_base + "_";
496                                    }
497                                }
498                                return errors;
499                              },
500                            });
501
502         define_model(this, {urlRoot: SLICEPRIVILEGE_API,
503                             foreignCollections: ["slices", "users", "sliceRoles"],
504                             modelName: "slicePrivilege",
505                             foreignFields: {"user": "users", "slice": "slices", "role": "sliceRoles"},
506                             listFields: ["backend_status", "id", "user", "slice", "role"],
507                             detailFields: ["backend_status", "user", "slice", "role"],
508                             });
509
510         define_model(this, {urlRoot: SLICEROLE_API,
511                             modelName: "sliceRole",
512                             listFields: ["backend_status", "id", "role"],
513                             detailFields: ["backend_status", "role"],
514                             });
515
516         define_model(this, {urlRoot: NODE_API,
517                             foreignCollections: ["sites", "deployments"],
518                             modelName: "node",
519                             foreignFields: {"site": "sites", "deployment": "deployments"},
520                             listFields: ["backend_status", "id", "name", "site", "deployment"],
521                             detailFields: ["backend_status", "name", "site", "deployment"],
522                             });
523
524         define_model(this, {urlRoot: SITE_API,
525                             relatedCollections: {"users": "site", "slices": "site", "nodes": "site", "siteDeployments": "site"},
526                             modelName: "site",
527                             listFields: ["backend_status", "id", "name", "site_url", "enabled", "login_base", "is_public", "abbreviated_name"],
528                             detailFields: ["backend_status", "name", "abbreviated_name", "url", "enabled", "is_public", "login_base"],
529                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
530                             });
531
532         define_model(this, {urlRoot: SITEDEPLOYMENT_API,
533                             foreignCollections: ["sites", "deployments", "controllers"],
534                             foreignFields: {"site": "sites", "deployment": "deployments", "controller": "controllers"},
535                             modelName: "siteDeployment",
536                             listFields: ["backend_status", "id", "site", "deployment", "controller", "availability_zone"],
537                             detailFields: ["backend_status", "site", "deployment", "controller", "availability_zone"],
538                             inputType: {"enabled": "checkbox", "is_public": "checkbox"},
539                             });
540
541         define_model(this, {urlRoot: USER_API,
542                             relatedCollections: {"slicePrivileges": "user", "slices": "owner"},
543                             foreignCollections: ["sites"],
544                             modelName: "user",
545                             foreignFields: {"site": "sites"},
546                             listFields: ["backend_status", "id", "username", "firstname", "lastname", "phone", "user_url", "site"],
547                             detailFields: ["backend_status", "username", "firstname", "lastname", "phone", "user_url", "site"],
548                             });
549
550         define_model(this, { urlRoot: DEPLOYMENT_API,
551                              relatedCollections: {"nodes": "deployment", "slivers": "deploymentNetwork"},
552                              m2mFields: {"flavors": "flavors", "sites": "sites", "images": "images"},
553                              modelName: "deployment",
554                              listFields: ["backend_status", "id", "name", "backend_type", "admin_tenant"],
555                              detailFields: ["backend_status", "name", "backend_type", "admin_tenant", "flavors", "sites", "images"],
556                              inputType: {"flavors": "picker", "sites": "picker", "images": "picker"},
557                              });
558
559         define_model(this, {urlRoot: IMAGE_API,
560                             model: this.image,
561                             modelName: "image",
562                             listFields: ["backend_status", "id", "name", "disk_format", "container_format", "path"],
563                             detailFields: ["backend_status", "name", "disk_format", "admin_tenant"],
564                             });
565
566         define_model(this, {urlRoot: NETWORKTEMPLATE_API,
567                             modelName: "networkTemplate",
568                             listFields: ["backend_status", "id", "name", "visibility", "translation", "shared_network_name", "shared_network_id"],
569                             detailFields: ["backend_status", "name", "description", "visibility", "translation", "shared_network_name", "shared_network_id"],
570                             });
571
572         define_model(this, {urlRoot: NETWORK_API,
573                             relatedCollections: {"networkSlivers": "network"},
574                             foreignCollections: ["slices", "networkTemplates"],
575                             modelName: "network",
576                             foreignFields: {"template": "networkTemplates", "owner": "slices"},
577                             listFields: ["backend_status", "id", "name", "template", "ports", "labels", "owner"],
578                             detailFields: ["backend_status", "name", "template", "ports", "labels", "owner"],
579                             });
580
581         define_model(this, {urlRoot: NETWORKSLIVER_API,
582                             modelName: "networkSliver",
583                             foreignFields: {"network": "networks", "sliver": "slivers"},
584                             listFields: ["backend_status", "id", "network", "sliver", "ip", "port_id"],
585                             detailFields: ["backend_status", "network", "sliver", "ip", "port_id"],
586                             });
587
588         define_model(this, {urlRoot: SERVICE_API,
589                             modelName: "service",
590                             listFields: ["backend_status", "id", "name", "enabled", "versionNumber", "published"],
591                             detailFields: ["backend_status", "name", "description", "versionNumber"],
592                             });
593
594         define_model(this, {urlRoot: FLAVOR_API,
595                             modelName: "flavor",
596                             m2mFields: {"deployments": "deployments"},
597                             listFields: ["backend_status", "id", "name", "flavor", "order", "default"],
598                             detailFields: ["backend_status", "name", "description", "flavor", "order", "default", "deployments"],
599                             inputType: {"default": "checkbox", "deployments": "picker"},
600                             });
601
602         define_model(this, {urlRoot: CONTROLLER_API,
603                             modelName: "controller",
604                             listFields: ["backend_status", "id", "name", "version", "backend_type"],
605                             detailFields: ["backend_status", "name", "version", "backend_type", "auth_url", "admin_user", "admin_password", "admin_tenant"],
606                             });
607
608         /* removed
609         define_model(this, {urlRoot: CONTROLLERSITEDEPLOYMENT_API,
610                             modelName: "controllerSiteDeployment",
611                             foreignCollections: ["site_deployments", "controllers"],
612                             foreignFields: {"site_deployment": "siteDeployments", "controller": "controllers"},
613                             listFields: ["backend_status", "id", "site_deployment", "controller", "tenant_id"],
614                             detailFields: ["backend_status", "site_deployment", "controller", "tenant_id"],
615                             });
616         */
617
618         /* DELETED in site-controller branch
619
620         define_model(this, {urlRoot: NETWORKDEPLOYMENT_API,
621                             modelName: "networkDeployment",
622                             foreignFields: {"network": "networks", "deployment": "deployments"},
623                             listFields: ["backend_status", "id", "network", "deployment", "net_id"],
624                             detailFields: ["backend_status", "network", "deployment", "net_id"],
625                             });
626
627         define_model(this, {urlRoot: SLICEDEPLOYMENT_API,
628                            foreignCollections: ["slices", "deployments"],
629                            modelName: "sliceDeployment",
630                            foreignFields: {"slice": "slices", "deployment": "deployments"},
631                            listFields: ["backend_status", "id", "slice", "deployment", "tenant_id"],
632                            detailFields: ["backend_status", "slice", "deployment", "tenant_id"],
633                            });
634
635         define_model(this, {urlRoot: USERDEPLOYMENT_API,
636                             foreignCollections: ["users","deployments"],
637                             modelName: "userDeployment",
638                             foreignFields: {"deployment": "deployments", "user": "users"},
639                             listFields: ["backend_status", "id", "user", "deployment", "kuser_id"],
640                             detailFields: ["backend_status", "user", "deployment", "kuser_id"],
641                             });
642
643         END stuff deleted in site-controller branch */
644
645         /* not deleted, but obsolete since it has degenerated to a ManyToMany with no other fields
646
647         define_model(this, {urlRoot: IMAGEDEPLOYMENTS_API,
648                             modelName: "imageDeployment",
649                             foreignCollections: ["images", "deployments"],
650                             listFields: ["backend_status", "id", "image", "deployment", "glance_image_id"],
651                             detailFields: ["backend_status", "image", "deployment", "glance_image_id"],
652                             });
653
654         */
655
656         // enhanced REST
657         // XXX this really needs to somehow be combined with Slice, to avoid duplication
658         define_model(this, {urlRoot: SLICEPLUS_API,
659                            relatedCollections: {"slivers": "slice", "slicePrivileges": "slice", "networks": "owner"},
660                            foreignCollections: ["services", "sites"],
661                            foreignFields: {"service": "services", "site": "sites"},
662                            listFields: ["backend_status", "id", "name", "enabled", "description", "slice_url", "site", "max_slivers", "service"],
663                            detailFields: ["backend_status", "name", "site", "enabled", "description", "slice_url", "max_slivers"],
664                            inputType: {"enabled": "checkbox"},
665                            modelName: "slicePlus",
666                            collectionName: "slicesPlus",
667                            defaults: extend_defaults("slice", {"network_ports": "", "site_allocation": []}),
668                            xosValidate: function(attrs, options) {
669                                errors = XOSModel.prototype.xosValidate(this, attrs, options);
670                                // validate that slice.name starts with site.login_base
671                                site = attrs.site || this.site;
672                                if ((site!=undefined) && (attrs.name!=undefined)) {
673                                    site = xos.sites.get(site);
674                                    if (attrs.name.indexOf(site.attributes.login_base+"_") != 0) {
675                                         errors = errors || {};
676                                         errors["name"] = "must start with " + site.attributes.login_base + "_";
677                                    }
678                                }
679                                return errors;
680                              },
681                            });
682
683         define_model(this, {urlRoot: TENANTVIEW_API,
684                             modelName: "tenantview",
685                             collectionName: "tenantview",
686                             listFields: [],
687                             detailFields: [],
688                             });
689
690         /* by default, have slicePlus only fetch the slices the user can see */
691         this.slicesPlus.currentUserCanSee = true;
692
693         this.tenant = function() { return this.tenantview.models[0].attributes; };
694
695         this.listObjects = function() { return this.allCollectionNames; };
696
697         this.getCollectionStatus = function() {
698             stats = {isLoaded: 0, failedLoad: 0, startedLoad: 0};
699             for (index in this.allCollections) {
700                 collection = this.allCollections[index];
701                 if (collection.isLoaded) {
702                     stats["isLoaded"] = stats["isLoaded"] + 1;
703                 }
704                 if (collection.failedLoad) {
705                     stats["failedLoad"] = stats["failedLoad"] + 1;
706                 }
707                 if (collection.startedLoad) {
708                     stats["startedLoad"] = stats["startedLoad"] + 1;
709                 }
710             }
711             stats["completedLoad"] = stats["failedLoad"] + stats["isLoaded"];
712             return stats;
713         };
714     };
715
716     xos = new xoslib();
717
718     function getCookie(name) {
719         var cookieValue = null;\r
720         if (document.cookie && document.cookie != '') {\r
721             var cookies = document.cookie.split(';');\r
722             for (var i = 0; i < cookies.length; i++) {\r
723                 var cookie = jQuery.trim(cookies[i]);\r
724                 // Does this cookie string begin with the name we want?\r
725                 if (cookie.substring(0, name.length + 1) == (name + '=')) {\r
726                     cookieValue = decodeURIComponent(cookie.substring(name.length + 1));\r
727                     break;\r
728                 }\r
729             }\r
730         }\r
731         return cookieValue;\r
732     }
733
734     (function() {
735       var _sync = Backbone.sync;\r
736       Backbone.sync = function(method, model, options){\r
737         options.beforeSend = function(xhr){\r
738           var token = getCookie("csrftoken");\r
739           xhr.setRequestHeader('X-CSRFToken', token);\r
740         };\r
741         return _sync(method, model, options);\r
742       };\r
743     })();
744 }