Controller replaces Deployment
[plstackapi.git] / planetstack / core / admin.py
1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
4
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple, AdminTextareaWidget
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
15 from suit.widgets import LinkedSelect
16 from django.core.exceptions import PermissionDenied
17 from django.core.urlresolvers import reverse, NoReverseMatch
18 from django.utils.encoding import force_text, python_2_unicode_compatible
19 from django.utils.html import conditional_escape, format_html
20 from django.forms.utils import flatatt, to_current_timezone
21 from cgi import escape as html_escape
22
23 import django_evolution
24 import threading
25
26 # thread locals necessary to work around a django-suit issue
27 _thread_locals = threading.local()
28
29 def backend_icon(obj): # backend_status, enacted, updated):
30     #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
31     if (obj.enacted is not None) and obj.enacted >= obj.updated:
32         return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
33     else:
34         if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
35             return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
36         else:
37             return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % html_escape(obj.backend_status, quote=True)
38
39 def backend_text(obj):
40     icon = backend_icon(obj)
41     if (obj.enacted is not None) and obj.enacted >= obj.updated:
42         return "%s %s" % (icon, "successfully enacted")
43     else:
44         return "%s %s" % (icon, html_escape(obj.backend_status, quote=True))
45
46 class UploadTextareaWidget(AdminTextareaWidget):
47     def render(self, name, value, attrs=None):
48         if value is None:
49             value = ''\r
50         final_attrs = self.build_attrs(attrs, name=name)\r
51         return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">' \\r
52                            '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>' \\r
53                            '<br><textarea{0}>\r\n{1}</textarea>' % (attrs["id"], attrs["id"], attrs["id"]),\r
54                            flatatt(final_attrs),\r
55                            force_text(value))
56
57 class PlainTextWidget(forms.HiddenInput):
58     input_type = 'hidden'
59
60     def render(self, name, value, attrs=None):
61         if value is None:
62             value = ''
63         return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
64
65 class PermissionCheckingAdminMixin(object):
66     # call save_by_user and delete_by_user instead of save and delete
67
68     def has_add_permission(self, request, obj=None):
69         return (not self.__user_is_readonly(request))
70
71     def has_delete_permission(self, request, obj=None):
72         return (not self.__user_is_readonly(request))
73
74     def save_model(self, request, obj, form, change):
75         if self.__user_is_readonly(request):
76             # this 'if' might be redundant if save_by_user is implemented right
77             raise PermissionDenied
78
79         obj.caller = request.user
80         # update openstack connection to use this site/tenant
81         obj.save_by_user(request.user)
82
83     def delete_model(self, request, obj):
84         obj.delete_by_user(request.user)
85
86     def save_formset(self, request, form, formset, change):
87         instances = formset.save(commit=False)
88         for instance in instances:
89             instance.save_by_user(request.user)
90
91         # BUG in django 1.7? Objects are not deleted by formset.save if
92         # commit is False. So let's delete them ourselves.
93         #
94         # code from forms/models.py save_existing_objects()
95         try:
96             forms_to_delete = formset.deleted_forms\r
97         except AttributeError:\r
98             forms_to_delete = []
99         if formset.initial_forms:
100             for form in formset.initial_forms:
101                 obj = form.instance
102                 if form in forms_to_delete:
103                     if obj.pk is None:
104                         continue
105                     formset.deleted_objects.append(obj)
106                     obj.delete()
107
108         formset.save_m2m()
109
110     def get_actions(self,request):
111         actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
112
113         if self.__user_is_readonly(request):
114             if 'delete_selected' in actions:
115                 del actions['delete_selected']
116
117         return actions
118
119     def change_view(self,request,object_id, extra_context=None):
120         if self.__user_is_readonly(request):
121             if not hasattr(self, "readonly_save"):\r
122                 # save the original readonly fields\r
123                 self.readonly_save = self.readonly_fields\r
124                 self.inlines_save = self.inlines\r
125             if hasattr(self, "user_readonly_fields"):\r
126                 self.readonly_fields=self.user_readonly_fields\r
127             if hasattr(self, "user_readonly_inlines"):\r
128                 self.inlines = self.user_readonly_inlines\r
129         else:\r
130             if hasattr(self, "readonly_save"):\r
131                 # restore the original readonly fields\r
132                 self.readonly_fields = self.readonly_save\r
133             if hasattr(self, "inlines_save"):\r
134                 self.inlines = self.inlines_save
135
136         try:
137             return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
138         except PermissionDenied:
139             pass
140         if request.method == 'POST':
141             raise PermissionDenied
142         request.readonly = True
143         return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
144
145     def __user_is_readonly(self, request):
146         return request.user.isReadOnlyUser()
147
148     def backend_status_text(self, obj):
149         return mark_safe(backend_text(obj))
150
151     def backend_status_icon(self, obj):
152         return mark_safe(backend_icon(obj))
153     backend_status_icon.short_description = ""
154
155     def get_form(self, request, obj=None, **kwargs):
156         # Save obj and request in thread-local storage, so suit_form_tabs can
157         # use it to determine whether we're in edit or add mode, and can
158         # determine whether the user is an admin.
159         _thread_locals.request = request
160         _thread_locals.obj = obj
161         return super(PermissionCheckingAdminMixin, self).get_form(request, obj, **kwargs)
162
163     def get_inline_instances(self, request, obj=None):
164         inlines = super(PermissionCheckingAdminMixin, self).get_inline_instances(request, obj)
165
166         # inlines that should only be shown to an admin user
167         if request.user.is_admin:
168             for inline_class in getattr(self, "admin_inlines", []):
169                 inlines.append(inline_class(self.model, self.admin_site))
170
171         return inlines
172
173 class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
174     # Note: Make sure PermissionCheckingAdminMixin is listed before
175     # admin.ModelAdmin in the class declaration.
176
177     pass
178
179 class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
180     save_on_top = False
181
182 class SingletonAdmin (ReadOnlyAwareAdmin):
183     def has_add_permission(self, request):
184         if not super(SingletonAdmin, self).has_add_permission(request):
185             return False
186
187         num_objects = self.model.objects.count()
188         if num_objects >= 1:
189             return False
190         else:
191             return True
192
193 class PlStackTabularInline(admin.TabularInline):
194     def __init__(self, *args, **kwargs):
195         super(PlStackTabularInline, self).__init__(*args, **kwargs)
196
197         # InlineModelAdmin as no get_fields() method, so in order to add
198         # the selflink field, we override __init__ to modify self.fields and
199         # self.readonly_fields.
200
201         self.setup_selflink()
202
203     def get_change_url(self, model, id):
204         """ Get the URL to a change form in the admin for this model """
205         reverse_path = "admin:%s_change" % (model._meta.db_table)
206         try:
207             url = reverse(reverse_path, args=(id,))
208         except NoReverseMatch:
209             return None
210
211         return url
212
213     def setup_selflink(self):
214         if hasattr(self, "selflink_fieldname"):
215             """ self.selflink_model can be defined to punch through a relation
216                 to its target object. For example, in SliceNetworkInline, set
217                 selflink_model = "network", and the URL will lead to the Network
218                 object instead of trying to bring up a change view of the
219                 SliceNetwork object.
220             """
221             self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
222         else:
223             self.selflink_model = self.model
224
225         url = self.get_change_url(self.selflink_model, 0)
226
227         # We don't have an admin for this object, so don't create the
228         # selflink.
229         if (url == None):
230             return
231
232         # Since we need to add "selflink" to the field list, we need to create
233         # self.fields if it is None.
234         if (self.fields is None):
235             self.fields = []
236             for f in self.model._meta.fields:
237                 if f.editable and f.name != "id":
238                     self.fields.append(f.name)
239
240         self.fields = tuple(self.fields) + ("selflink", )
241
242         if self.readonly_fields is None:
243             self.readonly_fields = ()
244
245         self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
246
247     def selflink(self, obj):
248         if hasattr(self, "selflink_fieldname"):
249             obj = getattr(obj, self.selflink_fieldname)
250
251         if obj.id:
252             url = self.get_change_url(self.selflink_model, obj.id)
253             return "<a href='%s'>Details</a>" % str(url)
254         else:\r
255             return "Not present"\r
256
257     selflink.allow_tags = True
258     selflink.short_description = "Details"
259
260     def has_add_permission(self, request):
261         return not request.user.isReadOnlyUser()
262
263     def get_readonly_fields(self, request, obj=None):
264         readonly_fields = list(self.readonly_fields)[:]
265         if request.user.isReadOnlyUser():
266             for field in self.fields:
267                 if not field in readonly_fields:
268                     readonly_fields.append(field)
269         return readonly_fields
270
271     def backend_status_icon(self, obj):
272         return mark_safe(backend_icon(obj))
273     backend_status_icon.short_description = ""
274
275 class PlStackGenericTabularInline(generic.GenericTabularInline):
276     def has_add_permission(self, request):
277         return not request.user.isReadOnlyUser()
278
279     def get_readonly_fields(self, request, obj=None):
280         readonly_fields = list(self.readonly_fields)[:]
281         if request.user.isReadOnlyUser():
282             for field in self.fields:
283                 if not field in readonly_fields:
284                     readonly_fields.append(field)
285         return readonly_fields
286
287     def backend_status_icon(self, obj):
288         return mark_safe(backend_icon(obj))
289     backend_status_icon.short_description = ""
290
291 class ReservationInline(PlStackTabularInline):
292     model = Reservation
293     extra = 0
294     suit_classes = 'suit-tab suit-tab-reservations'
295
296     def queryset(self, request):
297         return Reservation.select_by_user(request.user)
298
299 class TagInline(PlStackGenericTabularInline):
300     model = Tag
301     extra = 0
302     suit_classes = 'suit-tab suit-tab-tags'
303     fields = ['service', 'name', 'value']
304
305     def queryset(self, request):
306         return Tag.select_by_user(request.user)
307
308 class NetworkLookerUpper:
309     """ This is a callable that looks up a network name in a sliver and returns
310         the ip address for that network.
311     """
312
313     byNetworkName = {}    # class variable
314
315     def __init__(self, name):
316         self.short_description = name
317         self.__name__ = name
318         self.network_name = name
319
320     def __call__(self, obj):
321         if obj is not None:
322             for nbs in obj.networksliver_set.all():
323                 if (nbs.network.name == self.network_name):
324                     return nbs.ip
325         return ""
326
327     def __str__(self):
328         return self.network_name
329
330     @staticmethod
331     def get(network_name):
332         """ We want to make sure we alwars return the same NetworkLookerUpper
333             because sometimes django will cause them to be instantiated multiple
334             times (and we don't want different ones in form.fields vs
335             SliverInline.readonly_fields).
336         """
337         if network_name not in NetworkLookerUpper.byNetworkName:
338             NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
339         return NetworkLookerUpper.byNetworkName[network_name]
340
341 class SliverInline(PlStackTabularInline):
342     model = Sliver
343     fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
344     extra = 0
345     readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
346     suit_classes = 'suit-tab suit-tab-slivers'
347
348     def queryset(self, request):
349         return Sliver.select_by_user(request.user)
350
351     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
352         if db_field.name == 'deploymentNetwork':
353            kwargs['queryset'] = Deployment.select_by_acl(request.user)
354            kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
355         elif db_field.name == 'flavor':
356            kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
357
358         field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
359
360         return field
361
362 class SiteInline(PlStackTabularInline):
363     model = Site
364     extra = 0
365     suit_classes = 'suit-tab suit-tab-sites'
366
367     def queryset(self, request):
368         return Site.select_by_user(request.user)
369
370 class UserInline(PlStackTabularInline):
371     model = User
372     fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
373     readonly_fields = ('backend_status_icon', )
374     extra = 0
375     suit_classes = 'suit-tab suit-tab-users'
376
377     def queryset(self, request):
378         return User.select_by_user(request.user)
379
380 class SliceInline(PlStackTabularInline):
381     model = Slice
382     fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
383     readonly_fields = ('backend_status_icon', )
384     extra = 0
385     suit_classes = 'suit-tab suit-tab-slices'
386
387     def queryset(self, request):
388         return Slice.select_by_user(request.user)
389
390 class NodeInline(PlStackTabularInline):
391     model = Node
392     extra = 0
393     suit_classes = 'suit-tab suit-tab-nodes'
394     fields = ['backend_status_icon', 'name','deployment','site']
395     readonly_fields = ('backend_status_icon', )
396
397 class ControllerPrivilegeInline(PlStackTabularInline):
398     model = ControllerPrivilege
399     extra = 0
400     suit_classes = 'suit-tab suit-tab-admin-only'
401     fields = ['backend_status_icon', 'user','role','controller']
402     readonly_fields = ('backend_status_icon', )
403
404     def queryset(self, request):
405         return ControllerPrivilege.select_by_user(request.user)
406
407 class SitePrivilegeInline(PlStackTabularInline):
408     model = SitePrivilege
409     extra = 0
410     suit_classes = 'suit-tab suit-tab-siteprivileges'
411     fields = ['backend_status_icon', 'user','site', 'role']
412     readonly_fields = ('backend_status_icon', )
413
414     def formfield_for_foreignkey(self, db_field, request, **kwargs):
415         if db_field.name == 'site':
416             kwargs['queryset'] = Site.select_by_user(request.user)
417
418         if db_field.name == 'user':
419             kwargs['queryset'] = User.select_by_user(request.user)
420         return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
421
422     def queryset(self, request):
423         return SitePrivilege.select_by_user(request.user)
424
425 class SiteDeploymentsInline(PlStackTabularInline):
426     model = SiteDeployments
427     extra = 0
428     suit_classes = 'suit-tab suit-tab-deployments'
429     fields = ['backend_status_icon', 'deployment','site']
430     readonly_fields = ('backend_status_icon', )
431
432     def formfield_for_foreignkey(self, db_field, request, **kwargs):
433         if db_field.name == 'site':
434             kwargs['queryset'] = Site.select_by_user(request.user)
435
436         if db_field.name == 'deployment':
437             kwargs['queryset'] = Deployment.select_by_user(request.user)
438         return super(SiteDeploymentsInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
439
440     def queryset(self, request):
441         return SiteDeployments.select_by_user(request.user)
442
443 class ControllerSitesInline(PlStackTabularInline):
444     model = ControllerSites
445     extra = 0
446     suit_classes = 'suit-tab suit-tab-admin-only'
447     fields = ['backend_status_icon', 'controller','site']
448     readonly_fields = ('backend_status_icon', )
449
450     def formfield_for_foreignkey(self, db_field, request, **kwargs):
451         if db_field.name == 'site':
452             kwargs['queryset'] = Site.select_by_user(request.user)
453
454         if db_field.name == 'controller':
455             kwargs['queryset'] = Controller.select_by_user(request.user)
456         return super(ControllerSitesInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
457
458     def queryset(self, request):
459         return ControllerSites.select_by_user(request.user)
460
461
462 class SlicePrivilegeInline(PlStackTabularInline):
463     model = SlicePrivilege
464     suit_classes = 'suit-tab suit-tab-sliceprivileges'
465     extra = 0
466     fields = ('backend_status_icon', 'user', 'slice', 'role')
467     readonly_fields = ('backend_status_icon', )
468
469     def formfield_for_foreignkey(self, db_field, request, **kwargs):
470         if db_field.name == 'slice':
471            kwargs['queryset'] = Slice.select_by_user(request.user)
472         if db_field.name == 'user':
473            kwargs['queryset'] = User.select_by_user(request.user)
474
475         return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
476
477     def queryset(self, request):
478         return SlicePrivilege.select_by_user(request.user)
479
480 class SliceNetworkInline(PlStackTabularInline):
481     model = Network.slices.through
482     selflink_fieldname = "network"
483     extra = 0
484     verbose_name = "Network Connection"
485     verbose_name_plural = "Network Connections"
486     suit_classes = 'suit-tab suit-tab-slicenetworks'
487     fields = ['backend_status_icon', 'network']
488     readonly_fields = ('backend_status_icon', )
489
490 class ImageDeploymentsInline(PlStackTabularInline):
491     model = ImageDeployments
492     extra = 0
493     verbose_name = "Image Deployments"
494     verbose_name_plural = "Image Deployments"
495     suit_classes = 'suit-tab suit-tab-imagedeployments'
496     fields = ['backend_status_icon', 'image', 'deployment']
497     readonly_fields = ['backend_status_icon']
498
499 class ControllerImagesInline(PlStackTabularInline):
500     model = ControllerImages
501     extra = 0
502     verbose_name = "Controller Images"
503     verbose_name_plural = "Controller Images"
504     suit_classes = 'suit-tab suit-tab-admin-only'
505     fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
506     readonly_fields = ['backend_status_icon', 'glance_image_id']
507
508 class SliceRoleAdmin(PlanetStackBaseAdmin):
509     model = SliceRole
510     pass
511
512 class SiteRoleAdmin(PlanetStackBaseAdmin):
513     model = SiteRole
514     pass
515
516 class DeploymentAdminForm(forms.ModelForm):
517     sites = forms.ModelMultipleChoiceField(
518         queryset=Site.objects.all(),
519         required=False,
520         help_text="Select which sites are allowed to host nodes in this deployment",
521         widget=FilteredSelectMultiple(
522             verbose_name=('Sites'), is_stacked=False
523         )
524     )
525     images = forms.ModelMultipleChoiceField(
526         queryset=Image.objects.all(),
527         required=False,
528         help_text="Select which images should be deployed on this deployment",
529         widget=FilteredSelectMultiple(
530             verbose_name=('Images'), is_stacked=False
531         )
532     )
533     flavors = forms.ModelMultipleChoiceField(
534         queryset=Flavor.objects.all(),
535         required=False,
536         help_text="Select which flavors should be usable on this deployment",
537         widget=FilteredSelectMultiple(
538             verbose_name=('Flavors'), is_stacked=False
539         )
540     )
541     class Meta:
542         model = Deployment
543         many_to_many = ["flavors",]
544
545     def __init__(self, *args, **kwargs):
546       request = kwargs.pop('request', None)
547       super(DeploymentAdminForm, self).__init__(*args, **kwargs)
548
549       self.fields['accessControl'].initial = "allow site " + request.user.site.name
550
551       if self.instance and self.instance.pk:
552         self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
553         self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
554         self.fields['flavors'].initial = self.instance.flavors.all()
555
556     def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
557         """ helper function for handling m2m relations from the MultipleChoiceField
558
559             this_obj: the source object we want to link from
560
561             selected_objs: a list of destination objects we want to link to
562
563             all_relations: the full set of relations involving this_obj, including ones we don't want
564
565             relation_class: the class that implements the relation from source to dest
566
567             local_attrname: field name representing this_obj in relation_class
568
569             foreign_attrname: field name representing selected_objs in relation_class
570
571             This function will remove all newobjclass relations from this_obj
572             that are not contained in selected_objs, and add any relations that
573             are in selected_objs but don't exist in the data model yet.
574         """
575
576         existing_dest_objs = []
577         for relation in list(all_relations):
578             if getattr(relation, foreign_attrname) not in selected_objs:
579                 #print "deleting site", sdp.site
580                 relation.delete()
581             else:
582                 existing_dest_objs.append(getattr(relation, foreign_attrname))
583
584         for dest_obj in selected_objs:
585             if dest_obj not in existing_dest_objs:
586                 #print "adding site", site
587                 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
588                 relation = relation_class(**kwargs)
589                 relation.save()
590
591     def save(self, commit=True):
592       deployment = super(DeploymentAdminForm, self).save(commit=False)
593
594       if commit:
595         deployment.save()
596         # this has to be done after save() if/when a deployment is first created
597         deployment.flavors = self.cleaned_data['flavors']
598
599       if deployment.pk:
600         # save_m2m() doesn't seem to work with 'through' relations. So we
601         #    create/destroy the through models ourselves. There has to be
602         #    a better way...
603
604         self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployments, "deployment", "site")
605         self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ControllerImages, "deployment", "image")
606
607       self.save_m2m()
608
609       return deployment
610
611 class DeploymentAdminROForm(DeploymentAdminForm):
612     def save(self, commit=True):
613         raise PermissionDenied
614
615 class SiteAssocInline(PlStackTabularInline):
616     model = Site.deployments.through
617     extra = 0
618     suit_classes = 'suit-tab suit-tab-sites'
619
620 class DeploymentAdmin(PlanetStackBaseAdmin):
621     model = Deployment
622     fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
623     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
624     inlines = [ControllerPrivilegeInline,NodeInline,TagInline] # ,ControllerImagesInline]
625     list_display = ['backend_status_icon', 'name']
626     list_display_links = ('backend_status_icon', 'name', )
627     readonly_fields = ('backend_status_text', )
628
629     user_readonly_fields = ['name']
630
631     suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
632
633     def get_form(self, request, obj=None, **kwargs):
634         if request.user.isReadOnlyUser():
635             kwargs["form"] = DeploymentAdminROForm
636         else:
637             kwargs["form"] = DeploymentAdminForm
638         adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
639
640         # from stackexchange: pass the request object into the form
641
642         class AdminFormMetaClass(adminForm):
643            def __new__(cls, *args, **kwargs):
644                kwargs['request'] = request
645                return adminForm(*args, **kwargs)
646
647         return AdminFormMetaClass
648
649 class ServiceAttrAsTabInline(PlStackTabularInline):
650     model = ServiceAttribute
651     fields = ['name','value']
652     extra = 0
653     suit_classes = 'suit-tab suit-tab-serviceattrs'
654
655 class ServiceAdmin(PlanetStackBaseAdmin):
656     list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
657     list_display_links = ('backend_status_icon', 'name', )
658     fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
659     fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
660     inlines = [ServiceAttrAsTabInline,SliceInline]
661     readonly_fields = ('backend_status_text', )
662
663     user_readonly_fields = fieldList
664
665     suit_form_tabs =(('general', 'Service Details'),
666         ('slices','Slices'),
667         ('serviceattrs','Additional Attributes'),
668     )
669
670 class SiteAdmin(PlanetStackBaseAdmin):
671     fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
672     fieldsets = [
673         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
674         #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
675     ]
676     suit_form_tabs =(('general', 'Site Details'),
677         ('users','Users'),
678         ('siteprivileges','Privileges'),
679         ('deployments','Deployments'),
680         ('slices','Slices'),
681         ('nodes','Nodes'),
682         ('tags','Tags'),
683     )
684     readonly_fields = ['backend_status_text', 'accountLink']
685
686     user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
687
688     list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
689     list_display_links = ('backend_status_icon', 'name', )
690     filter_horizontal = ('deployments',)
691     inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentsInline]
692     search_fields = ['name']
693
694     def queryset(self, request):
695         return Site.select_by_user(request.user)
696
697     def get_formsets(self, request, obj=None):
698         for inline in self.get_inline_instances(request, obj):
699             # hide MyInline in the add view
700             if obj is None:
701                 continue
702             if isinstance(inline, SliverInline):
703                 inline.model.caller = request.user
704             yield inline.get_formset(request, obj)
705
706     def accountLink(self, obj):
707         link_obj = obj.accounts.all()
708         if link_obj:
709             reverse_path = "admin:core_account_change"
710             url = reverse(reverse_path, args =(link_obj[0].id,))
711             return "<a href='%s'>%s</a>" % (url, "view billing details")
712         else:
713             return "no billing data for this site"
714     accountLink.allow_tags = True
715     accountLink.short_description = "Billing"
716
717     def save_model(self, request, obj, form, change):
718         # update openstack connection to use this site/tenant
719         obj.save_by_user(request.user) 
720
721     def delete_model(self, request, obj):
722         obj.delete_by_user(request.user)
723         
724
725 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
726     fieldList = ['backend_status_text', 'user', 'site', 'role']
727     fieldsets = [
728         (None, {'fields': fieldList, 'classes':['collapse']})
729     ]
730     readonly_fields = ('backend_status_text', )
731     list_display = ('backend_status_icon', 'user', 'site', 'role')
732     list_display_links = list_display
733     user_readonly_fields = fieldList
734     user_readonly_inlines = []
735
736     def formfield_for_foreignkey(self, db_field, request, **kwargs):
737         if db_field.name == 'site':
738             if not request.user.is_admin:
739                 # only show sites where user is an admin or pi
740                 sites = set()
741                 for site_privilege in SitePrivilege.objects.filer(user=request.user):
742                     if site_privilege.role.role_type in ['admin', 'pi']:
743                         sites.add(site_privilege.site)
744                 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
745
746         if db_field.name == 'user':
747             if not request.user.is_admin:
748                 # only show users from sites where caller has admin or pi role
749                 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
750                 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
751                 sites = [site_privilege.site for site_privilege in site_privileges]
752                 site_privileges = SitePrivilege.objects.filter(site__in=sites)
753                 emails = [site_privilege.user.email for site_privilege in site_privileges]
754                 users = User.objects.filter(email__in=emails)
755                 kwargs['queryset'] = users
756
757         return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
758
759     def queryset(self, request):
760         # admins can see all privileges. Users can only see privileges at sites
761         # where they have the admin role or pi role.
762         qs = super(SitePrivilegeAdmin, self).queryset(request)
763         #if not request.user.is_admin:
764         #    roles = Role.objects.filter(role_type__in=['admin', 'pi'])
765         #    site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
766         #    login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
767         #    sites = Site.objects.filter(login_base__in=login_bases)
768         #    qs = qs.filter(site__in=sites)
769         return qs
770
771 class SliceForm(forms.ModelForm):
772     class Meta:
773         model = Slice
774         widgets = {
775             'service': LinkedSelect
776         }
777
778     def clean(self):
779         cleaned_data = super(SliceForm, self).clean()
780         name = cleaned_data.get('name')
781         site = cleaned_data.get('site')
782         slice_id = self.instance.id
783         if not site and slice_id:
784             site = Slice.objects.get(id=slice_id).site
785         if (not isinstance(site,Site)):
786             # previous code indicates 'site' could be a site_id and not a site?
787             site = Slice.objects.get(id=site.id)
788         if not name.startswith(site.login_base):
789             raise forms.ValidationError('slice name must begin with %s' % site.login_base)
790         return cleaned_data
791
792 class ControllerSlicesInline(PlStackTabularInline):
793     model = ControllerSlices
794     extra = 0
795     verbose_name = "Controller Slices"
796     verbose_name_plural = "Controller Slices"
797     suit_classes = 'suit-tab suit-tab-admin-only'
798     fields = ['backend_status_icon', 'controller', 'tenant_id']
799     readonly_fields = ('backend_status_icon', )
800
801 class SliceAdmin(PlanetStackBaseAdmin):
802     form = SliceForm
803     fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
804     fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
805     readonly_fields = ('backend_status_text', )
806     list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
807     list_display_links = ('backend_status_icon', 'name', )
808     inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
809     admin_inlines = [ControllerSlicesInline]
810
811     user_readonly_fields = fieldList
812
813     @property
814     def suit_form_tabs(self):
815         tabs =[('general', 'Slice Details'),
816           ('slicenetworks','Networks'),
817           ('sliceprivileges','Privileges'),
818           ('slivers','Slivers'),
819           ('tags','Tags'),
820           ('reservations','Reservations'),
821           ]
822
823         request=getattr(_thread_locals, "request", None)
824         if request and request.user.is_admin:
825             tabs.append( ('admin-only', 'Admin-Only') )
826
827         return tabs
828     
829     def add_view(self, request, form_url='', extra_context=None):
830         # revert to default read-only fields
831         self.readonly_fields = ('backend_status_text',)
832         return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
833
834     def change_view(self, request, object_id, form_url='', extra_context=None):
835         print object_id
836         # cannot change the site of an existing slice so make the site field read only
837         if object_id:
838             self.readonly_fields = ('backend_status_text','site')
839         return super(SliceAdmin, self).change_view(request, object_id, form_url)
840
841     def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
842         deployment_nodes = []
843         for node in Node.objects.all():
844             deployment_nodes.append( (node.deployment.id, node.id, node.name) )
845
846         deployment_flavors = []
847         for flavor in Flavor.objects.all():
848             for deployment in flavor.deployments.all():
849                 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
850
851         controller_images = []
852         for image in Image.objects.all():
853             for controller_image in image.controllerimages.all():
854                 controller_images.append( (controller_image.controller.id, image.id, image.name) )
855
856         site_login_bases = []
857         for site in Site.objects.all():
858             site_login_bases.append((site.id, site.login_base))
859
860         context["deployment_nodes"] = deployment_nodes
861         context["deployment_flavors"] = deployment_flavors
862         context["deployment_images"] = deployment_images
863         context["site_login_bases"] = site_login_bases
864         return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
865
866     def formfield_for_foreignkey(self, db_field, request, **kwargs):
867         if db_field.name == 'site':
868             kwargs['queryset'] = Site.select_by_user(request.user)
869             kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
870
871         return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
872
873     def queryset(self, request):
874         # admins can see all keys. Users can only see slices they belong to.
875         return Slice.select_by_user(request.user)
876
877     def get_formsets(self, request, obj=None):
878         for inline in self.get_inline_instances(request, obj):
879             # hide MyInline in the add view
880             if obj is None:
881                 continue
882             if isinstance(inline, SliverInline):
883                 inline.model.caller = request.user
884             yield inline.get_formset(request, obj)
885
886 class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
887     fieldsets = [
888         (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
889     ]
890     readonly_fields = ('backend_status_text', )
891     list_display = ('backend_status_icon', 'user', 'slice', 'role')
892     list_display_links = list_display
893
894     user_readonly_fields = ['user', 'slice', 'role']
895     user_readonly_inlines = []
896
897     def formfield_for_foreignkey(self, db_field, request, **kwargs):
898         if db_field.name == 'slice':
899             kwargs['queryset'] = Slice.select_by_user(request.user)
900         
901         if db_field.name == 'user':
902             kwargs['queryset'] = User.select_by_user(request.user)
903
904         return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
905
906     def queryset(self, request):
907         # admins can see all memberships. Users can only see memberships of
908         # slices where they have the admin role.
909         return SlicePrivilege.select_by_user(request.user)
910
911     def save_model(self, request, obj, form, change):
912         # update openstack connection to use this site/tenant
913         auth = request.session.get('auth', {})
914         auth['tenant'] = obj.slice.slicename
915         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
916         obj.save()
917
918     def delete_model(self, request, obj):
919         # update openstack connection to use this site/tenant
920         auth = request.session.get('auth', {})
921         auth['tenant'] = obj.slice.slicename
922         obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
923         obj.delete()
924
925
926 class ImageAdmin(PlanetStackBaseAdmin):
927
928     fieldsets = [('Image Details',
929                    {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
930                     'classes': ['suit-tab suit-tab-general']})
931                ]
932     readonly_fields = ('backend_status_text', )
933
934     suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
935
936     inlines = [SliverInline, ControllerImagesInline]
937
938     user_readonly_fields = ['name', 'disk_format', 'container_format']
939
940     list_display = ['backend_status_icon', 'name']
941     list_display_links = ('backend_status_icon', 'name', )
942
943 class NodeForm(forms.ModelForm):
944     class Meta:
945         widgets = {
946             'site': LinkedSelect,
947             'deployment': LinkedSelect
948         }
949
950 class NodeAdmin(PlanetStackBaseAdmin):
951     form = NodeForm
952     list_display = ('backend_status_icon', 'name', 'site', 'deployment')
953     list_display_links = ('backend_status_icon', 'name', )
954     list_filter = ('deployment',)
955
956     inlines = [TagInline,SliverInline]
957     fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
958     readonly_fields = ('backend_status_text', )
959
960     user_readonly_fields = ['name','site','deployment']
961     user_readonly_inlines = [TagInline,SliverInline]
962
963     suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
964
965
966 class SliverForm(forms.ModelForm):
967     class Meta:
968         model = Sliver
969         ip = forms.CharField(widget=PlainTextWidget)
970         instance_name = forms.CharField(widget=PlainTextWidget)
971         widgets = {
972             'ip': PlainTextWidget(),
973             'instance_name': PlainTextWidget(),
974             'slice': LinkedSelect,
975             'deploymentNetwork': LinkedSelect,
976             'node': LinkedSelect,
977             'image': LinkedSelect
978         }
979
980 class TagAdmin(PlanetStackBaseAdmin):
981     list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
982     list_display_links = list_display
983     user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
984     user_readonly_inlines = []
985
986 class SliverAdmin(PlanetStackBaseAdmin):
987     form = SliverForm
988     fieldsets = [
989         ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
990     ]
991     readonly_fields = ('backend_status_text', )
992     list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
993     list_display_links = ('backend_status_icon', 'ip',)
994
995     suit_form_tabs =(('general', 'Sliver Details'),
996         ('tags','Tags'),
997     )
998
999     inlines = [TagInline]
1000
1001     user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
1002
1003     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1004         if db_field.name == 'slice':
1005             kwargs['queryset'] = Slice.select_by_user(request.user)
1006
1007         return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1008
1009     def queryset(self, request):
1010         # admins can see all slivers. Users can only see slivers of
1011         # the slices they belong to.
1012         return Sliver.select_by_user(request.user)
1013
1014
1015     def get_formsets(self, request, obj=None):
1016         # make some fields read only if we are updating an existing record
1017         if obj == None:
1018             #self.readonly_fields = ('ip', 'instance_name')
1019             self.readonly_fields = ('backend_status_text',)
1020         else:
1021             self.readonly_fields = ('backend_status_text',)
1022             #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
1023
1024         for inline in self.get_inline_instances(request, obj):
1025             # hide MyInline in the add view
1026             if obj is None:
1027                 continue
1028             if isinstance(inline, SliverInline):
1029                 inline.model.caller = request.user
1030             yield inline.get_formset(request, obj)
1031
1032     #def save_model(self, request, obj, form, change):
1033     #    # update openstack connection to use this site/tenant
1034     #    auth = request.session.get('auth', {})
1035     #    auth['tenant'] = obj.slice.name
1036     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1037     #    obj.creator = request.user
1038     #    obj.save()
1039
1040     #def delete_model(self, request, obj):
1041     #    # update openstack connection to use this site/tenant
1042     #    auth = request.session.get('auth', {})
1043     #    auth['tenant'] = obj.slice.name
1044     #    obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1045     #    obj.delete()
1046
1047 class UserCreationForm(forms.ModelForm):
1048     """A form for creating new users. Includes all the required
1049     fields, plus a repeated password."""
1050     password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1051     password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1052
1053     class Meta:
1054         model = User
1055         fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
1056
1057     def clean_password2(self):
1058         # Check that the two password entries match
1059         password1 = self.cleaned_data.get("password1")
1060         password2 = self.cleaned_data.get("password2")
1061         if password1 and password2 and password1 != password2:
1062             raise forms.ValidationError("Passwords don't match")
1063         return password2
1064
1065     def save(self, commit=True):
1066         # Save the provided password in hashed format
1067         user = super(UserCreationForm, self).save(commit=False)
1068         user.password = self.cleaned_data["password1"]
1069         #user.set_password(self.cleaned_data["password1"])
1070         if commit:
1071             user.save()
1072         return user
1073
1074
1075 class UserChangeForm(forms.ModelForm):
1076     """A form for updating users. Includes all the fields on
1077     the user, but replaces the password field with admin's
1078     password hash display field.
1079     """
1080     password = ReadOnlyPasswordHashField(label='Password',
1081                    help_text= '<a href=\"password/\">Change Password</a>.')
1082
1083     class Meta:
1084         model = User
1085         widgets = { 'public_key': UploadTextareaWidget, }
1086
1087     def clean_password(self):
1088         # Regardless of what the user provides, return the initial value.
1089         # This is done here, rather than on the field, because the
1090         # field does not have access to the initial value
1091         return self.initial["password"]
1092
1093 class UserDashboardViewInline(PlStackTabularInline):
1094     model = UserDashboardView
1095     extra = 0
1096     suit_classes = 'suit-tab suit-tab-dashboards'
1097     fields = ['user', 'dashboardView', 'order']
1098
1099 class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1100     # Note: Make sure PermissionCheckingAdminMixin is listed before
1101     # admin.ModelAdmin in the class declaration.
1102
1103     class Meta:
1104         app_label = "core"
1105
1106     # The forms to add and change user instances
1107     form = UserChangeForm
1108     add_form = UserCreationForm
1109
1110     # The fields to be used in displaying the User model.
1111     # These override the definitions on the base UserAdmin
1112     # that reference specific fields on auth.User.
1113     list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
1114     list_filter = ('site',)
1115     inlines = [SlicePrivilegeInline,SitePrivilegeInline,ControllerPrivilegeInline,UserDashboardViewInline]
1116
1117     fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
1118     fieldListContactInfo = ['firstname','lastname','phone','timezone']
1119
1120     fieldsets = (
1121         ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
1122         ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
1123         #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
1124         #('Important dates', {'fields': ('last_login',)}),
1125     )
1126     add_fieldsets = (
1127         (None, {
1128             'classes': ('wide',),
1129             'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
1130         ),
1131     )
1132     readonly_fields = ('backend_status_text', )
1133     search_fields = ('email',)
1134     ordering = ('email',)
1135     filter_horizontal = ()
1136
1137     user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
1138
1139     @property
1140     def suit_form_tabs(self):
1141         if getattr(_thread_locals, "obj", None) is None:
1142             return []
1143         else:
1144             return (('general','Login Details'),
1145                          ('contact','Contact Information'),
1146                          ('sliceprivileges','Slice Privileges'),
1147                          ('siteprivileges','Site Privileges'),
1148                          ('controllerprivileges','Controller Privileges'),
1149                          ('dashboards','Dashboard Views'))
1150
1151     def formfield_for_foreignkey(self, db_field, request, **kwargs):
1152         if db_field.name == 'site':
1153             kwargs['queryset'] = Site.select_by_user(request.user)
1154
1155         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1156
1157     def queryset(self, request):
1158         return User.select_by_user(request.user)
1159
1160 class DashboardViewAdmin(PlanetStackBaseAdmin):
1161     fieldsets = [('Dashboard View Details',
1162                    {'fields': ['backend_status_text', 'name', 'url'],
1163                     'classes': ['suit-tab suit-tab-general']})
1164                ]
1165     readonly_fields = ('backend_status_text', )
1166
1167     suit_form_tabs =(('general','Dashboard View Details'),)
1168
1169 class ServiceResourceInline(PlStackTabularInline):
1170     model = ServiceResource
1171     extra = 0
1172
1173 class ServiceClassAdmin(PlanetStackBaseAdmin):
1174     list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1175     list_display_links = ('backend_status_icon', 'name', )
1176     inlines = [ServiceResourceInline]
1177
1178     user_readonly_fields = ['name', 'commitment', 'membershipFee']
1179     user_readonly_inlines = []
1180
1181 class ReservedResourceInline(PlStackTabularInline):
1182     model = ReservedResource
1183     extra = 0
1184     suit_classes = 'suit-tab suit-tab-reservedresources'
1185
1186     def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1187         field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1188
1189         if db_field.name == 'resource':
1190             # restrict resources to those that the slice's service class allows
1191             if request._slice is not None:
1192                 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1193                 if len(field.queryset) > 0:
1194                     field.initial = field.queryset.all()[0]
1195             else:\r
1196                 field.queryset = field.queryset.none()\r
1197         elif db_field.name == 'sliver':\r
1198             # restrict slivers to those that belong to the slice\r
1199             if request._slice is not None:\r
1200                 field.queryset = field.queryset.filter(slice = request._slice)
1201             else:
1202                 field.queryset = field.queryset.none()\r
1203 \r
1204         return field
1205
1206     def queryset(self, request):
1207         return ReservedResource.select_by_user(request.user)
1208
1209 class ReservationChangeForm(forms.ModelForm):
1210     class Meta:
1211         model = Reservation
1212         widgets = {
1213             'slice' : LinkedSelect
1214         }
1215
1216 class ReservationAddForm(forms.ModelForm):
1217     slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1218     refresh = forms.CharField(widget=forms.HiddenInput())
1219
1220     class Media:
1221        css = {'all': ('planetstack.css',)}   # .field-refresh { display: none; }
1222
1223     def clean_slice(self):
1224         slice = self.cleaned_data.get("slice")
1225         x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1226         if len(x) == 0:
1227             raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1228         return slice
1229
1230     class Meta:
1231         model = Reservation
1232         widgets = {
1233             'slice' : LinkedSelect
1234         }
1235
1236
1237 class ReservationAddRefreshForm(ReservationAddForm):
1238     """ This form is displayed when the Reservation Form receives an update
1239         from the Slice dropdown onChange handler. It doesn't validate the
1240         data and doesn't save the data. This will cause the form to be
1241         redrawn.
1242     """
1243
1244     """ don't validate anything other than slice """
1245     dont_validate_fields = ("startTime", "duration")
1246
1247     def full_clean(self):
1248         result = super(ReservationAddForm, self).full_clean()
1249
1250         for fieldname in self.dont_validate_fields:
1251             if fieldname in self._errors:
1252                 del self._errors[fieldname]
1253
1254         return result
1255
1256     """ don't save anything """
1257     def is_valid(self):
1258         return False
1259
1260 class ReservationAdmin(PlanetStackBaseAdmin):
1261     fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
1262     fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1263     readonly_fields = ('backend_status_text', )
1264     list_display = ('startTime', 'duration')
1265     form = ReservationAddForm
1266
1267     suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1268
1269     inlines = [ReservedResourceInline]
1270     user_readonly_fields = fieldList
1271
1272     def add_view(self, request, form_url='', extra_context=None):
1273         timezone.activate(request.user.timezone)
1274         request._refresh = False
1275         request._slice = None
1276         if request.method == 'POST':
1277             # "refresh" will be set to "1" if the form was submitted due to
1278             # a change in the Slice dropdown.
1279             if request.POST.get("refresh","1") == "1":
1280                 request._refresh = True
1281                 request.POST["refresh"] = "0"
1282
1283             # Keep track of the slice that was selected, so the
1284             # reservedResource inline can filter items for the slice.
1285             request._slice = request.POST.get("slice",None)
1286             if (request._slice is not None):
1287                 request._slice = Slice.objects.get(id=request._slice)
1288
1289         result =  super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1290         return result
1291
1292     def changelist_view(self, request, extra_context = None):
1293         timezone.activate(request.user.timezone)
1294         return super(ReservationAdmin, self).changelist_view(request, extra_context)
1295
1296     def get_form(self, request, obj=None, **kwargs):
1297         request._obj_ = obj
1298         if obj is not None:
1299             # For changes, set request._slice to the slice already set in the
1300             # object.
1301             request._slice = obj.slice
1302             self.form = ReservationChangeForm
1303         else:
1304             if getattr(request, "_refresh", False):
1305                 self.form = ReservationAddRefreshForm
1306             else:
1307                 self.form = ReservationAddForm
1308         return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1309
1310     def get_readonly_fields(self, request, obj=None):
1311         if (obj is not None):
1312             # Prevent slice from being changed after the reservation has been
1313             # created.
1314             return ['slice']
1315         else:
1316             return []
1317
1318     def queryset(self, request):
1319         return Reservation.select_by_user(request.user)
1320
1321 class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
1322     list_display = ("backend_status_icon", "name", )
1323     list_display_links = ('backend_status_icon', 'name', )
1324     user_readonly_fields = ['name']
1325     user_readonly_inlines = []
1326
1327 class RouterAdmin(PlanetStackBaseAdmin):
1328     list_display = ("backend_status_icon", "name", )
1329     list_display_links = ('backend_status_icon', 'name', )
1330     user_readonly_fields = ['name']
1331     user_readonly_inlines = []
1332
1333 class RouterInline(PlStackTabularInline):
1334     model = Router.networks.through
1335     extra = 0
1336     verbose_name_plural = "Routers"
1337     verbose_name = "Router"
1338     suit_classes = 'suit-tab suit-tab-routers'
1339
1340 class NetworkParameterInline(PlStackGenericTabularInline):
1341     model = NetworkParameter
1342     extra = 0
1343     verbose_name_plural = "Parameters"
1344     verbose_name = "Parameter"
1345     suit_classes = 'suit-tab suit-tab-netparams'
1346     fields = ['backend_status_icon', 'parameter', 'value']
1347     readonly_fields = ('backend_status_icon', )
1348
1349 class NetworkSliversInline(PlStackTabularInline):
1350     fields = ['backend_status_icon', 'network','sliver','ip']
1351     readonly_fields = ("backend_status_icon", "ip", )
1352     model = NetworkSliver
1353     selflink_fieldname = "sliver"
1354     extra = 0
1355     verbose_name_plural = "Slivers"
1356     verbose_name = "Sliver"
1357     suit_classes = 'suit-tab suit-tab-networkslivers'
1358
1359 class NetworkSlicesInline(PlStackTabularInline):
1360     model = NetworkSlice
1361     selflink_fieldname = "slice"
1362     extra = 0
1363     verbose_name_plural = "Slices"
1364     verbose_name = "Slice"
1365     suit_classes = 'suit-tab suit-tab-networkslices'
1366     fields = ['backend_status_icon', 'network','slice']
1367     readonly_fields = ('backend_status_icon', )
1368
1369 class ControllerNetworksInline(PlStackTabularInline):
1370     model = ControllerNetworks
1371     extra = 0
1372     verbose_name_plural = "Controller Networks"
1373     verbose_name = "Controller Network"
1374     suit_classes = 'suit-tab suit-tab-admin-only'
1375     fields = ['backend_status_icon', 'controller','net_id','subnet_id']
1376     readonly_fields = ('backend_status_icon', )
1377
1378 class NetworkForm(forms.ModelForm):
1379     class Meta:
1380         model = Network
1381         widgets = {
1382             'topologyParameters': UploadTextareaWidget,
1383             'controllerParameters': UploadTextareaWidget,
1384         }
1385
1386 class NetworkAdmin(PlanetStackBaseAdmin):
1387     list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1388     list_display_links = ('backend_status_icon', 'name', )
1389     readonly_fields = ("subnet", )
1390
1391     inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
1392     admin_inlines = [ControllerNetworksInline]
1393
1394     form=NetworkForm
1395
1396     fieldsets = [
1397         (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'],
1398                 'classes':['suit-tab suit-tab-general']}),
1399         (None, {'fields': ['topologyParameters', 'controllerUrl', 'controllerParameters'],
1400                 'classes':['suit-tab suit-tab-sdn']}),
1401                 ]
1402
1403     readonly_fields = ('backend_status_text', )
1404     user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
1405
1406     @property
1407     def suit_form_tabs(self):
1408         tabs=[('general','Network Details'),
1409             ('sdn', 'SDN Configuration'),
1410             ('netparams', 'Parameters'),
1411             ('networkslivers','Slivers'),
1412             ('networkslices','Slices'),
1413             ('routers','Routers'),
1414         ]
1415
1416         request=getattr(_thread_locals, "request", None)
1417         if request and request.user.is_admin:
1418             tabs.append( ('admin-only', 'Admin-Only') )
1419
1420         return tabs
1421
1422
1423 class NetworkTemplateAdmin(PlanetStackBaseAdmin):
1424     list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1425     list_display_links = ('backend_status_icon', 'name', )
1426     user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1427     user_readonly_inlines = []
1428     fieldsets = [
1429         (None, {'fields': ['name', 'description', 'guaranteedBandwidth', 'visibility', 'translation', 'sharedNetworkName', 'sharedNetworkId', 'topologyKind', 'controllerKind'],
1430                 'classes':['suit-tab suit-tab-general']}),]
1431     suit_form_tabs = (('general','Network Template Details'), )
1432
1433 class FlavorAdmin(PlanetStackBaseAdmin):
1434     list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1435     list_display_links = ("backend_status_icon", "name")
1436     user_readonly_fields = ("name", "flavor")
1437     fields = ("name", "description", "flavor", "order", "default")
1438
1439 # register a signal that caches the user's credentials when they log in
1440 def cache_credentials(sender, user, request, **kwds):
1441     auth = {'username': request.POST['username'],
1442             'password': request.POST['password']}
1443     request.session['auth'] = auth
1444 user_logged_in.connect(cache_credentials)
1445
1446 def dollar_field(fieldName, short_description):
1447     def newFunc(self, obj):
1448         try:
1449             x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1450         except:
1451             x=getattr(obj, fieldName, 0.0)
1452         return x
1453     newFunc.short_description = short_description
1454     return newFunc
1455
1456 def right_dollar_field(fieldName, short_description):
1457     def newFunc(self, obj):
1458         try:
1459             #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1460             x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1461         except:
1462             x=getattr(obj, fieldName, 0.0)
1463         return x
1464     newFunc.short_description = short_description
1465     newFunc.allow_tags = True
1466     return newFunc
1467
1468 class InvoiceChargeInline(PlStackTabularInline):
1469     model = Charge
1470     extra = 0
1471     verbose_name_plural = "Charges"
1472     verbose_name = "Charge"
1473     exclude = ['account']
1474     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1475     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1476     can_delete = False
1477     max_num = 0
1478
1479     dollar_amount = right_dollar_field("amount", "Amount")
1480
1481 class InvoiceAdmin(admin.ModelAdmin):
1482     list_display = ("date", "account")
1483
1484     inlines = [InvoiceChargeInline]
1485
1486     fields = ["date", "account", "dollar_amount"]
1487     readonly_fields = ["date", "account", "dollar_amount"]
1488
1489     dollar_amount = dollar_field("amount", "Amount")
1490
1491 class InvoiceInline(PlStackTabularInline):
1492     model = Invoice
1493     extra = 0
1494     verbose_name_plural = "Invoices"
1495     verbose_name = "Invoice"
1496     fields = ["date", "dollar_amount"]
1497     readonly_fields = ["date", "dollar_amount"]
1498     suit_classes = 'suit-tab suit-tab-accountinvoice'
1499     can_delete=False
1500     max_num=0
1501
1502     dollar_amount = right_dollar_field("amount", "Amount")
1503
1504 class PendingChargeInline(PlStackTabularInline):
1505     model = Charge
1506     extra = 0
1507     verbose_name_plural = "Charges"
1508     verbose_name = "Charge"
1509     exclude = ["invoice"]
1510     fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1511     readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1512     suit_classes = 'suit-tab suit-tab-accountpendingcharges'
1513     can_delete=False
1514     max_num=0
1515
1516     def queryset(self, request):
1517         qs = super(PendingChargeInline, self).queryset(request)
1518         qs = qs.filter(state="pending")
1519         return qs
1520
1521     dollar_amount = right_dollar_field("amount", "Amount")
1522
1523 class PaymentInline(PlStackTabularInline):
1524     model=Payment
1525     extra = 1
1526     verbose_name_plural = "Payments"
1527     verbose_name = "Payment"
1528     fields = ["date", "dollar_amount"]
1529     readonly_fields = ["date", "dollar_amount"]
1530     suit_classes = 'suit-tab suit-tab-accountpayments'
1531     can_delete=False
1532     max_num=0
1533
1534     dollar_amount = right_dollar_field("amount", "Amount")
1535
1536 class AccountAdmin(admin.ModelAdmin):
1537     list_display = ("site", "balance_due")
1538
1539     inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1540
1541     fieldsets = [
1542         (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
1543
1544     readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
1545
1546     suit_form_tabs =(
1547         ('general','Account Details'),
1548         ('accountinvoice', 'Invoices'),
1549         ('accountpayments', 'Payments'),
1550         ('accountpendingcharges','Pending Charges'),
1551     )
1552
1553     dollar_balance_due = dollar_field("balance_due", "Balance Due")
1554     dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1555     dollar_total_payments = dollar_field("total_payments", "Total Payments")
1556
1557 # Now register the new UserAdmin...
1558 admin.site.register(User, UserAdmin)
1559 # ... and, since we're not using Django's builtin permissions,
1560 # unregister the Group model from admin.
1561 #admin.site.unregister(Group)
1562
1563 #Do not show django evolution in the admin interface
1564 from django_evolution.models import Version, Evolution
1565 #admin.site.unregister(Version)
1566 #admin.site.unregister(Evolution)
1567
1568
1569 # When debugging it is often easier to see all the classes, but for regular use 
1570 # only the top-levels should be displayed
1571 showAll = False
1572
1573 admin.site.register(Deployment, DeploymentAdmin)
1574 admin.site.register(Controller, ControllerAdmin)
1575 admin.site.register(Site, SiteAdmin)
1576 admin.site.register(Slice, SliceAdmin)
1577 admin.site.register(Service, ServiceAdmin)
1578 admin.site.register(Reservation, ReservationAdmin)
1579 admin.site.register(Network, NetworkAdmin)
1580 admin.site.register(Router, RouterAdmin)
1581 admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
1582 admin.site.register(Account, AccountAdmin)
1583 admin.site.register(Invoice, InvoiceAdmin)
1584
1585 if True:
1586     admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1587     admin.site.register(ServiceClass, ServiceClassAdmin)
1588     #admin.site.register(PlanetStack)
1589     admin.site.register(Tag, TagAdmin)
1590     admin.site.register(ControllerRole)
1591     admin.site.register(SiteRole)
1592     admin.site.register(SliceRole)
1593     admin.site.register(PlanetStackRole)
1594     admin.site.register(Node, NodeAdmin)
1595     #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1596     #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
1597     admin.site.register(Sliver, SliverAdmin)
1598     admin.site.register(Image, ImageAdmin)
1599     admin.site.register(DashboardView, DashboardViewAdmin)
1600     admin.site.register(Flavor, FlavorAdmin)
1601