blob: ee6c6bf363ca94a16655c5e7d27f19e74521a2dd [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 Baker9f6b8ed2014-11-17 23:44:03 -080010from django.contrib.admin.widgets import FilteredSelectMultiple, AdminTextareaWidget
Scott Bakercbfb6002014-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 Mack7b6400d2015-02-16 19:54:24 -050017from django.core.urlresolvers import reverse, resolve, NoReverseMatch
Scott Baker9f6b8ed2014-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
Scott Baker7a056af2015-02-26 20:42:11 -080020from django.utils.text import capfirst
Scott Baker9f6b8ed2014-11-17 23:44:03 -080021from django.forms.utils import flatatt, to_current_timezone
Scott Baker2d281a92015-07-09 19:06:08 -070022from django.core.exceptions import PermissionDenied, ValidationError
Scott Baker3cde7372014-10-21 21:03:08 -070023from cgi import escape as html_escape
Scott Baker2d281a92015-07-09 19:06:08 -070024from django.contrib import messages
Tony Mack7130ac32013-03-22 21:58:00 -040025
Scott Baker0a5633b2014-10-06 17:51:20 -070026import threading
27
28# thread locals necessary to work around a django-suit issue
29_thread_locals = threading.local()
Scott Baker36f50872014-08-21 13:01:25 -070030
Scott Bakerb6b474d2015-02-10 18:24:20 -080031ICON_URLS = {"success": "/static/admin/img/icon_success.gif",
32 "clock": "/static/admin/img/icon_clock.gif",
33 "error": "/static/admin/img/icon_error.gif"}
34
35def backend_icon(obj):
36 (icon, tooltip) = obj.get_backend_icon()
Sapan Bhatia5ebfe8c2015-12-22 18:37:47 +010037
Scott Bakerb6b474d2015-02-10 18:24:20 -080038 icon_url = ICON_URLS.get(icon, "unknown")
39
Sapan Bhatia5ebfe8c2015-12-22 18:37:47 +010040 (exponent,last_success,last_failure,failures) = obj.get_backend_details()
41
Sapan Bhatia0253cf22015-12-22 18:38:36 +010042 # FIXME: Need to clean this up by separating Javascript from Python
Sapan Bhatia5ebfe8c2015-12-22 18:37:47 +010043 if (obj.pk):
44 script = """
Matteo Scandolo4647b672016-02-11 13:12:47 -080045 <script type="text/javascript">$(document).ready(function () {$("#show_details_%d").click(function () {$("#status%d").dialog({modal: true, height: 200, width: 200 });});});</script>
Sapan Bhatia5ebfe8c2015-12-22 18:37:47 +010046 """%(obj.pk,obj.pk)
47
48 div = """
49 <div style="display:none;" id="status%d" title="Details">
50 <p>Backoff Exponent: %r</p>
51 <p>Last Success: %r</p>
52 <p>Failures: %r</p>
53 <p>Last Failure: %r</p>
54 </div>
55 """%(obj.pk,exponent,last_success,failures,last_failure)
56 a = '<a id="show_details_%d" href="#">'%obj.pk
57 astop = '</a>'
58 else:
59 div = ''
60 script = ''
61 a = ''
62 astop = ''
63
Scott Bakerb6b474d2015-02-10 18:24:20 -080064 if tooltip:
Sapan Bhatia5ebfe8c2015-12-22 18:37:47 +010065 return '%s %s <span style="min-width:16px;" title="%s">%s<img src="%s">%s</span>' % (script, div, tooltip, a, icon_url, astop)
Scott Baker40c00762014-08-21 16:55:59 -070066 else:
Scott Bakerb6b474d2015-02-10 18:24:20 -080067 return '<span style="min-width:16px;"><img src="%s"></span>' % icon_url
Scott Baker40c00762014-08-21 16:55:59 -070068
69def backend_text(obj):
Scott Bakerb6b474d2015-02-10 18:24:20 -080070 (icon, tooltip) = obj.get_backend_icon()
71 icon_url = ICON_URLS.get(icon, "unknown")
72
73 return '<img src="%s"> %s' % (icon_url, tooltip)
Scott Baker63d1a552014-08-21 15:19:07 -070074
Scott Baker9f6b8ed2014-11-17 23:44:03 -080075class UploadTextareaWidget(AdminTextareaWidget):
76 def render(self, name, value, attrs=None):
77 if value is None:
S.Çağlar Onur0e591832015-02-24 17:28:09 -050078 value = ''
79 final_attrs = self.build_attrs(attrs, name=name)
80 return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">' \
81 '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>' \
82 '<br><textarea{0}>\r\n{1}</textarea>' % (attrs["id"], attrs["id"], attrs["id"]),
83 flatatt(final_attrs),
Scott Baker9f6b8ed2014-11-17 23:44:03 -080084 force_text(value))
85
Scott Bakerb92b5c72015-05-11 16:36:58 -070086class SliderWidget(forms.HiddenInput):
87 def render(self, name, value, attrs=None):
88 if value is None:
89 value = '0'
90 final_attrs = self.build_attrs(attrs, name=name)
91 attrs = attrs or attrs[:]
92 attrs["name"] = name
93 attrs["value"] = value
94 html = """<div style="width:640px"><span id="%(id)s_label">%(value)s</span><div id="%(id)s_slider" style="float:right;width:610px;margin-top:5px"></div></div>
95 <script>
96 $(function() {
97 $("#%(id)s_slider").slider({
98 value: %(value)s,
99 slide: function(event, ui) { $("#%(id)s").val( ui.value ); $("#%(id)s_label").html(ui.value); },
100 });
101 });
102 </script>
103 <input type="hidden" id="%(id)s" name="%(name)s" value="%(value)s"></input>
104 """ % attrs
105 html = html.replace("{","{{").replace("}","}}")
106 return format_html(html,
107 flatatt(final_attrs),
108 force_text(value))
109
110
Scott Baker36f50872014-08-21 13:01:25 -0700111class PlainTextWidget(forms.HiddenInput):
112 input_type = 'hidden'
113
114 def render(self, name, value, attrs=None):
115 if value is None:
116 value = ''
117 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
118
Scott Bakera9412c32015-02-27 12:21:22 -0800119class XOSAdminMixin(object):
Scott Bakercbfb6002014-10-03 00:32:37 -0700120 # call save_by_user and delete_by_user instead of save and delete
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500121
122 def has_add_permission(self, request, obj=None):
123 return (not self.__user_is_readonly(request))
Scott Baker36f50872014-08-21 13:01:25 -0700124
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500125 def has_delete_permission(self, request, obj=None):
126 return (not self.__user_is_readonly(request))
127
128 def save_model(self, request, obj, form, change):
129 if self.__user_is_readonly(request):
Scott Bakercbfb6002014-10-03 00:32:37 -0700130 # this 'if' might be redundant if save_by_user is implemented right
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500131 raise PermissionDenied
Scott Bakercbfb6002014-10-03 00:32:37 -0700132
Scott Bakerbf08f2f2015-12-01 10:24:01 -0800133 # reset exponential backoff
134 if hasattr(obj, "backend_register"):
135 obj.backend_register = "{}"
136
Scott Bakercbfb6002014-10-03 00:32:37 -0700137 obj.caller = request.user
138 # update openstack connection to use this site/tenant
139 obj.save_by_user(request.user)
140
141 def delete_model(self, request, obj):
142 obj.delete_by_user(request.user)
143
144 def save_formset(self, request, form, formset, change):
145 instances = formset.save(commit=False)
146 for instance in instances:
Scott Bakercf9cbad2015-07-08 18:23:17 -0700147 instance.caller = request.user
Scott Bakercbfb6002014-10-03 00:32:37 -0700148 instance.save_by_user(request.user)
149
150 # BUG in django 1.7? Objects are not deleted by formset.save if
151 # commit is False. So let's delete them ourselves.
152 #
153 # code from forms/models.py save_existing_objects()
154 try:
S.Çağlar Onur0e591832015-02-24 17:28:09 -0500155 forms_to_delete = formset.deleted_forms
156 except AttributeError:
Scott Bakercbfb6002014-10-03 00:32:37 -0700157 forms_to_delete = []
158 if formset.initial_forms:
159 for form in formset.initial_forms:
160 obj = form.instance
161 if form in forms_to_delete:
162 if obj.pk is None:
163 continue
164 formset.deleted_objects.append(obj)
165 obj.delete()
166
167 formset.save_m2m()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500168
169 def get_actions(self,request):
Scott Bakera9412c32015-02-27 12:21:22 -0800170 actions = super(XOSAdminMixin,self).get_actions(request)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500171
172 if self.__user_is_readonly(request):
173 if 'delete_selected' in actions:
174 del actions['delete_selected']
175
176 return actions
177
Scott Bakerff3c2d22015-04-03 17:44:31 -0700178 def url_for_model_changelist(self, request, model):
179 # used in add_extra_context
180 return reverse('admin:%s_%s_changelist' % (model._meta.app_label, model._meta.model_name), current_app=model._meta.app_label)
181
Scott Bakerba534e72015-04-02 22:32:40 -0700182 def add_extra_context(self, request, extra_context):
Scott Bakerc481b322015-02-27 12:12:14 -0800183 # allow custom application breadcrumb url and name
184 extra_context["custom_app_breadcrumb_url"] = getattr(self, "custom_app_breadcrumb_url", None)
185 extra_context["custom_app_breadcrumb_name"] = getattr(self, "custom_app_breadcrumb_name", None)
Scott Baker0998bcc2015-04-02 22:07:18 -0700186 extra_context["custom_changelist_breadcrumb_url"] = getattr(self, "custom_changelist_breadcrumb_url", None)
Scott Bakerc481b322015-02-27 12:12:14 -0800187
188 # for Service admins to render their Administration page
189 if getattr(self, "extracontext_registered_admins", False):
190 admins=[]
Sapan Bhatia67ea0d72016-01-14 11:41:38 -0500191 for model, model_admin in admin.site._registry.items():
192 if model == self.model:
193 continue
194 if model._meta.app_label == self.model._meta.app_label:
195 info = {"app": model._meta.app_label,
196 "model": model._meta.model_name,
197 "name": capfirst(model._meta.verbose_name_plural),
198 "url": self.url_for_model_changelist(request,model) }
199 admins.append(info)
Scott Bakerc481b322015-02-27 12:12:14 -0800200 extra_context["registered_admins"] = admins
201
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500202 def change_view(self,request,object_id, extra_context=None):
Scott Bakerc481b322015-02-27 12:12:14 -0800203 extra_context = extra_context or {}
204
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500205 if self.__user_is_readonly(request):
S.Çağlar Onur0e591832015-02-24 17:28:09 -0500206 if not hasattr(self, "readonly_save"):
207 # save the original readonly fields
208 self.readonly_save = self.readonly_fields
209 self.inlines_save = self.inlines
210 if hasattr(self, "user_readonly_fields"):
211 self.readonly_fields=self.user_readonly_fields
212 if hasattr(self, "user_readonly_inlines"):
213 self.inlines = self.user_readonly_inlines
214 else:
215 if hasattr(self, "readonly_save"):
216 # restore the original readonly fields
217 self.readonly_fields = self.readonly_save
218 if hasattr(self, "inlines_save"):
Scott Bakeraf73e102014-04-22 22:40:07 -0700219 self.inlines = self.inlines_save
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500220
Scott Bakerba534e72015-04-02 22:32:40 -0700221 self.add_extra_context(request, extra_context)
Scott Bakerc481b322015-02-27 12:12:14 -0800222
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500223 try:
Scott Bakera9412c32015-02-27 12:21:22 -0800224 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500225 except PermissionDenied:
226 pass
Scott Baker2d281a92015-07-09 19:06:08 -0700227 except ValidationError as e:
228 if (e.params is None):
229 # Validation errors that don't reference a specific field will
230 # often throw a non-descriptive 500 page to the user. The code
231 # below will cause an error message to be printed and the
232 # page refreshed instead.
233 # As a side-effect it turns the request back into a 'GET' which
234 # may wipe anything the user had changed on the page. But, at
235 # least the user gets a real error message.
236 # TODO: revisit this and display some kind of error view
237 request.method = 'GET'
238 messages.error(request, e.message)
239 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
240 else:
241 raise
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500242 if request.method == 'POST':
243 raise PermissionDenied
244 request.readonly = True
Scott Bakera9412c32015-02-27 12:21:22 -0800245 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500246
Scott Bakerc481b322015-02-27 12:12:14 -0800247 def changelist_view(self, request, extra_context = None):
248 extra_context = extra_context or {}
249
Scott Bakerba534e72015-04-02 22:32:40 -0700250 self.add_extra_context(request, extra_context)
Scott Bakerc481b322015-02-27 12:12:14 -0800251
Scott Bakera9412c32015-02-27 12:21:22 -0800252 return super(XOSAdminMixin, self).changelist_view(request, extra_context=extra_context)
Scott Bakerc481b322015-02-27 12:12:14 -0800253
Scott Baker2aea0362015-04-14 17:01:18 -0700254 def add_view(self, request, form_url='', extra_context = None):
Scott Bakerff3c2d22015-04-03 17:44:31 -0700255 extra_context = extra_context or {}
256
257 self.add_extra_context(request, extra_context)
258
Scott Baker2aea0362015-04-14 17:01:18 -0700259 return super(XOSAdminMixin, self).add_view(request, form_url, extra_context=extra_context)
Scott Bakerff3c2d22015-04-03 17:44:31 -0700260
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500261 def __user_is_readonly(self, request):
262 return request.user.isReadOnlyUser()
263
Scott Baker40c00762014-08-21 16:55:59 -0700264 def backend_status_text(self, obj):
265 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -0700266
Scott Baker63d1a552014-08-21 15:19:07 -0700267 def backend_status_icon(self, obj):
Scott Baker40c00762014-08-21 16:55:59 -0700268 return mark_safe(backend_icon(obj))
Scott Baker63d1a552014-08-21 15:19:07 -0700269 backend_status_icon.short_description = ""
270
Scott Baker24ded6a2014-11-05 09:05:38 -0800271 def get_form(self, request, obj=None, **kwargs):
Scott Baker5c432692014-10-16 00:57:55 -0700272 # Save obj and request in thread-local storage, so suit_form_tabs can
273 # use it to determine whether we're in edit or add mode, and can
274 # determine whether the user is an admin.
275 _thread_locals.request = request
276 _thread_locals.obj = obj
Scott Bakera9412c32015-02-27 12:21:22 -0800277 return super(XOSAdminMixin, self).get_form(request, obj, **kwargs)
Scott Baker5c432692014-10-16 00:57:55 -0700278
279 def get_inline_instances(self, request, obj=None):
Scott Bakera9412c32015-02-27 12:21:22 -0800280 inlines = super(XOSAdminMixin, self).get_inline_instances(request, obj)
Scott Baker5c432692014-10-16 00:57:55 -0700281
282 # inlines that should only be shown to an admin user
283 if request.user.is_admin:
284 for inline_class in getattr(self, "admin_inlines", []):
285 inlines.append(inline_class(self.model, self.admin_site))
286
287 return inlines
288
Scott Bakera9412c32015-02-27 12:21:22 -0800289class ReadOnlyAwareAdmin(XOSAdminMixin, admin.ModelAdmin):
290 # Note: Make sure XOSAdminMixin is listed before
Scott Baker86c83ab2014-10-03 13:10:47 -0700291 # admin.ModelAdmin in the class declaration.
292
Scott Bakercbfb6002014-10-03 00:32:37 -0700293 pass
294
Scott Baker022cdcd2015-02-18 15:50:11 -0800295class XOSBaseAdmin(ReadOnlyAwareAdmin):
Scott Bakercbfb6002014-10-03 00:32:37 -0700296 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700297
Scott Bakere8859f92014-05-23 12:42:40 -0700298class SingletonAdmin (ReadOnlyAwareAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400299 def has_add_permission(self, request):
Scott Bakere8859f92014-05-23 12:42:40 -0700300 if not super(SingletonAdmin, self).has_add_permission(request):
301 return False
302
Siobhan Tullyce652d02013-10-08 21:52:35 -0400303 num_objects = self.model.objects.count()
304 if num_objects >= 1:
305 return False
306 else:
307 return True
308
Scott Baker7a056af2015-02-26 20:42:11 -0800309class ServiceAppAdmin (SingletonAdmin):
Scott Bakerc481b322015-02-27 12:12:14 -0800310 extracontext_registered_admins = True
Scott Baker7a056af2015-02-26 20:42:11 -0800311
Scott Baker022cdcd2015-02-18 15:50:11 -0800312class XOSTabularInline(admin.TabularInline):
Scott Baker86568322014-01-12 16:53:31 -0800313 def __init__(self, *args, **kwargs):
Scott Baker022cdcd2015-02-18 15:50:11 -0800314 super(XOSTabularInline, self).__init__(*args, **kwargs)
Scott Baker86568322014-01-12 16:53:31 -0800315
316 # InlineModelAdmin as no get_fields() method, so in order to add
317 # the selflink field, we override __init__ to modify self.fields and
318 # self.readonly_fields.
319
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800320 self.setup_selflink()
321
Scott Bakerd5df9502015-07-24 09:32:14 -0700322 @property
323 def selflink_model(self):
324 if hasattr(self, "selflink_fieldname"):
325 """ self.selflink_model can be defined to punch through a relation
326 to its target object. For example, in SliceNetworkInline, set
327 selflink_model = "network", and the URL will lead to the Network
328 object instead of trying to bring up a change view of the
329 SliceNetwork object.
330 """
331 return getattr(self.model,self.selflink_fieldname).field.rel.to
332 else:
333 return self.model
334
335 @property
336 def selflink_reverse_path(self):
337 return "admin:%s_change" % (self.selflink_model._meta.db_table)
338
339 def get_change_url(self, id):
Scott Baker874936e2014-01-13 18:15:34 -0800340 """ Get the URL to a change form in the admin for this model """
Scott Baker29786782015-07-27 08:53:05 -0700341 reverse_path = self.selflink_reverse_path # "admin:%s_change" % (self.selflink_model._meta.db_table)
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800342 try:
Scott Baker874936e2014-01-13 18:15:34 -0800343 url = reverse(reverse_path, args=(id,))
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800344 except NoReverseMatch:
Scott Baker874936e2014-01-13 18:15:34 -0800345 return None
346
347 return url
348
349 def setup_selflink(self):
Scott Bakerd5df9502015-07-24 09:32:14 -0700350 url = self.get_change_url(0)
Scott Baker874936e2014-01-13 18:15:34 -0800351
352 # We don't have an admin for this object, so don't create the
353 # selflink.
354 if (url == None):
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800355 return
356
Scott Baker874936e2014-01-13 18:15:34 -0800357 # Since we need to add "selflink" to the field list, we need to create
358 # self.fields if it is None.
Scott Baker0165fac2014-01-13 11:49:26 -0800359 if (self.fields is None):
360 self.fields = []
361 for f in self.model._meta.fields:
362 if f.editable and f.name != "id":
363 self.fields.append(f.name)
Scott Baker86568322014-01-12 16:53:31 -0800364
Scott Baker874936e2014-01-13 18:15:34 -0800365 self.fields = tuple(self.fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800366
Scott Baker874936e2014-01-13 18:15:34 -0800367 if self.readonly_fields is None:
368 self.readonly_fields = ()
Scott Baker86568322014-01-12 16:53:31 -0800369
Scott Baker874936e2014-01-13 18:15:34 -0800370 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800371
372 def selflink(self, obj):
Scott Baker874936e2014-01-13 18:15:34 -0800373 if hasattr(self, "selflink_fieldname"):
374 obj = getattr(obj, self.selflink_fieldname)
375
Scott Baker86568322014-01-12 16:53:31 -0800376 if obj.id:
Scott Bakerd5df9502015-07-24 09:32:14 -0700377 url = self.get_change_url(obj.id)
Scott Baker874936e2014-01-13 18:15:34 -0800378 return "<a href='%s'>Details</a>" % str(url)
S.Çağlar Onur0e591832015-02-24 17:28:09 -0500379 else:
380 return "Not present"
Scott Baker86568322014-01-12 16:53:31 -0800381
382 selflink.allow_tags = True
383 selflink.short_description = "Details"
Siobhan Tullyd3515752013-06-21 16:34:53 -0400384
Scott Bakerb27b62c2014-08-15 16:29:16 -0700385 def has_add_permission(self, request):
386 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500387
388 def get_readonly_fields(self, request, obj=None):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700389 readonly_fields = list(self.readonly_fields)[:]
390 if request.user.isReadOnlyUser():
391 for field in self.fields:
392 if not field in readonly_fields:
393 readonly_fields.append(field)
394 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500395
Scott Baker40c00762014-08-21 16:55:59 -0700396 def backend_status_icon(self, obj):
397 return mark_safe(backend_icon(obj))
398 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -0700399
Scott Bakerb27b62c2014-08-15 16:29:16 -0700400class PlStackGenericTabularInline(generic.GenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500401 def has_add_permission(self, request):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700402 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500403
Scott Bakerb27b62c2014-08-15 16:29:16 -0700404 def get_readonly_fields(self, request, obj=None):
405 readonly_fields = list(self.readonly_fields)[:]
406 if request.user.isReadOnlyUser():
407 for field in self.fields:
408 if not field in readonly_fields:
409 readonly_fields.append(field)
410 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500411
Scott Baker40c00762014-08-21 16:55:59 -0700412 def backend_status_icon(self, obj):
413 return mark_safe(backend_icon(obj))
414 backend_status_icon.short_description = ""
415
Scott Baker022cdcd2015-02-18 15:50:11 -0800416class ReservationInline(XOSTabularInline):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400417 model = Reservation
418 extra = 0
419 suit_classes = 'suit-tab suit-tab-reservations'
Scott Baker36f50872014-08-21 13:01:25 -0700420
Tony Mack5b061472014-02-04 07:57:10 -0500421 def queryset(self, request):
422 return Reservation.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400423
Scott Bakerb27b62c2014-08-15 16:29:16 -0700424class TagInline(PlStackGenericTabularInline):
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400425 model = Tag
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400426 extra = 0
427 suit_classes = 'suit-tab suit-tab-tags'
Tony Mack5b061472014-02-04 07:57:10 -0500428 fields = ['service', 'name', 'value']
429
430 def queryset(self, request):
431 return Tag.select_by_user(request.user)
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400432
Tony Mack3de59e32015-08-19 11:58:18 -0400433class InstanceInline(XOSTabularInline):
434 model = Instance
Scott Baker4103eb32015-09-21 14:52:15 -0700435 fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'deployment', 'flavor', 'image', 'node']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400436 extra = 0
Scott Baker4103eb32015-09-21 14:52:15 -0700437 max_num = 0
Scott Baker0befcd72015-09-21 15:10:18 -0700438 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'deployment', 'flavor', 'image', 'node']
Tony Mack3de59e32015-08-19 11:58:18 -0400439 suit_classes = 'suit-tab suit-tab-instances'
Scott Baker74d8e622013-07-29 16:04:22 -0700440
Tony Mack5b061472014-02-04 07:57:10 -0500441 def queryset(self, request):
Tony Mack3de59e32015-08-19 11:58:18 -0400442 return Instance.select_by_user(request.user)
Tony Mack5b061472014-02-04 07:57:10 -0500443
Scott Bakerb24cc932014-06-09 10:51:16 -0700444 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Tony Mackbf6aa302014-12-26 13:38:02 -0500445 if db_field.name == 'deployment':
Tony Mackbd908ae2015-02-24 15:41:49 -0500446 kwargs['queryset'] = Deployment.select_by_acl(request.user).filter(sitedeployments__nodes__isnull=False).distinct()
Tony Mack3de59e32015-08-19 11:58:18 -0400447 kwargs['widget'] = forms.Select(attrs={'onChange': "instance_deployment_changed(this);"})
Tony Mackbf6aa302014-12-26 13:38:02 -0500448 if db_field.name == 'flavor':
Tony Mack3de59e32015-08-19 11:58:18 -0400449 kwargs['widget'] = forms.Select(attrs={'onChange': "instance_flavor_changed(this);"})
Scott Baker3b678742014-06-09 13:11:54 -0700450
Tony Mack3de59e32015-08-19 11:58:18 -0400451 field = super(InstanceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700452
453 return field
454
Tony Mack3de59e32015-08-19 11:58:18 -0400455class CordInstanceInline(XOSTabularInline):
456 model = Instance
Scott Baker0e289fd2015-06-12 10:40:15 -0700457 fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'flavor', 'image', 'node']
458 extra = 0
459 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name']
Tony Mack3de59e32015-08-19 11:58:18 -0400460 suit_classes = 'suit-tab suit-tab-instances'
Scott Baker0e289fd2015-06-12 10:40:15 -0700461
462 def queryset(self, request):
Tony Mack3de59e32015-08-19 11:58:18 -0400463 return Instance.select_by_user(request.user)
Scott Baker0e289fd2015-06-12 10:40:15 -0700464
465 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
466 if db_field.name == 'deployment':
467
468 kwargs['queryset'] = Deployment.select_by_acl(request.user).filter(sitedeployments__nodes__isnull=False).distinct()
Tony Mack3de59e32015-08-19 11:58:18 -0400469 kwargs['widget'] = forms.Select(attrs={'onChange': "instance_deployment_changed(this);"})
Scott Baker0e289fd2015-06-12 10:40:15 -0700470 if db_field.name == 'flavor':
Tony Mack3de59e32015-08-19 11:58:18 -0400471 kwargs['widget'] = forms.Select(attrs={'onChange': "instance_flavor_changed(this);"})
Scott Baker0e289fd2015-06-12 10:40:15 -0700472
Tony Mack3de59e32015-08-19 11:58:18 -0400473 field = super(CordInstanceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Baker0e289fd2015-06-12 10:40:15 -0700474
475 return field
476
Scott Baker022cdcd2015-02-18 15:50:11 -0800477class SiteInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400478 model = Site
479 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400480 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400481
Tony Mack5b061472014-02-04 07:57:10 -0500482 def queryset(self, request):
483 return Site.select_by_user(request.user)
484
Tony Mack15136b52015-08-04 17:53:23 -0400485class SiteHostsNodesInline(SiteInline):
486 def queryset(self, request):
487 return Site.select_by_user(request.user).filter(hosts_nodes=True)
488
489class SiteHostsUsersInline(SiteInline):
490 def queryset(self, request):
491 return Site.select_by_user(request.user).filter(hosts_users=True)
492
Scott Baker022cdcd2015-02-18 15:50:11 -0800493class UserInline(XOSTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400494 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700495 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
496 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400497 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400498 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400499
Tony Mack5b061472014-02-04 07:57:10 -0500500 def queryset(self, request):
501 return User.select_by_user(request.user)
502
Scott Baker022cdcd2015-02-18 15:50:11 -0800503class SliceInline(XOSTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400504 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700505 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
506 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400507 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400508 suit_classes = 'suit-tab suit-tab-slices'
509
Tony Mack5b061472014-02-04 07:57:10 -0500510 def queryset(self, request):
511 return Slice.select_by_user(request.user)
512
Scott Baker022cdcd2015-02-18 15:50:11 -0800513class NodeInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400514 model = Node
515 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400516 suit_classes = 'suit-tab suit-tab-nodes'
Tony Mack93d1b032014-12-08 16:43:02 -0500517 fields = ['backend_status_icon', 'name', 'site_deployment']
Scott Baker40c00762014-08-21 16:55:59 -0700518 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400519
Scott Baker022cdcd2015-02-18 15:50:11 -0800520class DeploymentPrivilegeInline(XOSTabularInline):
Tony Mack93d1b032014-12-08 16:43:02 -0500521 model = DeploymentPrivilege
522 extra = 0
Tony Mack4ce14c42015-02-09 21:41:57 -0500523 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
Tony Mack93d1b032014-12-08 16:43:02 -0500524 fields = ['backend_status_icon', 'user','role','deployment']
525 readonly_fields = ('backend_status_icon', )
526
527 def queryset(self, request):
528 return DeploymentPrivilege.select_by_user(request.user)
529
Scott Baker022cdcd2015-02-18 15:50:11 -0800530class ControllerSiteInline(XOSTabularInline):
Tony Macka7dbd422015-01-05 22:48:11 -0500531 model = ControllerSite
532 extra = 0
533 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack8f30ebe2015-01-06 15:08:20 -0500534 fields = ['controller', 'site', 'tenant_id']
Tony Macka7dbd422015-01-05 22:48:11 -0500535
536
Scott Baker022cdcd2015-02-18 15:50:11 -0800537class SitePrivilegeInline(XOSTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400538 model = SitePrivilege
539 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400540 suit_classes = 'suit-tab suit-tab-siteprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700541 fields = ['backend_status_icon', 'user','site', 'role']
542 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400543
Tony Mackc2835a92013-05-28 09:18:49 -0400544 def formfield_for_foreignkey(self, db_field, request, **kwargs):
545 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500546 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400547
548 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500549 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400550 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
551
Tony Mack5b061472014-02-04 07:57:10 -0500552 def queryset(self, request):
553 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400554
Tony Mack8d108e22015-05-11 20:39:32 -0400555
556class ServicePrivilegeInline(XOSTabularInline):
557 model = ServicePrivilege
558 extra = 0
559 suit_classes = 'suit-tab suit-tab-serviceprivileges'
560 fields = ['backend_status_icon', 'user','service', 'role']
561 readonly_fields = ('backend_status_icon', )
562
563 def formfield_for_foreignkey(self, db_field, request, **kwargs):
564 if db_field.name == 'service':
565 kwargs['queryset'] = Service.select_by_user(request.user)
Tony Mackcf88f8c2015-05-15 06:33:45 -0400566 if db_field.name == 'user':
567 kwargs['queryset'] = User.select_by_user(request.user)
568 return super(ServicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mack8d108e22015-05-11 20:39:32 -0400569
570 def queryset(self, request):
571 return ServicePrivilege.select_by_user(request.user)
572
Scott Baker022cdcd2015-02-18 15:50:11 -0800573class SiteDeploymentInline(XOSTabularInline):
Tony Macka7dbd422015-01-05 22:48:11 -0500574 model = SiteDeployment
Tony Macke4be32f2014-03-11 20:45:25 -0400575 extra = 0
Tony Mack10812252015-01-30 10:58:29 -0500576 suit_classes = 'suit-tab suit-tab-sitedeployments'
Tony Mack528d4222014-12-05 17:13:08 -0500577 fields = ['backend_status_icon', 'deployment','site', 'controller']
Scott Baker40c00762014-08-21 16:55:59 -0700578 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400579
580 def formfield_for_foreignkey(self, db_field, request, **kwargs):
581 if db_field.name == 'site':
582 kwargs['queryset'] = Site.select_by_user(request.user)
583
584 if db_field.name == 'deployment':
585 kwargs['queryset'] = Deployment.select_by_user(request.user)
Tony Mack528d4222014-12-05 17:13:08 -0500586
587 if db_field.name == 'controller':
Scott Baker4103eb32015-09-21 14:52:15 -0700588 if len(resolve(request.path).args) > 0:
589 kwargs['queryset'] = Controller.select_by_user(request.user).filter(deployment__id=int(resolve(request.path).args[0]))
Tony Mack528d4222014-12-05 17:13:08 -0500590
Tony Macka7dbd422015-01-05 22:48:11 -0500591 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Macke4be32f2014-03-11 20:45:25 -0400592
593 def queryset(self, request):
Tony Macka7dbd422015-01-05 22:48:11 -0500594 return SiteDeployment.select_by_user(request.user)
Tony Macke4be32f2014-03-11 20:45:25 -0400595
596
Scott Baker022cdcd2015-02-18 15:50:11 -0800597class SlicePrivilegeInline(XOSTabularInline):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400598 model = SlicePrivilege
599 suit_classes = 'suit-tab suit-tab-sliceprivileges'
600 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700601 fields = ('backend_status_icon', 'user', 'slice', 'role')
602 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400603
Tony Mackc2835a92013-05-28 09:18:49 -0400604 def formfield_for_foreignkey(self, db_field, request, **kwargs):
605 if db_field.name == 'slice':
Scott Baker36f50872014-08-21 13:01:25 -0700606 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400607 if db_field.name == 'user':
Scott Baker521abcc2015-07-16 12:40:07 -0700608 # all users are available to be granted SlicePrivilege
609 kwargs['queryset'] = User.objects.all()
Tony Mackc2835a92013-05-28 09:18:49 -0400610
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400611 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400612
Tony Mack5b061472014-02-04 07:57:10 -0500613 def queryset(self, request):
614 return SlicePrivilege.select_by_user(request.user)
615
Scott Baker022cdcd2015-02-18 15:50:11 -0800616class SliceNetworkInline(XOSTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700617 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800618 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700619 extra = 0
620 verbose_name = "Network Connection"
621 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400622 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700623 fields = ['backend_status_icon', 'network']
624 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700625
Scott Baker022cdcd2015-02-18 15:50:11 -0800626class ImageDeploymentsInline(XOSTabularInline):
Sapan Bhatiae9f96f62014-11-19 15:10:16 -0500627 model = ImageDeployments
Scott Baker2170b972014-06-03 12:14:07 -0700628 extra = 0
629 verbose_name = "Image Deployments"
630 verbose_name_plural = "Image Deployments"
631 suit_classes = 'suit-tab suit-tab-imagedeployments'
Tony Mack336e0f92014-11-30 15:53:08 -0500632 fields = ['backend_status_icon', 'image', 'deployment']
633 readonly_fields = ['backend_status_icon']
634
Scott Baker022cdcd2015-02-18 15:50:11 -0800635class ControllerImagesInline(XOSTabularInline):
Tony Mack336e0f92014-11-30 15:53:08 -0500636 model = ControllerImages
637 extra = 0
638 verbose_name = "Controller Images"
639 verbose_name_plural = "Controller Images"
640 suit_classes = 'suit-tab suit-tab-admin-only'
641 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
Scott Baker40c00762014-08-21 16:55:59 -0700642 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700643
Scott Baker022cdcd2015-02-18 15:50:11 -0800644class SliceRoleAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400645 model = SliceRole
646 pass
647
Scott Baker022cdcd2015-02-18 15:50:11 -0800648class SiteRoleAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400649 model = SiteRole
650 pass
651
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400652class DeploymentAdminForm(forms.ModelForm):
Scott Bakerde0f4412014-06-11 15:40:26 -0700653 images = forms.ModelMultipleChoiceField(
654 queryset=Image.objects.all(),
655 required=False,
656 help_text="Select which images should be deployed on this deployment",
657 widget=FilteredSelectMultiple(
658 verbose_name=('Images'), is_stacked=False
659 )
660 )
Scott Baker37b47902014-09-02 14:37:41 -0700661 flavors = forms.ModelMultipleChoiceField(
662 queryset=Flavor.objects.all(),
663 required=False,
664 help_text="Select which flavors should be usable on this deployment",
665 widget=FilteredSelectMultiple(
666 verbose_name=('Flavors'), is_stacked=False
667 )
668 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400669 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400670 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700671 many_to_many = ["flavors",]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400672
Siobhan Tully320b4622014-01-17 15:11:14 -0500673 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700674 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500675 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
676
Scott Baker5380c522014-06-06 14:49:43 -0700677 self.fields['accessControl'].initial = "allow site " + request.user.site.name
678
Siobhan Tully320b4622014-01-17 15:11:14 -0500679 if self.instance and self.instance.pk:
Scott Baker9f6b8ed2014-11-17 23:44:03 -0800680 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
Scott Baker37b47902014-09-02 14:37:41 -0700681 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700682
683 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
684 """ helper function for handling m2m relations from the MultipleChoiceField
685
686 this_obj: the source object we want to link from
687
688 selected_objs: a list of destination objects we want to link to
689
690 all_relations: the full set of relations involving this_obj, including ones we don't want
691
692 relation_class: the class that implements the relation from source to dest
693
694 local_attrname: field name representing this_obj in relation_class
695
696 foreign_attrname: field name representing selected_objs in relation_class
697
698 This function will remove all newobjclass relations from this_obj
699 that are not contained in selected_objs, and add any relations that
700 are in selected_objs but don't exist in the data model yet.
701 """
702
703 existing_dest_objs = []
704 for relation in list(all_relations):
705 if getattr(relation, foreign_attrname) not in selected_objs:
706 #print "deleting site", sdp.site
707 relation.delete()
708 else:
709 existing_dest_objs.append(getattr(relation, foreign_attrname))
710
711 for dest_obj in selected_objs:
712 if dest_obj not in existing_dest_objs:
713 #print "adding site", site
714 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
715 relation = relation_class(**kwargs)
716 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500717
718 def save(self, commit=True):
719 deployment = super(DeploymentAdminForm, self).save(commit=False)
720
721 if commit:
722 deployment.save()
Scott Baker61b6aec2014-10-06 17:17:40 -0700723 # this has to be done after save() if/when a deployment is first created
724 deployment.flavors = self.cleaned_data['flavors']
Siobhan Tully320b4622014-01-17 15:11:14 -0500725
726 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700727 # save_m2m() doesn't seem to work with 'through' relations. So we
728 # create/destroy the through models ourselves. There has to be
729 # a better way...
730
Tony Mack592aa952014-12-15 11:45:02 -0500731 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
732 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
733 # so well handle that manually here
734 for flavor in deployment.flavors.all():
735 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
Tony Mack11f4d202014-12-15 12:37:59 -0500736 deployment.flavors.remove(flavor)
Tony Mack592aa952014-12-15 11:45:02 -0500737 for flavor in self.cleaned_data['flavors']:
738 if flavor not in deployment.flavors.all():
739 flavor.deployments.add(deployment)
Scott Bakerc9b14f72014-05-22 13:44:20 -0700740
Scott Baker37b47902014-09-02 14:37:41 -0700741 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500742
743 return deployment
744
Scott Bakerff5e0f32014-05-22 14:40:27 -0700745class DeploymentAdminROForm(DeploymentAdminForm):
746 def save(self, commit=True):
747 raise PermissionDenied
748
Scott Baker022cdcd2015-02-18 15:50:11 -0800749class SiteAssocInline(XOSTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500750 model = Site.deployments.through
751 extra = 0
752 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400753
Scott Baker022cdcd2015-02-18 15:50:11 -0800754class DeploymentAdmin(XOSBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500755 model = Deployment
Scott Bakerae233f42015-02-10 08:40:34 -0800756 fieldList = ['backend_status_text', 'name', 'images', 'flavors', 'accessControl']
757 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Mack93d1b032014-12-08 16:43:02 -0500758 # node no longer directly connected to deployment
759 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
Tony Mack10812252015-01-30 10:58:29 -0500760 inlines = [DeploymentPrivilegeInline,TagInline,ImageDeploymentsInline,SiteDeploymentInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700761 list_display = ['backend_status_icon', 'name']
762 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700763 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500764
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500765 user_readonly_fields = ['name']
766
Tony Mack93d1b032014-12-08 16:43:02 -0500767 # nodes no longer direclty connected to deployments
Scott Bakerae233f42015-02-10 08:40:34 -0800768 suit_form_tabs =(('general','Deployment Details'),('deploymentprivileges','Privileges'), ('sitedeployments', 'Sites'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500769
Scott Bakerff5e0f32014-05-22 14:40:27 -0700770 def get_form(self, request, obj=None, **kwargs):
Tony Mackd893dfb2015-02-05 06:13:04 -0500771 if request.user.isReadOnlyUser() or not request.user.is_admin:
Scott Bakerff5e0f32014-05-22 14:40:27 -0700772 kwargs["form"] = DeploymentAdminROForm
773 else:
774 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700775 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
776
777 # from stackexchange: pass the request object into the form
778
779 class AdminFormMetaClass(adminForm):
780 def __new__(cls, *args, **kwargs):
781 kwargs['request'] = request
782 return adminForm(*args, **kwargs)
783
784 return AdminFormMetaClass
785
Scott Bakere497b3c2016-01-29 12:18:19 -0800786class ControllerAdminForm(forms.ModelForm):
787 backend_disabled = forms.BooleanField(required=False)
788 class Meta:
789 model = Controller
790
791 def __init__(self, *args, **kwargs):
792 request = kwargs.pop('request', None)
793 super(ControllerAdminForm, self).__init__(*args, **kwargs)
794
795 if self.instance and self.instance.pk:
796 self.fields['backend_disabled'].initial = self.instance.get_backend_register('disabled', False)
797 else:
798 # defaults when adding new controller
799 self.fields['backend_disabled'].initial = False
800
801 def save(self, commit=True):
802 self.instance.set_backend_register("disabled", self.cleaned_data["backend_disabled"])
803 return super(ControllerAdminForm, self).save(commit=commit)
804
Scott Baker022cdcd2015-02-18 15:50:11 -0800805class ControllerAdmin(XOSBaseAdmin):
Scott Bakerae233f42015-02-10 08:40:34 -0800806 model = Controller
Scott Bakere497b3c2016-01-29 12:18:19 -0800807 fieldList = ['deployment', 'name', 'backend_type', 'backend_disabled', 'version', 'auth_url', 'admin_user', 'admin_tenant','admin_password', 'domain', 'rabbit_host', 'rabbit_user', 'rabbit_password']
Scott Bakerae233f42015-02-10 08:40:34 -0800808 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Macka7dbd422015-01-05 22:48:11 -0500809 inlines = [ControllerSiteInline] # ,ControllerImagesInline]
Tony Mack528d4222014-12-05 17:13:08 -0500810 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
811 list_display_links = ('backend_status_icon', 'name', )
812 readonly_fields = ('backend_status_text',)
Scott Bakere497b3c2016-01-29 12:18:19 -0800813 form = ControllerAdminForm
Tony Mack528d4222014-12-05 17:13:08 -0500814
815 user_readonly_fields = []
816
Tony Mackc36cafb2015-01-13 17:33:08 -0500817 def save_model(self, request, obj, form, change):
818 # update openstack connection to use this site/tenant
819 obj.save_by_user(request.user)
820
821 def delete_model(self, request, obj):
Scott Bakerae233f42015-02-10 08:40:34 -0800822 obj.delete_by_user(request.user)
823
Tony Mack79e2e662015-02-18 11:41:36 -0500824 def queryset(self, request):
825 return Controller.select_by_user(request.user)
826
Scott Bakerae233f42015-02-10 08:40:34 -0800827 @property
828 def suit_form_tabs(self):
829 tabs = [('general', 'Controller Details'),
830 ]
831
832 request=getattr(_thread_locals, "request", None)
833 if request and request.user.is_admin:
834 tabs.append( ('admin-only', 'Admin-Only') )
835
836 return tabs
Tony Mackc36cafb2015-01-13 17:33:08 -0500837
Scott Baker462a1d92015-10-15 15:59:19 -0700838class TenantAttributeAdmin(XOSBaseAdmin):
839 model = TenantAttribute
840 list_display = ('backend_status_icon', 'tenant', 'name', 'value')
841 list_display_links = ('backend_status_icon', 'name')
842 fieldList = ('backend_status_text', 'tenant', 'name', 'value', )
843 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
844 readonly_fields = ('backend_status_text', )
845
846 suit_form_tabs =(('general', 'Tenant Root Details'),
847 )
848
849class TenantAttrAsTabInline(XOSTabularInline):
850 model = TenantAttribute
851 fields = ['name','value']
852 extra = 0
853 suit_classes = 'suit-tab suit-tab-tenantattrs'
854
Scott Baker1b06b6c2015-07-06 14:40:20 -0700855class TenantRootRoleAdmin(XOSBaseAdmin):
856 model = TenantRootRole
857 fields = ('role',)
858
859class TenantRootTenantInline(XOSTabularInline):
860 model = Tenant
861 fields = ['provider_service', 'subscriber_root']
862 extra = 0
863 suit_classes = 'suit-tab suit-tab-tenantroots'
864 fk_name = 'subscriber_root'
865 verbose_name = 'subscribed tenant'
866 verbose_name_plural = 'subscribed tenants'
867
868 #def queryset(self, request):
869 # qs = super(TenantRootTenantInline, self).queryset(request)
870 # return qs.filter(kind="coarse")
871
872class TenantRootPrivilegeInline(XOSTabularInline):
873 model = TenantRootPrivilege
874 extra = 0
875 suit_classes = 'suit-tab suit-tab-tenantrootprivileges'
876 fields = ['backend_status_icon', 'user', 'role', 'tenant_root']
877 readonly_fields = ('backend_status_icon', )
878
879 def queryset(self, request):
880 return TenantRootPrivilege.select_by_user(request.user)
881
882class TenantRootAdmin(XOSBaseAdmin):
883 model = TenantRoot
884 list_display = ('backend_status_icon', 'name', 'kind')
885 list_display_links = ('backend_status_icon', 'name')
886 fieldList = ('backend_status_text', 'name', 'kind', )
887 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
888 inlines = (TenantRootTenantInline, TenantRootPrivilegeInline)
889 readonly_fields = ('backend_status_text', )
890
891 suit_form_tabs =(('general', 'Tenant Root Details'),
892 ('tenantroots','Tenancy'),
893 ('tenantrootprivileges','Privileges')
894 )
895
Scott Baker925a8fa2015-04-26 20:30:40 -0700896class ProviderTenantInline(XOSTabularInline):
897 model = CoarseTenant
898 fields = ['provider_service', 'subscriber_service', 'connect_method']
899 extra = 0
900 suit_classes = 'suit-tab suit-tab-servicetenants'
901 fk_name = 'provider_service'
902 verbose_name = 'provided tenant'
903 verbose_name_plural = 'provided tenants'
904
905 def queryset(self, request):
906 qs = super(ProviderTenantInline, self).queryset(request)
907 return qs.filter(kind="coarse")
908
909class SubscriberTenantInline(XOSTabularInline):
910 model = CoarseTenant
911 fields = ['provider_service', 'subscriber_service', 'connect_method']
912 extra = 0
913 suit_classes = 'suit-tab suit-tab-servicetenants'
914 fk_name = 'subscriber_service'
915 verbose_name = 'subscribed tenant'
916 verbose_name_plural = 'subscribed tenants'
917
918 def queryset(self, request):
919 qs = super(SubscriberTenantInline, self).queryset(request)
920 return qs.filter(kind="coarse")
921
Scott Baker022cdcd2015-02-18 15:50:11 -0800922class ServiceAttrAsTabInline(XOSTabularInline):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400923 model = ServiceAttribute
924 fields = ['name','value']
925 extra = 0
926 suit_classes = 'suit-tab suit-tab-serviceattrs'
927
Scott Baker022cdcd2015-02-18 15:50:11 -0800928class ServiceAdmin(XOSBaseAdmin):
Scott Baker008a9962015-04-15 20:58:20 -0700929 list_display = ("backend_status_icon","name","kind","versionNumber","enabled","published")
Scott Baker63d1a552014-08-21 15:19:07 -0700930 list_display_links = ('backend_status_icon', 'name', )
Scott Bakerf60c0102015-11-12 16:22:52 -0800931 fieldList = ["backend_status_text","name","kind","description","versionNumber","enabled","published","view_url","icon_url","public_key","private_key_fn","service_specific_attribute","service_specific_id"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500932 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Mack8d108e22015-05-11 20:39:32 -0400933 inlines = [ServiceAttrAsTabInline,SliceInline,ProviderTenantInline,SubscriberTenantInline,ServicePrivilegeInline]
Scott Baker40c00762014-08-21 16:55:59 -0700934 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500935
936 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500937
938 suit_form_tabs =(('general', 'Service Details'),
939 ('slices','Slices'),
940 ('serviceattrs','Additional Attributes'),
Scott Baker925a8fa2015-04-26 20:30:40 -0700941 ('servicetenants','Tenancy'),
Scott Baker1b06b6c2015-07-06 14:40:20 -0700942 ('serviceprivileges','Privileges')
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500943 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400944
Scott Baker022cdcd2015-02-18 15:50:11 -0800945class SiteNodeInline(XOSTabularInline):
Tony Mack82df1d02015-01-14 20:58:38 -0500946 model = Node
947 fields = ['name', 'site_deployment']
948 extra = 0
949 suit_classes = 'suit-tab suit-tab-nodes'
950
Tony Mack26b59532015-02-25 11:39:34 -0500951 def formfield_for_foreignkey(self, db_field, request, **kwargs):
952 # only display site deployments associated with this site
953 if db_field.name == 'site_deployment':
954 kwargs['queryset'] = SiteDeployment.objects.filter(site__id=int(request.path.split('/')[-2]))
955
956 return super(SiteNodeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
957
Scott Baker022cdcd2015-02-18 15:50:11 -0800958class SiteAdmin(XOSBaseAdmin):
Tony Mack598eaf22015-01-25 12:35:29 -0500959 #fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
Tony Mack2862dca2015-08-04 17:21:55 -0400960 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'login_base', 'location', 'is_public', 'hosts_nodes', 'hosts_users']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400961 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500962 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400963 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400964 ]
Tony Mack598eaf22015-01-25 12:35:29 -0500965 #readonly_fields = ['backend_status_text', 'accountLink']
966 readonly_fields = ['backend_status_text']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500967
Tony Mack598eaf22015-01-25 12:35:29 -0500968 #user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
Tony Mack2862dca2015-08-04 17:21:55 -0400969 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'hosts_nodes', 'hosts_users']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500970
Scott Baker63d1a552014-08-21 15:19:07 -0700971 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
972 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400973 filter_horizontal = ('deployments',)
Tony Mack10812252015-01-30 10:58:29 -0500974 inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteNodeInline]
Tony Mack10328a12015-01-14 12:11:05 -0500975 admin_inlines = [ControllerSiteInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400976 search_fields = ['name']
977
Tony Mack3c01ff92015-01-10 23:08:10 -0500978 @property
979 def suit_form_tabs(self):
980 tabs = [('general', 'Site Details'),
981 ('users','Users'),
982 ('siteprivileges','Privileges'),
Tony Mack3c01ff92015-01-10 23:08:10 -0500983 ('slices','Slices'),
Tony Mack82df1d02015-01-14 20:58:38 -0500984 ('nodes','Nodes'),
Tony Mack3c01ff92015-01-10 23:08:10 -0500985 ]
986
987 request=getattr(_thread_locals, "request", None)
988 if request and request.user.is_admin:
989 tabs.append( ('admin-only', 'Admin-Only') )
990
991 return tabs
992
Tony Mack04062832013-05-10 08:22:44 -0400993 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500994 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400995
Tony Mack5cd13202013-05-01 21:48:38 -0400996 def get_formsets(self, request, obj=None):
997 for inline in self.get_inline_instances(request, obj):
998 # hide MyInline in the add view
999 if obj is None:
1000 continue
Tony Mack3de59e32015-08-19 11:58:18 -04001001 if isinstance(inline, InstanceInline):
Tony Mack2bd5b412013-06-11 21:05:06 -04001002 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -04001003 yield inline.get_formset(request, obj)
1004
Scott Baker545db2a2013-12-09 18:44:43 -08001005 def accountLink(self, obj):
1006 link_obj = obj.accounts.all()
1007 if link_obj:
1008 reverse_path = "admin:core_account_change"
1009 url = reverse(reverse_path, args =(link_obj[0].id,))
1010 return "<a href='%s'>%s</a>" % (url, "view billing details")
1011 else:
1012 return "no billing data for this site"
1013 accountLink.allow_tags = True
1014 accountLink.short_description = "Billing"
1015
Tony Mack332ee1d2014-02-04 15:33:45 -05001016 def save_model(self, request, obj, form, change):
1017 # update openstack connection to use this site/tenant
1018 obj.save_by_user(request.user)
1019
1020 def delete_model(self, request, obj):
1021 obj.delete_by_user(request.user)
1022
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001023
Scott Baker022cdcd2015-02-18 15:50:11 -08001024class SitePrivilegeAdmin(XOSBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001025 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -04001026 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001027 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -04001028 ]
Scott Baker40c00762014-08-21 16:55:59 -07001029 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -07001030 list_display = ('backend_status_icon', 'user', 'site', 'role')
1031 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001032 user_readonly_fields = fieldList
1033 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -04001034
Tony Mackc2835a92013-05-28 09:18:49 -04001035 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1036 if db_field.name == 'site':
1037 if not request.user.is_admin:
1038 # only show sites where user is an admin or pi
1039 sites = set()
1040 for site_privilege in SitePrivilege.objects.filer(user=request.user):
1041 if site_privilege.role.role_type in ['admin', 'pi']:
1042 sites.add(site_privilege.site)
1043 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
1044
1045 if db_field.name == 'user':
1046 if not request.user.is_admin:
1047 # only show users from sites where caller has admin or pi role
1048 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
1049 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
1050 sites = [site_privilege.site for site_privilege in site_privileges]
1051 site_privileges = SitePrivilege.objects.filter(site__in=sites)
1052 emails = [site_privilege.user.email for site_privilege in site_privileges]
1053 users = User.objects.filter(email__in=emails)
1054 kwargs['queryset'] = users
1055
1056 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1057
Tony Mack04062832013-05-10 08:22:44 -04001058 def queryset(self, request):
1059 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -04001060 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -04001061 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -05001062 #if not request.user.is_admin:
1063 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
1064 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
1065 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
1066 # sites = Site.objects.filter(login_base__in=login_bases)
1067 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -04001068 return qs
1069
Siobhan Tullyce652d02013-10-08 21:52:35 -04001070class SliceForm(forms.ModelForm):
1071 class Meta:
1072 model = Slice
1073 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -07001074 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -04001075 }
1076
Tony Mack2cbd3802014-09-29 16:10:52 -04001077 def clean(self):
1078 cleaned_data = super(SliceForm, self).clean()
1079 name = cleaned_data.get('name')
Scott Baker6efad462014-10-06 23:09:59 -07001080 site = cleaned_data.get('site')
Tony Mack585cb192014-10-22 12:54:19 -04001081 slice_id = self.instance.id
1082 if not site and slice_id:
1083 site = Slice.objects.get(id=slice_id).site
Scott Baker6efad462014-10-06 23:09:59 -07001084 if (not isinstance(site,Site)):
1085 # previous code indicates 'site' could be a site_id and not a site?
1086 site = Slice.objects.get(id=site.id)
Tony Mack2cbd3802014-09-29 16:10:52 -04001087 if not name.startswith(site.login_base):
1088 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
1089 return cleaned_data
1090
Scott Baker022cdcd2015-02-18 15:50:11 -08001091class ControllerSliceInline(XOSTabularInline):
Tony Macka7dbd422015-01-05 22:48:11 -05001092 model = ControllerSlice
Scott Bakerc4efdc72014-10-15 16:54:04 -07001093 extra = 0
Tony Mack336e0f92014-11-30 15:53:08 -05001094 verbose_name = "Controller Slices"
1095 verbose_name_plural = "Controller Slices"
Scott Bakerc4efdc72014-10-15 16:54:04 -07001096 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack336e0f92014-11-30 15:53:08 -05001097 fields = ['backend_status_icon', 'controller', 'tenant_id']
Tony Mack3c01ff92015-01-10 23:08:10 -05001098 readonly_fields = ('backend_status_icon', 'controller' )
Scott Bakerc4efdc72014-10-15 16:54:04 -07001099
Scott Baker022cdcd2015-02-18 15:50:11 -08001100class SliceAdmin(XOSBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -04001101 form = SliceForm
Scott Bakerde088532015-12-14 19:37:55 -08001102 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_instances', "default_isolation", "network"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001103 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -07001104 readonly_fields = ('backend_status_text', )
Tony Mack3de59e32015-08-19 11:58:18 -04001105 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_instances')
Tony Mack7d459902014-09-03 13:18:57 -04001106 list_display_links = ('backend_status_icon', 'name', )
Tony Mack3de59e32015-08-19 11:58:18 -04001107 normal_inlines = [SlicePrivilegeInline, InstanceInline, TagInline, ReservationInline, SliceNetworkInline]
Scott Baker0e289fd2015-06-12 10:40:15 -07001108 inlines = normal_inlines
Tony Macka7dbd422015-01-05 22:48:11 -05001109 admin_inlines = [ControllerSliceInline]
Scott Baker591fb062015-09-15 15:21:50 -07001110 suit_form_includes = (('slice_instance_tab.html', 'bottom', 'instances'),)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001111
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001112 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001113
Scott Bakerc4efdc72014-10-15 16:54:04 -07001114 @property
1115 def suit_form_tabs(self):
1116 tabs =[('general', 'Slice Details'),
1117 ('slicenetworks','Networks'),
1118 ('sliceprivileges','Privileges'),
Tony Mack3de59e32015-08-19 11:58:18 -04001119 ('instances','Instances'),
Tony Mack598eaf22015-01-25 12:35:29 -05001120 #('reservations','Reservations'),
Tony Mackb34553e2015-01-15 14:44:06 -05001121 ('tags','Tags'),
Scott Bakerc4efdc72014-10-15 16:54:04 -07001122 ]
1123
1124 request=getattr(_thread_locals, "request", None)
1125 if request and request.user.is_admin:
1126 tabs.append( ('admin-only', 'Admin-Only') )
1127
1128 return tabs
Tony Mack7b8505a2014-10-22 11:54:29 -04001129
1130 def add_view(self, request, form_url='', extra_context=None):
Scott Baker0e289fd2015-06-12 10:40:15 -07001131 # Ugly hack for CORD
1132 self.inlines = self.normal_inlines
Tony Mack7b8505a2014-10-22 11:54:29 -04001133 # revert to default read-only fields
1134 self.readonly_fields = ('backend_status_text',)
1135 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
1136
1137 def change_view(self, request, object_id, form_url='', extra_context=None):
Tony Mack7b8505a2014-10-22 11:54:29 -04001138 # cannot change the site of an existing slice so make the site field read only
1139 if object_id:
1140 self.readonly_fields = ('backend_status_text','site')
Scott Baker0e289fd2015-06-12 10:40:15 -07001141
Tony Mack7b8505a2014-10-22 11:54:29 -04001142 return super(SliceAdmin, self).change_view(request, object_id, form_url)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001143
Scott Baker510fdbb2014-08-05 17:19:24 -07001144 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -07001145 deployment_nodes = []
1146 for node in Node.objects.all():
Scott Baker39293d72015-01-21 16:24:07 -08001147 deployment_nodes.append( (node.site_deployment.deployment.id, node.id, node.name) )
Scott Baker510fdbb2014-08-05 17:19:24 -07001148
Scott Baker7a61dc42014-09-02 17:08:20 -07001149 deployment_flavors = []
1150 for flavor in Flavor.objects.all():
1151 for deployment in flavor.deployments.all():
1152 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
1153
Tony Mack93d1b032014-12-08 16:43:02 -05001154 deployment_images = []
Scott Baker93e80cd2014-09-09 09:58:49 -07001155 for image in Image.objects.all():
Tony Mack93d1b032014-12-08 16:43:02 -05001156 for deployment_image in image.imagedeployments.all():
Scott Bakera6a0c772014-12-22 17:35:34 -08001157 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
Scott Baker93e80cd2014-09-09 09:58:49 -07001158
Tony Mackec23b992014-09-02 21:18:45 -04001159 site_login_bases = []
1160 for site in Site.objects.all():
Scott Baker93e80cd2014-09-09 09:58:49 -07001161 site_login_bases.append((site.id, site.login_base))
1162
Scott Baker510fdbb2014-08-05 17:19:24 -07001163 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -07001164 context["deployment_flavors"] = deployment_flavors
Scott Baker93e80cd2014-09-09 09:58:49 -07001165 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -04001166 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -07001167 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
1168
Tony Mackc2835a92013-05-28 09:18:49 -04001169 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1170 if db_field.name == 'site':
Tony Mack15136b52015-08-04 17:53:23 -04001171 kwargs['queryset'] = Site.select_by_user(request.user).filter(hosts_users=True)
Tony Mackec23b992014-09-02 21:18:45 -04001172 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 -07001173
Tony Mackc2835a92013-05-28 09:18:49 -04001174 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1175
Tony Mack04062832013-05-10 08:22:44 -04001176 def queryset(self, request):
1177 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -05001178 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -04001179
Tony Mack79748612013-05-01 14:52:03 -04001180 def get_formsets(self, request, obj=None):
1181 for inline in self.get_inline_instances(request, obj):
1182 # hide MyInline in the add view
1183 if obj is None:
1184 continue
Tony Mack3de59e32015-08-19 11:58:18 -04001185 if isinstance(inline, InstanceInline):
Tony Mack2bd5b412013-06-11 21:05:06 -04001186 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -04001187 yield inline.get_formset(request, obj)
1188
Scott Baker03394b32015-09-15 17:48:57 -07001189 def add_extra_context(self, request, extra_context):
1190 super(SliceAdmin, self).add_extra_context(request, extra_context)
1191 # set context["slice_id"] to the PK passed in the URL to this view
1192 if len(request.resolver_match.args)>0:
1193 extra_context["slice_id"] = request.resolver_match.args[0]
1194
Scott Baker0e289fd2015-06-12 10:40:15 -07001195 def UNUSED_get_inline_instances(self, request, obj=None):
1196 # HACK for CORD to do something special on vcpe slice page
1197 # this was a good idea, but failed miserably, as something still
1198 # expects there to be a deployment field.
1199 # XXX this approach is better than clobbering self.inlines, so
1200 # try to make this work post-demo.
1201 if (obj is not None) and (obj.name == "mysite_vcpe"):
Tony Mack3de59e32015-08-19 11:58:18 -04001202 cord_vcpe_inlines = [ SlicePrivilegeInline, CordInstanceInline, TagInline, ReservationInline,SliceNetworkInline]
Scott Baker0e289fd2015-06-12 10:40:15 -07001203
1204 inlines=[]
1205 for inline_class in cord_vcpe_inlines:
1206 inlines.append(inline_class(self.model, self.admin_site))
1207 else:
1208 inlines = super(SliceAdmin, self).get_inline_instances(request, obj)
1209
1210 return inlines
1211
Scott Baker022cdcd2015-02-18 15:50:11 -08001212class SlicePrivilegeAdmin(XOSBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -04001213 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001214 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -04001215 ]
Scott Baker40c00762014-08-21 16:55:59 -07001216 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -07001217 list_display = ('backend_status_icon', 'user', 'slice', 'role')
1218 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -04001219
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001220 user_readonly_fields = ['user', 'slice', 'role']
1221 user_readonly_inlines = []
1222
Tony Mackc2835a92013-05-28 09:18:49 -04001223 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1224 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001225 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001226
1227 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -05001228 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001229
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001230 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -04001231
Tony Mack04062832013-05-10 08:22:44 -04001232 def queryset(self, request):
1233 # admins can see all memberships. Users can only see memberships of
1234 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -05001235 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001236
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001237 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -04001238 # update openstack connection to use this site/tenant
1239 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -04001240 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -04001241 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001242 obj.save()
1243
1244 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -04001245 # update openstack connection to use this site/tenant
1246 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -04001247 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -04001248 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001249 obj.delete()
1250
Scott Baker022cdcd2015-02-18 15:50:11 -08001251class ImageAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001252
Scott Baker36f50872014-08-21 13:01:25 -07001253 fieldsets = [('Image Details',
Scott Baker14266232015-12-14 10:11:56 -08001254 {'fields': ['backend_status_text', 'name', 'kind', 'disk_format', 'container_format', 'tag', 'path'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001255 'classes': ['suit-tab suit-tab-general']})
1256 ]
Scott Baker40c00762014-08-21 16:55:59 -07001257 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001258
Scott Baker13ed27c2016-02-22 22:11:20 -08001259 suit_form_tabs =(('general','Image Details'),('instances','Instances'),('imagedeployments','Deployments'), ('admin-only', 'Admin-Only'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001260
Tony Mack3de59e32015-08-19 11:58:18 -04001261 inlines = [InstanceInline, ControllerImagesInline]
Scott Bakerb6f99242014-06-11 11:34:44 -07001262
Scott Baker14266232015-12-14 10:11:56 -08001263 user_readonly_fields = ['name', 'disk_format', 'container_format', 'tag', 'path']
Scott Bakerb27b62c2014-08-15 16:29:16 -07001264
Scott Baker0cefa532015-11-09 16:17:11 -08001265 list_display = ['backend_status_icon', 'name', 'kind']
Scott Baker63d1a552014-08-21 15:19:07 -07001266 list_display_links = ('backend_status_icon', 'name', )
1267
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001268class NodeForm(forms.ModelForm):
Scott Baker1271a4d2016-04-06 14:34:49 -07001269 nodelabels = forms.ModelMultipleChoiceField(
Scott Baker3ad54ff2016-03-04 10:35:32 -08001270 queryset=NodeLabel.objects.all(),
1271 required=False,
1272 help_text="Select which labels apply to this node",
1273 widget=FilteredSelectMultiple(
1274 verbose_name=('Labels'), is_stacked=False
1275 )
1276 )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001277 class Meta:
Scott Baker2a11f852015-09-21 15:06:38 -07001278 model = Node
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001279 widgets = {
1280 'site': LinkedSelect,
1281 'deployment': LinkedSelect
1282 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001283
Scott Baker3ad54ff2016-03-04 10:35:32 -08001284 def __init__(self, *args, **kwargs):
1285 request = kwargs.pop('request', None)
1286 super(NodeForm, self).__init__(*args, **kwargs)
1287
1288 if self.instance and self.instance.pk:
Scott Baker1271a4d2016-04-06 14:34:49 -07001289 self.fields['nodelabels'].initial = self.instance.nodelabels.all()
Scott Baker3ad54ff2016-03-04 10:35:32 -08001290
1291 def save(self, commit=True):
1292 node = super(NodeForm, self).save(commit=False)
1293
Scott Baker1271a4d2016-04-06 14:34:49 -07001294 node.nodelabels = self.cleaned_data['nodelabels']
Scott Baker3ad54ff2016-03-04 10:35:32 -08001295
1296 if commit:
1297 node.save()
1298
1299 return node
1300
1301
1302class NodeLabelAdmin(XOSBaseAdmin):
1303 list_display = ('name',)
1304 list_display_links = ('name', )
1305
1306 fields = ('name', )
1307
1308
Scott Baker022cdcd2015-02-18 15:50:11 -08001309class NodeAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001310 form = NodeForm
Tony Mack93d1b032014-12-08 16:43:02 -05001311 list_display = ('backend_status_icon', 'name', 'site_deployment')
Scott Baker63d1a552014-08-21 15:19:07 -07001312 list_display_links = ('backend_status_icon', 'name', )
Tony Mack93d1b032014-12-08 16:43:02 -05001313 list_filter = ('site_deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001314
Tony Mack3de59e32015-08-19 11:58:18 -04001315 inlines = [TagInline,InstanceInline]
Scott Baker3ad54ff2016-03-04 10:35:32 -08001316 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name', 'site_deployment'], 'classes':['suit-tab suit-tab-details']}),
Scott Baker1271a4d2016-04-06 14:34:49 -07001317 ('Labels', {'fields': ['nodelabels'], 'classes':['suit-tab suit-tab-labels']})]
Scott Baker40c00762014-08-21 16:55:59 -07001318 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001319
Tony Mack93d1b032014-12-08 16:43:02 -05001320 user_readonly_fields = ['name','site_deployment']
Tony Mack3de59e32015-08-19 11:58:18 -04001321 user_readonly_inlines = [TagInline,InstanceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001322
Scott Baker2a67ed82016-03-31 15:04:05 -07001323 suit_form_tabs =(('details','Node Details'),('instances','Instances'), ('labels', 'Labels'), ('tags','Tags'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001324
Tony Mack17b97b82015-08-04 17:32:32 -04001325 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1326 if db_field.name == 'site':
1327 kwargs['queryset'] = Site.select_by_user(request.user).filter(hosts_nodes=True)
Siobhan Tully567e3e62013-06-21 18:03:16 -04001328
Scott Baker2a11f852015-09-21 15:06:38 -07001329 field = super(NodeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1330
1331 return field
1332
Tony Mack3de59e32015-08-19 11:58:18 -04001333class InstanceForm(forms.ModelForm):
Tony Mackd90cdbf2013-04-16 22:48:40 -04001334 class Meta:
Tony Mack3de59e32015-08-19 11:58:18 -04001335 model = Instance
Tony Mackd90cdbf2013-04-16 22:48:40 -04001336 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -04001337 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -04001338 widgets = {
1339 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -04001340 'instance_name': PlainTextWidget(),
Scott Baker887d4a82015-01-19 11:32:20 -08001341 'instance_id': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001342 'slice': LinkedSelect,
Tony Mackbf6aa302014-12-26 13:38:02 -05001343 'deployment': LinkedSelect,
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001344 'node': LinkedSelect,
1345 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -04001346 }
Tony Mackd90cdbf2013-04-16 22:48:40 -04001347
Scott Baker022cdcd2015-02-18 15:50:11 -08001348class TagAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001349 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1350 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001351 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1352 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -04001353
Tony Mack8bc36b92015-09-01 16:06:52 +00001354class InstancePortInline(XOSTabularInline):
Tony Mackea30da82015-09-10 21:58:15 +00001355 fields = ['backend_status_icon', 'network', 'instance', 'ip', 'mac']
Scott Baker0672e982015-09-08 18:22:15 -07001356 readonly_fields = ("backend_status_icon", "ip", "mac")
Scott Baker43facad2015-08-26 09:43:33 -07001357 model = Port
Scott Baker29514bc2015-11-16 16:21:47 -08001358 #selflink_fieldname = "network"
Scott Baker0bdb6a52015-08-25 18:00:15 -07001359 extra = 0
1360 verbose_name_plural = "Ports"
1361 verbose_name = "Port"
1362 suit_classes = 'suit-tab suit-tab-ports'
1363
Tony Mack3de59e32015-08-19 11:58:18 -04001364class InstanceAdmin(XOSBaseAdmin):
1365 form = InstanceForm
Tony Mackcdec0902013-04-15 00:38:49 -04001366 fieldsets = [
Scott Baker04a356a2015-11-12 17:25:53 -08001367 ('Instance Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'isolation', 'flavor', 'image', 'node', 'parent', 'all_ips_string', 'instance_id', 'instance_name', 'ssh_command', ], 'classes': ['suit-tab suit-tab-general'], }),
Scott Bakere1a6e0b2015-11-10 17:07:23 -08001368 ('Container Settings', {'fields': ['volumes'], 'classes': ['suit-tab suit-tab-container'], }),
Tony Mackcdec0902013-04-15 00:38:49 -04001369 ]
Tony Mack7d61efb2015-01-30 17:20:46 -05001370 readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string')
Scott Baker0cefa532015-11-09 16:17:11 -08001371 list_display = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'isolation', 'slice', 'flavor', 'image', 'node', 'deployment']
Scott Bakerf0b403f2015-02-13 14:38:21 -08001372 list_display_links = ('backend_status_icon', 'all_ips_string', 'instance_id', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001373
Scott Baker3cf51d62016-02-02 16:54:38 -08001374 suit_form_tabs =(('general', 'Instance Details'), ('ports', 'Ports'), ('container', 'Container Settings'), ('tags', 'Tags'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001375
Tony Mack6a782f92015-09-13 22:50:39 +00001376 inlines = [TagInline, InstancePortInline]
Tony Mack53106f32013-04-27 16:43:01 -04001377
Tony Mackbf6aa302014-12-26 13:38:02 -05001378 user_readonly_fields = ['slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001379
Scott Baker970314b2015-01-25 22:16:13 -08001380 def ssh_command(self, obj):
1381 ssh_command = obj.get_ssh_command()
1382 if ssh_command:
1383 return ssh_command
1384 else:
1385 return "(not available)"
1386
Tony Mackc2835a92013-05-28 09:18:49 -04001387 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1388 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001389 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001390
Tony Mack3de59e32015-08-19 11:58:18 -04001391 return super(InstanceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -04001392
Tony Mack04062832013-05-10 08:22:44 -04001393 def queryset(self, request):
Tony Mack3de59e32015-08-19 11:58:18 -04001394 # admins can see all instances. Users can only see instances of
Tony Mack04062832013-05-10 08:22:44 -04001395 # the slices they belong to.
Tony Mack3de59e32015-08-19 11:58:18 -04001396 return Instance.select_by_user(request.user)
Tony Mack5b061472014-02-04 07:57:10 -05001397
Scott Bakerb84392d2015-09-21 21:42:41 -07001398 def add_view(self, request, form_url='', extra_context = None):
1399 self.readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string')
1400 return super(InstanceAdmin,self).add_view(request, form_url, extra_context)
Tony Mack04062832013-05-10 08:22:44 -04001401
Scott Bakerb84392d2015-09-21 21:42:41 -07001402 def change_view(self, request, object_id, extra_context=None):
1403 self.readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string', 'deployment', 'slice', 'flavor', 'image', 'node')
1404 self.readonly_save = self.readonly_fields # for XOSAdminMixin.change_view's user_readonly_fields switching code
1405 return super(InstanceAdmin,self).change_view(request, object_id, extra_context)
Tony Mack53106f32013-04-27 16:43:01 -04001406
Scott Baker530e4de2015-09-21 16:02:54 -07001407 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
1408 deployment_nodes = []
Scott Baker34e16dc2016-03-22 09:37:57 -07001409# for node in Node.objects.all():
1410 for node in Node.objects.order_by("name"):
Scott Baker530e4de2015-09-21 16:02:54 -07001411 deployment_nodes.append( (node.site_deployment.deployment.id, node.id, node.name) )
1412
1413 deployment_flavors = []
1414 for flavor in Flavor.objects.all():
1415 for deployment in flavor.deployments.all():
1416 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
1417
1418 deployment_images = []
1419 for image in Image.objects.all():
1420 for deployment_image in image.imagedeployments.all():
1421 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
1422
1423 site_login_bases = []
1424 for site in Site.objects.all():
1425 site_login_bases.append((site.id, site.login_base))
1426
1427 context["deployment_nodes"] = deployment_nodes
1428 context["deployment_flavors"] = deployment_flavors
1429 context["deployment_images"] = deployment_images
1430 context["site_login_bases"] = site_login_bases
1431 return super(InstanceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
1432
1433 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1434 if db_field.name == 'deployment':
1435 kwargs['queryset'] = Deployment.select_by_acl(request.user).filter(sitedeployments__nodes__isnull=False).distinct()
1436 kwargs['widget'] = forms.Select(attrs={'onChange': "instance_deployment_changed(this);"})
1437 if db_field.name == 'flavor':
1438 kwargs['widget'] = forms.Select(attrs={'onChange': "instance_flavor_changed(this);"})
1439
1440 field = super(InstanceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1441
1442 return field
1443
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001444 #def save_model(self, request, obj, form, change):
1445 # # update openstack connection to use this site/tenant
1446 # auth = request.session.get('auth', {})
1447 # auth['tenant'] = obj.slice.name
1448 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1449 # obj.creator = request.user
1450 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -04001451
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001452 #def delete_model(self, request, obj):
1453 # # update openstack connection to use this site/tenant
1454 # auth = request.session.get('auth', {})
1455 # auth['tenant'] = obj.slice.name
1456 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1457 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -04001458
Scott Baker0cefa532015-11-09 16:17:11 -08001459#class ContainerPortInline(XOSTabularInline):
1460# fields = ['backend_status_icon', 'network', 'container', 'ip', 'mac', 'segmentation_id']
1461# readonly_fields = ("backend_status_icon", "ip", "mac", "segmentation_id")
1462# model = Port
1463# selflink_fieldname = "network"
1464# extra = 0
1465# verbose_name_plural = "Ports"
1466# verbose_name = "Port"
1467# suit_classes = 'suit-tab suit-tab-ports'
Scott Baker1de8b0d2015-10-26 19:52:10 -07001468
Scott Baker0cefa532015-11-09 16:17:11 -08001469#class ContainerAdmin(XOSBaseAdmin):
1470# fieldsets = [
1471# ('Container Details', {'fields': ['backend_status_text', 'slice', 'node', 'docker_image', 'volumes', 'no_sync'], 'classes': ['suit-tab suit-tab-general'], })
1472# ]
1473# readonly_fields = ('backend_status_text', )
1474# list_display = ['backend_status_icon', 'id']
1475# list_display_links = ('backend_status_icon', 'id', )
1476#
1477# suit_form_tabs =(('general', 'Container Details'), ('ports', 'Ports'))
1478#
1479# inlines = [TagInline, ContainerPortInline]
1480#
1481# def formfield_for_foreignkey(self, db_field, request, **kwargs):
1482# if db_field.name == 'slice':
1483# kwargs['queryset'] = Slice.select_by_user(request.user)
1484#
1485# return super(ContainerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1486#
1487# def queryset(self, request):
1488# # admins can see all instances. Users can only see instances of
1489# # the slices they belong to.
1490# return Container.select_by_user(request.user)
Scott Bakerda513652015-10-26 15:12:13 -07001491
Siobhan Tully53437282013-04-26 19:30:27 -04001492class UserCreationForm(forms.ModelForm):
1493 """A form for creating new users. Includes all the required
1494 fields, plus a repeated password."""
1495 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1496 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1497
1498 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001499 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001500 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -04001501
1502 def clean_password2(self):
1503 # Check that the two password entries match
1504 password1 = self.cleaned_data.get("password1")
1505 password2 = self.cleaned_data.get("password2")
1506 if password1 and password2 and password1 != password2:
1507 raise forms.ValidationError("Passwords don't match")
1508 return password2
1509
1510 def save(self, commit=True):
1511 # Save the provided password in hashed format
1512 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -04001513 user.password = self.cleaned_data["password1"]
1514 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -04001515 if commit:
1516 user.save()
1517 return user
1518
Siobhan Tully567e3e62013-06-21 18:03:16 -04001519
Siobhan Tully53437282013-04-26 19:30:27 -04001520class UserChangeForm(forms.ModelForm):
1521 """A form for updating users. Includes all the fields on
1522 the user, but replaces the password field with admin's
1523 password hash display field.
1524 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -05001525 password = ReadOnlyPasswordHashField(label='Password',
1526 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -04001527
Scott Baker811d4472015-05-19 16:39:48 -07001528 PROFILE_CHOICES = ((None, '------'), ('regular', 'Regular user'), ('cp', 'Content Provider'))
1529 profile = forms.ChoiceField(choices=PROFILE_CHOICES, required=False, label="Quick Profile")
1530
Siobhan Tully53437282013-04-26 19:30:27 -04001531 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001532 model = User
Scott Baker9f6b8ed2014-11-17 23:44:03 -08001533 widgets = { 'public_key': UploadTextareaWidget, }
Siobhan Tully53437282013-04-26 19:30:27 -04001534
1535 def clean_password(self):
1536 # Regardless of what the user provides, return the initial value.
1537 # This is done here, rather than on the field, because the
1538 # field does not have access to the initial value
1539 return self.initial["password"]
1540
Scott Baker811d4472015-05-19 16:39:48 -07001541 def save(self, *args, **kwargs):
1542 if self.cleaned_data['profile']:
1543 self.instance.apply_profile(self.cleaned_data['profile'])
1544
1545 return super(UserChangeForm, self).save(*args, **kwargs)
1546
Scott Baker022cdcd2015-02-18 15:50:11 -08001547class UserDashboardViewInline(XOSTabularInline):
Scott Baker2c3cb642014-05-19 17:55:56 -07001548 model = UserDashboardView
1549 extra = 0
1550 suit_classes = 'suit-tab suit-tab-dashboards'
1551 fields = ['user', 'dashboardView', 'order']
1552
Scott Baker022cdcd2015-02-18 15:50:11 -08001553class ControllerUserInline(XOSTabularInline):
Tony Mack3c01ff92015-01-10 23:08:10 -05001554 model = ControllerUser
1555 extra = 0
1556 suit_classes = 'suit-tab suit-tab-admin-only'
1557 fields = ['controller', 'user', 'kuser_id']
Tony Mack3c01ff92015-01-10 23:08:10 -05001558
1559
Scott Bakera9412c32015-02-27 12:21:22 -08001560class UserAdmin(XOSAdminMixin, UserAdmin):
1561 # Note: Make sure XOSAdminMixin is listed before
Scott Baker86c83ab2014-10-03 13:10:47 -07001562 # admin.ModelAdmin in the class declaration.
1563
Siobhan Tully53437282013-04-26 19:30:27 -04001564 class Meta:
1565 app_label = "core"
1566
1567 # The forms to add and change user instances
1568 form = UserChangeForm
1569 add_form = UserCreationForm
1570
1571 # The fields to be used in displaying the User model.
1572 # These override the definitions on the base UserAdmin
1573 # that reference specific fields on auth.User.
Scott Bakerf587f442015-01-24 13:33:26 -08001574 list_display = ('backend_status_icon', 'email', 'firstname', 'lastname', 'site', 'last_login')
1575 list_display_links = ("email",)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001576 list_filter = ('site',)
Scott Baker6e9027f2015-01-29 10:55:53 -08001577 inlines = [SlicePrivilegeInline,SitePrivilegeInline]
Tony Mack3c01ff92015-01-10 23:08:10 -05001578 admin_inlines = [ControllerUserInline]
Scott Bakere61e3a02015-06-10 16:14:58 -07001579 fieldListLoginDetails = ['backend_status_text', 'email', 'site','password','is_active','is_readonly','is_admin','is_appuser', 'public_key', 'login_page', 'profile']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001580 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1581
Siobhan Tully53437282013-04-26 19:30:27 -04001582 fieldsets = (
Scott Baker74ebc7a2015-05-15 09:19:36 -07001583 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'is_appuser', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001584 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001585 #('Important dates', {'fields': ('last_login',)}),
1586 )
1587 add_fieldsets = (
1588 (None, {
1589 'classes': ('wide',),
Scott Baker74ebc7a2015-05-15 09:19:36 -07001590 'fields': ('site', 'email', 'firstname', 'lastname', 'is_admin', 'is_readonly', 'is_appuser', 'phone', 'public_key','password1', 'password2')},
Siobhan Tully53437282013-04-26 19:30:27 -04001591 ),
1592 )
Scott Baker40c00762014-08-21 16:55:59 -07001593 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001594 search_fields = ('email',)
1595 ordering = ('email',)
1596 filter_horizontal = ()
1597
Scott Baker3ca51f62014-05-23 12:05:11 -07001598 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001599
Scott Baker0a5633b2014-10-06 17:51:20 -07001600 @property
1601 def suit_form_tabs(self):
1602 if getattr(_thread_locals, "obj", None) is None:
1603 return []
1604 else:
Tony Mack3c01ff92015-01-10 23:08:10 -05001605 tabs = [('general','Login Details'),
Scott Baker0a5633b2014-10-06 17:51:20 -07001606 ('contact','Contact Information'),
1607 ('sliceprivileges','Slice Privileges'),
Scott Baker6e9027f2015-01-29 10:55:53 -08001608 ('siteprivileges','Site Privileges')]
Tony Mack3c01ff92015-01-10 23:08:10 -05001609
1610 request=getattr(_thread_locals, "request", None)
1611 if request and request.user.is_admin:
1612 tabs.append( ('admin-only', 'Admin-Only') )
1613
1614 return tabs
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001615
Tony Mackc2835a92013-05-28 09:18:49 -04001616 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1617 if db_field.name == 'site':
Tony Mack17b97b82015-08-04 17:32:32 -04001618 kwargs['queryset'] = Site.select_by_user(request.user).filter(hosts_users=True)
Tony Mackc2835a92013-05-28 09:18:49 -04001619
1620 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1621
Tony Mack5b061472014-02-04 07:57:10 -05001622 def queryset(self, request):
1623 return User.select_by_user(request.user)
1624
Tony Mack6235fc82015-01-25 21:58:30 -05001625 def get_form(self, request, obj=None, **kwargs):
Tony Mackb2c407f2015-01-28 12:37:12 -05001626 # copy login details list
1627 login_details_fields = list(self.fieldListLoginDetails)
Tony Mack92b12052015-01-28 12:49:58 -05001628 if not request.user.is_admin:
Scott Baker6e9027f2015-01-29 10:55:53 -08001629 # only admins can see 'is_admin' and 'is_readonly' fields
Tony Mackb2c407f2015-01-28 12:37:12 -05001630 if 'is_admin' in login_details_fields:
1631 login_details_fields.remove('is_admin')
1632 if 'is_readonly' in login_details_fields:
Scott Baker811d4472015-05-19 16:39:48 -07001633 login_details_fields.remove('is_readonly')
1634 if 'is_appuser' in login_details_fields:
1635 login_details_fields.remove('is_admin')
1636 if 'profile' in login_details_fields:
1637 login_details_fields.remove('profile')
Tony Mack92b12052015-01-28 12:49:58 -05001638 #if len(request.user.siteprivileges.filter(role__role = 'pi')) > 0:
Scott Baker811d4472015-05-19 16:39:48 -07001639 # only admins and pis can change a user's site
Tony Mack92b12052015-01-28 12:49:58 -05001640 # self.readonly_fields = ('backend_status_text', 'site')
Tony Mackb2c407f2015-01-28 12:37:12 -05001641 self.fieldsets = (
1642 ('Login Details', {'fields': login_details_fields, 'classes':['suit-tab suit-tab-general']}),
1643 ('Contact Information', {'fields': self.fieldListContactInfo, 'classes':['suit-tab suit-tab-contact']}),
1644 )
Tony Mack6235fc82015-01-25 21:58:30 -05001645 return super(UserAdmin, self).get_form(request, obj, **kwargs)
1646
Scott Baker022cdcd2015-02-18 15:50:11 -08001647class ControllerDashboardViewInline(XOSTabularInline):
Scott Bakera6a0c772014-12-22 17:35:34 -08001648 model = ControllerDashboardView
Scott Bakereef5a6b2014-12-19 16:41:12 -08001649 extra = 0
1650 fields = ["controller", "url"]
1651 suit_classes = 'suit-tab suit-tab-controllers'
1652
Scott Baker022cdcd2015-02-18 15:50:11 -08001653class DashboardViewAdmin(XOSBaseAdmin):
Scott Baker2c3cb642014-05-19 17:55:56 -07001654 fieldsets = [('Dashboard View Details',
Scott Bakerecc55ac2015-02-17 13:34:32 -08001655 {'fields': ['backend_status_text', 'name', 'url', 'enabled', 'deployments'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001656 'classes': ['suit-tab suit-tab-general']})
1657 ]
Scott Baker9daf19c2015-01-18 16:46:26 -08001658 list_display = ["name", "enabled", "url"]
Scott Baker40c00762014-08-21 16:55:59 -07001659 readonly_fields = ('backend_status_text', )
Scott Bakera6a0c772014-12-22 17:35:34 -08001660 inlines = [ControllerDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001661
Scott Bakereef5a6b2014-12-19 16:41:12 -08001662 suit_form_tabs =(('general','Dashboard View Details'),
1663 ('controllers', 'Per-controller Dashboard Details'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001664
Scott Baker022cdcd2015-02-18 15:50:11 -08001665class ServiceResourceInline(XOSTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001666 model = ServiceResource
1667 extra = 0
1668
Scott Baker022cdcd2015-02-18 15:50:11 -08001669class ServiceClassAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001670 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1671 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001672 inlines = [ServiceResourceInline]
1673
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001674 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1675 user_readonly_inlines = []
1676
Scott Baker022cdcd2015-02-18 15:50:11 -08001677class ReservedResourceInline(XOSTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001678 model = ReservedResource
1679 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001680 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001681
1682 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1683 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1684
1685 if db_field.name == 'resource':
1686 # restrict resources to those that the slice's service class allows
1687 if request._slice is not None:
1688 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1689 if len(field.queryset) > 0:
1690 field.initial = field.queryset.all()[0]
S.Çağlar Onur0e591832015-02-24 17:28:09 -05001691 else:
1692 field.queryset = field.queryset.none()
Tony Mack3de59e32015-08-19 11:58:18 -04001693 elif db_field.name == 'instance':
1694 # restrict instances to those that belong to the slice
S.Çağlar Onur0e591832015-02-24 17:28:09 -05001695 if request._slice is not None:
Scott Baker133c9212013-05-17 09:09:11 -07001696 field.queryset = field.queryset.filter(slice = request._slice)
1697 else:
S.Çağlar Onur0e591832015-02-24 17:28:09 -05001698 field.queryset = field.queryset.none()
1699
Scott Baker133c9212013-05-17 09:09:11 -07001700 return field
1701
Tony Mack5b061472014-02-04 07:57:10 -05001702 def queryset(self, request):
1703 return ReservedResource.select_by_user(request.user)
1704
Scott Baker133c9212013-05-17 09:09:11 -07001705class ReservationChangeForm(forms.ModelForm):
1706 class Meta:
1707 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001708 widgets = {
1709 'slice' : LinkedSelect
1710 }
Scott Baker133c9212013-05-17 09:09:11 -07001711
1712class ReservationAddForm(forms.ModelForm):
1713 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1714 refresh = forms.CharField(widget=forms.HiddenInput())
1715
1716 class Media:
Scott Baker97468b72015-02-18 15:15:58 -08001717 css = {'all': ('xos.css',)} # .field-refresh { display: none; }
Scott Baker133c9212013-05-17 09:09:11 -07001718
1719 def clean_slice(self):
1720 slice = self.cleaned_data.get("slice")
1721 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1722 if len(x) == 0:
1723 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1724 return slice
1725
1726 class Meta:
1727 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001728 widgets = {
1729 'slice' : LinkedSelect
1730 }
1731
Scott Baker133c9212013-05-17 09:09:11 -07001732
1733class ReservationAddRefreshForm(ReservationAddForm):
1734 """ This form is displayed when the Reservation Form receives an update
1735 from the Slice dropdown onChange handler. It doesn't validate the
1736 data and doesn't save the data. This will cause the form to be
1737 redrawn.
1738 """
1739
Scott Baker8737e5f2013-05-17 09:35:32 -07001740 """ don't validate anything other than slice """
1741 dont_validate_fields = ("startTime", "duration")
1742
Scott Baker133c9212013-05-17 09:09:11 -07001743 def full_clean(self):
1744 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001745
1746 for fieldname in self.dont_validate_fields:
1747 if fieldname in self._errors:
1748 del self._errors[fieldname]
1749
Scott Baker133c9212013-05-17 09:09:11 -07001750 return result
1751
1752 """ don't save anything """
1753 def is_valid(self):
1754 return False
1755
Scott Baker022cdcd2015-02-18 15:50:11 -08001756class ReservationAdmin(XOSBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001757 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001758 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001759 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001760 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001761 form = ReservationAddForm
1762
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001763 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1764
1765 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001766 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001767
Scott Baker133c9212013-05-17 09:09:11 -07001768 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001769 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001770 request._refresh = False
1771 request._slice = None
1772 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001773 # "refresh" will be set to "1" if the form was submitted due to
1774 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001775 if request.POST.get("refresh","1") == "1":
1776 request._refresh = True
1777 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001778
1779 # Keep track of the slice that was selected, so the
1780 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001781 request._slice = request.POST.get("slice",None)
1782 if (request._slice is not None):
1783 request._slice = Slice.objects.get(id=request._slice)
1784
1785 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1786 return result
1787
Scott Bakeracd45142013-05-19 16:19:16 -07001788 def changelist_view(self, request, extra_context = None):
1789 timezone.activate(request.user.timezone)
1790 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1791
Scott Baker133c9212013-05-17 09:09:11 -07001792 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001793 request._obj_ = obj
1794 if obj is not None:
1795 # For changes, set request._slice to the slice already set in the
1796 # object.
1797 request._slice = obj.slice
1798 self.form = ReservationChangeForm
1799 else:
1800 if getattr(request, "_refresh", False):
1801 self.form = ReservationAddRefreshForm
1802 else:
1803 self.form = ReservationAddForm
1804 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1805
Scott Baker133c9212013-05-17 09:09:11 -07001806 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001807 if (obj is not None):
1808 # Prevent slice from being changed after the reservation has been
1809 # created.
1810 return ['slice']
1811 else:
Scott Baker133c9212013-05-17 09:09:11 -07001812 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001813
Tony Mack5b061472014-02-04 07:57:10 -05001814 def queryset(self, request):
1815 return Reservation.select_by_user(request.user)
1816
Scott Baker022cdcd2015-02-18 15:50:11 -08001817class NetworkParameterTypeAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001818 list_display = ("backend_status_icon", "name", )
1819 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001820 user_readonly_fields = ['name']
1821 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001822
Scott Baker022cdcd2015-02-18 15:50:11 -08001823class RouterAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001824 list_display = ("backend_status_icon", "name", )
1825 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001826 user_readonly_fields = ['name']
1827 user_readonly_inlines = []
1828
Scott Baker022cdcd2015-02-18 15:50:11 -08001829class RouterInline(XOSTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001830 model = Router.networks.through
1831 extra = 0
1832 verbose_name_plural = "Routers"
1833 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001834 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001835
Scott Bakerb27b62c2014-08-15 16:29:16 -07001836class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001837 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001838 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001839 verbose_name_plural = "Parameters"
1840 verbose_name = "Parameter"
1841 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001842 fields = ['backend_status_icon', 'parameter', 'value']
1843 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001844
Scott Baker0bdb6a52015-08-25 18:00:15 -07001845class NetworkPortInline(XOSTabularInline):
Scott Baker0cefa532015-11-09 16:17:11 -08001846 fields = ['backend_status_icon', 'network', 'instance', 'ip', 'mac']
Scott Baker0672e982015-09-08 18:22:15 -07001847 readonly_fields = ("backend_status_icon", "ip", "mac")
Scott Baker43facad2015-08-26 09:43:33 -07001848 model = Port
Scott Bakerf3e905d2015-11-16 13:43:38 -08001849 #selflink_fieldname = "instance"
Scott Baker74d8e622013-07-29 16:04:22 -07001850 extra = 0
Scott Bakera68e6e32015-08-25 17:11:30 -07001851 verbose_name_plural = "Ports"
1852 verbose_name = "Port"
1853 suit_classes = 'suit-tab suit-tab-ports'
Scott Baker74d8e622013-07-29 16:04:22 -07001854
Scott Baker022cdcd2015-02-18 15:50:11 -08001855class NetworkSlicesInline(XOSTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001856 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001857 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001858 extra = 0
1859 verbose_name_plural = "Slices"
1860 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001861 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001862 fields = ['backend_status_icon', 'network','slice']
1863 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001864
Scott Baker022cdcd2015-02-18 15:50:11 -08001865class ControllerNetworkInline(XOSTabularInline):
Tony Macka7dbd422015-01-05 22:48:11 -05001866 model = ControllerNetwork
Scott Bakerfbb45862014-10-17 16:27:23 -07001867 extra = 0
Tony Mack336e0f92014-11-30 15:53:08 -05001868 verbose_name_plural = "Controller Networks"
1869 verbose_name = "Controller Network"
Scott Bakerfbb45862014-10-17 16:27:23 -07001870 suit_classes = 'suit-tab suit-tab-admin-only'
Scott Baker49a1b4d2016-02-08 16:02:52 -08001871 fields = ['backend_status_icon', 'controller','net_id','subnet_id','subnet']
Scott Bakerfbb45862014-10-17 16:27:23 -07001872 readonly_fields = ('backend_status_icon', )
1873
Scott Baker9f6b8ed2014-11-17 23:44:03 -08001874class NetworkForm(forms.ModelForm):
1875 class Meta:
1876 model = Network
1877 widgets = {
1878 'topologyParameters': UploadTextareaWidget,
1879 'controllerParameters': UploadTextareaWidget,
1880 }
1881
Scott Baker022cdcd2015-02-18 15:50:11 -08001882class NetworkAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001883 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1884 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001885 readonly_fields = ("subnet", )
Scott Baker0bdb6a52015-08-25 18:00:15 -07001886 inlines = [NetworkParameterInline, NetworkPortInline, NetworkSlicesInline, RouterInline]
Tony Macka7dbd422015-01-05 22:48:11 -05001887 admin_inlines = [ControllerNetworkInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001888
Scott Baker9f6b8ed2014-11-17 23:44:03 -08001889 form=NetworkForm
1890
Siobhan Tully2d95e482013-09-06 10:56:06 -04001891 fieldsets = [
Scott Bakera4c11bd2015-08-21 16:40:53 -07001892 (None, {'fields': ['backend_status_text', 'name','template','ports','labels',
1893 'owner','guaranteed_bandwidth', 'permit_all_slices',
1894 'permitted_slices','network_id','router_id','subnet_id',
1895 'subnet', 'autoconnect'],
Scott Baker40248712014-11-17 16:04:45 -08001896 'classes':['suit-tab suit-tab-general']}),
Scott Baker0451fb62015-01-03 12:29:29 -08001897 (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
Scott Baker40248712014-11-17 16:04:45 -08001898 'classes':['suit-tab suit-tab-sdn']}),
1899 ]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001900
Scott Baker40c00762014-08-21 16:55:59 -07001901 readonly_fields = ('backend_status_text', )
Scott Bakera4c11bd2015-08-21 16:40:53 -07001902 user_readonly_fields = ['name','template','ports','labels','owner','guaranteed_bandwidth',
1903 'permit_all_slices','permitted_slices','network_id','router_id',
1904 'subnet_id','subnet','autoconnect']
Siobhan Tully2d95e482013-09-06 10:56:06 -04001905
Scott Bakerfbb45862014-10-17 16:27:23 -07001906 @property
1907 def suit_form_tabs(self):
1908 tabs=[('general','Network Details'),
Scott Baker40248712014-11-17 16:04:45 -08001909 ('sdn', 'SDN Configuration'),
Scott Bakerfbb45862014-10-17 16:27:23 -07001910 ('netparams', 'Parameters'),
Scott Bakera68e6e32015-08-25 17:11:30 -07001911 ('ports','Ports'),
Scott Bakerfbb45862014-10-17 16:27:23 -07001912 ('networkslices','Slices'),
1913 ('routers','Routers'),
1914 ]
1915
1916 request=getattr(_thread_locals, "request", None)
1917 if request and request.user.is_admin:
1918 tabs.append( ('admin-only', 'Admin-Only') )
1919
1920 return tabs
1921
1922
Scott Baker022cdcd2015-02-18 15:50:11 -08001923class NetworkTemplateAdmin(XOSBaseAdmin):
Scott Baker81fa17f2015-01-03 12:03:38 -08001924 list_display = ("backend_status_icon", "name", "guaranteed_bandwidth", "visibility")
Scott Baker63d1a552014-08-21 15:19:07 -07001925 list_display_links = ('backend_status_icon', 'name', )
Scott Baker81fa17f2015-01-03 12:03:38 -08001926 user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001927 user_readonly_inlines = []
Scott Baker29514bc2015-11-16 16:21:47 -08001928 inlines = [NetworkParameterInline,]
Scott Baker40248712014-11-17 16:04:45 -08001929 fieldsets = [
Scott Baker9d5bf3b2015-12-09 15:44:55 -08001930 (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'access', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
Scott Baker40248712014-11-17 16:04:45 -08001931 'classes':['suit-tab suit-tab-general']}),]
Scott Baker29514bc2015-11-16 16:21:47 -08001932 suit_form_tabs = (('general','Network Template Details'), ('netparams', 'Parameters') )
Scott Baker74d8e622013-07-29 16:04:22 -07001933
Scott Bakerf3e905d2015-11-16 13:43:38 -08001934class PortAdmin(XOSBaseAdmin):
Scott Bakerc708e302016-02-15 17:03:20 -08001935 list_display = ("backend_status_icon", "id", "ip")
Scott Bakerf3e905d2015-11-16 13:43:38 -08001936 list_display_links = ('backend_status_icon', 'id')
1937 readonly_fields = ("subnet", )
1938 inlines = [NetworkParameterInline]
1939
1940 fieldsets = [
1941 (None, {'fields': ['backend_status_text', 'network', 'instance', 'ip', 'port_id', 'mac'],
1942 'classes':['suit-tab suit-tab-general']}),
1943 ]
1944
1945 readonly_fields = ('backend_status_text', )
1946 suit_form_tabs = (('general', 'Port Details'), ('netparams', 'Parameters'))
1947
Scott Baker022cdcd2015-02-18 15:50:11 -08001948class FlavorAdmin(XOSBaseAdmin):
Scott Baker37b47902014-09-02 14:37:41 -07001949 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1950 list_display_links = ("backend_status_icon", "name")
1951 user_readonly_fields = ("name", "flavor")
1952 fields = ("name", "description", "flavor", "order", "default")
1953
Tony Mack31c2b8f2013-04-26 20:01:42 -04001954# register a signal that caches the user's credentials when they log in
1955def cache_credentials(sender, user, request, **kwds):
1956 auth = {'username': request.POST['username'],
1957 'password': request.POST['password']}
1958 request.session['auth'] = auth
1959user_logged_in.connect(cache_credentials)
1960
Scott Baker15cddfa2013-12-09 13:45:19 -08001961def dollar_field(fieldName, short_description):
1962 def newFunc(self, obj):
1963 try:
1964 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1965 except:
1966 x=getattr(obj, fieldName, 0.0)
1967 return x
1968 newFunc.short_description = short_description
1969 return newFunc
1970
1971def right_dollar_field(fieldName, short_description):
1972 def newFunc(self, obj):
1973 try:
1974 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1975 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1976 except:
1977 x=getattr(obj, fieldName, 0.0)
1978 return x
1979 newFunc.short_description = short_description
1980 newFunc.allow_tags = True
1981 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001982
Scott Baker022cdcd2015-02-18 15:50:11 -08001983class InvoiceChargeInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001984 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001985 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001986 verbose_name_plural = "Charges"
1987 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001988 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001989 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1990 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1991 can_delete = False
1992 max_num = 0
1993
1994 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001995
1996class InvoiceAdmin(admin.ModelAdmin):
1997 list_display = ("date", "account")
1998
1999 inlines = [InvoiceChargeInline]
2000
Scott Baker9cb88a22013-12-09 18:56:00 -08002001 fields = ["date", "account", "dollar_amount"]
2002 readonly_fields = ["date", "account", "dollar_amount"]
2003
2004 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08002005
Scott Baker022cdcd2015-02-18 15:50:11 -08002006class InvoiceInline(XOSTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08002007 model = Invoice
2008 extra = 0
2009 verbose_name_plural = "Invoices"
2010 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08002011 fields = ["date", "dollar_amount"]
2012 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08002013 suit_classes = 'suit-tab suit-tab-accountinvoice'
2014 can_delete=False
2015 max_num=0
2016
2017 dollar_amount = right_dollar_field("amount", "Amount")
2018
Scott Baker022cdcd2015-02-18 15:50:11 -08002019class PendingChargeInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08002020 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08002021 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08002022 verbose_name_plural = "Charges"
2023 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002024 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08002025 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
2026 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08002027 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08002028 can_delete=False
2029 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08002030
2031 def queryset(self, request):
2032 qs = super(PendingChargeInline, self).queryset(request)
2033 qs = qs.filter(state="pending")
2034 return qs
2035
Scott Baker15cddfa2013-12-09 13:45:19 -08002036 dollar_amount = right_dollar_field("amount", "Amount")
2037
Scott Baker022cdcd2015-02-18 15:50:11 -08002038class PaymentInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08002039 model=Payment
2040 extra = 1
2041 verbose_name_plural = "Payments"
2042 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08002043 fields = ["date", "dollar_amount"]
2044 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08002045 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08002046 can_delete=False
2047 max_num=0
2048
2049 dollar_amount = right_dollar_field("amount", "Amount")
2050
Scott Baker43105042013-12-06 23:23:36 -08002051class AccountAdmin(admin.ModelAdmin):
2052 list_display = ("site", "balance_due")
2053
2054 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
2055
2056 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002057 (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 -08002058
Scott Baker15cddfa2013-12-09 13:45:19 -08002059 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08002060
2061 suit_form_tabs =(
2062 ('general','Account Details'),
2063 ('accountinvoice', 'Invoices'),
2064 ('accountpayments', 'Payments'),
2065 ('accountpendingcharges','Pending Charges'),
2066 )
2067
Scott Baker15cddfa2013-12-09 13:45:19 -08002068 dollar_balance_due = dollar_field("balance_due", "Balance Due")
2069 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
2070 dollar_total_payments = dollar_field("total_payments", "Total Payments")
2071
Scott Baker2461bec2015-08-14 09:10:11 -07002072class ProgramForm(forms.ModelForm):
2073 class Meta:
2074 model = Program
2075 widgets = {
2076 'contents': UploadTextareaWidget(attrs={'rows': 20, 'cols': 80, 'class': "input-xxlarge"}),
2077 'description': forms.Textarea(attrs={'rows': 3, 'cols': 80, 'class': 'input-xxlarge'}),
2078 'messages': forms.Textarea(attrs={'rows': 20, 'cols': 80, 'class': 'input-xxlarge'}),
2079 'output': forms.Textarea(attrs={'rows': 3, 'cols': 80, 'class': 'input-xxlarge'})
2080 }
2081
2082class ProgramAdmin(XOSBaseAdmin):
2083 list_display = ("name", "status")
2084 list_display_links = ('name', "status")
2085
2086 form=ProgramForm
2087
2088 fieldsets = [
2089 (None, {'fields': ['name', 'command', 'kind', 'description', 'output', 'status'],
2090 'classes':['suit-tab suit-tab-general']}),
2091 (None, {'fields': ['contents'],
2092 'classes':['suit-tab suit-tab-contents']}),
2093 (None, {'fields': ['messages'],
2094 'classes':['suit-tab suit-tab-messages']}),
2095 ]
2096
2097 readonly_fields = ("status",)
2098
2099 @property
2100 def suit_form_tabs(self):
2101 tabs=[('general','Program Details'),
2102 ('contents','Program Source'),
2103 ('messages','Messages'),
2104 ]
2105
2106 request=getattr(_thread_locals, "request", None)
2107 if request and request.user.is_admin:
2108 tabs.append( ('admin-only', 'Admin-Only') )
2109
2110 return tabs
2111
Scott Baker23ab1992016-04-13 15:55:16 -07002112class AddressPoolForm(forms.ModelForm):
2113 class Meta:
2114 model = Program
2115 widgets = {
2116 'addresses': UploadTextareaWidget(attrs={'rows': 20, 'cols': 80, 'class': "input-xxlarge"}),
2117 }
2118
2119class AddressPoolAdmin(XOSBaseAdmin):
Scott Baker44ded762016-04-13 16:40:50 -07002120 list_display = ("name", "cidr")
Scott Baker23ab1992016-04-13 15:55:16 -07002121 list_display_links = ('name',)
2122
2123 form=AddressPoolForm
2124
2125 fieldsets = [
Scott Baker44ded762016-04-13 16:40:50 -07002126 (None, {'fields': ['name', 'cidr', 'gateway_ip', 'gateway_mac', 'addresses', 'inuse', 'service'],
Scott Baker23ab1992016-04-13 15:55:16 -07002127 'classes':['suit-tab suit-tab-general']}),
2128 ]
2129
2130 readonly_fields = ("status",)
2131
2132 @property
2133 def suit_form_tabs(self):
2134 tabs=[('general','Program Details'),
2135 ('contents','Program Source'),
2136 ('messages','Messages'),
2137 ]
2138
2139# request=getattr(_thread_locals, "request", None)
2140# if request and request.user.is_admin:
2141# tabs.append( ('admin-only', 'Admin-Only') )
2142
2143 return tabs
2144
Siobhan Tully53437282013-04-26 19:30:27 -04002145# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04002146admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04002147# ... and, since we're not using Django's builtin permissions,
2148# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04002149#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04002150
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002151# When debugging it is often easier to see all the classes, but for regular use
2152# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002153showAll = False
Scott Baker43105042013-12-06 23:23:36 -08002154
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002155admin.site.register(Deployment, DeploymentAdmin)
Tony Mack336e0f92014-11-30 15:53:08 -05002156admin.site.register(Controller, ControllerAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04002157admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04002158admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04002159admin.site.register(Service, ServiceAdmin)
Tony Mack598eaf22015-01-25 12:35:29 -05002160#admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07002161admin.site.register(Network, NetworkAdmin)
Scott Bakerf3e905d2015-11-16 13:43:38 -08002162admin.site.register(Port, PortAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07002163admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07002164admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Scott Baker2461bec2015-08-14 09:10:11 -07002165admin.site.register(Program, ProgramAdmin)
Tony Mack598eaf22015-01-25 12:35:29 -05002166#admin.site.register(Account, AccountAdmin)
2167#admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002168
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002169if True:
2170 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
2171 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullyd3515752013-06-21 16:34:53 -04002172 admin.site.register(Tag, TagAdmin)
Tony Mack336e0f92014-11-30 15:53:08 -05002173 admin.site.register(ControllerRole)
Siobhan Tullyce652d02013-10-08 21:52:35 -04002174 admin.site.register(SiteRole)
2175 admin.site.register(SliceRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002176 admin.site.register(Node, NodeAdmin)
Scott Baker3ad54ff2016-03-04 10:35:32 -08002177 admin.site.register(NodeLabel, NodeLabelAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04002178 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
2179 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Tony Mack3de59e32015-08-19 11:58:18 -04002180 admin.site.register(Instance, InstanceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002181 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07002182 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07002183 admin.site.register(Flavor, FlavorAdmin)
Scott Baker1b06b6c2015-07-06 14:40:20 -07002184 admin.site.register(TenantRoot, TenantRootAdmin)
2185 admin.site.register(TenantRootRole, TenantRootRoleAdmin)
Scott Baker462a1d92015-10-15 15:59:19 -07002186 admin.site.register(TenantAttribute, TenantAttributeAdmin)
Scott Baker23ab1992016-04-13 15:55:16 -07002187 admin.site.register(AddressPool, AddressPoolAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04002188