blob: 3931f75a5c27dfc8fa6917f1fd6df316367f8277 [file] [log] [blame]
Siobhan Tully30fd4292013-05-10 08:59:56 -04001from core.models import Site
2from core.models import *
3from openstack.manager import OpenStackManager
Tony Macke59a7c82013-04-27 11:08:10 -04004
Tony Mack7130ac32013-03-22 21:58:00 -04005from django.contrib import admin
Siobhan Tully53437282013-04-26 19:30:27 -04006from django.contrib.auth.models import Group
Siobhan Tully4bc09f22013-04-10 21:15:21 -04007from django import forms
Tony Mackd90cdbf2013-04-16 22:48:40 -04008from django.utils.safestring import mark_safe
Tony Mack7130ac32013-03-22 21:58:00 -04009from django.contrib.auth.admin import UserAdmin
Scott Baker69e045d2014-11-17 23:44:03 -080010from django.contrib.admin.widgets import FilteredSelectMultiple, AdminTextareaWidget
Scott Baker1a6a3902014-10-03 00:32:37 -070011from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
Scott Bakeracd45142013-05-19 16:19:16 -070012from django.contrib.auth.signals import user_logged_in
13from django.utils import timezone
Siobhan Tullyde5450d2013-06-21 11:35:33 -040014from django.contrib.contenttypes import generic
Siobhan Tullybfd11dc2013-09-03 12:59:24 -040015from suit.widgets import LinkedSelect
Siobhan Tullycf04fb62014-01-11 11:25:57 -050016from django.core.exceptions import PermissionDenied
Scott Bakere2bbf7e2014-01-13 12:09:31 -080017from django.core.urlresolvers import reverse, NoReverseMatch
Scott Baker69e045d2014-11-17 23:44:03 -080018from django.utils.encoding import force_text, python_2_unicode_compatible
19from django.utils.html import conditional_escape, format_html
20from django.forms.utils import flatatt, to_current_timezone
Scott Baker92d22172014-10-21 21:03:08 -070021from cgi import escape as html_escape
Tony Mack7130ac32013-03-22 21:58:00 -040022
Scott Baker36f50872014-08-21 13:01:25 -070023import django_evolution
Scott Baker6a995352014-10-06 17:51:20 -070024import threading
25
26# thread locals necessary to work around a django-suit issue
27_thread_locals = threading.local()
Scott Baker36f50872014-08-21 13:01:25 -070028
Scott Baker40c00762014-08-21 16:55:59 -070029def backend_icon(obj): # backend_status, enacted, updated):
30 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
Sapan Bhatia4c877d62015-01-27 03:49:42 +000031 if (obj.enacted is not None) and obj.enacted >= obj.updated or obj.backend_status.startswith("1 -"):
Scott Bakerb171e522014-09-09 10:38:15 -070032 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
Scott Baker40c00762014-08-21 16:55:59 -070033 else:
Scott Baker95638be2015-01-22 15:14:50 -080034 if ((obj.backend_status is not None) and obj.backend_status.startswith("0 -")) or obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
Scott Bakerb171e522014-09-09 10:38:15 -070035 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
Scott Baker63d1a552014-08-21 15:19:07 -070036 else:
Scott Baker92d22172014-10-21 21:03:08 -070037 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % html_escape(obj.backend_status, quote=True)
Scott Baker40c00762014-08-21 16:55:59 -070038
39def backend_text(obj):
40 icon = backend_icon(obj)
41 if (obj.enacted is not None) and obj.enacted >= obj.updated:
Scott Baker92d22172014-10-21 21:03:08 -070042 return "%s %s" % (icon, "successfully enacted")
Scott Baker40c00762014-08-21 16:55:59 -070043 else:
Scott Baker92d22172014-10-21 21:03:08 -070044 return "%s %s" % (icon, html_escape(obj.backend_status, quote=True))
Scott Baker63d1a552014-08-21 15:19:07 -070045
Scott Baker69e045d2014-11-17 23:44:03 -080046class UploadTextareaWidget(AdminTextareaWidget):
47 def render(self, name, value, attrs=None):
48 if value is None:
49 value = ''
50 final_attrs = self.build_attrs(attrs, name=name)
51 return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">' \
52 '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>' \
53 '<br><textarea{0}>\r\n{1}</textarea>' % (attrs["id"], attrs["id"], attrs["id"]),
54 flatatt(final_attrs),
55 force_text(value))
56
Scott Baker36f50872014-08-21 13:01:25 -070057class 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
Scott Bakerf4aeedc2014-10-03 13:10:47 -070065class PermissionCheckingAdminMixin(object):
Scott Baker1a6a3902014-10-03 00:32:37 -070066 # call save_by_user and delete_by_user instead of save and delete
Siobhan Tullycf04fb62014-01-11 11:25:57 -050067
68 def has_add_permission(self, request, obj=None):
69 return (not self.__user_is_readonly(request))
Scott Baker36f50872014-08-21 13:01:25 -070070
Siobhan Tullycf04fb62014-01-11 11:25:57 -050071 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):
Scott Baker1a6a3902014-10-03 00:32:37 -070076 # this 'if' might be redundant if save_by_user is implemented right
Siobhan Tullycf04fb62014-01-11 11:25:57 -050077 raise PermissionDenied
Scott Baker1a6a3902014-10-03 00:32:37 -070078
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
97 except AttributeError:
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()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500109
110 def get_actions(self,request):
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700111 actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500112
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):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500120 if self.__user_is_readonly(request):
Scott Bakeraf73e102014-04-22 22:40:07 -0700121 if not hasattr(self, "readonly_save"):
122 # save the original readonly fields
123 self.readonly_save = self.readonly_fields
124 self.inlines_save = self.inlines
Scott Bakere8859f92014-05-23 12:42:40 -0700125 if hasattr(self, "user_readonly_fields"):
126 self.readonly_fields=self.user_readonly_fields
127 if hasattr(self, "user_readonly_inlines"):
128 self.inlines = self.user_readonly_inlines
Scott Bakeraf73e102014-04-22 22:40:07 -0700129 else:
130 if hasattr(self, "readonly_save"):
131 # restore the original readonly fields
132 self.readonly_fields = self.readonly_save
Scott Bakere8859f92014-05-23 12:42:40 -0700133 if hasattr(self, "inlines_save"):
Scott Bakeraf73e102014-04-22 22:40:07 -0700134 self.inlines = self.inlines_save
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500135
136 try:
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700137 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500138 except PermissionDenied:
139 pass
140 if request.method == 'POST':
141 raise PermissionDenied
142 request.readonly = True
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700143 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500144
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500145 def __user_is_readonly(self, request):
146 return request.user.isReadOnlyUser()
147
Scott Baker40c00762014-08-21 16:55:59 -0700148 def backend_status_text(self, obj):
149 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -0700150
Scott Baker63d1a552014-08-21 15:19:07 -0700151 def backend_status_icon(self, obj):
Scott Baker40c00762014-08-21 16:55:59 -0700152 return mark_safe(backend_icon(obj))
Scott Baker63d1a552014-08-21 15:19:07 -0700153 backend_status_icon.short_description = ""
154
Scott Bakerdc4724c2014-11-05 09:05:38 -0800155 def get_form(self, request, obj=None, **kwargs):
Scott Baker9b3c1af2014-10-16 00:57:55 -0700156 # 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
Scott Bakerdc4724c2014-11-05 09:05:38 -0800161 return super(PermissionCheckingAdminMixin, self).get_form(request, obj, **kwargs)
Scott Baker9b3c1af2014-10-16 00:57:55 -0700162
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
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700173class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
174 # Note: Make sure PermissionCheckingAdminMixin is listed before
175 # admin.ModelAdmin in the class declaration.
176
Scott Baker1a6a3902014-10-03 00:32:37 -0700177 pass
178
179class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
180 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700181
Scott Bakere8859f92014-05-23 12:42:40 -0700182class SingletonAdmin (ReadOnlyAwareAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400183 def has_add_permission(self, request):
Scott Bakere8859f92014-05-23 12:42:40 -0700184 if not super(SingletonAdmin, self).has_add_permission(request):
185 return False
186
Siobhan Tullyce652d02013-10-08 21:52:35 -0400187 num_objects = self.model.objects.count()
188 if num_objects >= 1:
189 return False
190 else:
191 return True
192
Siobhan Tullyd3515752013-06-21 16:34:53 -0400193class PlStackTabularInline(admin.TabularInline):
Scott Baker86568322014-01-12 16:53:31 -0800194 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
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800201 self.setup_selflink()
202
Scott Baker874936e2014-01-13 18:15:34 -0800203 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)
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800206 try:
Scott Baker874936e2014-01-13 18:15:34 -0800207 url = reverse(reverse_path, args=(id,))
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800208 except NoReverseMatch:
Scott Baker874936e2014-01-13 18:15:34 -0800209 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):
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800230 return
231
Scott Baker874936e2014-01-13 18:15:34 -0800232 # Since we need to add "selflink" to the field list, we need to create
233 # self.fields if it is None.
Scott Baker0165fac2014-01-13 11:49:26 -0800234 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)
Scott Baker86568322014-01-12 16:53:31 -0800239
Scott Baker874936e2014-01-13 18:15:34 -0800240 self.fields = tuple(self.fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800241
Scott Baker874936e2014-01-13 18:15:34 -0800242 if self.readonly_fields is None:
243 self.readonly_fields = ()
Scott Baker86568322014-01-12 16:53:31 -0800244
Scott Baker874936e2014-01-13 18:15:34 -0800245 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800246
247 def selflink(self, obj):
Scott Baker874936e2014-01-13 18:15:34 -0800248 if hasattr(self, "selflink_fieldname"):
249 obj = getattr(obj, self.selflink_fieldname)
250
Scott Baker86568322014-01-12 16:53:31 -0800251 if obj.id:
Scott Baker874936e2014-01-13 18:15:34 -0800252 url = self.get_change_url(self.selflink_model, obj.id)
253 return "<a href='%s'>Details</a>" % str(url)
Scott Baker86568322014-01-12 16:53:31 -0800254 else:
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800255 return "Not present"
Scott Baker86568322014-01-12 16:53:31 -0800256
257 selflink.allow_tags = True
258 selflink.short_description = "Details"
Siobhan Tullyd3515752013-06-21 16:34:53 -0400259
Scott Bakerb27b62c2014-08-15 16:29:16 -0700260 def has_add_permission(self, request):
261 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500262
263 def get_readonly_fields(self, request, obj=None):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700264 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
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500270
Scott Baker40c00762014-08-21 16:55:59 -0700271 def backend_status_icon(self, obj):
272 return mark_safe(backend_icon(obj))
273 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -0700274
Scott Bakerb27b62c2014-08-15 16:29:16 -0700275class PlStackGenericTabularInline(generic.GenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500276 def has_add_permission(self, request):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700277 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500278
Scott Bakerb27b62c2014-08-15 16:29:16 -0700279 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
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500286
Scott Baker40c00762014-08-21 16:55:59 -0700287 def backend_status_icon(self, obj):
288 return mark_safe(backend_icon(obj))
289 backend_status_icon.short_description = ""
290
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400291class ReservationInline(PlStackTabularInline):
292 model = Reservation
293 extra = 0
294 suit_classes = 'suit-tab suit-tab-reservations'
Scott Baker36f50872014-08-21 13:01:25 -0700295
Tony Mack5b061472014-02-04 07:57:10 -0500296 def queryset(self, request):
297 return Reservation.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400298
Scott Bakerb27b62c2014-08-15 16:29:16 -0700299class TagInline(PlStackGenericTabularInline):
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400300 model = Tag
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400301 extra = 0
302 suit_classes = 'suit-tab suit-tab-tags'
Tony Mack5b061472014-02-04 07:57:10 -0500303 fields = ['service', 'name', 'value']
304
305 def queryset(self, request):
306 return Tag.select_by_user(request.user)
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400307
Scott Baker74d8e622013-07-29 16:04:22 -0700308class NetworkLookerUpper:
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400309 """ This is a callable that looks up a network name in a sliver and returns
310 the ip address for that network.
311 """
312
Scott Baker434ca7e2014-08-15 12:29:20 -0700313 byNetworkName = {} # class variable
314
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400315 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
Scott Baker74d8e622013-07-29 16:04:22 -0700325 return ""
326
327 def __str__(self):
328 return self.network_name
329
Scott Baker434ca7e2014-08-15 12:29:20 -0700330 @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
Siobhan Tullyd3515752013-06-21 16:34:53 -0400341class SliverInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400342 model = Sliver
Scott Baker9d856052015-01-19 11:32:20 -0800343 fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'deployment', 'flavor', 'image', 'node']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400344 extra = 0
Scott Baker9d856052015-01-19 11:32:20 -0800345 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name']
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400346 suit_classes = 'suit-tab suit-tab-slivers'
Scott Baker74d8e622013-07-29 16:04:22 -0700347
Tony Mack5b061472014-02-04 07:57:10 -0500348 def queryset(self, request):
349 return Sliver.select_by_user(request.user)
350
Scott Bakerb24cc932014-06-09 10:51:16 -0700351 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Tony Mackb2dba4b2014-12-26 13:38:02 -0500352 if db_field.name == 'deployment':
Scott Baker3b678742014-06-09 13:11:54 -0700353 kwargs['queryset'] = Deployment.select_by_acl(request.user)
Scott Baker7a61dc42014-09-02 17:08:20 -0700354 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
Tony Mackb2dba4b2014-12-26 13:38:02 -0500355 if db_field.name == 'flavor':
Scott Baker4b6d9442014-09-08 12:14:14 -0700356 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
Scott Baker3b678742014-06-09 13:11:54 -0700357
358 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700359
360 return field
361
Siobhan Tullyd3515752013-06-21 16:34:53 -0400362class SiteInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400363 model = Site
364 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400365 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400366
Tony Mack5b061472014-02-04 07:57:10 -0500367 def queryset(self, request):
368 return Site.select_by_user(request.user)
369
Siobhan Tullyd3515752013-06-21 16:34:53 -0400370class UserInline(PlStackTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400371 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700372 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
373 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400374 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400375 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400376
Tony Mack5b061472014-02-04 07:57:10 -0500377 def queryset(self, request):
378 return User.select_by_user(request.user)
379
Siobhan Tullyd3515752013-06-21 16:34:53 -0400380class SliceInline(PlStackTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400381 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700382 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
383 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400384 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400385 suit_classes = 'suit-tab suit-tab-slices'
386
Tony Mack5b061472014-02-04 07:57:10 -0500387 def queryset(self, request):
388 return Slice.select_by_user(request.user)
389
Siobhan Tullyd3515752013-06-21 16:34:53 -0400390class NodeInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400391 model = Node
392 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400393 suit_classes = 'suit-tab suit-tab-nodes'
Tony Mack68a1e422014-12-08 16:43:02 -0500394 fields = ['backend_status_icon', 'name', 'site_deployment']
Scott Baker40c00762014-08-21 16:55:59 -0700395 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400396
Tony Mack68a1e422014-12-08 16:43:02 -0500397class DeploymentPrivilegeInline(PlStackTabularInline):
398 model = DeploymentPrivilege
399 extra = 0
400 suit_classes = 'suit-tab suit-tab-admin-only'
401 fields = ['backend_status_icon', 'user','role','deployment']
402 readonly_fields = ('backend_status_icon', )
403
404 def queryset(self, request):
405 return DeploymentPrivilege.select_by_user(request.user)
406
Tony Mack3066a952015-01-05 22:48:11 -0500407class ControllerSiteInline(PlStackTabularInline):
408 model = ControllerSite
409 extra = 0
410 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Macke2363c12015-01-06 15:08:20 -0500411 fields = ['controller', 'site', 'tenant_id']
Tony Mack3066a952015-01-05 22:48:11 -0500412
413
Siobhan Tullyd3515752013-06-21 16:34:53 -0400414class SitePrivilegeInline(PlStackTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400415 model = SitePrivilege
416 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400417 suit_classes = 'suit-tab suit-tab-siteprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700418 fields = ['backend_status_icon', 'user','site', 'role']
419 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400420
Tony Mackc2835a92013-05-28 09:18:49 -0400421 def formfield_for_foreignkey(self, db_field, request, **kwargs):
422 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500423 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400424
425 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500426 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400427 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
428
Tony Mack5b061472014-02-04 07:57:10 -0500429 def queryset(self, request):
430 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400431
Tony Mack3066a952015-01-05 22:48:11 -0500432class SiteDeploymentInline(PlStackTabularInline):
433 model = SiteDeployment
Tony Macke4be32f2014-03-11 20:45:25 -0400434 extra = 0
Tony Mackb81d5e42015-01-30 10:58:29 -0500435 suit_classes = 'suit-tab suit-tab-sitedeployments'
Tony Mackd14d48f2014-12-05 17:13:08 -0500436 fields = ['backend_status_icon', 'deployment','site', 'controller']
Scott Baker40c00762014-08-21 16:55:59 -0700437 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400438
439 def formfield_for_foreignkey(self, db_field, request, **kwargs):
440 if db_field.name == 'site':
441 kwargs['queryset'] = Site.select_by_user(request.user)
442
443 if db_field.name == 'deployment':
444 kwargs['queryset'] = Deployment.select_by_user(request.user)
Tony Mackd14d48f2014-12-05 17:13:08 -0500445
446 if db_field.name == 'controller':
447 kwargs['queryset'] = Controller.select_by_user(request.user)
448
Tony Mack3066a952015-01-05 22:48:11 -0500449 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Macke4be32f2014-03-11 20:45:25 -0400450
451 def queryset(self, request):
Tony Mack3066a952015-01-05 22:48:11 -0500452 return SiteDeployment.select_by_user(request.user)
Tony Macke4be32f2014-03-11 20:45:25 -0400453
454
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400455class SlicePrivilegeInline(PlStackTabularInline):
456 model = SlicePrivilege
457 suit_classes = 'suit-tab suit-tab-sliceprivileges'
458 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700459 fields = ('backend_status_icon', 'user', 'slice', 'role')
460 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400461
Tony Mackc2835a92013-05-28 09:18:49 -0400462 def formfield_for_foreignkey(self, db_field, request, **kwargs):
463 if db_field.name == 'slice':
Scott Baker36f50872014-08-21 13:01:25 -0700464 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400465 if db_field.name == 'user':
Scott Baker36f50872014-08-21 13:01:25 -0700466 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400467
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400468 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400469
Tony Mack5b061472014-02-04 07:57:10 -0500470 def queryset(self, request):
471 return SlicePrivilege.select_by_user(request.user)
472
Scott Bakera0015eb2013-08-14 17:28:14 -0700473class SliceNetworkInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700474 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800475 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700476 extra = 0
477 verbose_name = "Network Connection"
478 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400479 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700480 fields = ['backend_status_icon', 'network']
481 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700482
Sapan Bhatia1b6bba22014-11-19 15:10:16 -0500483class ImageDeploymentsInline(PlStackTabularInline):
484 model = ImageDeployments
Scott Baker2170b972014-06-03 12:14:07 -0700485 extra = 0
486 verbose_name = "Image Deployments"
487 verbose_name_plural = "Image Deployments"
488 suit_classes = 'suit-tab suit-tab-imagedeployments'
Tony Mack06c8e472014-11-30 15:53:08 -0500489 fields = ['backend_status_icon', 'image', 'deployment']
490 readonly_fields = ['backend_status_icon']
491
492class ControllerImagesInline(PlStackTabularInline):
493 model = ControllerImages
494 extra = 0
495 verbose_name = "Controller Images"
496 verbose_name_plural = "Controller Images"
497 suit_classes = 'suit-tab suit-tab-admin-only'
498 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
Scott Baker40c00762014-08-21 16:55:59 -0700499 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700500
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400501class SliceRoleAdmin(PlanetStackBaseAdmin):
502 model = SliceRole
503 pass
504
505class SiteRoleAdmin(PlanetStackBaseAdmin):
506 model = SiteRole
507 pass
508
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400509class DeploymentAdminForm(forms.ModelForm):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400510 sites = forms.ModelMultipleChoiceField(
511 queryset=Site.objects.all(),
512 required=False,
Scott Baker70983182014-06-09 22:10:00 -0700513 help_text="Select which sites are allowed to host nodes in this deployment",
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400514 widget=FilteredSelectMultiple(
515 verbose_name=('Sites'), is_stacked=False
516 )
517 )
Scott Bakerde0f4412014-06-11 15:40:26 -0700518 images = forms.ModelMultipleChoiceField(
519 queryset=Image.objects.all(),
520 required=False,
521 help_text="Select which images should be deployed on this deployment",
522 widget=FilteredSelectMultiple(
523 verbose_name=('Images'), is_stacked=False
524 )
525 )
Scott Baker37b47902014-09-02 14:37:41 -0700526 flavors = forms.ModelMultipleChoiceField(
527 queryset=Flavor.objects.all(),
528 required=False,
529 help_text="Select which flavors should be usable on this deployment",
530 widget=FilteredSelectMultiple(
531 verbose_name=('Flavors'), is_stacked=False
532 )
533 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400534 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400535 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700536 many_to_many = ["flavors",]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400537
Siobhan Tully320b4622014-01-17 15:11:14 -0500538 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700539 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500540 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
541
Scott Baker5380c522014-06-06 14:49:43 -0700542 self.fields['accessControl'].initial = "allow site " + request.user.site.name
543
Siobhan Tully320b4622014-01-17 15:11:14 -0500544 if self.instance and self.instance.pk:
Scott Baker494d0db2015-01-20 17:59:55 -0800545 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
Scott Baker69e045d2014-11-17 23:44:03 -0800546 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
Scott Baker37b47902014-09-02 14:37:41 -0700547 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700548
549 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
550 """ helper function for handling m2m relations from the MultipleChoiceField
551
552 this_obj: the source object we want to link from
553
554 selected_objs: a list of destination objects we want to link to
555
556 all_relations: the full set of relations involving this_obj, including ones we don't want
557
558 relation_class: the class that implements the relation from source to dest
559
560 local_attrname: field name representing this_obj in relation_class
561
562 foreign_attrname: field name representing selected_objs in relation_class
563
564 This function will remove all newobjclass relations from this_obj
565 that are not contained in selected_objs, and add any relations that
566 are in selected_objs but don't exist in the data model yet.
567 """
568
569 existing_dest_objs = []
570 for relation in list(all_relations):
571 if getattr(relation, foreign_attrname) not in selected_objs:
572 #print "deleting site", sdp.site
573 relation.delete()
574 else:
575 existing_dest_objs.append(getattr(relation, foreign_attrname))
576
577 for dest_obj in selected_objs:
578 if dest_obj not in existing_dest_objs:
579 #print "adding site", site
580 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
581 relation = relation_class(**kwargs)
582 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500583
584 def save(self, commit=True):
585 deployment = super(DeploymentAdminForm, self).save(commit=False)
586
587 if commit:
588 deployment.save()
Scott Baker0057d052014-10-06 17:17:40 -0700589 # this has to be done after save() if/when a deployment is first created
590 deployment.flavors = self.cleaned_data['flavors']
Siobhan Tully320b4622014-01-17 15:11:14 -0500591
592 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700593 # save_m2m() doesn't seem to work with 'through' relations. So we
594 # create/destroy the through models ourselves. There has to be
595 # a better way...
596
Scott Bakeraa9a7052015-01-20 17:12:43 -0800597 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployment, "deployment", "site")
Tony Mackb2fde612014-12-15 11:45:02 -0500598 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
599 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
600 # so well handle that manually here
601 for flavor in deployment.flavors.all():
602 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
Tony Mackd4ab7822014-12-15 12:37:59 -0500603 deployment.flavors.remove(flavor)
Tony Mackb2fde612014-12-15 11:45:02 -0500604 for flavor in self.cleaned_data['flavors']:
605 if flavor not in deployment.flavors.all():
606 flavor.deployments.add(deployment)
Scott Bakerc9b14f72014-05-22 13:44:20 -0700607
Scott Baker37b47902014-09-02 14:37:41 -0700608 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500609
610 return deployment
611
Scott Bakerff5e0f32014-05-22 14:40:27 -0700612class DeploymentAdminROForm(DeploymentAdminForm):
613 def save(self, commit=True):
614 raise PermissionDenied
615
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500616class SiteAssocInline(PlStackTabularInline):
617 model = Site.deployments.through
618 extra = 0
619 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400620
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400621class DeploymentAdmin(PlanetStackBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500622 model = Deployment
Tony Mack68a1e422014-12-08 16:43:02 -0500623 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500624 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
Tony Mack68a1e422014-12-08 16:43:02 -0500625 # node no longer directly connected to deployment
626 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
Tony Mackb81d5e42015-01-30 10:58:29 -0500627 inlines = [DeploymentPrivilegeInline,TagInline,ImageDeploymentsInline,SiteDeploymentInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700628 list_display = ['backend_status_icon', 'name']
629 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700630 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500631
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500632 user_readonly_fields = ['name']
633
Tony Mack68a1e422014-12-08 16:43:02 -0500634 # nodes no longer direclty connected to deployments
635 #suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags'),('imagedeployments','Images'))
Tony Mackb81d5e42015-01-30 10:58:29 -0500636 suit_form_tabs =(('sites','Deployment Details'),('deploymentprivileges','Privileges'), ('sitedeployments', 'Site Deployments'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500637
Scott Bakerff5e0f32014-05-22 14:40:27 -0700638 def get_form(self, request, obj=None, **kwargs):
639 if request.user.isReadOnlyUser():
640 kwargs["form"] = DeploymentAdminROForm
641 else:
642 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700643 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
644
645 # from stackexchange: pass the request object into the form
646
647 class AdminFormMetaClass(adminForm):
648 def __new__(cls, *args, **kwargs):
649 kwargs['request'] = request
650 return adminForm(*args, **kwargs)
651
652 return AdminFormMetaClass
653
Tony Mackd14d48f2014-12-05 17:13:08 -0500654class ControllerAdminForm(forms.ModelForm):
Tony Mack74364932015-01-06 17:49:25 -0500655 sites = forms.ModelMultipleChoiceField(
656 queryset=Site.objects.all(),
Tony Mackd14d48f2014-12-05 17:13:08 -0500657 required=False,
Tony Mack74364932015-01-06 17:49:25 -0500658 help_text="Select which sites are managed by this controller",
Tony Mackd14d48f2014-12-05 17:13:08 -0500659 widget=FilteredSelectMultiple(
Tony Mack74364932015-01-06 17:49:25 -0500660 verbose_name=('Sites'), is_stacked=False
Tony Mackd14d48f2014-12-05 17:13:08 -0500661 )
662 )
663
664 class Meta:
665 model = Controller
666
Tony Mack68a1e422014-12-08 16:43:02 -0500667 def __init__(self, *args, **kwargs):
Tony Mackd14d48f2014-12-05 17:13:08 -0500668 request = kwargs.pop('request', None)
669 super(ControllerAdminForm, self).__init__(*args, **kwargs)
670
671 if self.instance and self.instance.pk:
Tony Mackde100182015-01-14 12:11:05 -0500672 self.fields['sites'].initial = [x.site for x in self.instance.controllersite.all()]
Tony Mackd14d48f2014-12-05 17:13:08 -0500673
Tony Mack68a1e422014-12-08 16:43:02 -0500674 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
Tony Mackd14d48f2014-12-05 17:13:08 -0500675 """ helper function for handling m2m relations from the MultipleChoiceField
676 this_obj: the source object we want to link from
677 selected_objs: a list of destination objects we want to link to
678 all_relations: the full set of relations involving this_obj, including ones we don't want
679 relation_class: the class that implements the relation from source to dest
680 local_attrname: field name representing this_obj in relation_class
681 foreign_attrname: field name representing selected_objs in relation_class
682 This function will remove all newobjclass relations from this_obj
683 that are not contained in selected_objs, and add any relations that
684 are in selected_objs but don't exist in the data model yet.
685 """
686 existing_dest_objs = []
687 for relation in list(all_relations):
688 if getattr(relation, foreign_attrname) not in selected_objs:
689 #print "deleting site", sdp.site
690 relation.delete()
691 else:
692 existing_dest_objs.append(getattr(relation, foreign_attrname))
693
694 for dest_obj in selected_objs:
695 if dest_obj not in existing_dest_objs:
696 #print "adding site", site
697 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
698 relation = relation_class(**kwargs)
699 relation.save()
700
Tony Mack68a1e422014-12-08 16:43:02 -0500701 def save(self, commit=True):
702 controller = super(ControllerAdminForm, self).save(commit=False)
703 if commit:
704 controller.save()
Tony Mackd14d48f2014-12-05 17:13:08 -0500705
Tony Mack68a1e422014-12-08 16:43:02 -0500706 if controller.pk:
707 # save_m2m() doesn't seem to work with 'through' relations. So we
708 # create/destroy the through models ourselves. There has to be
709 # a better way...
Tony Mack74364932015-01-06 17:49:25 -0500710 self.manipulate_m2m_objs(controller, self.cleaned_data['sites'], controller.controllersite.all(), ControllerSite, "controller", "site")
Tony Mack4f692d92015-01-03 17:40:42 -0500711 pass
712
Tony Mack68a1e422014-12-08 16:43:02 -0500713 self.save_m2m()
Tony Mackd14d48f2014-12-05 17:13:08 -0500714
Tony Mack68a1e422014-12-08 16:43:02 -0500715 return controller
Tony Mackd14d48f2014-12-05 17:13:08 -0500716
717class ControllerAdmin(PlanetStackBaseAdmin):
718 model = Controller
Tony Mackad2208f2015-01-19 15:05:42 -0500719 fieldList = ['name', 'backend_type', 'version', 'auth_url', 'admin_user', 'admin_tenant','admin_password']
Tony Mack68a1e422014-12-08 16:43:02 -0500720 #fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Mack3066a952015-01-05 22:48:11 -0500721 inlines = [ControllerSiteInline] # ,ControllerImagesInline]
Tony Mackd14d48f2014-12-05 17:13:08 -0500722 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
723 list_display_links = ('backend_status_icon', 'name', )
724 readonly_fields = ('backend_status_text',)
725
726 user_readonly_fields = []
727
728 def get_form(self, request, obj=None, **kwargs):
Tony Mack68a1e422014-12-08 16:43:02 -0500729 print self.fieldsets
Tony Mackd14d48f2014-12-05 17:13:08 -0500730 if request.user.isReadOnlyUser():
731 kwargs["form"] = ControllerAdminROForm
732 else:
733 kwargs["form"] = ControllerAdminForm
Tony Mack68a1e422014-12-08 16:43:02 -0500734 adminForm = super(ControllerAdmin,self).get_form(request, obj, **kwargs)
Tony Mackd14d48f2014-12-05 17:13:08 -0500735
736 # from stackexchange: pass the request object into the form
737
738 class AdminFormMetaClass(adminForm):
739 def __new__(cls, *args, **kwargs):
740 kwargs['request'] = request
741 return adminForm(*args, **kwargs)
742
743 return AdminFormMetaClass
744
Tony Mack2e897fa2015-01-13 17:33:08 -0500745 def save_model(self, request, obj, form, change):
746 # update openstack connection to use this site/tenant
747 obj.save_by_user(request.user)
748
749 def delete_model(self, request, obj):
750 obj.delete_by_user(request.user)
751
Siobhan Tullyce652d02013-10-08 21:52:35 -0400752class ServiceAttrAsTabInline(PlStackTabularInline):
753 model = ServiceAttribute
754 fields = ['name','value']
755 extra = 0
756 suit_classes = 'suit-tab suit-tab-serviceattrs'
757
Siobhan Tullyce652d02013-10-08 21:52:35 -0400758class ServiceAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700759 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
760 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700761 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500762 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
763 inlines = [ServiceAttrAsTabInline,SliceInline]
Scott Baker40c00762014-08-21 16:55:59 -0700764 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500765
766 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500767
768 suit_form_tabs =(('general', 'Service Details'),
769 ('slices','Slices'),
770 ('serviceattrs','Additional Attributes'),
771 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400772
Tony Mack4f134e62015-01-14 20:58:38 -0500773class SiteNodeInline(PlStackTabularInline):
774 model = Node
775 fields = ['name', 'site_deployment']
776 extra = 0
777 suit_classes = 'suit-tab suit-tab-nodes'
778
Tony Mack0553f282013-06-10 22:54:50 -0400779class SiteAdmin(PlanetStackBaseAdmin):
Tony Mack450b6e02015-01-25 12:35:29 -0500780 #fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
781 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'location']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400782 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500783 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400784 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400785 ]
Tony Mack450b6e02015-01-25 12:35:29 -0500786 #readonly_fields = ['backend_status_text', 'accountLink']
787 readonly_fields = ['backend_status_text']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500788
Tony Mack450b6e02015-01-25 12:35:29 -0500789 #user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
790 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500791
Scott Baker63d1a552014-08-21 15:19:07 -0700792 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
793 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400794 filter_horizontal = ('deployments',)
Tony Mackb81d5e42015-01-30 10:58:29 -0500795 inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteNodeInline]
Tony Mackde100182015-01-14 12:11:05 -0500796 admin_inlines = [ControllerSiteInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400797 search_fields = ['name']
798
Tony Mack30dfcd72015-01-10 23:08:10 -0500799 @property
800 def suit_form_tabs(self):
801 tabs = [('general', 'Site Details'),
802 ('users','Users'),
803 ('siteprivileges','Privileges'),
Tony Mack30dfcd72015-01-10 23:08:10 -0500804 ('slices','Slices'),
Tony Mack4f134e62015-01-14 20:58:38 -0500805 ('nodes','Nodes'),
Tony Mack30dfcd72015-01-10 23:08:10 -0500806 ]
807
808 request=getattr(_thread_locals, "request", None)
809 if request and request.user.is_admin:
810 tabs.append( ('admin-only', 'Admin-Only') )
811
812 return tabs
813
Tony Mack04062832013-05-10 08:22:44 -0400814 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500815 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400816
Tony Mack5cd13202013-05-01 21:48:38 -0400817 def get_formsets(self, request, obj=None):
818 for inline in self.get_inline_instances(request, obj):
819 # hide MyInline in the add view
820 if obj is None:
821 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400822 if isinstance(inline, SliverInline):
823 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -0400824 yield inline.get_formset(request, obj)
825
Scott Baker545db2a2013-12-09 18:44:43 -0800826 def accountLink(self, obj):
827 link_obj = obj.accounts.all()
828 if link_obj:
829 reverse_path = "admin:core_account_change"
830 url = reverse(reverse_path, args =(link_obj[0].id,))
831 return "<a href='%s'>%s</a>" % (url, "view billing details")
832 else:
833 return "no billing data for this site"
834 accountLink.allow_tags = True
835 accountLink.short_description = "Billing"
836
Tony Mack332ee1d2014-02-04 15:33:45 -0500837 def save_model(self, request, obj, form, change):
838 # update openstack connection to use this site/tenant
839 obj.save_by_user(request.user)
840
841 def delete_model(self, request, obj):
842 obj.delete_by_user(request.user)
843
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500844
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400845class SitePrivilegeAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700846 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -0400847 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500848 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -0400849 ]
Scott Baker40c00762014-08-21 16:55:59 -0700850 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700851 list_display = ('backend_status_icon', 'user', 'site', 'role')
852 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500853 user_readonly_fields = fieldList
854 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -0400855
Tony Mackc2835a92013-05-28 09:18:49 -0400856 def formfield_for_foreignkey(self, db_field, request, **kwargs):
857 if db_field.name == 'site':
858 if not request.user.is_admin:
859 # only show sites where user is an admin or pi
860 sites = set()
861 for site_privilege in SitePrivilege.objects.filer(user=request.user):
862 if site_privilege.role.role_type in ['admin', 'pi']:
863 sites.add(site_privilege.site)
864 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
865
866 if db_field.name == 'user':
867 if not request.user.is_admin:
868 # only show users from sites where caller has admin or pi role
869 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
870 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
871 sites = [site_privilege.site for site_privilege in site_privileges]
872 site_privileges = SitePrivilege.objects.filter(site__in=sites)
873 emails = [site_privilege.user.email for site_privilege in site_privileges]
874 users = User.objects.filter(email__in=emails)
875 kwargs['queryset'] = users
876
877 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
878
Tony Mack04062832013-05-10 08:22:44 -0400879 def queryset(self, request):
880 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -0400881 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -0400882 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -0500883 #if not request.user.is_admin:
884 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
885 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
886 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
887 # sites = Site.objects.filter(login_base__in=login_bases)
888 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -0400889 return qs
890
Siobhan Tullyce652d02013-10-08 21:52:35 -0400891class SliceForm(forms.ModelForm):
892 class Meta:
893 model = Slice
894 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -0700895 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -0400896 }
897
Tony Macke75841e2014-09-29 16:10:52 -0400898 def clean(self):
899 cleaned_data = super(SliceForm, self).clean()
900 name = cleaned_data.get('name')
Scott Baker3cb382c2014-10-06 23:09:59 -0700901 site = cleaned_data.get('site')
Tony Mackcc9e2592014-10-22 12:54:19 -0400902 slice_id = self.instance.id
903 if not site and slice_id:
904 site = Slice.objects.get(id=slice_id).site
Scott Baker3cb382c2014-10-06 23:09:59 -0700905 if (not isinstance(site,Site)):
906 # previous code indicates 'site' could be a site_id and not a site?
907 site = Slice.objects.get(id=site.id)
Tony Macke75841e2014-09-29 16:10:52 -0400908 if not name.startswith(site.login_base):
909 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
910 return cleaned_data
911
Tony Mack3066a952015-01-05 22:48:11 -0500912class ControllerSliceInline(PlStackTabularInline):
913 model = ControllerSlice
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700914 extra = 0
Tony Mack06c8e472014-11-30 15:53:08 -0500915 verbose_name = "Controller Slices"
916 verbose_name_plural = "Controller Slices"
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700917 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack06c8e472014-11-30 15:53:08 -0500918 fields = ['backend_status_icon', 'controller', 'tenant_id']
Tony Mack30dfcd72015-01-10 23:08:10 -0500919 readonly_fields = ('backend_status_icon', 'controller' )
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700920
Tony Mack2bd5b412013-06-11 21:05:06 -0400921class SliceAdmin(PlanetStackBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400922 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -0400923 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500924 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -0700925 readonly_fields = ('backend_status_text', )
Tony Mack7d459902014-09-03 13:18:57 -0400926 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
927 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully2d95e482013-09-06 10:56:06 -0400928 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
Tony Mack3066a952015-01-05 22:48:11 -0500929 admin_inlines = [ControllerSliceInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400930
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500931 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400932
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700933 @property
934 def suit_form_tabs(self):
935 tabs =[('general', 'Slice Details'),
936 ('slicenetworks','Networks'),
937 ('sliceprivileges','Privileges'),
938 ('slivers','Slivers'),
Tony Mack450b6e02015-01-25 12:35:29 -0500939 #('reservations','Reservations'),
Tony Mackd2433382015-01-15 14:44:06 -0500940 ('tags','Tags'),
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700941 ]
942
943 request=getattr(_thread_locals, "request", None)
944 if request and request.user.is_admin:
945 tabs.append( ('admin-only', 'Admin-Only') )
946
947 return tabs
Tony Mack0aa732a2014-10-22 11:54:29 -0400948
949 def add_view(self, request, form_url='', extra_context=None):
950 # revert to default read-only fields
951 self.readonly_fields = ('backend_status_text',)
952 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
953
954 def change_view(self, request, object_id, form_url='', extra_context=None):
Tony Mack0aa732a2014-10-22 11:54:29 -0400955 # cannot change the site of an existing slice so make the site field read only
956 if object_id:
957 self.readonly_fields = ('backend_status_text','site')
958 return super(SliceAdmin, self).change_view(request, object_id, form_url)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400959
Scott Baker510fdbb2014-08-05 17:19:24 -0700960 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -0700961 deployment_nodes = []
962 for node in Node.objects.all():
Scott Baker66b11e22015-01-21 16:24:07 -0800963 deployment_nodes.append( (node.site_deployment.deployment.id, node.id, node.name) )
Scott Baker510fdbb2014-08-05 17:19:24 -0700964
Scott Baker7a61dc42014-09-02 17:08:20 -0700965 deployment_flavors = []
966 for flavor in Flavor.objects.all():
967 for deployment in flavor.deployments.all():
968 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
969
Tony Mack68a1e422014-12-08 16:43:02 -0500970 deployment_images = []
Scott Bakeraf36c4d2014-09-09 09:58:49 -0700971 for image in Image.objects.all():
Tony Mack68a1e422014-12-08 16:43:02 -0500972 for deployment_image in image.imagedeployments.all():
Scott Bakerf2c0c512014-12-22 17:35:34 -0800973 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
Scott Bakeraf36c4d2014-09-09 09:58:49 -0700974
Tony Mackec23b992014-09-02 21:18:45 -0400975 site_login_bases = []
976 for site in Site.objects.all():
Scott Bakeraf36c4d2014-09-09 09:58:49 -0700977 site_login_bases.append((site.id, site.login_base))
978
Scott Baker510fdbb2014-08-05 17:19:24 -0700979 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -0700980 context["deployment_flavors"] = deployment_flavors
Scott Bakeraf36c4d2014-09-09 09:58:49 -0700981 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -0400982 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -0700983 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
984
Tony Mackc2835a92013-05-28 09:18:49 -0400985 def formfield_for_foreignkey(self, db_field, request, **kwargs):
986 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500987 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackec23b992014-09-02 21:18:45 -0400988 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
Scott Baker40c00762014-08-21 16:55:59 -0700989
Tony Mackc2835a92013-05-28 09:18:49 -0400990 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
991
Tony Mack04062832013-05-10 08:22:44 -0400992 def queryset(self, request):
993 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500994 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400995
Tony Mack79748612013-05-01 14:52:03 -0400996 def get_formsets(self, request, obj=None):
997 for inline in self.get_inline_instances(request, obj):
998 # hide MyInline in the add view
999 if obj is None:
1000 continue
Tony Mack2bd5b412013-06-11 21:05:06 -04001001 if isinstance(inline, SliverInline):
1002 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -04001003 yield inline.get_formset(request, obj)
1004
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001005class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -04001006 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001007 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -04001008 ]
Scott Baker40c00762014-08-21 16:55:59 -07001009 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -07001010 list_display = ('backend_status_icon', 'user', 'slice', 'role')
1011 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -04001012
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001013 user_readonly_fields = ['user', 'slice', 'role']
1014 user_readonly_inlines = []
1015
Tony Mackc2835a92013-05-28 09:18:49 -04001016 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1017 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001018 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001019
1020 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -05001021 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001022
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001023 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -04001024
Tony Mack04062832013-05-10 08:22:44 -04001025 def queryset(self, request):
1026 # admins can see all memberships. Users can only see memberships of
1027 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -05001028 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001029
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001030 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -04001031 # update openstack connection to use this site/tenant
1032 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -04001033 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -04001034 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001035 obj.save()
1036
1037 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -04001038 # update openstack connection to use this site/tenant
1039 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -04001040 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -04001041 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001042 obj.delete()
1043
Siobhan Tully567e3e62013-06-21 18:03:16 -04001044
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001045class ImageAdmin(PlanetStackBaseAdmin):
1046
Scott Baker36f50872014-08-21 13:01:25 -07001047 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -07001048 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001049 'classes': ['suit-tab suit-tab-general']})
1050 ]
Scott Baker40c00762014-08-21 16:55:59 -07001051 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001052
Tony Mack06c8e472014-11-30 15:53:08 -05001053 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001054
Tony Mack06c8e472014-11-30 15:53:08 -05001055 inlines = [SliverInline, ControllerImagesInline]
Scott Bakerb6f99242014-06-11 11:34:44 -07001056
Tony Mack32e1ce32014-05-07 13:29:41 -04001057 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -07001058
Scott Baker63d1a552014-08-21 15:19:07 -07001059 list_display = ['backend_status_icon', 'name']
1060 list_display_links = ('backend_status_icon', 'name', )
1061
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001062class NodeForm(forms.ModelForm):
1063 class Meta:
1064 widgets = {
1065 'site': LinkedSelect,
1066 'deployment': LinkedSelect
1067 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001068
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001069class NodeAdmin(PlanetStackBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001070 form = NodeForm
Tony Mack68a1e422014-12-08 16:43:02 -05001071 list_display = ('backend_status_icon', 'name', 'site_deployment')
Scott Baker63d1a552014-08-21 15:19:07 -07001072 list_display_links = ('backend_status_icon', 'name', )
Tony Mack68a1e422014-12-08 16:43:02 -05001073 list_filter = ('site_deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001074
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001075 inlines = [TagInline,SliverInline]
Tony Mack68a1e422014-12-08 16:43:02 -05001076 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site_deployment'], 'classes':['suit-tab suit-tab-details']})]
Scott Baker40c00762014-08-21 16:55:59 -07001077 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001078
Tony Mack68a1e422014-12-08 16:43:02 -05001079 user_readonly_fields = ['name','site_deployment']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001080 user_readonly_inlines = [TagInline,SliverInline]
1081
Tony Mack5fecf712015-01-12 21:40:09 -05001082 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001083
Siobhan Tully567e3e62013-06-21 18:03:16 -04001084
Tony Mackd90cdbf2013-04-16 22:48:40 -04001085class SliverForm(forms.ModelForm):
1086 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -04001087 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -04001088 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -04001089 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -04001090 widgets = {
1091 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -04001092 'instance_name': PlainTextWidget(),
Scott Baker9d856052015-01-19 11:32:20 -08001093 'instance_id': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001094 'slice': LinkedSelect,
Tony Mackb2dba4b2014-12-26 13:38:02 -05001095 'deployment': LinkedSelect,
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001096 'node': LinkedSelect,
1097 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -04001098 }
Tony Mackd90cdbf2013-04-16 22:48:40 -04001099
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001100class TagAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001101 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1102 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001103 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1104 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -04001105
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001106class SliverAdmin(PlanetStackBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -04001107 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -04001108 fieldsets = [
Tony Mack707f7d72015-01-30 12:52:46 -05001109 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'node', 'all_ips_string', 'instance_id', 'instance_name', 'flavor', 'image', 'ssh_command'], 'classes': ['suit-tab suit-tab-general'], })
Tony Mackcdec0902013-04-15 00:38:49 -04001110 ]
Scott Baker7ccc6ad2015-01-25 22:16:13 -08001111 readonly_fields = ('backend_status_text', 'ssh_command', )
Tony Mack707f7d72015-01-30 12:52:46 -05001112 list_display = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deployment']
1113 list_display_links = ('backend_status_icon', 'all_ips_string',)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001114
Scott Bakerc72997a2015-01-19 08:24:08 -08001115 suit_form_tabs =(('general', 'Sliver Details'),)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001116
Siobhan Tullyde5450d2013-06-21 11:35:33 -04001117 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -04001118
Tony Mackb2dba4b2014-12-26 13:38:02 -05001119 user_readonly_fields = ['slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001120
Scott Baker7ccc6ad2015-01-25 22:16:13 -08001121 def ssh_command(self, obj):
1122 ssh_command = obj.get_ssh_command()
1123 if ssh_command:
1124 return ssh_command
1125 else:
1126 return "(not available)"
1127
Tony Mackc2835a92013-05-28 09:18:49 -04001128 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1129 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001130 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001131
1132 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1133
Tony Mack04062832013-05-10 08:22:44 -04001134 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -07001135 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -04001136 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -05001137 return Sliver.select_by_user(request.user)
1138
Tony Mack04062832013-05-10 08:22:44 -04001139
Tony Mack1d6b85f2013-05-07 18:49:14 -04001140 def get_formsets(self, request, obj=None):
1141 # make some fields read only if we are updating an existing record
1142 if obj == None:
Scott Baker7ccc6ad2015-01-25 22:16:13 -08001143 self.readonly_fields = ('backend_status_text', 'ssh_command', )
Tony Mack1d6b85f2013-05-07 18:49:14 -04001144 else:
Scott Baker7ccc6ad2015-01-25 22:16:13 -08001145 self.readonly_fields = ('backend_status_text', 'ssh_command',)
Tony Mack1d6b85f2013-05-07 18:49:14 -04001146
1147 for inline in self.get_inline_instances(request, obj):
1148 # hide MyInline in the add view
1149 if obj is None:
1150 continue
Scott Baker526b71e2014-05-13 13:18:01 -07001151 if isinstance(inline, SliverInline):
1152 inline.model.caller = request.user
1153 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -04001154
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001155 #def save_model(self, request, obj, form, change):
1156 # # update openstack connection to use this site/tenant
1157 # auth = request.session.get('auth', {})
1158 # auth['tenant'] = obj.slice.name
1159 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1160 # obj.creator = request.user
1161 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -04001162
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001163 #def delete_model(self, request, obj):
1164 # # update openstack connection to use this site/tenant
1165 # auth = request.session.get('auth', {})
1166 # auth['tenant'] = obj.slice.name
1167 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1168 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -04001169
Siobhan Tully53437282013-04-26 19:30:27 -04001170class UserCreationForm(forms.ModelForm):
1171 """A form for creating new users. Includes all the required
1172 fields, plus a repeated password."""
1173 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1174 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1175
1176 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001177 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001178 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -04001179
1180 def clean_password2(self):
1181 # Check that the two password entries match
1182 password1 = self.cleaned_data.get("password1")
1183 password2 = self.cleaned_data.get("password2")
1184 if password1 and password2 and password1 != password2:
1185 raise forms.ValidationError("Passwords don't match")
1186 return password2
1187
1188 def save(self, commit=True):
1189 # Save the provided password in hashed format
1190 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -04001191 user.password = self.cleaned_data["password1"]
1192 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -04001193 if commit:
1194 user.save()
1195 return user
1196
Siobhan Tully567e3e62013-06-21 18:03:16 -04001197
Siobhan Tully53437282013-04-26 19:30:27 -04001198class UserChangeForm(forms.ModelForm):
1199 """A form for updating users. Includes all the fields on
1200 the user, but replaces the password field with admin's
1201 password hash display field.
1202 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -05001203 password = ReadOnlyPasswordHashField(label='Password',
1204 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -04001205
1206 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001207 model = User
Scott Baker69e045d2014-11-17 23:44:03 -08001208 widgets = { 'public_key': UploadTextareaWidget, }
Siobhan Tully53437282013-04-26 19:30:27 -04001209
1210 def clean_password(self):
1211 # Regardless of what the user provides, return the initial value.
1212 # This is done here, rather than on the field, because the
1213 # field does not have access to the initial value
1214 return self.initial["password"]
1215
Scott Baker2c3cb642014-05-19 17:55:56 -07001216class UserDashboardViewInline(PlStackTabularInline):
1217 model = UserDashboardView
1218 extra = 0
1219 suit_classes = 'suit-tab suit-tab-dashboards'
1220 fields = ['user', 'dashboardView', 'order']
1221
Tony Mack30dfcd72015-01-10 23:08:10 -05001222class ControllerUserInline(PlStackTabularInline):
1223 model = ControllerUser
1224 extra = 0
1225 suit_classes = 'suit-tab suit-tab-admin-only'
1226 fields = ['controller', 'user', 'kuser_id']
1227 readonly_fields=['controller']
1228
1229
Scott Bakerf4aeedc2014-10-03 13:10:47 -07001230class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1231 # Note: Make sure PermissionCheckingAdminMixin is listed before
1232 # admin.ModelAdmin in the class declaration.
1233
Siobhan Tully53437282013-04-26 19:30:27 -04001234 class Meta:
1235 app_label = "core"
1236
1237 # The forms to add and change user instances
1238 form = UserChangeForm
1239 add_form = UserCreationForm
1240
1241 # The fields to be used in displaying the User model.
1242 # These override the definitions on the base UserAdmin
1243 # that reference specific fields on auth.User.
Scott Bakera111f442015-01-24 13:33:26 -08001244 list_display = ('backend_status_icon', 'email', 'firstname', 'lastname', 'site', 'last_login')
1245 list_display_links = ("email",)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001246 list_filter = ('site',)
Scott Baker90472612015-01-29 10:55:53 -08001247 inlines = [SlicePrivilegeInline,SitePrivilegeInline]
Tony Mack30dfcd72015-01-10 23:08:10 -05001248 admin_inlines = [ControllerUserInline]
Scott Baker1a6a3902014-10-03 00:32:37 -07001249 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001250 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1251
Siobhan Tully53437282013-04-26 19:30:27 -04001252 fieldsets = (
Scott Baker40c00762014-08-21 16:55:59 -07001253 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001254 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001255 #('Important dates', {'fields': ('last_login',)}),
1256 )
1257 add_fieldsets = (
1258 (None, {
1259 'classes': ('wide',),
Tony Mack365545d2015-01-28 12:03:15 -05001260 'fields': ('site', 'email', 'firstname', 'lastname', 'is_admin', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
Siobhan Tully53437282013-04-26 19:30:27 -04001261 ),
1262 )
Scott Baker40c00762014-08-21 16:55:59 -07001263 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001264 search_fields = ('email',)
1265 ordering = ('email',)
1266 filter_horizontal = ()
1267
Scott Baker3ca51f62014-05-23 12:05:11 -07001268 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001269
Scott Baker6a995352014-10-06 17:51:20 -07001270 @property
1271 def suit_form_tabs(self):
1272 if getattr(_thread_locals, "obj", None) is None:
1273 return []
1274 else:
Tony Mack30dfcd72015-01-10 23:08:10 -05001275 tabs = [('general','Login Details'),
Scott Baker6a995352014-10-06 17:51:20 -07001276 ('contact','Contact Information'),
1277 ('sliceprivileges','Slice Privileges'),
Scott Baker90472612015-01-29 10:55:53 -08001278 ('siteprivileges','Site Privileges')]
Tony Mack30dfcd72015-01-10 23:08:10 -05001279
1280 request=getattr(_thread_locals, "request", None)
1281 if request and request.user.is_admin:
1282 tabs.append( ('admin-only', 'Admin-Only') )
1283
1284 return tabs
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001285
Tony Mackc2835a92013-05-28 09:18:49 -04001286 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1287 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001288 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001289
1290 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1291
Tony Mack5b061472014-02-04 07:57:10 -05001292 def queryset(self, request):
1293 return User.select_by_user(request.user)
1294
Tony Mackc8f443d2015-01-25 21:58:30 -05001295 def get_form(self, request, obj=None, **kwargs):
Tony Mack03b92292015-01-28 12:37:12 -05001296 # copy login details list
1297 login_details_fields = list(self.fieldListLoginDetails)
Tony Mack933b2912015-01-28 12:49:58 -05001298 if not request.user.is_admin:
Scott Baker90472612015-01-29 10:55:53 -08001299 # only admins can see 'is_admin' and 'is_readonly' fields
Tony Mack03b92292015-01-28 12:37:12 -05001300 if 'is_admin' in login_details_fields:
1301 login_details_fields.remove('is_admin')
1302 if 'is_readonly' in login_details_fields:
1303 login_details_fields.remove('is_readonly')
Tony Mack933b2912015-01-28 12:49:58 -05001304 #if len(request.user.siteprivileges.filter(role__role = 'pi')) > 0:
Tony Mack03b92292015-01-28 12:37:12 -05001305 # only admins and pis can change a user's site
Tony Mack933b2912015-01-28 12:49:58 -05001306 # self.readonly_fields = ('backend_status_text', 'site')
Tony Mack03b92292015-01-28 12:37:12 -05001307 self.fieldsets = (
1308 ('Login Details', {'fields': login_details_fields, 'classes':['suit-tab suit-tab-general']}),
1309 ('Contact Information', {'fields': self.fieldListContactInfo, 'classes':['suit-tab suit-tab-contact']}),
1310 )
Tony Mackc8f443d2015-01-25 21:58:30 -05001311 return super(UserAdmin, self).get_form(request, obj, **kwargs)
1312
Scott Bakerf2c0c512014-12-22 17:35:34 -08001313class ControllerDashboardViewInline(PlStackTabularInline):
1314 model = ControllerDashboardView
Scott Baker786a9c12014-12-19 16:41:12 -08001315 extra = 0
1316 fields = ["controller", "url"]
1317 suit_classes = 'suit-tab suit-tab-controllers'
1318
Scott Baker2c3cb642014-05-19 17:55:56 -07001319class DashboardViewAdmin(PlanetStackBaseAdmin):
1320 fieldsets = [('Dashboard View Details',
Scott Bakerc15ad1b2015-01-18 16:33:30 -08001321 {'fields': ['backend_status_text', 'name', 'url', 'enabled'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001322 'classes': ['suit-tab suit-tab-general']})
1323 ]
Scott Baker2c44e6e2015-01-18 16:46:26 -08001324 list_display = ["name", "enabled", "url"]
Scott Baker40c00762014-08-21 16:55:59 -07001325 readonly_fields = ('backend_status_text', )
Scott Bakerf2c0c512014-12-22 17:35:34 -08001326 inlines = [ControllerDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001327
Scott Baker786a9c12014-12-19 16:41:12 -08001328 suit_form_tabs =(('general','Dashboard View Details'),
1329 ('controllers', 'Per-controller Dashboard Details'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001330
Scott Baker0165fac2014-01-13 11:49:26 -08001331class ServiceResourceInline(PlStackTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001332 model = ServiceResource
1333 extra = 0
1334
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001335class ServiceClassAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001336 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1337 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001338 inlines = [ServiceResourceInline]
1339
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001340 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1341 user_readonly_inlines = []
1342
Scott Baker0165fac2014-01-13 11:49:26 -08001343class ReservedResourceInline(PlStackTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001344 model = ReservedResource
1345 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001346 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001347
1348 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1349 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1350
1351 if db_field.name == 'resource':
1352 # restrict resources to those that the slice's service class allows
1353 if request._slice is not None:
1354 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1355 if len(field.queryset) > 0:
1356 field.initial = field.queryset.all()[0]
1357 else:
1358 field.queryset = field.queryset.none()
1359 elif db_field.name == 'sliver':
1360 # restrict slivers to those that belong to the slice
1361 if request._slice is not None:
1362 field.queryset = field.queryset.filter(slice = request._slice)
1363 else:
1364 field.queryset = field.queryset.none()
1365
1366 return field
1367
Tony Mack5b061472014-02-04 07:57:10 -05001368 def queryset(self, request):
1369 return ReservedResource.select_by_user(request.user)
1370
Scott Baker133c9212013-05-17 09:09:11 -07001371class ReservationChangeForm(forms.ModelForm):
1372 class Meta:
1373 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001374 widgets = {
1375 'slice' : LinkedSelect
1376 }
Scott Baker133c9212013-05-17 09:09:11 -07001377
1378class ReservationAddForm(forms.ModelForm):
1379 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1380 refresh = forms.CharField(widget=forms.HiddenInput())
1381
1382 class Media:
1383 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1384
1385 def clean_slice(self):
1386 slice = self.cleaned_data.get("slice")
1387 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1388 if len(x) == 0:
1389 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1390 return slice
1391
1392 class Meta:
1393 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001394 widgets = {
1395 'slice' : LinkedSelect
1396 }
1397
Scott Baker133c9212013-05-17 09:09:11 -07001398
1399class ReservationAddRefreshForm(ReservationAddForm):
1400 """ This form is displayed when the Reservation Form receives an update
1401 from the Slice dropdown onChange handler. It doesn't validate the
1402 data and doesn't save the data. This will cause the form to be
1403 redrawn.
1404 """
1405
Scott Baker8737e5f2013-05-17 09:35:32 -07001406 """ don't validate anything other than slice """
1407 dont_validate_fields = ("startTime", "duration")
1408
Scott Baker133c9212013-05-17 09:09:11 -07001409 def full_clean(self):
1410 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001411
1412 for fieldname in self.dont_validate_fields:
1413 if fieldname in self._errors:
1414 del self._errors[fieldname]
1415
Scott Baker133c9212013-05-17 09:09:11 -07001416 return result
1417
1418 """ don't save anything """
1419 def is_valid(self):
1420 return False
1421
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001422class ReservationAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001423 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001424 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001425 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001426 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001427 form = ReservationAddForm
1428
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001429 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1430
1431 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001432 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001433
Scott Baker133c9212013-05-17 09:09:11 -07001434 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001435 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001436 request._refresh = False
1437 request._slice = None
1438 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001439 # "refresh" will be set to "1" if the form was submitted due to
1440 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001441 if request.POST.get("refresh","1") == "1":
1442 request._refresh = True
1443 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001444
1445 # Keep track of the slice that was selected, so the
1446 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001447 request._slice = request.POST.get("slice",None)
1448 if (request._slice is not None):
1449 request._slice = Slice.objects.get(id=request._slice)
1450
1451 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1452 return result
1453
Scott Bakeracd45142013-05-19 16:19:16 -07001454 def changelist_view(self, request, extra_context = None):
1455 timezone.activate(request.user.timezone)
1456 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1457
Scott Baker133c9212013-05-17 09:09:11 -07001458 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001459 request._obj_ = obj
1460 if obj is not None:
1461 # For changes, set request._slice to the slice already set in the
1462 # object.
1463 request._slice = obj.slice
1464 self.form = ReservationChangeForm
1465 else:
1466 if getattr(request, "_refresh", False):
1467 self.form = ReservationAddRefreshForm
1468 else:
1469 self.form = ReservationAddForm
1470 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1471
Scott Baker133c9212013-05-17 09:09:11 -07001472 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001473 if (obj is not None):
1474 # Prevent slice from being changed after the reservation has been
1475 # created.
1476 return ['slice']
1477 else:
Scott Baker133c9212013-05-17 09:09:11 -07001478 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001479
Tony Mack5b061472014-02-04 07:57:10 -05001480 def queryset(self, request):
1481 return Reservation.select_by_user(request.user)
1482
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001483class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001484 list_display = ("backend_status_icon", "name", )
1485 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001486 user_readonly_fields = ['name']
1487 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001488
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001489class RouterAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001490 list_display = ("backend_status_icon", "name", )
1491 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001492 user_readonly_fields = ['name']
1493 user_readonly_inlines = []
1494
Scott Baker0165fac2014-01-13 11:49:26 -08001495class RouterInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001496 model = Router.networks.through
1497 extra = 0
1498 verbose_name_plural = "Routers"
1499 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001500 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001501
Scott Bakerb27b62c2014-08-15 16:29:16 -07001502class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001503 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001504 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001505 verbose_name_plural = "Parameters"
1506 verbose_name = "Parameter"
1507 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001508 fields = ['backend_status_icon', 'parameter', 'value']
1509 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001510
Scott Baker0165fac2014-01-13 11:49:26 -08001511class NetworkSliversInline(PlStackTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001512 fields = ['backend_status_icon', 'network','sliver','ip']
1513 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001514 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001515 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001516 extra = 0
1517 verbose_name_plural = "Slivers"
1518 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001519 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001520
Scott Baker0165fac2014-01-13 11:49:26 -08001521class NetworkSlicesInline(PlStackTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001522 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001523 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001524 extra = 0
1525 verbose_name_plural = "Slices"
1526 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001527 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001528 fields = ['backend_status_icon', 'network','slice']
1529 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001530
Tony Mack3066a952015-01-05 22:48:11 -05001531class ControllerNetworkInline(PlStackTabularInline):
1532 model = ControllerNetwork
Scott Baker8806cdf2014-10-17 16:27:23 -07001533 extra = 0
Tony Mack06c8e472014-11-30 15:53:08 -05001534 verbose_name_plural = "Controller Networks"
1535 verbose_name = "Controller Network"
Scott Baker8806cdf2014-10-17 16:27:23 -07001536 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack06c8e472014-11-30 15:53:08 -05001537 fields = ['backend_status_icon', 'controller','net_id','subnet_id']
Scott Baker8806cdf2014-10-17 16:27:23 -07001538 readonly_fields = ('backend_status_icon', )
1539
Scott Baker69e045d2014-11-17 23:44:03 -08001540class NetworkForm(forms.ModelForm):
1541 class Meta:
1542 model = Network
1543 widgets = {
1544 'topologyParameters': UploadTextareaWidget,
1545 'controllerParameters': UploadTextareaWidget,
1546 }
1547
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001548class NetworkAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001549 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1550 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001551 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001552
Scott Bakerd7d2a392013-08-06 08:57:30 -07001553 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Tony Mack3066a952015-01-05 22:48:11 -05001554 admin_inlines = [ControllerNetworkInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001555
Scott Baker69e045d2014-11-17 23:44:03 -08001556 form=NetworkForm
1557
Siobhan Tully2d95e482013-09-06 10:56:06 -04001558 fieldsets = [
Scott Baker549aa252015-01-03 12:29:29 -08001559 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet'],
Scott Baker3e28dd72014-11-17 16:04:45 -08001560 'classes':['suit-tab suit-tab-general']}),
Scott Baker549aa252015-01-03 12:29:29 -08001561 (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
Scott Baker3e28dd72014-11-17 16:04:45 -08001562 'classes':['suit-tab suit-tab-sdn']}),
1563 ]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001564
Scott Baker40c00762014-08-21 16:55:59 -07001565 readonly_fields = ('backend_status_text', )
Scott Baker549aa252015-01-03 12:29:29 -08001566 user_readonly_fields = ['name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet']
Siobhan Tully2d95e482013-09-06 10:56:06 -04001567
Scott Baker8806cdf2014-10-17 16:27:23 -07001568 @property
1569 def suit_form_tabs(self):
1570 tabs=[('general','Network Details'),
Scott Baker3e28dd72014-11-17 16:04:45 -08001571 ('sdn', 'SDN Configuration'),
Scott Baker8806cdf2014-10-17 16:27:23 -07001572 ('netparams', 'Parameters'),
1573 ('networkslivers','Slivers'),
1574 ('networkslices','Slices'),
1575 ('routers','Routers'),
1576 ]
1577
1578 request=getattr(_thread_locals, "request", None)
1579 if request and request.user.is_admin:
1580 tabs.append( ('admin-only', 'Admin-Only') )
1581
1582 return tabs
1583
1584
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001585class NetworkTemplateAdmin(PlanetStackBaseAdmin):
Scott Baker369f9b92015-01-03 12:03:38 -08001586 list_display = ("backend_status_icon", "name", "guaranteed_bandwidth", "visibility")
Scott Baker63d1a552014-08-21 15:19:07 -07001587 list_display_links = ('backend_status_icon', 'name', )
Scott Baker369f9b92015-01-03 12:03:38 -08001588 user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001589 user_readonly_inlines = []
Scott Baker3e28dd72014-11-17 16:04:45 -08001590 fieldsets = [
Scott Baker369f9b92015-01-03 12:03:38 -08001591 (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
Scott Baker3e28dd72014-11-17 16:04:45 -08001592 'classes':['suit-tab suit-tab-general']}),]
1593 suit_form_tabs = (('general','Network Template Details'), )
Scott Baker74d8e622013-07-29 16:04:22 -07001594
Scott Baker37b47902014-09-02 14:37:41 -07001595class FlavorAdmin(PlanetStackBaseAdmin):
1596 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1597 list_display_links = ("backend_status_icon", "name")
1598 user_readonly_fields = ("name", "flavor")
1599 fields = ("name", "description", "flavor", "order", "default")
1600
Tony Mack31c2b8f2013-04-26 20:01:42 -04001601# register a signal that caches the user's credentials when they log in
1602def cache_credentials(sender, user, request, **kwds):
1603 auth = {'username': request.POST['username'],
1604 'password': request.POST['password']}
1605 request.session['auth'] = auth
1606user_logged_in.connect(cache_credentials)
1607
Scott Baker15cddfa2013-12-09 13:45:19 -08001608def dollar_field(fieldName, short_description):
1609 def newFunc(self, obj):
1610 try:
1611 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1612 except:
1613 x=getattr(obj, fieldName, 0.0)
1614 return x
1615 newFunc.short_description = short_description
1616 return newFunc
1617
1618def right_dollar_field(fieldName, short_description):
1619 def newFunc(self, obj):
1620 try:
1621 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1622 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1623 except:
1624 x=getattr(obj, fieldName, 0.0)
1625 return x
1626 newFunc.short_description = short_description
1627 newFunc.allow_tags = True
1628 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001629
Scott Baker0165fac2014-01-13 11:49:26 -08001630class InvoiceChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001631 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001632 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001633 verbose_name_plural = "Charges"
1634 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001635 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001636 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1637 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1638 can_delete = False
1639 max_num = 0
1640
1641 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001642
1643class InvoiceAdmin(admin.ModelAdmin):
1644 list_display = ("date", "account")
1645
1646 inlines = [InvoiceChargeInline]
1647
Scott Baker9cb88a22013-12-09 18:56:00 -08001648 fields = ["date", "account", "dollar_amount"]
1649 readonly_fields = ["date", "account", "dollar_amount"]
1650
1651 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001652
Scott Baker0165fac2014-01-13 11:49:26 -08001653class InvoiceInline(PlStackTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001654 model = Invoice
1655 extra = 0
1656 verbose_name_plural = "Invoices"
1657 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001658 fields = ["date", "dollar_amount"]
1659 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001660 suit_classes = 'suit-tab suit-tab-accountinvoice'
1661 can_delete=False
1662 max_num=0
1663
1664 dollar_amount = right_dollar_field("amount", "Amount")
1665
Scott Baker0165fac2014-01-13 11:49:26 -08001666class PendingChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001667 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001668 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001669 verbose_name_plural = "Charges"
1670 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001671 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001672 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1673 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001674 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001675 can_delete=False
1676 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001677
1678 def queryset(self, request):
1679 qs = super(PendingChargeInline, self).queryset(request)
1680 qs = qs.filter(state="pending")
1681 return qs
1682
Scott Baker15cddfa2013-12-09 13:45:19 -08001683 dollar_amount = right_dollar_field("amount", "Amount")
1684
Scott Baker0165fac2014-01-13 11:49:26 -08001685class PaymentInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001686 model=Payment
1687 extra = 1
1688 verbose_name_plural = "Payments"
1689 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001690 fields = ["date", "dollar_amount"]
1691 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001692 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001693 can_delete=False
1694 max_num=0
1695
1696 dollar_amount = right_dollar_field("amount", "Amount")
1697
Scott Baker43105042013-12-06 23:23:36 -08001698class AccountAdmin(admin.ModelAdmin):
1699 list_display = ("site", "balance_due")
1700
1701 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1702
1703 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001704 (None, {'fields': ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments'],'classes':['suit-tab suit-tab-general']}),]
Scott Baker43105042013-12-06 23:23:36 -08001705
Scott Baker15cddfa2013-12-09 13:45:19 -08001706 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001707
1708 suit_form_tabs =(
1709 ('general','Account Details'),
1710 ('accountinvoice', 'Invoices'),
1711 ('accountpayments', 'Payments'),
1712 ('accountpendingcharges','Pending Charges'),
1713 )
1714
Scott Baker15cddfa2013-12-09 13:45:19 -08001715 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1716 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1717 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1718
Siobhan Tully53437282013-04-26 19:30:27 -04001719# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001720admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001721# ... and, since we're not using Django's builtin permissions,
1722# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001723#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001724
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001725#Do not show django evolution in the admin interface
1726from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001727#admin.site.unregister(Version)
1728#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001729
1730
1731# When debugging it is often easier to see all the classes, but for regular use
1732# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001733showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001734
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001735admin.site.register(Deployment, DeploymentAdmin)
Tony Mack06c8e472014-11-30 15:53:08 -05001736admin.site.register(Controller, ControllerAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001737admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001738admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001739admin.site.register(Service, ServiceAdmin)
Tony Mack450b6e02015-01-25 12:35:29 -05001740#admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001741admin.site.register(Network, NetworkAdmin)
1742admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001743admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Tony Mack450b6e02015-01-25 12:35:29 -05001744#admin.site.register(Account, AccountAdmin)
1745#admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001746
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001747if True:
1748 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1749 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001750 #admin.site.register(PlanetStack)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001751 admin.site.register(Tag, TagAdmin)
Tony Mack06c8e472014-11-30 15:53:08 -05001752 admin.site.register(ControllerRole)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001753 admin.site.register(SiteRole)
1754 admin.site.register(SliceRole)
1755 admin.site.register(PlanetStackRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001756 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001757 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1758 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001759 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001760 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001761 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07001762 admin.site.register(Flavor, FlavorAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001763