blob: 5af689f8b0e7381f9f5f838f530b0c019bc213b3 [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
Tony Mack5817cb42015-02-16 19:54:24 -050017from django.core.urlresolvers import reverse, resolve, 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 Bakere5f9d7d2015-02-10 18:24:20 -080029ICON_URLS = {"success": "/static/admin/img/icon_success.gif",
30 "clock": "/static/admin/img/icon_clock.gif",
31 "error": "/static/admin/img/icon_error.gif"}
32
33def backend_icon(obj):
34 (icon, tooltip) = obj.get_backend_icon()
35 icon_url = ICON_URLS.get(icon, "unknown")
36
37 if tooltip:
38 return '<span style="min-width:16px;" title="%s"><img src="%s"></span>' % (tooltip, icon_url)
Scott Baker40c00762014-08-21 16:55:59 -070039 else:
Scott Bakere5f9d7d2015-02-10 18:24:20 -080040 return '<span style="min-width:16px;"><img src="%s"></span>' % icon_url
Scott Baker40c00762014-08-21 16:55:59 -070041
42def backend_text(obj):
Scott Bakere5f9d7d2015-02-10 18:24:20 -080043 (icon, tooltip) = obj.get_backend_icon()
44 icon_url = ICON_URLS.get(icon, "unknown")
45
46 return '<img src="%s"> %s' % (icon_url, tooltip)
Scott Baker63d1a552014-08-21 15:19:07 -070047
Scott Baker69e045d2014-11-17 23:44:03 -080048class UploadTextareaWidget(AdminTextareaWidget):
49 def render(self, name, value, attrs=None):
50 if value is None:
51 value = ''
52 final_attrs = self.build_attrs(attrs, name=name)
53 return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">' \
54 '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>' \
55 '<br><textarea{0}>\r\n{1}</textarea>' % (attrs["id"], attrs["id"], attrs["id"]),
56 flatatt(final_attrs),
57 force_text(value))
58
Scott Baker36f50872014-08-21 13:01:25 -070059class PlainTextWidget(forms.HiddenInput):
60 input_type = 'hidden'
61
62 def render(self, name, value, attrs=None):
63 if value is None:
64 value = ''
65 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
66
Scott Bakerf4aeedc2014-10-03 13:10:47 -070067class PermissionCheckingAdminMixin(object):
Scott Baker1a6a3902014-10-03 00:32:37 -070068 # call save_by_user and delete_by_user instead of save and delete
Siobhan Tullycf04fb62014-01-11 11:25:57 -050069
70 def has_add_permission(self, request, obj=None):
71 return (not self.__user_is_readonly(request))
Scott Baker36f50872014-08-21 13:01:25 -070072
Siobhan Tullycf04fb62014-01-11 11:25:57 -050073 def has_delete_permission(self, request, obj=None):
74 return (not self.__user_is_readonly(request))
75
76 def save_model(self, request, obj, form, change):
77 if self.__user_is_readonly(request):
Scott Baker1a6a3902014-10-03 00:32:37 -070078 # this 'if' might be redundant if save_by_user is implemented right
Siobhan Tullycf04fb62014-01-11 11:25:57 -050079 raise PermissionDenied
Scott Baker1a6a3902014-10-03 00:32:37 -070080
81 obj.caller = request.user
82 # update openstack connection to use this site/tenant
83 obj.save_by_user(request.user)
84
85 def delete_model(self, request, obj):
86 obj.delete_by_user(request.user)
87
88 def save_formset(self, request, form, formset, change):
89 instances = formset.save(commit=False)
90 for instance in instances:
91 instance.save_by_user(request.user)
92
93 # BUG in django 1.7? Objects are not deleted by formset.save if
94 # commit is False. So let's delete them ourselves.
95 #
96 # code from forms/models.py save_existing_objects()
97 try:
98 forms_to_delete = formset.deleted_forms
99 except AttributeError:
100 forms_to_delete = []
101 if formset.initial_forms:
102 for form in formset.initial_forms:
103 obj = form.instance
104 if form in forms_to_delete:
105 if obj.pk is None:
106 continue
107 formset.deleted_objects.append(obj)
108 obj.delete()
109
110 formset.save_m2m()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500111
112 def get_actions(self,request):
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700113 actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500114
115 if self.__user_is_readonly(request):
116 if 'delete_selected' in actions:
117 del actions['delete_selected']
118
119 return actions
120
121 def change_view(self,request,object_id, extra_context=None):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500122 if self.__user_is_readonly(request):
Scott Bakeraf73e102014-04-22 22:40:07 -0700123 if not hasattr(self, "readonly_save"):
124 # save the original readonly fields
125 self.readonly_save = self.readonly_fields
126 self.inlines_save = self.inlines
Scott Bakere8859f92014-05-23 12:42:40 -0700127 if hasattr(self, "user_readonly_fields"):
128 self.readonly_fields=self.user_readonly_fields
129 if hasattr(self, "user_readonly_inlines"):
130 self.inlines = self.user_readonly_inlines
Scott Bakeraf73e102014-04-22 22:40:07 -0700131 else:
132 if hasattr(self, "readonly_save"):
133 # restore the original readonly fields
134 self.readonly_fields = self.readonly_save
Scott Bakere8859f92014-05-23 12:42:40 -0700135 if hasattr(self, "inlines_save"):
Scott Bakeraf73e102014-04-22 22:40:07 -0700136 self.inlines = self.inlines_save
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500137
138 try:
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700139 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500140 except PermissionDenied:
141 pass
142 if request.method == 'POST':
143 raise PermissionDenied
144 request.readonly = True
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700145 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500146
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500147 def __user_is_readonly(self, request):
148 return request.user.isReadOnlyUser()
149
Scott Baker40c00762014-08-21 16:55:59 -0700150 def backend_status_text(self, obj):
151 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -0700152
Scott Baker63d1a552014-08-21 15:19:07 -0700153 def backend_status_icon(self, obj):
Scott Baker40c00762014-08-21 16:55:59 -0700154 return mark_safe(backend_icon(obj))
Scott Baker63d1a552014-08-21 15:19:07 -0700155 backend_status_icon.short_description = ""
156
Scott Bakerdc4724c2014-11-05 09:05:38 -0800157 def get_form(self, request, obj=None, **kwargs):
Scott Baker9b3c1af2014-10-16 00:57:55 -0700158 # Save obj and request in thread-local storage, so suit_form_tabs can
159 # use it to determine whether we're in edit or add mode, and can
160 # determine whether the user is an admin.
161 _thread_locals.request = request
162 _thread_locals.obj = obj
Scott Bakerdc4724c2014-11-05 09:05:38 -0800163 return super(PermissionCheckingAdminMixin, self).get_form(request, obj, **kwargs)
Scott Baker9b3c1af2014-10-16 00:57:55 -0700164
165 def get_inline_instances(self, request, obj=None):
166 inlines = super(PermissionCheckingAdminMixin, self).get_inline_instances(request, obj)
167
168 # inlines that should only be shown to an admin user
169 if request.user.is_admin:
170 for inline_class in getattr(self, "admin_inlines", []):
171 inlines.append(inline_class(self.model, self.admin_site))
172
173 return inlines
174
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700175class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
176 # Note: Make sure PermissionCheckingAdminMixin is listed before
177 # admin.ModelAdmin in the class declaration.
178
Scott Baker1a6a3902014-10-03 00:32:37 -0700179 pass
180
Scott Baker67db95f2015-02-18 15:50:11 -0800181class XOSBaseAdmin(ReadOnlyAwareAdmin):
Scott Baker1a6a3902014-10-03 00:32:37 -0700182 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700183
Scott Bakere8859f92014-05-23 12:42:40 -0700184class SingletonAdmin (ReadOnlyAwareAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400185 def has_add_permission(self, request):
Scott Bakere8859f92014-05-23 12:42:40 -0700186 if not super(SingletonAdmin, self).has_add_permission(request):
187 return False
188
Siobhan Tullyce652d02013-10-08 21:52:35 -0400189 num_objects = self.model.objects.count()
190 if num_objects >= 1:
191 return False
192 else:
193 return True
194
Scott Baker67db95f2015-02-18 15:50:11 -0800195class XOSTabularInline(admin.TabularInline):
Scott Baker86568322014-01-12 16:53:31 -0800196 def __init__(self, *args, **kwargs):
Scott Baker67db95f2015-02-18 15:50:11 -0800197 super(XOSTabularInline, self).__init__(*args, **kwargs)
Scott Baker86568322014-01-12 16:53:31 -0800198
199 # InlineModelAdmin as no get_fields() method, so in order to add
200 # the selflink field, we override __init__ to modify self.fields and
201 # self.readonly_fields.
202
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800203 self.setup_selflink()
204
Scott Baker874936e2014-01-13 18:15:34 -0800205 def get_change_url(self, model, id):
206 """ Get the URL to a change form in the admin for this model """
207 reverse_path = "admin:%s_change" % (model._meta.db_table)
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800208 try:
Scott Baker874936e2014-01-13 18:15:34 -0800209 url = reverse(reverse_path, args=(id,))
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800210 except NoReverseMatch:
Scott Baker874936e2014-01-13 18:15:34 -0800211 return None
212
213 return url
214
215 def setup_selflink(self):
216 if hasattr(self, "selflink_fieldname"):
217 """ self.selflink_model can be defined to punch through a relation
218 to its target object. For example, in SliceNetworkInline, set
219 selflink_model = "network", and the URL will lead to the Network
220 object instead of trying to bring up a change view of the
221 SliceNetwork object.
222 """
223 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
224 else:
225 self.selflink_model = self.model
226
227 url = self.get_change_url(self.selflink_model, 0)
228
229 # We don't have an admin for this object, so don't create the
230 # selflink.
231 if (url == None):
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800232 return
233
Scott Baker874936e2014-01-13 18:15:34 -0800234 # Since we need to add "selflink" to the field list, we need to create
235 # self.fields if it is None.
Scott Baker0165fac2014-01-13 11:49:26 -0800236 if (self.fields is None):
237 self.fields = []
238 for f in self.model._meta.fields:
239 if f.editable and f.name != "id":
240 self.fields.append(f.name)
Scott Baker86568322014-01-12 16:53:31 -0800241
Scott Baker874936e2014-01-13 18:15:34 -0800242 self.fields = tuple(self.fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800243
Scott Baker874936e2014-01-13 18:15:34 -0800244 if self.readonly_fields is None:
245 self.readonly_fields = ()
Scott Baker86568322014-01-12 16:53:31 -0800246
Scott Baker874936e2014-01-13 18:15:34 -0800247 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800248
249 def selflink(self, obj):
Scott Baker874936e2014-01-13 18:15:34 -0800250 if hasattr(self, "selflink_fieldname"):
251 obj = getattr(obj, self.selflink_fieldname)
252
Scott Baker86568322014-01-12 16:53:31 -0800253 if obj.id:
Scott Baker874936e2014-01-13 18:15:34 -0800254 url = self.get_change_url(self.selflink_model, obj.id)
255 return "<a href='%s'>Details</a>" % str(url)
Scott Baker86568322014-01-12 16:53:31 -0800256 else:
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800257 return "Not present"
Scott Baker86568322014-01-12 16:53:31 -0800258
259 selflink.allow_tags = True
260 selflink.short_description = "Details"
Siobhan Tullyd3515752013-06-21 16:34:53 -0400261
Scott Bakerb27b62c2014-08-15 16:29:16 -0700262 def has_add_permission(self, request):
263 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500264
265 def get_readonly_fields(self, request, obj=None):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700266 readonly_fields = list(self.readonly_fields)[:]
267 if request.user.isReadOnlyUser():
268 for field in self.fields:
269 if not field in readonly_fields:
270 readonly_fields.append(field)
271 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500272
Scott Baker40c00762014-08-21 16:55:59 -0700273 def backend_status_icon(self, obj):
274 return mark_safe(backend_icon(obj))
275 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -0700276
Scott Bakerb27b62c2014-08-15 16:29:16 -0700277class PlStackGenericTabularInline(generic.GenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500278 def has_add_permission(self, request):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700279 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500280
Scott Bakerb27b62c2014-08-15 16:29:16 -0700281 def get_readonly_fields(self, request, obj=None):
282 readonly_fields = list(self.readonly_fields)[:]
283 if request.user.isReadOnlyUser():
284 for field in self.fields:
285 if not field in readonly_fields:
286 readonly_fields.append(field)
287 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500288
Scott Baker40c00762014-08-21 16:55:59 -0700289 def backend_status_icon(self, obj):
290 return mark_safe(backend_icon(obj))
291 backend_status_icon.short_description = ""
292
Scott Baker67db95f2015-02-18 15:50:11 -0800293class ReservationInline(XOSTabularInline):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400294 model = Reservation
295 extra = 0
296 suit_classes = 'suit-tab suit-tab-reservations'
Scott Baker36f50872014-08-21 13:01:25 -0700297
Tony Mack5b061472014-02-04 07:57:10 -0500298 def queryset(self, request):
299 return Reservation.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400300
Scott Bakerb27b62c2014-08-15 16:29:16 -0700301class TagInline(PlStackGenericTabularInline):
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400302 model = Tag
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400303 extra = 0
304 suit_classes = 'suit-tab suit-tab-tags'
Tony Mack5b061472014-02-04 07:57:10 -0500305 fields = ['service', 'name', 'value']
306
307 def queryset(self, request):
308 return Tag.select_by_user(request.user)
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400309
Scott Baker74d8e622013-07-29 16:04:22 -0700310class NetworkLookerUpper:
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400311 """ This is a callable that looks up a network name in a sliver and returns
312 the ip address for that network.
313 """
314
Scott Baker434ca7e2014-08-15 12:29:20 -0700315 byNetworkName = {} # class variable
316
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400317 def __init__(self, name):
318 self.short_description = name
319 self.__name__ = name
320 self.network_name = name
321
322 def __call__(self, obj):
323 if obj is not None:
324 for nbs in obj.networksliver_set.all():
325 if (nbs.network.name == self.network_name):
326 return nbs.ip
Scott Baker74d8e622013-07-29 16:04:22 -0700327 return ""
328
329 def __str__(self):
330 return self.network_name
331
Scott Baker434ca7e2014-08-15 12:29:20 -0700332 @staticmethod
333 def get(network_name):
334 """ We want to make sure we alwars return the same NetworkLookerUpper
335 because sometimes django will cause them to be instantiated multiple
336 times (and we don't want different ones in form.fields vs
337 SliverInline.readonly_fields).
338 """
339 if network_name not in NetworkLookerUpper.byNetworkName:
340 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
341 return NetworkLookerUpper.byNetworkName[network_name]
342
Scott Baker67db95f2015-02-18 15:50:11 -0800343class SliverInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400344 model = Sliver
Scott Baker9d856052015-01-19 11:32:20 -0800345 fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'deployment', 'flavor', 'image', 'node']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400346 extra = 0
Scott Baker9d856052015-01-19 11:32:20 -0800347 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name']
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400348 suit_classes = 'suit-tab suit-tab-slivers'
Scott Baker74d8e622013-07-29 16:04:22 -0700349
Tony Mack5b061472014-02-04 07:57:10 -0500350 def queryset(self, request):
351 return Sliver.select_by_user(request.user)
352
Scott Bakerb24cc932014-06-09 10:51:16 -0700353 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Tony Mackb2dba4b2014-12-26 13:38:02 -0500354 if db_field.name == 'deployment':
Scott Baker3b678742014-06-09 13:11:54 -0700355 kwargs['queryset'] = Deployment.select_by_acl(request.user)
Scott Baker7a61dc42014-09-02 17:08:20 -0700356 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
Tony Mackb2dba4b2014-12-26 13:38:02 -0500357 if db_field.name == 'flavor':
Scott Baker4b6d9442014-09-08 12:14:14 -0700358 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
Scott Baker3b678742014-06-09 13:11:54 -0700359
360 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700361
362 return field
363
Scott Baker67db95f2015-02-18 15:50:11 -0800364class SiteInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400365 model = Site
366 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400367 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400368
Tony Mack5b061472014-02-04 07:57:10 -0500369 def queryset(self, request):
370 return Site.select_by_user(request.user)
371
Scott Baker67db95f2015-02-18 15:50:11 -0800372class UserInline(XOSTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400373 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700374 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
375 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400376 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400377 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400378
Tony Mack5b061472014-02-04 07:57:10 -0500379 def queryset(self, request):
380 return User.select_by_user(request.user)
381
Scott Baker67db95f2015-02-18 15:50:11 -0800382class SliceInline(XOSTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400383 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700384 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
385 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400386 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400387 suit_classes = 'suit-tab suit-tab-slices'
388
Tony Mack5b061472014-02-04 07:57:10 -0500389 def queryset(self, request):
390 return Slice.select_by_user(request.user)
391
Scott Baker67db95f2015-02-18 15:50:11 -0800392class NodeInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400393 model = Node
394 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400395 suit_classes = 'suit-tab suit-tab-nodes'
Tony Mack68a1e422014-12-08 16:43:02 -0500396 fields = ['backend_status_icon', 'name', 'site_deployment']
Scott Baker40c00762014-08-21 16:55:59 -0700397 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400398
Scott Baker67db95f2015-02-18 15:50:11 -0800399class DeploymentPrivilegeInline(XOSTabularInline):
Tony Mack68a1e422014-12-08 16:43:02 -0500400 model = DeploymentPrivilege
401 extra = 0
Tony Mack88c89902015-02-09 21:41:57 -0500402 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
Tony Mack68a1e422014-12-08 16:43:02 -0500403 fields = ['backend_status_icon', 'user','role','deployment']
404 readonly_fields = ('backend_status_icon', )
405
406 def queryset(self, request):
407 return DeploymentPrivilege.select_by_user(request.user)
408
Scott Baker67db95f2015-02-18 15:50:11 -0800409class ControllerSiteInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -0500410 model = ControllerSite
411 extra = 0
412 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Macke2363c12015-01-06 15:08:20 -0500413 fields = ['controller', 'site', 'tenant_id']
Tony Mack3066a952015-01-05 22:48:11 -0500414
415
Scott Baker67db95f2015-02-18 15:50:11 -0800416class SitePrivilegeInline(XOSTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400417 model = SitePrivilege
418 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400419 suit_classes = 'suit-tab suit-tab-siteprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700420 fields = ['backend_status_icon', 'user','site', 'role']
421 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400422
Tony Mackc2835a92013-05-28 09:18:49 -0400423 def formfield_for_foreignkey(self, db_field, request, **kwargs):
424 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500425 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400426
427 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500428 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400429 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
430
Tony Mack5b061472014-02-04 07:57:10 -0500431 def queryset(self, request):
432 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400433
Scott Baker67db95f2015-02-18 15:50:11 -0800434class SiteDeploymentInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -0500435 model = SiteDeployment
Tony Macke4be32f2014-03-11 20:45:25 -0400436 extra = 0
Tony Mackb81d5e42015-01-30 10:58:29 -0500437 suit_classes = 'suit-tab suit-tab-sitedeployments'
Tony Mackd14d48f2014-12-05 17:13:08 -0500438 fields = ['backend_status_icon', 'deployment','site', 'controller']
Scott Baker40c00762014-08-21 16:55:59 -0700439 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400440
441 def formfield_for_foreignkey(self, db_field, request, **kwargs):
442 if db_field.name == 'site':
443 kwargs['queryset'] = Site.select_by_user(request.user)
444
445 if db_field.name == 'deployment':
446 kwargs['queryset'] = Deployment.select_by_user(request.user)
Tony Mackd14d48f2014-12-05 17:13:08 -0500447
448 if db_field.name == 'controller':
Tony Mack5817cb42015-02-16 19:54:24 -0500449 kwargs['queryset'] = Controller.select_by_user(request.user).filter(deployment__id=int(resolve(request.path).args[0]))
Tony Mackd14d48f2014-12-05 17:13:08 -0500450
Tony Mack3066a952015-01-05 22:48:11 -0500451 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Macke4be32f2014-03-11 20:45:25 -0400452
453 def queryset(self, request):
Tony Mack3066a952015-01-05 22:48:11 -0500454 return SiteDeployment.select_by_user(request.user)
Tony Macke4be32f2014-03-11 20:45:25 -0400455
456
Scott Baker67db95f2015-02-18 15:50:11 -0800457class SlicePrivilegeInline(XOSTabularInline):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400458 model = SlicePrivilege
459 suit_classes = 'suit-tab suit-tab-sliceprivileges'
460 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700461 fields = ('backend_status_icon', 'user', 'slice', 'role')
462 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400463
Tony Mackc2835a92013-05-28 09:18:49 -0400464 def formfield_for_foreignkey(self, db_field, request, **kwargs):
465 if db_field.name == 'slice':
Scott Baker36f50872014-08-21 13:01:25 -0700466 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400467 if db_field.name == 'user':
Scott Baker36f50872014-08-21 13:01:25 -0700468 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400469
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400470 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400471
Tony Mack5b061472014-02-04 07:57:10 -0500472 def queryset(self, request):
473 return SlicePrivilege.select_by_user(request.user)
474
Scott Baker67db95f2015-02-18 15:50:11 -0800475class SliceNetworkInline(XOSTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700476 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800477 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700478 extra = 0
479 verbose_name = "Network Connection"
480 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400481 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700482 fields = ['backend_status_icon', 'network']
483 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700484
Scott Baker67db95f2015-02-18 15:50:11 -0800485class ImageDeploymentsInline(XOSTabularInline):
Sapan Bhatia1b6bba22014-11-19 15:10:16 -0500486 model = ImageDeployments
Scott Baker2170b972014-06-03 12:14:07 -0700487 extra = 0
488 verbose_name = "Image Deployments"
489 verbose_name_plural = "Image Deployments"
490 suit_classes = 'suit-tab suit-tab-imagedeployments'
Tony Mack06c8e472014-11-30 15:53:08 -0500491 fields = ['backend_status_icon', 'image', 'deployment']
492 readonly_fields = ['backend_status_icon']
493
Scott Baker67db95f2015-02-18 15:50:11 -0800494class ControllerImagesInline(XOSTabularInline):
Tony Mack06c8e472014-11-30 15:53:08 -0500495 model = ControllerImages
496 extra = 0
497 verbose_name = "Controller Images"
498 verbose_name_plural = "Controller Images"
499 suit_classes = 'suit-tab suit-tab-admin-only'
500 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
Scott Baker40c00762014-08-21 16:55:59 -0700501 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700502
Scott Baker67db95f2015-02-18 15:50:11 -0800503class SliceRoleAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400504 model = SliceRole
505 pass
506
Scott Baker67db95f2015-02-18 15:50:11 -0800507class SiteRoleAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400508 model = SiteRole
509 pass
510
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400511class DeploymentAdminForm(forms.ModelForm):
Scott Bakerde0f4412014-06-11 15:40:26 -0700512 images = forms.ModelMultipleChoiceField(
513 queryset=Image.objects.all(),
514 required=False,
515 help_text="Select which images should be deployed on this deployment",
516 widget=FilteredSelectMultiple(
517 verbose_name=('Images'), is_stacked=False
518 )
519 )
Scott Baker37b47902014-09-02 14:37:41 -0700520 flavors = forms.ModelMultipleChoiceField(
521 queryset=Flavor.objects.all(),
522 required=False,
523 help_text="Select which flavors should be usable on this deployment",
524 widget=FilteredSelectMultiple(
525 verbose_name=('Flavors'), is_stacked=False
526 )
527 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400528 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400529 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700530 many_to_many = ["flavors",]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400531
Siobhan Tully320b4622014-01-17 15:11:14 -0500532 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700533 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500534 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
535
Scott Baker5380c522014-06-06 14:49:43 -0700536 self.fields['accessControl'].initial = "allow site " + request.user.site.name
537
Siobhan Tully320b4622014-01-17 15:11:14 -0500538 if self.instance and self.instance.pk:
Scott Baker69e045d2014-11-17 23:44:03 -0800539 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
Scott Baker37b47902014-09-02 14:37:41 -0700540 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700541
542 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
543 """ helper function for handling m2m relations from the MultipleChoiceField
544
545 this_obj: the source object we want to link from
546
547 selected_objs: a list of destination objects we want to link to
548
549 all_relations: the full set of relations involving this_obj, including ones we don't want
550
551 relation_class: the class that implements the relation from source to dest
552
553 local_attrname: field name representing this_obj in relation_class
554
555 foreign_attrname: field name representing selected_objs in relation_class
556
557 This function will remove all newobjclass relations from this_obj
558 that are not contained in selected_objs, and add any relations that
559 are in selected_objs but don't exist in the data model yet.
560 """
561
562 existing_dest_objs = []
563 for relation in list(all_relations):
564 if getattr(relation, foreign_attrname) not in selected_objs:
565 #print "deleting site", sdp.site
566 relation.delete()
567 else:
568 existing_dest_objs.append(getattr(relation, foreign_attrname))
569
570 for dest_obj in selected_objs:
571 if dest_obj not in existing_dest_objs:
572 #print "adding site", site
573 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
574 relation = relation_class(**kwargs)
575 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500576
577 def save(self, commit=True):
578 deployment = super(DeploymentAdminForm, self).save(commit=False)
579
580 if commit:
581 deployment.save()
Scott Baker0057d052014-10-06 17:17:40 -0700582 # this has to be done after save() if/when a deployment is first created
583 deployment.flavors = self.cleaned_data['flavors']
Siobhan Tully320b4622014-01-17 15:11:14 -0500584
585 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700586 # save_m2m() doesn't seem to work with 'through' relations. So we
587 # create/destroy the through models ourselves. There has to be
588 # a better way...
589
Tony Mackb2fde612014-12-15 11:45:02 -0500590 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
591 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
592 # so well handle that manually here
593 for flavor in deployment.flavors.all():
594 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
Tony Mackd4ab7822014-12-15 12:37:59 -0500595 deployment.flavors.remove(flavor)
Tony Mackb2fde612014-12-15 11:45:02 -0500596 for flavor in self.cleaned_data['flavors']:
597 if flavor not in deployment.flavors.all():
598 flavor.deployments.add(deployment)
Scott Bakerc9b14f72014-05-22 13:44:20 -0700599
Scott Baker37b47902014-09-02 14:37:41 -0700600 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500601
602 return deployment
603
Scott Bakerff5e0f32014-05-22 14:40:27 -0700604class DeploymentAdminROForm(DeploymentAdminForm):
605 def save(self, commit=True):
606 raise PermissionDenied
607
Scott Baker67db95f2015-02-18 15:50:11 -0800608class SiteAssocInline(XOSTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500609 model = Site.deployments.through
610 extra = 0
611 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400612
Scott Baker67db95f2015-02-18 15:50:11 -0800613class DeploymentAdmin(XOSBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500614 model = Deployment
Scott Baker622bcf02015-02-10 08:40:34 -0800615 fieldList = ['backend_status_text', 'name', 'images', 'flavors', 'accessControl']
616 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Mack68a1e422014-12-08 16:43:02 -0500617 # node no longer directly connected to deployment
618 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
Tony Mackb81d5e42015-01-30 10:58:29 -0500619 inlines = [DeploymentPrivilegeInline,TagInline,ImageDeploymentsInline,SiteDeploymentInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700620 list_display = ['backend_status_icon', 'name']
621 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700622 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500623
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500624 user_readonly_fields = ['name']
625
Tony Mack68a1e422014-12-08 16:43:02 -0500626 # nodes no longer direclty connected to deployments
Scott Baker622bcf02015-02-10 08:40:34 -0800627 suit_form_tabs =(('general','Deployment Details'),('deploymentprivileges','Privileges'), ('sitedeployments', 'Sites'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500628
Scott Bakerff5e0f32014-05-22 14:40:27 -0700629 def get_form(self, request, obj=None, **kwargs):
Tony Mackcf29cfa2015-02-05 06:13:04 -0500630 if request.user.isReadOnlyUser() or not request.user.is_admin:
Scott Bakerff5e0f32014-05-22 14:40:27 -0700631 kwargs["form"] = DeploymentAdminROForm
632 else:
633 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700634 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
635
636 # from stackexchange: pass the request object into the form
637
638 class AdminFormMetaClass(adminForm):
639 def __new__(cls, *args, **kwargs):
640 kwargs['request'] = request
641 return adminForm(*args, **kwargs)
642
643 return AdminFormMetaClass
644
Scott Baker67db95f2015-02-18 15:50:11 -0800645class ControllerAdmin(XOSBaseAdmin):
Scott Baker622bcf02015-02-10 08:40:34 -0800646 model = Controller
Scott Baker180148a2015-02-16 11:55:09 -0800647 fieldList = ['deployment', 'name', 'backend_type', 'version', 'auth_url', 'admin_user', 'admin_tenant','admin_password', 'domain']
Scott Baker622bcf02015-02-10 08:40:34 -0800648 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Mack3066a952015-01-05 22:48:11 -0500649 inlines = [ControllerSiteInline] # ,ControllerImagesInline]
Tony Mackd14d48f2014-12-05 17:13:08 -0500650 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
651 list_display_links = ('backend_status_icon', 'name', )
652 readonly_fields = ('backend_status_text',)
653
654 user_readonly_fields = []
655
Tony Mack2e897fa2015-01-13 17:33:08 -0500656 def save_model(self, request, obj, form, change):
657 # update openstack connection to use this site/tenant
658 obj.save_by_user(request.user)
659
660 def delete_model(self, request, obj):
Scott Baker622bcf02015-02-10 08:40:34 -0800661 obj.delete_by_user(request.user)
662
Tony Mack78fc1362015-02-18 11:41:36 -0500663 def queryset(self, request):
664 return Controller.select_by_user(request.user)
665
Scott Baker622bcf02015-02-10 08:40:34 -0800666 @property
667 def suit_form_tabs(self):
668 tabs = [('general', 'Controller Details'),
669 ]
670
671 request=getattr(_thread_locals, "request", None)
672 if request and request.user.is_admin:
673 tabs.append( ('admin-only', 'Admin-Only') )
674
675 return tabs
Tony Mack2e897fa2015-01-13 17:33:08 -0500676
Scott Baker67db95f2015-02-18 15:50:11 -0800677class ServiceAttrAsTabInline(XOSTabularInline):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400678 model = ServiceAttribute
679 fields = ['name','value']
680 extra = 0
681 suit_classes = 'suit-tab suit-tab-serviceattrs'
682
Scott Baker67db95f2015-02-18 15:50:11 -0800683class ServiceAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700684 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
685 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700686 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500687 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
688 inlines = [ServiceAttrAsTabInline,SliceInline]
Scott Baker40c00762014-08-21 16:55:59 -0700689 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500690
691 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500692
693 suit_form_tabs =(('general', 'Service Details'),
694 ('slices','Slices'),
695 ('serviceattrs','Additional Attributes'),
696 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400697
Scott Baker67db95f2015-02-18 15:50:11 -0800698class SiteNodeInline(XOSTabularInline):
Tony Mack4f134e62015-01-14 20:58:38 -0500699 model = Node
700 fields = ['name', 'site_deployment']
701 extra = 0
702 suit_classes = 'suit-tab suit-tab-nodes'
703
Scott Baker67db95f2015-02-18 15:50:11 -0800704class SiteAdmin(XOSBaseAdmin):
Tony Mack450b6e02015-01-25 12:35:29 -0500705 #fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
706 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'location']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400707 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500708 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400709 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400710 ]
Tony Mack450b6e02015-01-25 12:35:29 -0500711 #readonly_fields = ['backend_status_text', 'accountLink']
712 readonly_fields = ['backend_status_text']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500713
Tony Mack450b6e02015-01-25 12:35:29 -0500714 #user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
715 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500716
Scott Baker63d1a552014-08-21 15:19:07 -0700717 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
718 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400719 filter_horizontal = ('deployments',)
Tony Mackb81d5e42015-01-30 10:58:29 -0500720 inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteNodeInline]
Tony Mackde100182015-01-14 12:11:05 -0500721 admin_inlines = [ControllerSiteInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400722 search_fields = ['name']
723
Tony Mack30dfcd72015-01-10 23:08:10 -0500724 @property
725 def suit_form_tabs(self):
726 tabs = [('general', 'Site Details'),
727 ('users','Users'),
728 ('siteprivileges','Privileges'),
Tony Mack30dfcd72015-01-10 23:08:10 -0500729 ('slices','Slices'),
Tony Mack4f134e62015-01-14 20:58:38 -0500730 ('nodes','Nodes'),
Tony Mack30dfcd72015-01-10 23:08:10 -0500731 ]
732
733 request=getattr(_thread_locals, "request", None)
734 if request and request.user.is_admin:
735 tabs.append( ('admin-only', 'Admin-Only') )
736
737 return tabs
738
Tony Mack04062832013-05-10 08:22:44 -0400739 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500740 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400741
Tony Mack5cd13202013-05-01 21:48:38 -0400742 def get_formsets(self, request, obj=None):
743 for inline in self.get_inline_instances(request, obj):
744 # hide MyInline in the add view
745 if obj is None:
746 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400747 if isinstance(inline, SliverInline):
748 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -0400749 yield inline.get_formset(request, obj)
750
Scott Baker545db2a2013-12-09 18:44:43 -0800751 def accountLink(self, obj):
752 link_obj = obj.accounts.all()
753 if link_obj:
754 reverse_path = "admin:core_account_change"
755 url = reverse(reverse_path, args =(link_obj[0].id,))
756 return "<a href='%s'>%s</a>" % (url, "view billing details")
757 else:
758 return "no billing data for this site"
759 accountLink.allow_tags = True
760 accountLink.short_description = "Billing"
761
Tony Mack332ee1d2014-02-04 15:33:45 -0500762 def save_model(self, request, obj, form, change):
763 # update openstack connection to use this site/tenant
764 obj.save_by_user(request.user)
765
766 def delete_model(self, request, obj):
767 obj.delete_by_user(request.user)
768
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500769
Scott Baker67db95f2015-02-18 15:50:11 -0800770class SitePrivilegeAdmin(XOSBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700771 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -0400772 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500773 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -0400774 ]
Scott Baker40c00762014-08-21 16:55:59 -0700775 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700776 list_display = ('backend_status_icon', 'user', 'site', 'role')
777 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500778 user_readonly_fields = fieldList
779 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -0400780
Tony Mackc2835a92013-05-28 09:18:49 -0400781 def formfield_for_foreignkey(self, db_field, request, **kwargs):
782 if db_field.name == 'site':
783 if not request.user.is_admin:
784 # only show sites where user is an admin or pi
785 sites = set()
786 for site_privilege in SitePrivilege.objects.filer(user=request.user):
787 if site_privilege.role.role_type in ['admin', 'pi']:
788 sites.add(site_privilege.site)
789 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
790
791 if db_field.name == 'user':
792 if not request.user.is_admin:
793 # only show users from sites where caller has admin or pi role
794 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
795 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
796 sites = [site_privilege.site for site_privilege in site_privileges]
797 site_privileges = SitePrivilege.objects.filter(site__in=sites)
798 emails = [site_privilege.user.email for site_privilege in site_privileges]
799 users = User.objects.filter(email__in=emails)
800 kwargs['queryset'] = users
801
802 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
803
Tony Mack04062832013-05-10 08:22:44 -0400804 def queryset(self, request):
805 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -0400806 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -0400807 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -0500808 #if not request.user.is_admin:
809 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
810 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
811 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
812 # sites = Site.objects.filter(login_base__in=login_bases)
813 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -0400814 return qs
815
Siobhan Tullyce652d02013-10-08 21:52:35 -0400816class SliceForm(forms.ModelForm):
817 class Meta:
818 model = Slice
819 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -0700820 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -0400821 }
822
Tony Macke75841e2014-09-29 16:10:52 -0400823 def clean(self):
824 cleaned_data = super(SliceForm, self).clean()
825 name = cleaned_data.get('name')
Scott Baker3cb382c2014-10-06 23:09:59 -0700826 site = cleaned_data.get('site')
Tony Mackcc9e2592014-10-22 12:54:19 -0400827 slice_id = self.instance.id
828 if not site and slice_id:
829 site = Slice.objects.get(id=slice_id).site
Scott Baker3cb382c2014-10-06 23:09:59 -0700830 if (not isinstance(site,Site)):
831 # previous code indicates 'site' could be a site_id and not a site?
832 site = Slice.objects.get(id=site.id)
Tony Macke75841e2014-09-29 16:10:52 -0400833 if not name.startswith(site.login_base):
834 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
835 return cleaned_data
836
Scott Baker67db95f2015-02-18 15:50:11 -0800837class ControllerSliceInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -0500838 model = ControllerSlice
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700839 extra = 0
Tony Mack06c8e472014-11-30 15:53:08 -0500840 verbose_name = "Controller Slices"
841 verbose_name_plural = "Controller Slices"
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700842 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack06c8e472014-11-30 15:53:08 -0500843 fields = ['backend_status_icon', 'controller', 'tenant_id']
Tony Mack30dfcd72015-01-10 23:08:10 -0500844 readonly_fields = ('backend_status_icon', 'controller' )
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700845
Scott Baker67db95f2015-02-18 15:50:11 -0800846class SliceAdmin(XOSBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400847 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -0400848 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500849 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -0700850 readonly_fields = ('backend_status_text', )
Tony Mack7d459902014-09-03 13:18:57 -0400851 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
852 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully2d95e482013-09-06 10:56:06 -0400853 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
Tony Mack3066a952015-01-05 22:48:11 -0500854 admin_inlines = [ControllerSliceInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400855
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500856 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400857
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700858 @property
859 def suit_form_tabs(self):
860 tabs =[('general', 'Slice Details'),
861 ('slicenetworks','Networks'),
862 ('sliceprivileges','Privileges'),
863 ('slivers','Slivers'),
Tony Mack450b6e02015-01-25 12:35:29 -0500864 #('reservations','Reservations'),
Tony Mackd2433382015-01-15 14:44:06 -0500865 ('tags','Tags'),
Scott Bakerf9f1ef42014-10-15 16:54:04 -0700866 ]
867
868 request=getattr(_thread_locals, "request", None)
869 if request and request.user.is_admin:
870 tabs.append( ('admin-only', 'Admin-Only') )
871
872 return tabs
Tony Mack0aa732a2014-10-22 11:54:29 -0400873
874 def add_view(self, request, form_url='', extra_context=None):
875 # revert to default read-only fields
876 self.readonly_fields = ('backend_status_text',)
877 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
878
879 def change_view(self, request, object_id, form_url='', extra_context=None):
Tony Mack0aa732a2014-10-22 11:54:29 -0400880 # cannot change the site of an existing slice so make the site field read only
881 if object_id:
882 self.readonly_fields = ('backend_status_text','site')
883 return super(SliceAdmin, self).change_view(request, object_id, form_url)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400884
Scott Baker510fdbb2014-08-05 17:19:24 -0700885 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -0700886 deployment_nodes = []
887 for node in Node.objects.all():
Scott Baker66b11e22015-01-21 16:24:07 -0800888 deployment_nodes.append( (node.site_deployment.deployment.id, node.id, node.name) )
Scott Baker510fdbb2014-08-05 17:19:24 -0700889
Scott Baker7a61dc42014-09-02 17:08:20 -0700890 deployment_flavors = []
891 for flavor in Flavor.objects.all():
892 for deployment in flavor.deployments.all():
893 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
894
Tony Mack68a1e422014-12-08 16:43:02 -0500895 deployment_images = []
Scott Bakeraf36c4d2014-09-09 09:58:49 -0700896 for image in Image.objects.all():
Tony Mack68a1e422014-12-08 16:43:02 -0500897 for deployment_image in image.imagedeployments.all():
Scott Bakerf2c0c512014-12-22 17:35:34 -0800898 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
Scott Bakeraf36c4d2014-09-09 09:58:49 -0700899
Tony Mackec23b992014-09-02 21:18:45 -0400900 site_login_bases = []
901 for site in Site.objects.all():
Scott Bakeraf36c4d2014-09-09 09:58:49 -0700902 site_login_bases.append((site.id, site.login_base))
903
Scott Baker510fdbb2014-08-05 17:19:24 -0700904 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -0700905 context["deployment_flavors"] = deployment_flavors
Scott Bakeraf36c4d2014-09-09 09:58:49 -0700906 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -0400907 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -0700908 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
909
Tony Mackc2835a92013-05-28 09:18:49 -0400910 def formfield_for_foreignkey(self, db_field, request, **kwargs):
911 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500912 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackec23b992014-09-02 21:18:45 -0400913 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 -0700914
Tony Mackc2835a92013-05-28 09:18:49 -0400915 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
916
Tony Mack04062832013-05-10 08:22:44 -0400917 def queryset(self, request):
918 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500919 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400920
Tony Mack79748612013-05-01 14:52:03 -0400921 def get_formsets(self, request, obj=None):
922 for inline in self.get_inline_instances(request, obj):
923 # hide MyInline in the add view
924 if obj is None:
925 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400926 if isinstance(inline, SliverInline):
927 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -0400928 yield inline.get_formset(request, obj)
929
Scott Baker67db95f2015-02-18 15:50:11 -0800930class SlicePrivilegeAdmin(XOSBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -0400931 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700932 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -0400933 ]
Scott Baker40c00762014-08-21 16:55:59 -0700934 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700935 list_display = ('backend_status_icon', 'user', 'slice', 'role')
936 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -0400937
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500938 user_readonly_fields = ['user', 'slice', 'role']
939 user_readonly_inlines = []
940
Tony Mackc2835a92013-05-28 09:18:49 -0400941 def formfield_for_foreignkey(self, db_field, request, **kwargs):
942 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500943 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400944
945 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500946 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400947
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400948 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400949
Tony Mack04062832013-05-10 08:22:44 -0400950 def queryset(self, request):
951 # admins can see all memberships. Users can only see memberships of
952 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -0500953 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400954
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400955 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -0400956 # update openstack connection to use this site/tenant
957 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400958 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400959 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400960 obj.save()
961
962 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -0400963 # update openstack connection to use this site/tenant
964 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400965 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400966 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400967 obj.delete()
968
Siobhan Tully567e3e62013-06-21 18:03:16 -0400969
Scott Baker67db95f2015-02-18 15:50:11 -0800970class ImageAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400971
Scott Baker36f50872014-08-21 13:01:25 -0700972 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -0700973 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400974 'classes': ['suit-tab suit-tab-general']})
975 ]
Scott Baker40c00762014-08-21 16:55:59 -0700976 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400977
Tony Mack06c8e472014-11-30 15:53:08 -0500978 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400979
Tony Mack06c8e472014-11-30 15:53:08 -0500980 inlines = [SliverInline, ControllerImagesInline]
Scott Bakerb6f99242014-06-11 11:34:44 -0700981
Tony Mack32e1ce32014-05-07 13:29:41 -0400982 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -0700983
Scott Baker63d1a552014-08-21 15:19:07 -0700984 list_display = ['backend_status_icon', 'name']
985 list_display_links = ('backend_status_icon', 'name', )
986
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400987class NodeForm(forms.ModelForm):
988 class Meta:
989 widgets = {
990 'site': LinkedSelect,
991 'deployment': LinkedSelect
992 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400993
Scott Baker67db95f2015-02-18 15:50:11 -0800994class NodeAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400995 form = NodeForm
Tony Mack68a1e422014-12-08 16:43:02 -0500996 list_display = ('backend_status_icon', 'name', 'site_deployment')
Scott Baker63d1a552014-08-21 15:19:07 -0700997 list_display_links = ('backend_status_icon', 'name', )
Tony Mack68a1e422014-12-08 16:43:02 -0500998 list_filter = ('site_deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500999
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001000 inlines = [TagInline,SliverInline]
Tony Mack68a1e422014-12-08 16:43:02 -05001001 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site_deployment'], 'classes':['suit-tab suit-tab-details']})]
Scott Baker40c00762014-08-21 16:55:59 -07001002 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001003
Tony Mack68a1e422014-12-08 16:43:02 -05001004 user_readonly_fields = ['name','site_deployment']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001005 user_readonly_inlines = [TagInline,SliverInline]
1006
Tony Mack5fecf712015-01-12 21:40:09 -05001007 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001008
Siobhan Tully567e3e62013-06-21 18:03:16 -04001009
Tony Mackd90cdbf2013-04-16 22:48:40 -04001010class SliverForm(forms.ModelForm):
1011 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -04001012 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -04001013 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -04001014 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -04001015 widgets = {
1016 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -04001017 'instance_name': PlainTextWidget(),
Scott Baker9d856052015-01-19 11:32:20 -08001018 'instance_id': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001019 'slice': LinkedSelect,
Tony Mackb2dba4b2014-12-26 13:38:02 -05001020 'deployment': LinkedSelect,
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001021 'node': LinkedSelect,
1022 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -04001023 }
Tony Mackd90cdbf2013-04-16 22:48:40 -04001024
Scott Baker67db95f2015-02-18 15:50:11 -08001025class TagAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001026 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1027 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001028 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1029 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -04001030
Scott Baker67db95f2015-02-18 15:50:11 -08001031class SliverAdmin(XOSBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -04001032 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -04001033 fieldsets = [
Tony Mack707f7d72015-01-30 12:52:46 -05001034 ('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 -04001035 ]
Tony Mackdb8580b2015-01-30 17:20:46 -05001036 readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string')
Tony Mack707f7d72015-01-30 12:52:46 -05001037 list_display = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deployment']
Scott Baker2f295402015-02-13 14:38:21 -08001038 list_display_links = ('backend_status_icon', 'all_ips_string', 'instance_id', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001039
Scott Bakerc72997a2015-01-19 08:24:08 -08001040 suit_form_tabs =(('general', 'Sliver Details'),)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001041
Siobhan Tullyde5450d2013-06-21 11:35:33 -04001042 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -04001043
Tony Mackb2dba4b2014-12-26 13:38:02 -05001044 user_readonly_fields = ['slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001045
Scott Baker7ccc6ad2015-01-25 22:16:13 -08001046 def ssh_command(self, obj):
1047 ssh_command = obj.get_ssh_command()
1048 if ssh_command:
1049 return ssh_command
1050 else:
1051 return "(not available)"
1052
Tony Mackc2835a92013-05-28 09:18:49 -04001053 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1054 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001055 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001056
1057 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1058
Tony Mack04062832013-05-10 08:22:44 -04001059 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -07001060 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -04001061 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -05001062 return Sliver.select_by_user(request.user)
1063
Tony Mack04062832013-05-10 08:22:44 -04001064
Tony Mack1d6b85f2013-05-07 18:49:14 -04001065 def get_formsets(self, request, obj=None):
1066 # make some fields read only if we are updating an existing record
1067 if obj == None:
Tony Mackb428feb2015-01-30 17:42:10 -05001068 self.readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string')
Tony Mack1d6b85f2013-05-07 18:49:14 -04001069 else:
Tony Mackb428feb2015-01-30 17:42:10 -05001070 self.readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string', 'slice', 'flavor', 'image', 'node')
Tony Mack1d6b85f2013-05-07 18:49:14 -04001071
1072 for inline in self.get_inline_instances(request, obj):
1073 # hide MyInline in the add view
1074 if obj is None:
1075 continue
Scott Baker526b71e2014-05-13 13:18:01 -07001076 if isinstance(inline, SliverInline):
1077 inline.model.caller = request.user
1078 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -04001079
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001080 #def save_model(self, request, obj, form, change):
1081 # # update openstack connection to use this site/tenant
1082 # auth = request.session.get('auth', {})
1083 # auth['tenant'] = obj.slice.name
1084 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1085 # obj.creator = request.user
1086 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -04001087
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001088 #def delete_model(self, request, obj):
1089 # # update openstack connection to use this site/tenant
1090 # auth = request.session.get('auth', {})
1091 # auth['tenant'] = obj.slice.name
1092 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1093 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -04001094
Siobhan Tully53437282013-04-26 19:30:27 -04001095class UserCreationForm(forms.ModelForm):
1096 """A form for creating new users. Includes all the required
1097 fields, plus a repeated password."""
1098 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1099 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1100
1101 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001102 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001103 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -04001104
1105 def clean_password2(self):
1106 # Check that the two password entries match
1107 password1 = self.cleaned_data.get("password1")
1108 password2 = self.cleaned_data.get("password2")
1109 if password1 and password2 and password1 != password2:
1110 raise forms.ValidationError("Passwords don't match")
1111 return password2
1112
1113 def save(self, commit=True):
1114 # Save the provided password in hashed format
1115 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -04001116 user.password = self.cleaned_data["password1"]
1117 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -04001118 if commit:
1119 user.save()
1120 return user
1121
Siobhan Tully567e3e62013-06-21 18:03:16 -04001122
Siobhan Tully53437282013-04-26 19:30:27 -04001123class UserChangeForm(forms.ModelForm):
1124 """A form for updating users. Includes all the fields on
1125 the user, but replaces the password field with admin's
1126 password hash display field.
1127 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -05001128 password = ReadOnlyPasswordHashField(label='Password',
1129 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -04001130
1131 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001132 model = User
Scott Baker69e045d2014-11-17 23:44:03 -08001133 widgets = { 'public_key': UploadTextareaWidget, }
Siobhan Tully53437282013-04-26 19:30:27 -04001134
1135 def clean_password(self):
1136 # Regardless of what the user provides, return the initial value.
1137 # This is done here, rather than on the field, because the
1138 # field does not have access to the initial value
1139 return self.initial["password"]
1140
Scott Baker67db95f2015-02-18 15:50:11 -08001141class UserDashboardViewInline(XOSTabularInline):
Scott Baker2c3cb642014-05-19 17:55:56 -07001142 model = UserDashboardView
1143 extra = 0
1144 suit_classes = 'suit-tab suit-tab-dashboards'
1145 fields = ['user', 'dashboardView', 'order']
1146
Scott Baker67db95f2015-02-18 15:50:11 -08001147class ControllerUserInline(XOSTabularInline):
Tony Mack30dfcd72015-01-10 23:08:10 -05001148 model = ControllerUser
1149 extra = 0
1150 suit_classes = 'suit-tab suit-tab-admin-only'
1151 fields = ['controller', 'user', 'kuser_id']
1152 readonly_fields=['controller']
1153
1154
Scott Bakerf4aeedc2014-10-03 13:10:47 -07001155class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1156 # Note: Make sure PermissionCheckingAdminMixin is listed before
1157 # admin.ModelAdmin in the class declaration.
1158
Siobhan Tully53437282013-04-26 19:30:27 -04001159 class Meta:
1160 app_label = "core"
1161
1162 # The forms to add and change user instances
1163 form = UserChangeForm
1164 add_form = UserCreationForm
1165
1166 # The fields to be used in displaying the User model.
1167 # These override the definitions on the base UserAdmin
1168 # that reference specific fields on auth.User.
Scott Bakera111f442015-01-24 13:33:26 -08001169 list_display = ('backend_status_icon', 'email', 'firstname', 'lastname', 'site', 'last_login')
1170 list_display_links = ("email",)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001171 list_filter = ('site',)
Scott Baker90472612015-01-29 10:55:53 -08001172 inlines = [SlicePrivilegeInline,SitePrivilegeInline]
Tony Mack30dfcd72015-01-10 23:08:10 -05001173 admin_inlines = [ControllerUserInline]
Scott Baker1a6a3902014-10-03 00:32:37 -07001174 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001175 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1176
Siobhan Tully53437282013-04-26 19:30:27 -04001177 fieldsets = (
Scott Baker40c00762014-08-21 16:55:59 -07001178 ('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 -04001179 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001180 #('Important dates', {'fields': ('last_login',)}),
1181 )
1182 add_fieldsets = (
1183 (None, {
1184 'classes': ('wide',),
Tony Mack365545d2015-01-28 12:03:15 -05001185 'fields': ('site', 'email', 'firstname', 'lastname', 'is_admin', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
Siobhan Tully53437282013-04-26 19:30:27 -04001186 ),
1187 )
Scott Baker40c00762014-08-21 16:55:59 -07001188 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001189 search_fields = ('email',)
1190 ordering = ('email',)
1191 filter_horizontal = ()
1192
Scott Baker3ca51f62014-05-23 12:05:11 -07001193 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001194
Scott Baker6a995352014-10-06 17:51:20 -07001195 @property
1196 def suit_form_tabs(self):
1197 if getattr(_thread_locals, "obj", None) is None:
1198 return []
1199 else:
Tony Mack30dfcd72015-01-10 23:08:10 -05001200 tabs = [('general','Login Details'),
Scott Baker6a995352014-10-06 17:51:20 -07001201 ('contact','Contact Information'),
1202 ('sliceprivileges','Slice Privileges'),
Scott Baker90472612015-01-29 10:55:53 -08001203 ('siteprivileges','Site Privileges')]
Tony Mack30dfcd72015-01-10 23:08:10 -05001204
1205 request=getattr(_thread_locals, "request", None)
1206 if request and request.user.is_admin:
1207 tabs.append( ('admin-only', 'Admin-Only') )
1208
1209 return tabs
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001210
Tony Mackc2835a92013-05-28 09:18:49 -04001211 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1212 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001213 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001214
1215 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1216
Tony Mack5b061472014-02-04 07:57:10 -05001217 def queryset(self, request):
1218 return User.select_by_user(request.user)
1219
Tony Mackc8f443d2015-01-25 21:58:30 -05001220 def get_form(self, request, obj=None, **kwargs):
Tony Mack03b92292015-01-28 12:37:12 -05001221 # copy login details list
1222 login_details_fields = list(self.fieldListLoginDetails)
Tony Mack933b2912015-01-28 12:49:58 -05001223 if not request.user.is_admin:
Scott Baker90472612015-01-29 10:55:53 -08001224 # only admins can see 'is_admin' and 'is_readonly' fields
Tony Mack03b92292015-01-28 12:37:12 -05001225 if 'is_admin' in login_details_fields:
1226 login_details_fields.remove('is_admin')
1227 if 'is_readonly' in login_details_fields:
1228 login_details_fields.remove('is_readonly')
Tony Mack933b2912015-01-28 12:49:58 -05001229 #if len(request.user.siteprivileges.filter(role__role = 'pi')) > 0:
Tony Mack03b92292015-01-28 12:37:12 -05001230 # only admins and pis can change a user's site
Tony Mack933b2912015-01-28 12:49:58 -05001231 # self.readonly_fields = ('backend_status_text', 'site')
Tony Mack03b92292015-01-28 12:37:12 -05001232 self.fieldsets = (
1233 ('Login Details', {'fields': login_details_fields, 'classes':['suit-tab suit-tab-general']}),
1234 ('Contact Information', {'fields': self.fieldListContactInfo, 'classes':['suit-tab suit-tab-contact']}),
1235 )
Tony Mackc8f443d2015-01-25 21:58:30 -05001236 return super(UserAdmin, self).get_form(request, obj, **kwargs)
1237
Scott Baker67db95f2015-02-18 15:50:11 -08001238class ControllerDashboardViewInline(XOSTabularInline):
Scott Bakerf2c0c512014-12-22 17:35:34 -08001239 model = ControllerDashboardView
Scott Baker786a9c12014-12-19 16:41:12 -08001240 extra = 0
1241 fields = ["controller", "url"]
1242 suit_classes = 'suit-tab suit-tab-controllers'
1243
Scott Baker67db95f2015-02-18 15:50:11 -08001244class DashboardViewAdmin(XOSBaseAdmin):
Scott Baker2c3cb642014-05-19 17:55:56 -07001245 fieldsets = [('Dashboard View Details',
Scott Baker59248182015-02-17 13:34:32 -08001246 {'fields': ['backend_status_text', 'name', 'url', 'enabled', 'deployments'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001247 'classes': ['suit-tab suit-tab-general']})
1248 ]
Scott Baker2c44e6e2015-01-18 16:46:26 -08001249 list_display = ["name", "enabled", "url"]
Scott Baker40c00762014-08-21 16:55:59 -07001250 readonly_fields = ('backend_status_text', )
Scott Bakerf2c0c512014-12-22 17:35:34 -08001251 inlines = [ControllerDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001252
Scott Baker786a9c12014-12-19 16:41:12 -08001253 suit_form_tabs =(('general','Dashboard View Details'),
1254 ('controllers', 'Per-controller Dashboard Details'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001255
Scott Baker67db95f2015-02-18 15:50:11 -08001256class ServiceResourceInline(XOSTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001257 model = ServiceResource
1258 extra = 0
1259
Scott Baker67db95f2015-02-18 15:50:11 -08001260class ServiceClassAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001261 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1262 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001263 inlines = [ServiceResourceInline]
1264
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001265 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1266 user_readonly_inlines = []
1267
Scott Baker67db95f2015-02-18 15:50:11 -08001268class ReservedResourceInline(XOSTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001269 model = ReservedResource
1270 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001271 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001272
1273 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1274 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1275
1276 if db_field.name == 'resource':
1277 # restrict resources to those that the slice's service class allows
1278 if request._slice is not None:
1279 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1280 if len(field.queryset) > 0:
1281 field.initial = field.queryset.all()[0]
1282 else:
1283 field.queryset = field.queryset.none()
1284 elif db_field.name == 'sliver':
1285 # restrict slivers to those that belong to the slice
1286 if request._slice is not None:
1287 field.queryset = field.queryset.filter(slice = request._slice)
1288 else:
1289 field.queryset = field.queryset.none()
1290
1291 return field
1292
Tony Mack5b061472014-02-04 07:57:10 -05001293 def queryset(self, request):
1294 return ReservedResource.select_by_user(request.user)
1295
Scott Baker133c9212013-05-17 09:09:11 -07001296class ReservationChangeForm(forms.ModelForm):
1297 class Meta:
1298 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001299 widgets = {
1300 'slice' : LinkedSelect
1301 }
Scott Baker133c9212013-05-17 09:09:11 -07001302
1303class ReservationAddForm(forms.ModelForm):
1304 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1305 refresh = forms.CharField(widget=forms.HiddenInput())
1306
1307 class Media:
Scott Baker06868952015-02-18 15:15:58 -08001308 css = {'all': ('xos.css',)} # .field-refresh { display: none; }
Scott Baker133c9212013-05-17 09:09:11 -07001309
1310 def clean_slice(self):
1311 slice = self.cleaned_data.get("slice")
1312 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1313 if len(x) == 0:
1314 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1315 return slice
1316
1317 class Meta:
1318 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001319 widgets = {
1320 'slice' : LinkedSelect
1321 }
1322
Scott Baker133c9212013-05-17 09:09:11 -07001323
1324class ReservationAddRefreshForm(ReservationAddForm):
1325 """ This form is displayed when the Reservation Form receives an update
1326 from the Slice dropdown onChange handler. It doesn't validate the
1327 data and doesn't save the data. This will cause the form to be
1328 redrawn.
1329 """
1330
Scott Baker8737e5f2013-05-17 09:35:32 -07001331 """ don't validate anything other than slice """
1332 dont_validate_fields = ("startTime", "duration")
1333
Scott Baker133c9212013-05-17 09:09:11 -07001334 def full_clean(self):
1335 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001336
1337 for fieldname in self.dont_validate_fields:
1338 if fieldname in self._errors:
1339 del self._errors[fieldname]
1340
Scott Baker133c9212013-05-17 09:09:11 -07001341 return result
1342
1343 """ don't save anything """
1344 def is_valid(self):
1345 return False
1346
Scott Baker67db95f2015-02-18 15:50:11 -08001347class ReservationAdmin(XOSBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001348 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001349 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001350 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001351 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001352 form = ReservationAddForm
1353
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001354 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1355
1356 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001357 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001358
Scott Baker133c9212013-05-17 09:09:11 -07001359 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001360 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001361 request._refresh = False
1362 request._slice = None
1363 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001364 # "refresh" will be set to "1" if the form was submitted due to
1365 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001366 if request.POST.get("refresh","1") == "1":
1367 request._refresh = True
1368 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001369
1370 # Keep track of the slice that was selected, so the
1371 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001372 request._slice = request.POST.get("slice",None)
1373 if (request._slice is not None):
1374 request._slice = Slice.objects.get(id=request._slice)
1375
1376 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1377 return result
1378
Scott Bakeracd45142013-05-19 16:19:16 -07001379 def changelist_view(self, request, extra_context = None):
1380 timezone.activate(request.user.timezone)
1381 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1382
Scott Baker133c9212013-05-17 09:09:11 -07001383 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001384 request._obj_ = obj
1385 if obj is not None:
1386 # For changes, set request._slice to the slice already set in the
1387 # object.
1388 request._slice = obj.slice
1389 self.form = ReservationChangeForm
1390 else:
1391 if getattr(request, "_refresh", False):
1392 self.form = ReservationAddRefreshForm
1393 else:
1394 self.form = ReservationAddForm
1395 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1396
Scott Baker133c9212013-05-17 09:09:11 -07001397 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001398 if (obj is not None):
1399 # Prevent slice from being changed after the reservation has been
1400 # created.
1401 return ['slice']
1402 else:
Scott Baker133c9212013-05-17 09:09:11 -07001403 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001404
Tony Mack5b061472014-02-04 07:57:10 -05001405 def queryset(self, request):
1406 return Reservation.select_by_user(request.user)
1407
Scott Baker67db95f2015-02-18 15:50:11 -08001408class NetworkParameterTypeAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001409 list_display = ("backend_status_icon", "name", )
1410 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001411 user_readonly_fields = ['name']
1412 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001413
Scott Baker67db95f2015-02-18 15:50:11 -08001414class RouterAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001415 list_display = ("backend_status_icon", "name", )
1416 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001417 user_readonly_fields = ['name']
1418 user_readonly_inlines = []
1419
Scott Baker67db95f2015-02-18 15:50:11 -08001420class RouterInline(XOSTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001421 model = Router.networks.through
1422 extra = 0
1423 verbose_name_plural = "Routers"
1424 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001425 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001426
Scott Bakerb27b62c2014-08-15 16:29:16 -07001427class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001428 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001429 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001430 verbose_name_plural = "Parameters"
1431 verbose_name = "Parameter"
1432 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001433 fields = ['backend_status_icon', 'parameter', 'value']
1434 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001435
Scott Baker67db95f2015-02-18 15:50:11 -08001436class NetworkSliversInline(XOSTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001437 fields = ['backend_status_icon', 'network','sliver','ip']
1438 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001439 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001440 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001441 extra = 0
1442 verbose_name_plural = "Slivers"
1443 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001444 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001445
Scott Baker67db95f2015-02-18 15:50:11 -08001446class NetworkSlicesInline(XOSTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001447 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001448 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001449 extra = 0
1450 verbose_name_plural = "Slices"
1451 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001452 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001453 fields = ['backend_status_icon', 'network','slice']
1454 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001455
Scott Baker67db95f2015-02-18 15:50:11 -08001456class ControllerNetworkInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -05001457 model = ControllerNetwork
Scott Baker8806cdf2014-10-17 16:27:23 -07001458 extra = 0
Tony Mack06c8e472014-11-30 15:53:08 -05001459 verbose_name_plural = "Controller Networks"
1460 verbose_name = "Controller Network"
Scott Baker8806cdf2014-10-17 16:27:23 -07001461 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack06c8e472014-11-30 15:53:08 -05001462 fields = ['backend_status_icon', 'controller','net_id','subnet_id']
Scott Baker8806cdf2014-10-17 16:27:23 -07001463 readonly_fields = ('backend_status_icon', )
1464
Scott Baker69e045d2014-11-17 23:44:03 -08001465class NetworkForm(forms.ModelForm):
1466 class Meta:
1467 model = Network
1468 widgets = {
1469 'topologyParameters': UploadTextareaWidget,
1470 'controllerParameters': UploadTextareaWidget,
1471 }
1472
Scott Baker67db95f2015-02-18 15:50:11 -08001473class NetworkAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001474 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1475 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001476 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001477
Scott Bakerd7d2a392013-08-06 08:57:30 -07001478 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Tony Mack3066a952015-01-05 22:48:11 -05001479 admin_inlines = [ControllerNetworkInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001480
Scott Baker69e045d2014-11-17 23:44:03 -08001481 form=NetworkForm
1482
Siobhan Tully2d95e482013-09-06 10:56:06 -04001483 fieldsets = [
Scott Baker549aa252015-01-03 12:29:29 -08001484 (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 -08001485 'classes':['suit-tab suit-tab-general']}),
Scott Baker549aa252015-01-03 12:29:29 -08001486 (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
Scott Baker3e28dd72014-11-17 16:04:45 -08001487 'classes':['suit-tab suit-tab-sdn']}),
1488 ]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001489
Scott Baker40c00762014-08-21 16:55:59 -07001490 readonly_fields = ('backend_status_text', )
Scott Baker549aa252015-01-03 12:29:29 -08001491 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 -04001492
Scott Baker8806cdf2014-10-17 16:27:23 -07001493 @property
1494 def suit_form_tabs(self):
1495 tabs=[('general','Network Details'),
Scott Baker3e28dd72014-11-17 16:04:45 -08001496 ('sdn', 'SDN Configuration'),
Scott Baker8806cdf2014-10-17 16:27:23 -07001497 ('netparams', 'Parameters'),
1498 ('networkslivers','Slivers'),
1499 ('networkslices','Slices'),
1500 ('routers','Routers'),
1501 ]
1502
1503 request=getattr(_thread_locals, "request", None)
1504 if request and request.user.is_admin:
1505 tabs.append( ('admin-only', 'Admin-Only') )
1506
1507 return tabs
1508
1509
Scott Baker67db95f2015-02-18 15:50:11 -08001510class NetworkTemplateAdmin(XOSBaseAdmin):
Scott Baker369f9b92015-01-03 12:03:38 -08001511 list_display = ("backend_status_icon", "name", "guaranteed_bandwidth", "visibility")
Scott Baker63d1a552014-08-21 15:19:07 -07001512 list_display_links = ('backend_status_icon', 'name', )
Scott Baker369f9b92015-01-03 12:03:38 -08001513 user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001514 user_readonly_inlines = []
Scott Baker3e28dd72014-11-17 16:04:45 -08001515 fieldsets = [
Scott Baker369f9b92015-01-03 12:03:38 -08001516 (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 -08001517 'classes':['suit-tab suit-tab-general']}),]
1518 suit_form_tabs = (('general','Network Template Details'), )
Scott Baker74d8e622013-07-29 16:04:22 -07001519
Scott Baker67db95f2015-02-18 15:50:11 -08001520class FlavorAdmin(XOSBaseAdmin):
Scott Baker37b47902014-09-02 14:37:41 -07001521 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1522 list_display_links = ("backend_status_icon", "name")
1523 user_readonly_fields = ("name", "flavor")
1524 fields = ("name", "description", "flavor", "order", "default")
1525
Tony Mack31c2b8f2013-04-26 20:01:42 -04001526# register a signal that caches the user's credentials when they log in
1527def cache_credentials(sender, user, request, **kwds):
1528 auth = {'username': request.POST['username'],
1529 'password': request.POST['password']}
1530 request.session['auth'] = auth
1531user_logged_in.connect(cache_credentials)
1532
Scott Baker15cddfa2013-12-09 13:45:19 -08001533def dollar_field(fieldName, short_description):
1534 def newFunc(self, obj):
1535 try:
1536 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1537 except:
1538 x=getattr(obj, fieldName, 0.0)
1539 return x
1540 newFunc.short_description = short_description
1541 return newFunc
1542
1543def right_dollar_field(fieldName, short_description):
1544 def newFunc(self, obj):
1545 try:
1546 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1547 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1548 except:
1549 x=getattr(obj, fieldName, 0.0)
1550 return x
1551 newFunc.short_description = short_description
1552 newFunc.allow_tags = True
1553 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001554
Scott Baker67db95f2015-02-18 15:50:11 -08001555class InvoiceChargeInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001556 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001557 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001558 verbose_name_plural = "Charges"
1559 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001560 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001561 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1562 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1563 can_delete = False
1564 max_num = 0
1565
1566 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001567
1568class InvoiceAdmin(admin.ModelAdmin):
1569 list_display = ("date", "account")
1570
1571 inlines = [InvoiceChargeInline]
1572
Scott Baker9cb88a22013-12-09 18:56:00 -08001573 fields = ["date", "account", "dollar_amount"]
1574 readonly_fields = ["date", "account", "dollar_amount"]
1575
1576 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001577
Scott Baker67db95f2015-02-18 15:50:11 -08001578class InvoiceInline(XOSTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001579 model = Invoice
1580 extra = 0
1581 verbose_name_plural = "Invoices"
1582 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001583 fields = ["date", "dollar_amount"]
1584 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001585 suit_classes = 'suit-tab suit-tab-accountinvoice'
1586 can_delete=False
1587 max_num=0
1588
1589 dollar_amount = right_dollar_field("amount", "Amount")
1590
Scott Baker67db95f2015-02-18 15:50:11 -08001591class PendingChargeInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001592 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001593 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001594 verbose_name_plural = "Charges"
1595 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001596 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001597 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1598 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001599 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001600 can_delete=False
1601 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001602
1603 def queryset(self, request):
1604 qs = super(PendingChargeInline, self).queryset(request)
1605 qs = qs.filter(state="pending")
1606 return qs
1607
Scott Baker15cddfa2013-12-09 13:45:19 -08001608 dollar_amount = right_dollar_field("amount", "Amount")
1609
Scott Baker67db95f2015-02-18 15:50:11 -08001610class PaymentInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001611 model=Payment
1612 extra = 1
1613 verbose_name_plural = "Payments"
1614 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001615 fields = ["date", "dollar_amount"]
1616 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001617 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001618 can_delete=False
1619 max_num=0
1620
1621 dollar_amount = right_dollar_field("amount", "Amount")
1622
Scott Baker43105042013-12-06 23:23:36 -08001623class AccountAdmin(admin.ModelAdmin):
1624 list_display = ("site", "balance_due")
1625
1626 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1627
1628 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001629 (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 -08001630
Scott Baker15cddfa2013-12-09 13:45:19 -08001631 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001632
1633 suit_form_tabs =(
1634 ('general','Account Details'),
1635 ('accountinvoice', 'Invoices'),
1636 ('accountpayments', 'Payments'),
1637 ('accountpendingcharges','Pending Charges'),
1638 )
1639
Scott Baker15cddfa2013-12-09 13:45:19 -08001640 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1641 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1642 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1643
Siobhan Tully53437282013-04-26 19:30:27 -04001644# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001645admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001646# ... and, since we're not using Django's builtin permissions,
1647# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001648#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001649
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001650#Do not show django evolution in the admin interface
1651from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001652#admin.site.unregister(Version)
1653#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001654
1655
1656# When debugging it is often easier to see all the classes, but for regular use
1657# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001658showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001659
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001660admin.site.register(Deployment, DeploymentAdmin)
Tony Mack06c8e472014-11-30 15:53:08 -05001661admin.site.register(Controller, ControllerAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001662admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001663admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001664admin.site.register(Service, ServiceAdmin)
Tony Mack450b6e02015-01-25 12:35:29 -05001665#admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001666admin.site.register(Network, NetworkAdmin)
1667admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001668admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Tony Mack450b6e02015-01-25 12:35:29 -05001669#admin.site.register(Account, AccountAdmin)
1670#admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001671
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001672if True:
1673 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1674 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001675 #admin.site.register(PlanetStack)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001676 admin.site.register(Tag, TagAdmin)
Tony Mack06c8e472014-11-30 15:53:08 -05001677 admin.site.register(ControllerRole)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001678 admin.site.register(SiteRole)
1679 admin.site.register(SliceRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001680 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001681 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1682 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001683 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001684 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001685 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07001686 admin.site.register(Flavor, FlavorAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001687