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