blob: 1c4847731450b2d7c0cf04680621361bd1a643ca [file] [log] [blame]
Jeremy Mowery18cde4f2016-04-03 13:04:39 -07001import threading
2from cgi import escape as html_escape
Tony Macke59a7c82013-04-27 11:08:10 -04003
Jeremy Moweryfda63ce2016-04-19 10:30:28 -07004from core.models import *
5from core.models import Site
Siobhan Tully4bc09f22013-04-10 21:15:21 -04006from django import forms
Jeremy Mowery18cde4f2016-04-03 13:04:39 -07007from django.contrib import admin, messages
8from django.contrib.admin.widgets import (AdminTextareaWidget,
9 FilteredSelectMultiple)
Tony Mack7130ac32013-03-22 21:58:00 -040010from django.contrib.auth.admin import UserAdmin
Jeremy Mowery18cde4f2016-04-03 13:04:39 -070011from django.contrib.auth.forms import (AdminPasswordChangeForm,
12 ReadOnlyPasswordHashField)
13from django.contrib.auth.models import Group
Scott Bakeracd45142013-05-19 16:19:16 -070014from django.contrib.auth.signals import user_logged_in
Siobhan Tullyde5450d2013-06-21 11:35:33 -040015from django.contrib.contenttypes import generic
Jeremy Mowery18cde4f2016-04-03 13:04:39 -070016from django.core.exceptions import PermissionDenied, ValidationError
17from django.core.urlresolvers import NoReverseMatch, resolve, reverse
18from django.forms.utils import flatatt, to_current_timezone
19from django.utils import timezone
Scott Baker69e045d2014-11-17 23:44:03 -080020from django.utils.encoding import force_text, python_2_unicode_compatible
21from django.utils.html import conditional_escape, format_html
Jeremy Mowery18cde4f2016-04-03 13:04:39 -070022from django.utils.safestring import mark_safe
Scott Bakera9b8f612015-02-26 20:42:11 -080023from django.utils.text import capfirst
Jeremy Mowery18cde4f2016-04-03 13:04:39 -070024from openstack.manager import OpenStackManager
25from suit.widgets import LinkedSelect
Scott Baker6a995352014-10-06 17:51:20 -070026
27# thread locals necessary to work around a django-suit issue
28_thread_locals = threading.local()
Scott Baker36f50872014-08-21 13:01:25 -070029
Scott Bakere5f9d7d2015-02-10 18:24:20 -080030ICON_URLS = {"success": "/static/admin/img/icon_success.gif",
Jeremy Moweryda57d402016-04-15 17:39:49 -070031 "clock": "/static/admin/img/icon_clock.gif",
32 "error": "/static/admin/img/icon_error.gif"}
33
Scott Bakere5f9d7d2015-02-10 18:24:20 -080034
35def backend_icon(obj):
36 (icon, tooltip) = obj.get_backend_icon()
Sapan Bhatia02dc8332015-12-22 18:37:47 +010037
Scott Bakere5f9d7d2015-02-10 18:24:20 -080038 icon_url = ICON_URLS.get(icon, "unknown")
39
Jeremy Moweryda57d402016-04-15 17:39:49 -070040 (exponent, last_success, last_failure, failures) = obj.get_backend_details()
Sapan Bhatia02dc8332015-12-22 18:37:47 +010041
Sapan Bhatia9f7538e2015-12-22 18:38:36 +010042 # FIXME: Need to clean this up by separating Javascript from Python
Sapan Bhatia02dc8332015-12-22 18:37:47 +010043 if (obj.pk):
44 script = """
Matteo Scandolo81c752f2016-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>
Jeremy Moweryda57d402016-04-15 17:39:49 -070046 """ % (obj.pk, obj.pk)
Sapan Bhatia02dc8332015-12-22 18:37:47 +010047
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>
Jeremy Moweryda57d402016-04-15 17:39:49 -070055 """ % (obj.pk, exponent, last_success, failures, last_failure)
56 a = '<a id="show_details_%d" href="#">' % obj.pk
Sapan Bhatia02dc8332015-12-22 18:37:47 +010057 astop = '</a>'
58 else:
59 div = ''
60 script = ''
61 a = ''
62 astop = ''
63
Scott Bakere5f9d7d2015-02-10 18:24:20 -080064 if tooltip:
Sapan Bhatia02dc8332015-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 Bakere5f9d7d2015-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
Jeremy Moweryda57d402016-04-15 17:39:49 -070069
Scott Baker40c00762014-08-21 16:55:59 -070070def backend_text(obj):
Scott Bakere5f9d7d2015-02-10 18:24:20 -080071 (icon, tooltip) = obj.get_backend_icon()
72 icon_url = ICON_URLS.get(icon, "unknown")
73
74 return '<img src="%s"> %s' % (icon_url, tooltip)
Scott Baker63d1a552014-08-21 15:19:07 -070075
Jeremy Moweryda57d402016-04-15 17:39:49 -070076
Scott Baker69e045d2014-11-17 23:44:03 -080077class UploadTextareaWidget(AdminTextareaWidget):
Jeremy Moweryda57d402016-04-15 17:39:49 -070078
Scott Baker69e045d2014-11-17 23:44:03 -080079 def render(self, name, value, attrs=None):
80 if value is None:
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -050081 value = ''
82 final_attrs = self.build_attrs(attrs, name=name)
Jeremy Moweryda57d402016-04-15 17:39:49 -070083 return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">'
84 '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>'
85 '<br><textarea{0}>\r\n{1}</textarea>' % (
86 attrs["id"], attrs["id"], attrs["id"]),
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -050087 flatatt(final_attrs),
Scott Baker69e045d2014-11-17 23:44:03 -080088 force_text(value))
89
Jeremy Moweryda57d402016-04-15 17:39:49 -070090
Scott Baker50ac4192015-05-11 16:36:58 -070091class SliderWidget(forms.HiddenInput):
Jeremy Moweryda57d402016-04-15 17:39:49 -070092
Scott Baker50ac4192015-05-11 16:36:58 -070093 def render(self, name, value, attrs=None):
94 if value is None:
95 value = '0'
96 final_attrs = self.build_attrs(attrs, name=name)
97 attrs = attrs or attrs[:]
98 attrs["name"] = name
99 attrs["value"] = value
100 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>
101 <script>
102 $(function() {
103 $("#%(id)s_slider").slider({
104 value: %(value)s,
105 slide: function(event, ui) { $("#%(id)s").val( ui.value ); $("#%(id)s_label").html(ui.value); },
106 });
107 });
108 </script>
109 <input type="hidden" id="%(id)s" name="%(name)s" value="%(value)s"></input>
110 """ % attrs
Jeremy Moweryda57d402016-04-15 17:39:49 -0700111 html = html.replace("{", "{{").replace("}", "}}")
Scott Baker50ac4192015-05-11 16:36:58 -0700112 return format_html(html,
113 flatatt(final_attrs),
114 force_text(value))
115
116
Scott Baker36f50872014-08-21 13:01:25 -0700117class PlainTextWidget(forms.HiddenInput):
118 input_type = 'hidden'
119
120 def render(self, name, value, attrs=None):
121 if value is None:
122 value = ''
123 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
124
Jeremy Moweryda57d402016-04-15 17:39:49 -0700125
Scott Baker3a8aed62015-02-27 12:21:22 -0800126class XOSAdminMixin(object):
Scott Baker1a6a3902014-10-03 00:32:37 -0700127 # call save_by_user and delete_by_user instead of save and delete
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500128
129 def has_add_permission(self, request, obj=None):
130 return (not self.__user_is_readonly(request))
Scott Baker36f50872014-08-21 13:01:25 -0700131
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500132 def has_delete_permission(self, request, obj=None):
133 return (not self.__user_is_readonly(request))
134
135 def save_model(self, request, obj, form, change):
136 if self.__user_is_readonly(request):
Scott Baker1a6a3902014-10-03 00:32:37 -0700137 # this 'if' might be redundant if save_by_user is implemented right
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500138 raise PermissionDenied
Scott Baker1a6a3902014-10-03 00:32:37 -0700139
Scott Baker5ca2a522015-12-01 10:24:01 -0800140 # reset exponential backoff
141 if hasattr(obj, "backend_register"):
142 obj.backend_register = "{}"
143
Scott Baker1a6a3902014-10-03 00:32:37 -0700144 obj.caller = request.user
145 # update openstack connection to use this site/tenant
146 obj.save_by_user(request.user)
147
148 def delete_model(self, request, obj):
149 obj.delete_by_user(request.user)
150
151 def save_formset(self, request, form, formset, change):
152 instances = formset.save(commit=False)
153 for instance in instances:
Scott Bakerf8cbac72015-07-08 18:23:17 -0700154 instance.caller = request.user
Scott Baker1a6a3902014-10-03 00:32:37 -0700155 instance.save_by_user(request.user)
156
157 # BUG in django 1.7? Objects are not deleted by formset.save if
158 # commit is False. So let's delete them ourselves.
159 #
160 # code from forms/models.py save_existing_objects()
161 try:
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -0500162 forms_to_delete = formset.deleted_forms
163 except AttributeError:
Scott Baker1a6a3902014-10-03 00:32:37 -0700164 forms_to_delete = []
165 if formset.initial_forms:
166 for form in formset.initial_forms:
167 obj = form.instance
168 if form in forms_to_delete:
169 if obj.pk is None:
170 continue
171 formset.deleted_objects.append(obj)
172 obj.delete()
173
174 formset.save_m2m()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500175
Jeremy Moweryda57d402016-04-15 17:39:49 -0700176 def get_actions(self, request):
177 actions = super(XOSAdminMixin, self).get_actions(request)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500178
179 if self.__user_is_readonly(request):
180 if 'delete_selected' in actions:
181 del actions['delete_selected']
182
183 return actions
184
Scott Bakerfbe0f652015-04-03 17:44:31 -0700185 def url_for_model_changelist(self, request, model):
186 # used in add_extra_context
187 return reverse('admin:%s_%s_changelist' % (model._meta.app_label, model._meta.model_name), current_app=model._meta.app_label)
188
Scott Bakera8ef2742015-04-02 22:32:40 -0700189 def add_extra_context(self, request, extra_context):
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800190 # allow custom application breadcrumb url and name
Jeremy Moweryda57d402016-04-15 17:39:49 -0700191 extra_context["custom_app_breadcrumb_url"] = getattr(
192 self, "custom_app_breadcrumb_url", None)
193 extra_context["custom_app_breadcrumb_name"] = getattr(
194 self, "custom_app_breadcrumb_name", None)
195 extra_context["custom_changelist_breadcrumb_url"] = getattr(
196 self, "custom_changelist_breadcrumb_url", None)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800197
198 # for Service admins to render their Administration page
199 if getattr(self, "extracontext_registered_admins", False):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700200 admins = []
Sapan Bhatia16be1432016-01-14 11:41:38 -0500201 for model, model_admin in admin.site._registry.items():
202 if model == self.model:
203 continue
204 if model._meta.app_label == self.model._meta.app_label:
205 info = {"app": model._meta.app_label,
206 "model": model._meta.model_name,
207 "name": capfirst(model._meta.verbose_name_plural),
Jeremy Moweryda57d402016-04-15 17:39:49 -0700208 "url": self.url_for_model_changelist(request, model)}
Sapan Bhatia16be1432016-01-14 11:41:38 -0500209 admins.append(info)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800210 extra_context["registered_admins"] = admins
211
Jeremy Moweryda57d402016-04-15 17:39:49 -0700212 def change_view(self, request, object_id, extra_context=None):
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800213 extra_context = extra_context or {}
214
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500215 if self.__user_is_readonly(request):
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -0500216 if not hasattr(self, "readonly_save"):
217 # save the original readonly fields
218 self.readonly_save = self.readonly_fields
219 self.inlines_save = self.inlines
220 if hasattr(self, "user_readonly_fields"):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700221 self.readonly_fields = self.user_readonly_fields
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -0500222 if hasattr(self, "user_readonly_inlines"):
223 self.inlines = self.user_readonly_inlines
224 else:
225 if hasattr(self, "readonly_save"):
226 # restore the original readonly fields
227 self.readonly_fields = self.readonly_save
228 if hasattr(self, "inlines_save"):
Scott Bakeraf73e102014-04-22 22:40:07 -0700229 self.inlines = self.inlines_save
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500230
Scott Bakera8ef2742015-04-02 22:32:40 -0700231 self.add_extra_context(request, extra_context)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800232
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500233 try:
Scott Baker3a8aed62015-02-27 12:21:22 -0800234 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500235 except PermissionDenied:
236 pass
Scott Bakerf41fe2c2015-07-09 19:06:08 -0700237 except ValidationError as e:
238 if (e.params is None):
239 # Validation errors that don't reference a specific field will
240 # often throw a non-descriptive 500 page to the user. The code
241 # below will cause an error message to be printed and the
242 # page refreshed instead.
243 # As a side-effect it turns the request back into a 'GET' which
244 # may wipe anything the user had changed on the page. But, at
245 # least the user gets a real error message.
246 # TODO: revisit this and display some kind of error view
247 request.method = 'GET'
248 messages.error(request, e.message)
249 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
250 else:
251 raise
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500252 if request.method == 'POST':
253 raise PermissionDenied
254 request.readonly = True
Scott Baker3a8aed62015-02-27 12:21:22 -0800255 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500256
Jeremy Moweryda57d402016-04-15 17:39:49 -0700257 def changelist_view(self, request, extra_context=None):
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800258 extra_context = extra_context or {}
259
Scott Bakera8ef2742015-04-02 22:32:40 -0700260 self.add_extra_context(request, extra_context)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800261
Scott Baker3a8aed62015-02-27 12:21:22 -0800262 return super(XOSAdminMixin, self).changelist_view(request, extra_context=extra_context)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800263
Jeremy Moweryda57d402016-04-15 17:39:49 -0700264 def add_view(self, request, form_url='', extra_context=None):
Scott Bakerfbe0f652015-04-03 17:44:31 -0700265 extra_context = extra_context or {}
266
267 self.add_extra_context(request, extra_context)
268
Scott Baker88ac9d62015-04-14 17:01:18 -0700269 return super(XOSAdminMixin, self).add_view(request, form_url, extra_context=extra_context)
Scott Bakerfbe0f652015-04-03 17:44:31 -0700270
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500271 def __user_is_readonly(self, request):
272 return request.user.isReadOnlyUser()
273
Scott Baker40c00762014-08-21 16:55:59 -0700274 def backend_status_text(self, obj):
275 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -0700276
Scott Baker63d1a552014-08-21 15:19:07 -0700277 def backend_status_icon(self, obj):
Scott Baker40c00762014-08-21 16:55:59 -0700278 return mark_safe(backend_icon(obj))
Scott Baker63d1a552014-08-21 15:19:07 -0700279 backend_status_icon.short_description = ""
280
Scott Bakerdc4724c2014-11-05 09:05:38 -0800281 def get_form(self, request, obj=None, **kwargs):
Scott Baker9b3c1af2014-10-16 00:57:55 -0700282 # Save obj and request in thread-local storage, so suit_form_tabs can
283 # use it to determine whether we're in edit or add mode, and can
284 # determine whether the user is an admin.
285 _thread_locals.request = request
286 _thread_locals.obj = obj
Scott Baker3a8aed62015-02-27 12:21:22 -0800287 return super(XOSAdminMixin, self).get_form(request, obj, **kwargs)
Scott Baker9b3c1af2014-10-16 00:57:55 -0700288
289 def get_inline_instances(self, request, obj=None):
Scott Baker3a8aed62015-02-27 12:21:22 -0800290 inlines = super(XOSAdminMixin, self).get_inline_instances(request, obj)
Scott Baker9b3c1af2014-10-16 00:57:55 -0700291
292 # inlines that should only be shown to an admin user
293 if request.user.is_admin:
294 for inline_class in getattr(self, "admin_inlines", []):
295 inlines.append(inline_class(self.model, self.admin_site))
296
297 return inlines
298
Jeremy Moweryda57d402016-04-15 17:39:49 -0700299
Scott Baker3a8aed62015-02-27 12:21:22 -0800300class ReadOnlyAwareAdmin(XOSAdminMixin, admin.ModelAdmin):
301 # Note: Make sure XOSAdminMixin is listed before
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700302 # admin.ModelAdmin in the class declaration.
303
Scott Baker1a6a3902014-10-03 00:32:37 -0700304 pass
305
Jeremy Moweryda57d402016-04-15 17:39:49 -0700306
Scott Baker67db95f2015-02-18 15:50:11 -0800307class XOSBaseAdmin(ReadOnlyAwareAdmin):
Scott Baker1a6a3902014-10-03 00:32:37 -0700308 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700309
Jeremy Moweryda57d402016-04-15 17:39:49 -0700310
Scott Bakere8859f92014-05-23 12:42:40 -0700311class SingletonAdmin (ReadOnlyAwareAdmin):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700312
Siobhan Tullyce652d02013-10-08 21:52:35 -0400313 def has_add_permission(self, request):
Scott Bakere8859f92014-05-23 12:42:40 -0700314 if not super(SingletonAdmin, self).has_add_permission(request):
315 return False
316
Siobhan Tullyce652d02013-10-08 21:52:35 -0400317 num_objects = self.model.objects.count()
318 if num_objects >= 1:
319 return False
320 else:
321 return True
322
Jeremy Moweryda57d402016-04-15 17:39:49 -0700323
Scott Bakera9b8f612015-02-26 20:42:11 -0800324class ServiceAppAdmin (SingletonAdmin):
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800325 extracontext_registered_admins = True
Scott Bakera9b8f612015-02-26 20:42:11 -0800326
Jeremy Moweryda57d402016-04-15 17:39:49 -0700327
Scott Baker67db95f2015-02-18 15:50:11 -0800328class XOSTabularInline(admin.TabularInline):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700329
Scott Baker86568322014-01-12 16:53:31 -0800330 def __init__(self, *args, **kwargs):
Scott Baker67db95f2015-02-18 15:50:11 -0800331 super(XOSTabularInline, self).__init__(*args, **kwargs)
Scott Baker86568322014-01-12 16:53:31 -0800332
333 # InlineModelAdmin as no get_fields() method, so in order to add
334 # the selflink field, we override __init__ to modify self.fields and
335 # self.readonly_fields.
336
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800337 self.setup_selflink()
338
Scott Baker54c9b9b2015-07-24 09:32:14 -0700339 @property
340 def selflink_model(self):
341 if hasattr(self, "selflink_fieldname"):
342 """ self.selflink_model can be defined to punch through a relation
343 to its target object. For example, in SliceNetworkInline, set
344 selflink_model = "network", and the URL will lead to the Network
345 object instead of trying to bring up a change view of the
346 SliceNetwork object.
347 """
Jeremy Moweryda57d402016-04-15 17:39:49 -0700348 return getattr(self.model, self.selflink_fieldname).field.rel.to
Scott Baker54c9b9b2015-07-24 09:32:14 -0700349 else:
350 return self.model
351
352 @property
353 def selflink_reverse_path(self):
354 return "admin:%s_change" % (self.selflink_model._meta.db_table)
355
356 def get_change_url(self, id):
Scott Baker874936e2014-01-13 18:15:34 -0800357 """ Get the URL to a change form in the admin for this model """
Jeremy Moweryda57d402016-04-15 17:39:49 -0700358 reverse_path = self.selflink_reverse_path # "admin:%s_change" % (self.selflink_model._meta.db_table)
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800359 try:
Scott Baker874936e2014-01-13 18:15:34 -0800360 url = reverse(reverse_path, args=(id,))
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800361 except NoReverseMatch:
Scott Baker874936e2014-01-13 18:15:34 -0800362 return None
363
364 return url
365
366 def setup_selflink(self):
Scott Baker54c9b9b2015-07-24 09:32:14 -0700367 url = self.get_change_url(0)
Scott Baker874936e2014-01-13 18:15:34 -0800368
369 # We don't have an admin for this object, so don't create the
370 # selflink.
371 if (url == None):
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800372 return
373
Scott Baker874936e2014-01-13 18:15:34 -0800374 # Since we need to add "selflink" to the field list, we need to create
375 # self.fields if it is None.
Scott Baker0165fac2014-01-13 11:49:26 -0800376 if (self.fields is None):
377 self.fields = []
378 for f in self.model._meta.fields:
379 if f.editable and f.name != "id":
380 self.fields.append(f.name)
Scott Baker86568322014-01-12 16:53:31 -0800381
Scott Baker874936e2014-01-13 18:15:34 -0800382 self.fields = tuple(self.fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800383
Scott Baker874936e2014-01-13 18:15:34 -0800384 if self.readonly_fields is None:
385 self.readonly_fields = ()
Scott Baker86568322014-01-12 16:53:31 -0800386
Scott Baker874936e2014-01-13 18:15:34 -0800387 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800388
389 def selflink(self, obj):
Scott Baker874936e2014-01-13 18:15:34 -0800390 if hasattr(self, "selflink_fieldname"):
391 obj = getattr(obj, self.selflink_fieldname)
392
Scott Baker86568322014-01-12 16:53:31 -0800393 if obj.id:
Scott Baker54c9b9b2015-07-24 09:32:14 -0700394 url = self.get_change_url(obj.id)
Scott Baker874936e2014-01-13 18:15:34 -0800395 return "<a href='%s'>Details</a>" % str(url)
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -0500396 else:
397 return "Not present"
Scott Baker86568322014-01-12 16:53:31 -0800398
399 selflink.allow_tags = True
400 selflink.short_description = "Details"
Siobhan Tullyd3515752013-06-21 16:34:53 -0400401
Scott Bakerb27b62c2014-08-15 16:29:16 -0700402 def has_add_permission(self, request):
403 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500404
405 def get_readonly_fields(self, request, obj=None):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700406 readonly_fields = list(self.readonly_fields)[:]
407 if request.user.isReadOnlyUser():
408 for field in self.fields:
409 if not field in readonly_fields:
410 readonly_fields.append(field)
411 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500412
Scott Baker40c00762014-08-21 16:55:59 -0700413 def backend_status_icon(self, obj):
414 return mark_safe(backend_icon(obj))
415 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -0700416
Jeremy Moweryda57d402016-04-15 17:39:49 -0700417
Scott Bakerb27b62c2014-08-15 16:29:16 -0700418class PlStackGenericTabularInline(generic.GenericTabularInline):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700419
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500420 def has_add_permission(self, request):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700421 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500422
Scott Bakerb27b62c2014-08-15 16:29:16 -0700423 def get_readonly_fields(self, request, obj=None):
424 readonly_fields = list(self.readonly_fields)[:]
425 if request.user.isReadOnlyUser():
426 for field in self.fields:
427 if not field in readonly_fields:
428 readonly_fields.append(field)
429 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500430
Scott Baker40c00762014-08-21 16:55:59 -0700431 def backend_status_icon(self, obj):
432 return mark_safe(backend_icon(obj))
433 backend_status_icon.short_description = ""
434
Jeremy Moweryda57d402016-04-15 17:39:49 -0700435
Scott Baker67db95f2015-02-18 15:50:11 -0800436class ReservationInline(XOSTabularInline):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400437 model = Reservation
438 extra = 0
439 suit_classes = 'suit-tab suit-tab-reservations'
Scott Baker36f50872014-08-21 13:01:25 -0700440
Tony Mack5b061472014-02-04 07:57:10 -0500441 def queryset(self, request):
442 return Reservation.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400443
Jeremy Moweryda57d402016-04-15 17:39:49 -0700444
Scott Bakerb27b62c2014-08-15 16:29:16 -0700445class TagInline(PlStackGenericTabularInline):
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400446 model = Tag
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400447 extra = 0
448 suit_classes = 'suit-tab suit-tab-tags'
Tony Mack5b061472014-02-04 07:57:10 -0500449 fields = ['service', 'name', 'value']
450
451 def queryset(self, request):
452 return Tag.select_by_user(request.user)
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400453
Jeremy Moweryda57d402016-04-15 17:39:49 -0700454
Tony Mackd8515472015-08-19 11:58:18 -0400455class InstanceInline(XOSTabularInline):
456 model = Instance
Jeremy Moweryda57d402016-04-15 17:39:49 -0700457 fields = ['backend_status_icon', 'all_ips_string', 'instance_id',
458 'instance_name', 'slice', 'deployment', 'flavor', 'image', 'node']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400459 extra = 0
Scott Bakerebe89232015-09-21 14:52:15 -0700460 max_num = 0
Jeremy Moweryda57d402016-04-15 17:39:49 -0700461 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_id',
462 'instance_name', 'slice', 'deployment', 'flavor', 'image', 'node']
Tony Mackd8515472015-08-19 11:58:18 -0400463 suit_classes = 'suit-tab suit-tab-instances'
Scott Baker74d8e622013-07-29 16:04:22 -0700464
Tony Mack5b061472014-02-04 07:57:10 -0500465 def queryset(self, request):
Tony Mackd8515472015-08-19 11:58:18 -0400466 return Instance.select_by_user(request.user)
Tony Mack5b061472014-02-04 07:57:10 -0500467
Scott Bakerb24cc932014-06-09 10:51:16 -0700468 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Tony Mackb2dba4b2014-12-26 13:38:02 -0500469 if db_field.name == 'deployment':
Jeremy Moweryda57d402016-04-15 17:39:49 -0700470 kwargs['queryset'] = Deployment.select_by_acl(request.user).filter(
471 sitedeployments__nodes__isnull=False).distinct()
472 kwargs['widget'] = forms.Select(
473 attrs={'onChange': "instance_deployment_changed(this);"})
Tony Mackb2dba4b2014-12-26 13:38:02 -0500474 if db_field.name == 'flavor':
Jeremy Moweryda57d402016-04-15 17:39:49 -0700475 kwargs['widget'] = forms.Select(
476 attrs={'onChange': "instance_flavor_changed(this);"})
Scott Baker3b678742014-06-09 13:11:54 -0700477
Jeremy Moweryda57d402016-04-15 17:39:49 -0700478 field = super(InstanceInline, self).formfield_for_foreignkey(
479 db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700480
481 return field
482
Jeremy Moweryda57d402016-04-15 17:39:49 -0700483
Tony Mackd8515472015-08-19 11:58:18 -0400484class CordInstanceInline(XOSTabularInline):
485 model = Instance
Jeremy Moweryda57d402016-04-15 17:39:49 -0700486 fields = ['backend_status_icon', 'all_ips_string', 'instance_id',
487 'instance_name', 'slice', 'flavor', 'image', 'node']
Scott Baker25881992015-06-12 10:40:15 -0700488 extra = 0
Jeremy Moweryda57d402016-04-15 17:39:49 -0700489 readonly_fields = ['backend_status_icon',
490 'all_ips_string', 'instance_id', 'instance_name']
Tony Mackd8515472015-08-19 11:58:18 -0400491 suit_classes = 'suit-tab suit-tab-instances'
Scott Baker25881992015-06-12 10:40:15 -0700492
493 def queryset(self, request):
Tony Mackd8515472015-08-19 11:58:18 -0400494 return Instance.select_by_user(request.user)
Scott Baker25881992015-06-12 10:40:15 -0700495
496 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
497 if db_field.name == 'deployment':
498
Jeremy Moweryda57d402016-04-15 17:39:49 -0700499 kwargs['queryset'] = Deployment.select_by_acl(request.user).filter(
500 sitedeployments__nodes__isnull=False).distinct()
501 kwargs['widget'] = forms.Select(
502 attrs={'onChange': "instance_deployment_changed(this);"})
Scott Baker25881992015-06-12 10:40:15 -0700503 if db_field.name == 'flavor':
Jeremy Moweryda57d402016-04-15 17:39:49 -0700504 kwargs['widget'] = forms.Select(
505 attrs={'onChange': "instance_flavor_changed(this);"})
Scott Baker25881992015-06-12 10:40:15 -0700506
Jeremy Moweryda57d402016-04-15 17:39:49 -0700507 field = super(CordInstanceInline, self).formfield_for_foreignkey(
508 db_field, request, **kwargs)
Scott Baker25881992015-06-12 10:40:15 -0700509
510 return field
511
Jeremy Moweryda57d402016-04-15 17:39:49 -0700512
Scott Baker67db95f2015-02-18 15:50:11 -0800513class SiteInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400514 model = Site
515 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400516 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400517
Tony Mack5b061472014-02-04 07:57:10 -0500518 def queryset(self, request):
519 return Site.select_by_user(request.user)
520
Jeremy Moweryda57d402016-04-15 17:39:49 -0700521
Tony Mack8d60ba32015-08-04 17:53:23 -0400522class SiteHostsNodesInline(SiteInline):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700523
Tony Mack8d60ba32015-08-04 17:53:23 -0400524 def queryset(self, request):
525 return Site.select_by_user(request.user).filter(hosts_nodes=True)
526
Jeremy Moweryda57d402016-04-15 17:39:49 -0700527
Tony Mack8d60ba32015-08-04 17:53:23 -0400528class SiteHostsUsersInline(SiteInline):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700529
Tony Mack8d60ba32015-08-04 17:53:23 -0400530 def queryset(self, request):
Jeremy Moweryc2e8f162016-01-10 20:36:51 -0700531 return Site.select_by_user(request.user).filter(hosts_users=True)
Tony Mack8d60ba32015-08-04 17:53:23 -0400532
Jeremy Moweryda57d402016-04-15 17:39:49 -0700533
Scott Baker67db95f2015-02-18 15:50:11 -0800534class UserInline(XOSTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400535 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700536 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
537 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400538 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400539 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400540
Tony Mack5b061472014-02-04 07:57:10 -0500541 def queryset(self, request):
542 return User.select_by_user(request.user)
543
Jeremy Moweryda57d402016-04-15 17:39:49 -0700544
Scott Baker67db95f2015-02-18 15:50:11 -0800545class SliceInline(XOSTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400546 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700547 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
548 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400549 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400550 suit_classes = 'suit-tab suit-tab-slices'
551
Tony Mack5b061472014-02-04 07:57:10 -0500552 def queryset(self, request):
553 return Slice.select_by_user(request.user)
554
Jeremy Moweryda57d402016-04-15 17:39:49 -0700555
Scott Baker67db95f2015-02-18 15:50:11 -0800556class NodeInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400557 model = Node
558 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400559 suit_classes = 'suit-tab suit-tab-nodes'
Tony Mack68a1e422014-12-08 16:43:02 -0500560 fields = ['backend_status_icon', 'name', 'site_deployment']
Scott Baker40c00762014-08-21 16:55:59 -0700561 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400562
Jeremy Moweryda57d402016-04-15 17:39:49 -0700563
Scott Baker67db95f2015-02-18 15:50:11 -0800564class DeploymentPrivilegeInline(XOSTabularInline):
Tony Mack68a1e422014-12-08 16:43:02 -0500565 model = DeploymentPrivilege
566 extra = 0
Tony Mack88c89902015-02-09 21:41:57 -0500567 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
Jeremy Moweryda57d402016-04-15 17:39:49 -0700568 fields = ['backend_status_icon', 'user', 'role', 'deployment']
Tony Mack68a1e422014-12-08 16:43:02 -0500569 readonly_fields = ('backend_status_icon', )
570
571 def queryset(self, request):
572 return DeploymentPrivilege.select_by_user(request.user)
573
Jeremy Moweryda57d402016-04-15 17:39:49 -0700574
Scott Baker67db95f2015-02-18 15:50:11 -0800575class ControllerSiteInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -0500576 model = ControllerSite
577 extra = 0
578 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Macke2363c12015-01-06 15:08:20 -0500579 fields = ['controller', 'site', 'tenant_id']
Tony Mack3066a952015-01-05 22:48:11 -0500580
581
Scott Baker67db95f2015-02-18 15:50:11 -0800582class SitePrivilegeInline(XOSTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400583 model = SitePrivilege
584 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400585 suit_classes = 'suit-tab suit-tab-siteprivileges'
Jeremy Moweryda57d402016-04-15 17:39:49 -0700586 fields = ['backend_status_icon', 'user', 'site', 'role']
Scott Baker40c00762014-08-21 16:55:59 -0700587 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400588
Tony Mackc2835a92013-05-28 09:18:49 -0400589 def formfield_for_foreignkey(self, db_field, request, **kwargs):
590 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500591 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400592
593 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500594 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400595 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
596
Tony Mack5b061472014-02-04 07:57:10 -0500597 def queryset(self, request):
598 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400599
Tony Mack60789ac2015-05-11 20:39:32 -0400600
601class ServicePrivilegeInline(XOSTabularInline):
602 model = ServicePrivilege
603 extra = 0
604 suit_classes = 'suit-tab suit-tab-serviceprivileges'
Jeremy Moweryda57d402016-04-15 17:39:49 -0700605 fields = ['backend_status_icon', 'user', 'service', 'role']
Tony Mack60789ac2015-05-11 20:39:32 -0400606 readonly_fields = ('backend_status_icon', )
607
608 def formfield_for_foreignkey(self, db_field, request, **kwargs):
609 if db_field.name == 'service':
610 kwargs['queryset'] = Service.select_by_user(request.user)
Tony Mack5fa0f402015-05-15 06:33:45 -0400611 if db_field.name == 'user':
612 kwargs['queryset'] = User.select_by_user(request.user)
Jeremy Moweryc2e8f162016-01-10 20:36:51 -0700613 return super(ServicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mack60789ac2015-05-11 20:39:32 -0400614
615 def queryset(self, request):
616 return ServicePrivilege.select_by_user(request.user)
617
Jeremy Moweryda57d402016-04-15 17:39:49 -0700618
Scott Baker67db95f2015-02-18 15:50:11 -0800619class SiteDeploymentInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -0500620 model = SiteDeployment
Tony Macke4be32f2014-03-11 20:45:25 -0400621 extra = 0
Tony Mackb81d5e42015-01-30 10:58:29 -0500622 suit_classes = 'suit-tab suit-tab-sitedeployments'
Jeremy Moweryda57d402016-04-15 17:39:49 -0700623 fields = ['backend_status_icon', 'deployment', 'site', 'controller']
Scott Baker40c00762014-08-21 16:55:59 -0700624 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400625
626 def formfield_for_foreignkey(self, db_field, request, **kwargs):
627 if db_field.name == 'site':
628 kwargs['queryset'] = Site.select_by_user(request.user)
629
630 if db_field.name == 'deployment':
631 kwargs['queryset'] = Deployment.select_by_user(request.user)
Tony Mackd14d48f2014-12-05 17:13:08 -0500632
633 if db_field.name == 'controller':
Scott Bakerebe89232015-09-21 14:52:15 -0700634 if len(resolve(request.path).args) > 0:
Jeremy Moweryda57d402016-04-15 17:39:49 -0700635 kwargs['queryset'] = Controller.select_by_user(request.user).filter(
636 deployment__id=int(resolve(request.path).args[0]))
Tony Mackd14d48f2014-12-05 17:13:08 -0500637
Tony Mack3066a952015-01-05 22:48:11 -0500638 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Macke4be32f2014-03-11 20:45:25 -0400639
640 def queryset(self, request):
Tony Mack3066a952015-01-05 22:48:11 -0500641 return SiteDeployment.select_by_user(request.user)
Tony Macke4be32f2014-03-11 20:45:25 -0400642
643
Scott Baker67db95f2015-02-18 15:50:11 -0800644class SlicePrivilegeInline(XOSTabularInline):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400645 model = SlicePrivilege
646 suit_classes = 'suit-tab suit-tab-sliceprivileges'
647 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700648 fields = ('backend_status_icon', 'user', 'slice', 'role')
649 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400650
Tony Mackc2835a92013-05-28 09:18:49 -0400651 def formfield_for_foreignkey(self, db_field, request, **kwargs):
652 if db_field.name == 'slice':
Jeremy Moweryda57d402016-04-15 17:39:49 -0700653 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400654 if db_field.name == 'user':
Jeremy Moweryda57d402016-04-15 17:39:49 -0700655 # all users are available to be granted SlicePrivilege
656 kwargs['queryset'] = User.objects.all()
Tony Mackc2835a92013-05-28 09:18:49 -0400657
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400658 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400659
Tony Mack5b061472014-02-04 07:57:10 -0500660 def queryset(self, request):
661 return SlicePrivilege.select_by_user(request.user)
662
Jeremy Moweryda57d402016-04-15 17:39:49 -0700663
Scott Baker67db95f2015-02-18 15:50:11 -0800664class SliceNetworkInline(XOSTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700665 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800666 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700667 extra = 0
668 verbose_name = "Network Connection"
669 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400670 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700671 fields = ['backend_status_icon', 'network']
672 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700673
Jeremy Moweryda57d402016-04-15 17:39:49 -0700674
Scott Baker67db95f2015-02-18 15:50:11 -0800675class ImageDeploymentsInline(XOSTabularInline):
Sapan Bhatia1b6bba22014-11-19 15:10:16 -0500676 model = ImageDeployments
Scott Baker2170b972014-06-03 12:14:07 -0700677 extra = 0
678 verbose_name = "Image Deployments"
679 verbose_name_plural = "Image Deployments"
680 suit_classes = 'suit-tab suit-tab-imagedeployments'
Tony Mack06c8e472014-11-30 15:53:08 -0500681 fields = ['backend_status_icon', 'image', 'deployment']
682 readonly_fields = ['backend_status_icon']
683
Jeremy Moweryda57d402016-04-15 17:39:49 -0700684
Scott Baker67db95f2015-02-18 15:50:11 -0800685class ControllerImagesInline(XOSTabularInline):
Tony Mack06c8e472014-11-30 15:53:08 -0500686 model = ControllerImages
687 extra = 0
688 verbose_name = "Controller Images"
689 verbose_name_plural = "Controller Images"
690 suit_classes = 'suit-tab suit-tab-admin-only'
691 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
Scott Baker40c00762014-08-21 16:55:59 -0700692 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700693
Jeremy Moweryda57d402016-04-15 17:39:49 -0700694
Scott Baker67db95f2015-02-18 15:50:11 -0800695class SliceRoleAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400696 model = SliceRole
697 pass
698
Jeremy Moweryda57d402016-04-15 17:39:49 -0700699
Scott Baker67db95f2015-02-18 15:50:11 -0800700class SiteRoleAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400701 model = SiteRole
702 pass
703
Jeremy Moweryda57d402016-04-15 17:39:49 -0700704
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400705class DeploymentAdminForm(forms.ModelForm):
Scott Bakerde0f4412014-06-11 15:40:26 -0700706 images = forms.ModelMultipleChoiceField(
707 queryset=Image.objects.all(),
708 required=False,
709 help_text="Select which images should be deployed on this deployment",
710 widget=FilteredSelectMultiple(
711 verbose_name=('Images'), is_stacked=False
712 )
713 )
Scott Baker37b47902014-09-02 14:37:41 -0700714 flavors = forms.ModelMultipleChoiceField(
715 queryset=Flavor.objects.all(),
716 required=False,
717 help_text="Select which flavors should be usable on this deployment",
718 widget=FilteredSelectMultiple(
719 verbose_name=('Flavors'), is_stacked=False
720 )
721 )
Jeremy Moweryda57d402016-04-15 17:39:49 -0700722
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400723 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400724 model = Deployment
Jeremy Moweryda57d402016-04-15 17:39:49 -0700725 many_to_many = ["flavors", ]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400726
Siobhan Tully320b4622014-01-17 15:11:14 -0500727 def __init__(self, *args, **kwargs):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700728 request = kwargs.pop('request', None)
729 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
Siobhan Tully320b4622014-01-17 15:11:14 -0500730
Jeremy Moweryda57d402016-04-15 17:39:49 -0700731 self.fields['accessControl'].initial = "allow site " + \
732 request.user.site.name
Scott Baker5380c522014-06-06 14:49:43 -0700733
Jeremy Moweryda57d402016-04-15 17:39:49 -0700734 if self.instance and self.instance.pk:
735 self.fields['images'].initial = [
736 x.image for x in self.instance.imagedeployments.all()]
737 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700738
739 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
740 """ helper function for handling m2m relations from the MultipleChoiceField
741
742 this_obj: the source object we want to link from
743
744 selected_objs: a list of destination objects we want to link to
745
746 all_relations: the full set of relations involving this_obj, including ones we don't want
747
748 relation_class: the class that implements the relation from source to dest
749
750 local_attrname: field name representing this_obj in relation_class
751
752 foreign_attrname: field name representing selected_objs in relation_class
753
754 This function will remove all newobjclass relations from this_obj
755 that are not contained in selected_objs, and add any relations that
756 are in selected_objs but don't exist in the data model yet.
757 """
758
759 existing_dest_objs = []
760 for relation in list(all_relations):
761 if getattr(relation, foreign_attrname) not in selected_objs:
Jeremy Moweryda57d402016-04-15 17:39:49 -0700762 # print "deleting site", sdp.site
Scott Bakerde0f4412014-06-11 15:40:26 -0700763 relation.delete()
764 else:
765 existing_dest_objs.append(getattr(relation, foreign_attrname))
766
767 for dest_obj in selected_objs:
768 if dest_obj not in existing_dest_objs:
Jeremy Moweryda57d402016-04-15 17:39:49 -0700769 # print "adding site", site
Scott Bakerde0f4412014-06-11 15:40:26 -0700770 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
771 relation = relation_class(**kwargs)
772 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500773
774 def save(self, commit=True):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700775 deployment = super(DeploymentAdminForm, self).save(commit=False)
Siobhan Tully320b4622014-01-17 15:11:14 -0500776
Jeremy Moweryda57d402016-04-15 17:39:49 -0700777 if commit:
778 deployment.save()
779 # this has to be done after save() if/when a deployment is first
780 # created
781 deployment.flavors = self.cleaned_data['flavors']
Siobhan Tully320b4622014-01-17 15:11:14 -0500782
Jeremy Moweryda57d402016-04-15 17:39:49 -0700783 if deployment.pk:
784 # save_m2m() doesn't seem to work with 'through' relations. So we
785 # create/destroy the through models ourselves. There has to be
786 # a better way...
Scott Bakerc9b14f72014-05-22 13:44:20 -0700787
Jeremy Moweryda57d402016-04-15 17:39:49 -0700788 self.manipulate_m2m_objs(deployment, self.cleaned_data[
789 'images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
790 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
791 # so well handle that manually here
792 for flavor in deployment.flavors.all():
793 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
794 deployment.flavors.remove(flavor)
795 for flavor in self.cleaned_data['flavors']:
796 if flavor not in deployment.flavors.all():
797 flavor.deployments.add(deployment)
Scott Bakerc9b14f72014-05-22 13:44:20 -0700798
Jeremy Moweryda57d402016-04-15 17:39:49 -0700799 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500800
Jeremy Moweryda57d402016-04-15 17:39:49 -0700801 return deployment
802
Siobhan Tully320b4622014-01-17 15:11:14 -0500803
Scott Bakerff5e0f32014-05-22 14:40:27 -0700804class DeploymentAdminROForm(DeploymentAdminForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700805
Scott Bakerff5e0f32014-05-22 14:40:27 -0700806 def save(self, commit=True):
807 raise PermissionDenied
808
Jeremy Moweryda57d402016-04-15 17:39:49 -0700809
Scott Baker67db95f2015-02-18 15:50:11 -0800810class SiteAssocInline(XOSTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500811 model = Site.deployments.through
812 extra = 0
813 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400814
Jeremy Moweryda57d402016-04-15 17:39:49 -0700815
Scott Baker67db95f2015-02-18 15:50:11 -0800816class DeploymentAdmin(XOSBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500817 model = Deployment
Jeremy Moweryda57d402016-04-15 17:39:49 -0700818 fieldList = ['backend_status_text', 'name',
819 'images', 'flavors', 'accessControl']
820 fieldsets = [
821 (None, {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Tony Mack68a1e422014-12-08 16:43:02 -0500822 # node no longer directly connected to deployment
823 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
Jeremy Moweryda57d402016-04-15 17:39:49 -0700824 inlines = [DeploymentPrivilegeInline, TagInline,
825 ImageDeploymentsInline, SiteDeploymentInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700826 list_display = ['backend_status_icon', 'name']
827 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700828 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500829
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500830 user_readonly_fields = ['name']
831
Tony Mack68a1e422014-12-08 16:43:02 -0500832 # nodes no longer direclty connected to deployments
Jeremy Moweryda57d402016-04-15 17:39:49 -0700833 suit_form_tabs = (('general', 'Deployment Details'),
834 ('deploymentprivileges', 'Privileges'), ('sitedeployments', 'Sites'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500835
Scott Bakerff5e0f32014-05-22 14:40:27 -0700836 def get_form(self, request, obj=None, **kwargs):
Tony Mackcf29cfa2015-02-05 06:13:04 -0500837 if request.user.isReadOnlyUser() or not request.user.is_admin:
Scott Bakerff5e0f32014-05-22 14:40:27 -0700838 kwargs["form"] = DeploymentAdminROForm
839 else:
840 kwargs["form"] = DeploymentAdminForm
Jeremy Moweryda57d402016-04-15 17:39:49 -0700841 adminForm = super(DeploymentAdmin, self).get_form(
842 request, obj, **kwargs)
Scott Baker5380c522014-06-06 14:49:43 -0700843
844 # from stackexchange: pass the request object into the form
845
846 class AdminFormMetaClass(adminForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700847
848 def __new__(cls, *args, **kwargs):
849 kwargs['request'] = request
850 return adminForm(*args, **kwargs)
Scott Baker5380c522014-06-06 14:49:43 -0700851
852 return AdminFormMetaClass
853
Jeremy Moweryda57d402016-04-15 17:39:49 -0700854
Scott Bakerecfbec72016-01-29 12:18:19 -0800855class ControllerAdminForm(forms.ModelForm):
856 backend_disabled = forms.BooleanField(required=False)
Jeremy Moweryda57d402016-04-15 17:39:49 -0700857
Scott Bakerecfbec72016-01-29 12:18:19 -0800858 class Meta:
859 model = Controller
860
861 def __init__(self, *args, **kwargs):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700862 request = kwargs.pop('request', None)
863 super(ControllerAdminForm, self).__init__(*args, **kwargs)
Scott Bakerecfbec72016-01-29 12:18:19 -0800864
Jeremy Moweryda57d402016-04-15 17:39:49 -0700865 if self.instance and self.instance.pk:
866 self.fields['backend_disabled'].initial = self.instance.get_backend_register(
867 'disabled', False)
868 else:
869 # defaults when adding new controller
870 self.fields['backend_disabled'].initial = False
Scott Bakerecfbec72016-01-29 12:18:19 -0800871
872 def save(self, commit=True):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700873 self.instance.set_backend_register(
874 "disabled", self.cleaned_data["backend_disabled"])
875 return super(ControllerAdminForm, self).save(commit=commit)
876
Scott Bakerecfbec72016-01-29 12:18:19 -0800877
Scott Baker67db95f2015-02-18 15:50:11 -0800878class ControllerAdmin(XOSBaseAdmin):
Scott Baker622bcf02015-02-10 08:40:34 -0800879 model = Controller
Jeremy Moweryda57d402016-04-15 17:39:49 -0700880 fieldList = ['deployment', 'name', 'backend_type', 'backend_disabled', 'version', 'auth_url', 'admin_user',
881 'admin_tenant', 'admin_password', 'domain', 'rabbit_host', 'rabbit_user', 'rabbit_password']
882 fieldsets = [
883 (None, {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
884 inlines = [ControllerSiteInline] # ,ControllerImagesInline]
Tony Mackd14d48f2014-12-05 17:13:08 -0500885 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
886 list_display_links = ('backend_status_icon', 'name', )
887 readonly_fields = ('backend_status_text',)
Scott Bakerecfbec72016-01-29 12:18:19 -0800888 form = ControllerAdminForm
Tony Mackd14d48f2014-12-05 17:13:08 -0500889
890 user_readonly_fields = []
891
Tony Mack2e897fa2015-01-13 17:33:08 -0500892 def save_model(self, request, obj, form, change):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700893 # update openstack connection to use this site/tenant
Tony Mack2e897fa2015-01-13 17:33:08 -0500894 obj.save_by_user(request.user)
Jeremy Moweryc2e8f162016-01-10 20:36:51 -0700895
Tony Mack2e897fa2015-01-13 17:33:08 -0500896 def delete_model(self, request, obj):
Scott Baker622bcf02015-02-10 08:40:34 -0800897 obj.delete_by_user(request.user)
898
Tony Mack78fc1362015-02-18 11:41:36 -0500899 def queryset(self, request):
Jeremy Moweryc2e8f162016-01-10 20:36:51 -0700900 return Controller.select_by_user(request.user)
Tony Mack78fc1362015-02-18 11:41:36 -0500901
Scott Baker622bcf02015-02-10 08:40:34 -0800902 @property
903 def suit_form_tabs(self):
904 tabs = [('general', 'Controller Details'),
Jeremy Moweryda57d402016-04-15 17:39:49 -0700905 ]
Scott Baker622bcf02015-02-10 08:40:34 -0800906
Jeremy Moweryda57d402016-04-15 17:39:49 -0700907 request = getattr(_thread_locals, "request", None)
Scott Baker622bcf02015-02-10 08:40:34 -0800908 if request and request.user.is_admin:
Jeremy Moweryda57d402016-04-15 17:39:49 -0700909 tabs.append(('admin-only', 'Admin-Only'))
Scott Baker622bcf02015-02-10 08:40:34 -0800910
911 return tabs
Tony Mack2e897fa2015-01-13 17:33:08 -0500912
Jeremy Moweryda57d402016-04-15 17:39:49 -0700913
Scott Baker1e7e3482015-10-15 15:59:19 -0700914class TenantAttributeAdmin(XOSBaseAdmin):
915 model = TenantAttribute
916 list_display = ('backend_status_icon', 'tenant', 'name', 'value')
917 list_display_links = ('backend_status_icon', 'name')
918 fieldList = ('backend_status_text', 'tenant', 'name', 'value', )
Jeremy Moweryda57d402016-04-15 17:39:49 -0700919 fieldsets = [
920 (None, {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker1e7e3482015-10-15 15:59:19 -0700921 readonly_fields = ('backend_status_text', )
922
Jeremy Moweryda57d402016-04-15 17:39:49 -0700923 suit_form_tabs = (('general', 'Tenant Root Details'),
924 )
925
Scott Baker1e7e3482015-10-15 15:59:19 -0700926
927class TenantAttrAsTabInline(XOSTabularInline):
928 model = TenantAttribute
Jeremy Moweryda57d402016-04-15 17:39:49 -0700929 fields = ['name', 'value']
Scott Baker1e7e3482015-10-15 15:59:19 -0700930 extra = 0
931 suit_classes = 'suit-tab suit-tab-tenantattrs'
932
Jeremy Moweryda57d402016-04-15 17:39:49 -0700933
Scott Bakerb3cf9212015-07-06 14:40:20 -0700934class TenantRootRoleAdmin(XOSBaseAdmin):
935 model = TenantRootRole
936 fields = ('role',)
937
Jeremy Moweryda57d402016-04-15 17:39:49 -0700938
Scott Bakerb3cf9212015-07-06 14:40:20 -0700939class TenantRootTenantInline(XOSTabularInline):
940 model = Tenant
941 fields = ['provider_service', 'subscriber_root']
942 extra = 0
943 suit_classes = 'suit-tab suit-tab-tenantroots'
944 fk_name = 'subscriber_root'
945 verbose_name = 'subscribed tenant'
946 verbose_name_plural = 'subscribed tenants'
947
Jeremy Moweryda57d402016-04-15 17:39:49 -0700948 # def queryset(self, request):
Scott Bakerb3cf9212015-07-06 14:40:20 -0700949 # qs = super(TenantRootTenantInline, self).queryset(request)
950 # return qs.filter(kind="coarse")
951
Jeremy Moweryda57d402016-04-15 17:39:49 -0700952
Scott Bakerb3cf9212015-07-06 14:40:20 -0700953class TenantRootPrivilegeInline(XOSTabularInline):
954 model = TenantRootPrivilege
955 extra = 0
956 suit_classes = 'suit-tab suit-tab-tenantrootprivileges'
957 fields = ['backend_status_icon', 'user', 'role', 'tenant_root']
958 readonly_fields = ('backend_status_icon', )
959
960 def queryset(self, request):
961 return TenantRootPrivilege.select_by_user(request.user)
962
Jeremy Moweryda57d402016-04-15 17:39:49 -0700963
Scott Bakerb3cf9212015-07-06 14:40:20 -0700964class TenantRootAdmin(XOSBaseAdmin):
965 model = TenantRoot
966 list_display = ('backend_status_icon', 'name', 'kind')
967 list_display_links = ('backend_status_icon', 'name')
968 fieldList = ('backend_status_text', 'name', 'kind', )
Jeremy Moweryda57d402016-04-15 17:39:49 -0700969 fieldsets = [
970 (None, {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Bakerb3cf9212015-07-06 14:40:20 -0700971 inlines = (TenantRootTenantInline, TenantRootPrivilegeInline)
972 readonly_fields = ('backend_status_text', )
973
Jeremy Moweryda57d402016-04-15 17:39:49 -0700974 suit_form_tabs = (('general', 'Tenant Root Details'),
975 ('tenantroots', 'Tenancy'),
976 ('tenantrootprivileges', 'Privileges')
977 )
978
Scott Bakerb3cf9212015-07-06 14:40:20 -0700979
Jeremy Mowery18cde4f2016-04-03 13:04:39 -0700980class TenantRoleAdmin(XOSBaseAdmin):
Jeremy Mowery308e8f02016-04-15 00:05:27 -0700981 """Admin for TenantRoles."""
Jeremy Mowery18cde4f2016-04-03 13:04:39 -0700982 model = TenantRole
Jeremy Mowerya74c31d2016-04-04 22:30:44 -0700983 fields = ('role',)
Jeremy Mowery18cde4f2016-04-03 13:04:39 -0700984
Jeremy Moweryda57d402016-04-15 17:39:49 -0700985
Jeremy Mowery18cde4f2016-04-03 13:04:39 -0700986class TenantPrivilegeInline(XOSTabularInline):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700987 """Inline for adding a TenantPrivilege to a Tenant."""
Jeremy Mowery18cde4f2016-04-03 13:04:39 -0700988 model = TenantPrivilege
989 extra = 0
990 suit_classes = 'suit-tab suit-tab-tenantprivileges'
991 fields = ['backend_status_icon', 'user', 'role', 'tenant']
992 readonly_fields = ('backend_status_icon', )
993
994 def queryset(self, request):
995 return TenantPrivilege.select_by_user(request.user)
996
Jeremy Moweryda57d402016-04-15 17:39:49 -0700997
Scott Bakeref58a842015-04-26 20:30:40 -0700998class ProviderTenantInline(XOSTabularInline):
999 model = CoarseTenant
1000 fields = ['provider_service', 'subscriber_service', 'connect_method']
1001 extra = 0
1002 suit_classes = 'suit-tab suit-tab-servicetenants'
1003 fk_name = 'provider_service'
1004 verbose_name = 'provided tenant'
1005 verbose_name_plural = 'provided tenants'
1006
1007 def queryset(self, request):
1008 qs = super(ProviderTenantInline, self).queryset(request)
1009 return qs.filter(kind="coarse")
1010
Jeremy Moweryda57d402016-04-15 17:39:49 -07001011
Scott Bakeref58a842015-04-26 20:30:40 -07001012class SubscriberTenantInline(XOSTabularInline):
1013 model = CoarseTenant
1014 fields = ['provider_service', 'subscriber_service', 'connect_method']
1015 extra = 0
1016 suit_classes = 'suit-tab suit-tab-servicetenants'
1017 fk_name = 'subscriber_service'
1018 verbose_name = 'subscribed tenant'
1019 verbose_name_plural = 'subscribed tenants'
1020
1021 def queryset(self, request):
1022 qs = super(SubscriberTenantInline, self).queryset(request)
1023 return qs.filter(kind="coarse")
1024
Jeremy Moweryda57d402016-04-15 17:39:49 -07001025
Scott Baker67db95f2015-02-18 15:50:11 -08001026class ServiceAttrAsTabInline(XOSTabularInline):
Siobhan Tullyce652d02013-10-08 21:52:35 -04001027 model = ServiceAttribute
Jeremy Moweryda57d402016-04-15 17:39:49 -07001028 fields = ['name', 'value']
Siobhan Tullyce652d02013-10-08 21:52:35 -04001029 extra = 0
1030 suit_classes = 'suit-tab suit-tab-serviceattrs'
1031
Jeremy Moweryda57d402016-04-15 17:39:49 -07001032
Scott Baker67db95f2015-02-18 15:50:11 -08001033class ServiceAdmin(XOSBaseAdmin):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001034 list_display = ("backend_status_icon", "name", "kind",
1035 "versionNumber", "enabled", "published")
Scott Baker63d1a552014-08-21 15:19:07 -07001036 list_display_links = ('backend_status_icon', 'name', )
Jeremy Moweryda57d402016-04-15 17:39:49 -07001037 fieldList = ["backend_status_text", "name", "kind", "description", "versionNumber", "enabled", "published",
1038 "view_url", "icon_url", "public_key", "private_key_fn", "service_specific_attribute", "service_specific_id"]
1039 fieldsets = [
1040 (None, {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
1041 inlines = [ServiceAttrAsTabInline, SliceInline, ProviderTenantInline,
1042 SubscriberTenantInline, ServicePrivilegeInline]
Scott Baker40c00762014-08-21 16:55:59 -07001043 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001044
1045 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001046
Jeremy Moweryda57d402016-04-15 17:39:49 -07001047 suit_form_tabs = (('general', 'Service Details'),
1048 ('slices', 'Slices'),
1049 ('serviceattrs', 'Additional Attributes'),
1050 ('servicetenants', 'Tenancy'),
1051 ('serviceprivileges', 'Privileges')
1052 )
1053
Siobhan Tullyce652d02013-10-08 21:52:35 -04001054
Scott Baker67db95f2015-02-18 15:50:11 -08001055class SiteNodeInline(XOSTabularInline):
Tony Mack4f134e62015-01-14 20:58:38 -05001056 model = Node
1057 fields = ['name', 'site_deployment']
1058 extra = 0
1059 suit_classes = 'suit-tab suit-tab-nodes'
1060
Tony Mackc2a0d312015-02-25 11:39:34 -05001061 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1062 # only display site deployments associated with this site
Jeremy Moweryda57d402016-04-15 17:39:49 -07001063 if db_field.name == 'site_deployment':
1064 kwargs['queryset'] = SiteDeployment.objects.filter(
1065 site__id=int(request.path.split('/')[-2]))
Tony Mackc2a0d312015-02-25 11:39:34 -05001066
1067 return super(SiteNodeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1068
Jeremy Moweryda57d402016-04-15 17:39:49 -07001069
Scott Baker67db95f2015-02-18 15:50:11 -08001070class SiteAdmin(XOSBaseAdmin):
Tony Mack450b6e02015-01-25 12:35:29 -05001071 #fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
Jeremy Moweryda57d402016-04-15 17:39:49 -07001072 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled',
1073 'login_base', 'location', 'is_public', 'hosts_nodes', 'hosts_users']
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001074 fieldsets = [
Jeremy Moweryda57d402016-04-15 17:39:49 -07001075 (None, {'fields': fieldList, 'classes': [
1076 'suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -04001077 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001078 ]
Tony Mack450b6e02015-01-25 12:35:29 -05001079 #readonly_fields = ['backend_status_text', 'accountLink']
1080 readonly_fields = ['backend_status_text']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001081
Tony Mack450b6e02015-01-25 12:35:29 -05001082 #user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
Jeremy Moweryda57d402016-04-15 17:39:49 -07001083 user_readonly_fields = ['name', 'deployments', 'site_url',
1084 'enabled', 'is_public', 'login_base', 'hosts_nodes', 'hosts_users']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001085
Jeremy Moweryda57d402016-04-15 17:39:49 -07001086 list_display = ('backend_status_icon', 'name',
1087 'login_base', 'site_url', 'enabled')
Scott Baker63d1a552014-08-21 15:19:07 -07001088 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001089 filter_horizontal = ('deployments',)
Jeremy Moweryda57d402016-04-15 17:39:49 -07001090 inlines = [SliceInline, UserInline, TagInline,
1091 SitePrivilegeInline, SiteNodeInline]
Tony Mackde100182015-01-14 12:11:05 -05001092 admin_inlines = [ControllerSiteInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001093 search_fields = ['name']
1094
Tony Mack30dfcd72015-01-10 23:08:10 -05001095 @property
1096 def suit_form_tabs(self):
1097 tabs = [('general', 'Site Details'),
Jeremy Moweryda57d402016-04-15 17:39:49 -07001098 ('users', 'Users'),
1099 ('siteprivileges', 'Privileges'),
1100 ('slices', 'Slices'),
1101 ('nodes', 'Nodes'),
1102 ]
Tony Mack30dfcd72015-01-10 23:08:10 -05001103
Jeremy Moweryda57d402016-04-15 17:39:49 -07001104 request = getattr(_thread_locals, "request", None)
Tony Mack30dfcd72015-01-10 23:08:10 -05001105 if request and request.user.is_admin:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001106 tabs.append(('admin-only', 'Admin-Only'))
Tony Mack30dfcd72015-01-10 23:08:10 -05001107
1108 return tabs
1109
Tony Mack04062832013-05-10 08:22:44 -04001110 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -05001111 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -04001112
Tony Mack5cd13202013-05-01 21:48:38 -04001113 def get_formsets(self, request, obj=None):
1114 for inline in self.get_inline_instances(request, obj):
1115 # hide MyInline in the add view
1116 if obj is None:
1117 continue
Tony Mackd8515472015-08-19 11:58:18 -04001118 if isinstance(inline, InstanceInline):
Tony Mack2bd5b412013-06-11 21:05:06 -04001119 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -04001120 yield inline.get_formset(request, obj)
1121
Scott Baker545db2a2013-12-09 18:44:43 -08001122 def accountLink(self, obj):
1123 link_obj = obj.accounts.all()
1124 if link_obj:
1125 reverse_path = "admin:core_account_change"
Jeremy Moweryda57d402016-04-15 17:39:49 -07001126 url = reverse(reverse_path, args=(link_obj[0].id,))
Scott Baker545db2a2013-12-09 18:44:43 -08001127 return "<a href='%s'>%s</a>" % (url, "view billing details")
1128 else:
1129 return "no billing data for this site"
1130 accountLink.allow_tags = True
1131 accountLink.short_description = "Billing"
1132
Tony Mack332ee1d2014-02-04 15:33:45 -05001133 def save_model(self, request, obj, form, change):
1134 # update openstack connection to use this site/tenant
Jeremy Moweryc2e8f162016-01-10 20:36:51 -07001135 obj.save_by_user(request.user)
Tony Mack332ee1d2014-02-04 15:33:45 -05001136
1137 def delete_model(self, request, obj):
1138 obj.delete_by_user(request.user)
Jeremy Moweryc2e8f162016-01-10 20:36:51 -07001139
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001140
Scott Baker67db95f2015-02-18 15:50:11 -08001141class SitePrivilegeAdmin(XOSBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001142 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -04001143 fieldsets = [
Jeremy Moweryda57d402016-04-15 17:39:49 -07001144 (None, {'fields': fieldList, 'classes': ['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -04001145 ]
Scott Baker40c00762014-08-21 16:55:59 -07001146 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -07001147 list_display = ('backend_status_icon', 'user', 'site', 'role')
1148 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001149 user_readonly_fields = fieldList
1150 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -04001151
Tony Mackc2835a92013-05-28 09:18:49 -04001152 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1153 if db_field.name == 'site':
1154 if not request.user.is_admin:
1155 # only show sites where user is an admin or pi
1156 sites = set()
1157 for site_privilege in SitePrivilege.objects.filer(user=request.user):
1158 if site_privilege.role.role_type in ['admin', 'pi']:
1159 sites.add(site_privilege.site)
1160 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
1161
1162 if db_field.name == 'user':
1163 if not request.user.is_admin:
1164 # only show users from sites where caller has admin or pi role
1165 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
Jeremy Moweryda57d402016-04-15 17:39:49 -07001166 site_privileges = SitePrivilege.objects.filter(
1167 user=request.user).filter(role__in=roles)
Tony Mackc2835a92013-05-28 09:18:49 -04001168 sites = [site_privilege.site for site_privilege in site_privileges]
1169 site_privileges = SitePrivilege.objects.filter(site__in=sites)
Jeremy Moweryda57d402016-04-15 17:39:49 -07001170 emails = [
1171 site_privilege.user.email for site_privilege in site_privileges]
Tony Mackc2835a92013-05-28 09:18:49 -04001172 users = User.objects.filter(email__in=emails)
1173 kwargs['queryset'] = users
1174
1175 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1176
Tony Mack04062832013-05-10 08:22:44 -04001177 def queryset(self, request):
1178 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -04001179 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -04001180 qs = super(SitePrivilegeAdmin, self).queryset(request)
Jeremy Moweryda57d402016-04-15 17:39:49 -07001181 # if not request.user.is_admin:
Tony Mack5b061472014-02-04 07:57:10 -05001182 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
1183 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
1184 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
1185 # sites = Site.objects.filter(login_base__in=login_bases)
1186 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -04001187 return qs
1188
Jeremy Moweryda57d402016-04-15 17:39:49 -07001189
Siobhan Tullyce652d02013-10-08 21:52:35 -04001190class SliceForm(forms.ModelForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001191
Siobhan Tullyce652d02013-10-08 21:52:35 -04001192 class Meta:
1193 model = Slice
1194 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -07001195 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -04001196 }
1197
Tony Macke75841e2014-09-29 16:10:52 -04001198 def clean(self):
1199 cleaned_data = super(SliceForm, self).clean()
1200 name = cleaned_data.get('name')
Scott Baker3cb382c2014-10-06 23:09:59 -07001201 site = cleaned_data.get('site')
Tony Mackcc9e2592014-10-22 12:54:19 -04001202 slice_id = self.instance.id
1203 if not site and slice_id:
1204 site = Slice.objects.get(id=slice_id).site
Jeremy Moweryda57d402016-04-15 17:39:49 -07001205 if (not isinstance(site, Site)):
Scott Baker3cb382c2014-10-06 23:09:59 -07001206 # previous code indicates 'site' could be a site_id and not a site?
1207 site = Slice.objects.get(id=site.id)
Tony Macke75841e2014-09-29 16:10:52 -04001208 if not name.startswith(site.login_base):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001209 raise forms.ValidationError(
1210 'slice name must begin with %s' % site.login_base)
Tony Macke75841e2014-09-29 16:10:52 -04001211 return cleaned_data
1212
Jeremy Moweryda57d402016-04-15 17:39:49 -07001213
Scott Baker67db95f2015-02-18 15:50:11 -08001214class ControllerSliceInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -05001215 model = ControllerSlice
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001216 extra = 0
Tony Mack06c8e472014-11-30 15:53:08 -05001217 verbose_name = "Controller Slices"
1218 verbose_name_plural = "Controller Slices"
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001219 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack06c8e472014-11-30 15:53:08 -05001220 fields = ['backend_status_icon', 'controller', 'tenant_id']
Jeremy Moweryda57d402016-04-15 17:39:49 -07001221 readonly_fields = ('backend_status_icon', 'controller')
1222
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001223
Scott Baker67db95f2015-02-18 15:50:11 -08001224class SliceAdmin(XOSBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -04001225 form = SliceForm
Jeremy Moweryda57d402016-04-15 17:39:49 -07001226 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled',
1227 'description', 'service', 'slice_url', 'max_instances', "default_isolation", "network"]
1228 fieldsets = [('Slice Details', {'fields': fieldList, 'classes': [
1229 'suit-tab suit-tab-general']}), ]
Scott Baker40c00762014-08-21 16:55:59 -07001230 readonly_fields = ('backend_status_text', )
Jeremy Moweryda57d402016-04-15 17:39:49 -07001231 list_display = ('backend_status_icon', 'name', 'site',
1232 'serviceClass', 'slice_url', 'max_instances')
Tony Mack7d459902014-09-03 13:18:57 -04001233 list_display_links = ('backend_status_icon', 'name', )
Jeremy Moweryda57d402016-04-15 17:39:49 -07001234 normal_inlines = [SlicePrivilegeInline, InstanceInline,
1235 TagInline, ReservationInline, SliceNetworkInline]
Scott Baker25881992015-06-12 10:40:15 -07001236 inlines = normal_inlines
Tony Mack3066a952015-01-05 22:48:11 -05001237 admin_inlines = [ControllerSliceInline]
Scott Baker22beb6a2015-09-15 15:21:50 -07001238 suit_form_includes = (('slice_instance_tab.html', 'bottom', 'instances'),)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001239
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001240 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001241
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001242 @property
1243 def suit_form_tabs(self):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001244 tabs = [('general', 'Slice Details'),
1245 ('slicenetworks', 'Networks'),
1246 ('sliceprivileges', 'Privileges'),
1247 ('instances', 'Instances'),
1248 #('reservations','Reservations'),
1249 ('tags', 'Tags'),
1250 ]
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001251
Jeremy Moweryda57d402016-04-15 17:39:49 -07001252 request = getattr(_thread_locals, "request", None)
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001253 if request and request.user.is_admin:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001254 tabs.append(('admin-only', 'Admin-Only'))
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001255
1256 return tabs
Jeremy Moweryc2e8f162016-01-10 20:36:51 -07001257
Tony Mack0aa732a2014-10-22 11:54:29 -04001258 def add_view(self, request, form_url='', extra_context=None):
Scott Baker25881992015-06-12 10:40:15 -07001259 # Ugly hack for CORD
1260 self.inlines = self.normal_inlines
Tony Mack0aa732a2014-10-22 11:54:29 -04001261 # revert to default read-only fields
1262 self.readonly_fields = ('backend_status_text',)
1263 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
1264
1265 def change_view(self, request, object_id, form_url='', extra_context=None):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001266 # cannot change the site of an existing slice so make the site field
1267 # read only
Tony Mack0aa732a2014-10-22 11:54:29 -04001268 if object_id:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001269 self.readonly_fields = ('backend_status_text', 'site')
Scott Baker25881992015-06-12 10:40:15 -07001270
Tony Mack0aa732a2014-10-22 11:54:29 -04001271 return super(SliceAdmin, self).change_view(request, object_id, form_url)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001272
Scott Baker510fdbb2014-08-05 17:19:24 -07001273 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -07001274 deployment_nodes = []
1275 for node in Node.objects.all():
Jeremy Moweryda57d402016-04-15 17:39:49 -07001276 deployment_nodes.append(
1277 (node.site_deployment.deployment.id, node.id, node.name))
Scott Baker510fdbb2014-08-05 17:19:24 -07001278
Scott Baker7a61dc42014-09-02 17:08:20 -07001279 deployment_flavors = []
1280 for flavor in Flavor.objects.all():
1281 for deployment in flavor.deployments.all():
Jeremy Moweryda57d402016-04-15 17:39:49 -07001282 deployment_flavors.append(
1283 (deployment.id, flavor.id, flavor.name))
Scott Baker7a61dc42014-09-02 17:08:20 -07001284
Tony Mack68a1e422014-12-08 16:43:02 -05001285 deployment_images = []
Scott Bakeraf36c4d2014-09-09 09:58:49 -07001286 for image in Image.objects.all():
Tony Mack68a1e422014-12-08 16:43:02 -05001287 for deployment_image in image.imagedeployments.all():
Jeremy Moweryda57d402016-04-15 17:39:49 -07001288 deployment_images.append(
1289 (deployment_image.deployment.id, image.id, image.name))
Scott Bakeraf36c4d2014-09-09 09:58:49 -07001290
Tony Mackec23b992014-09-02 21:18:45 -04001291 site_login_bases = []
1292 for site in Site.objects.all():
Scott Bakeraf36c4d2014-09-09 09:58:49 -07001293 site_login_bases.append((site.id, site.login_base))
1294
Scott Baker510fdbb2014-08-05 17:19:24 -07001295 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -07001296 context["deployment_flavors"] = deployment_flavors
Scott Bakeraf36c4d2014-09-09 09:58:49 -07001297 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -04001298 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -07001299 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
1300
Tony Mackc2835a92013-05-28 09:18:49 -04001301 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1302 if db_field.name == 'site':
Jeremy Moweryda57d402016-04-15 17:39:49 -07001303 kwargs['queryset'] = Site.select_by_user(
1304 request.user).filter(hosts_users=True)
1305 kwargs['widget'] = forms.Select(
1306 attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
Scott Baker40c00762014-08-21 16:55:59 -07001307
Tony Mackc2835a92013-05-28 09:18:49 -04001308 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1309
Tony Mack04062832013-05-10 08:22:44 -04001310 def queryset(self, request):
1311 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -05001312 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -04001313
Tony Mack79748612013-05-01 14:52:03 -04001314 def get_formsets(self, request, obj=None):
1315 for inline in self.get_inline_instances(request, obj):
1316 # hide MyInline in the add view
1317 if obj is None:
1318 continue
Tony Mackd8515472015-08-19 11:58:18 -04001319 if isinstance(inline, InstanceInline):
Tony Mack2bd5b412013-06-11 21:05:06 -04001320 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -04001321 yield inline.get_formset(request, obj)
1322
Scott Baker533c2152015-09-15 17:48:57 -07001323 def add_extra_context(self, request, extra_context):
1324 super(SliceAdmin, self).add_extra_context(request, extra_context)
1325 # set context["slice_id"] to the PK passed in the URL to this view
Jeremy Moweryda57d402016-04-15 17:39:49 -07001326 if len(request.resolver_match.args) > 0:
Scott Baker533c2152015-09-15 17:48:57 -07001327 extra_context["slice_id"] = request.resolver_match.args[0]
1328
Scott Baker25881992015-06-12 10:40:15 -07001329 def UNUSED_get_inline_instances(self, request, obj=None):
1330 # HACK for CORD to do something special on vcpe slice page
1331 # this was a good idea, but failed miserably, as something still
1332 # expects there to be a deployment field.
1333 # XXX this approach is better than clobbering self.inlines, so
1334 # try to make this work post-demo.
1335 if (obj is not None) and (obj.name == "mysite_vcpe"):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001336 cord_vcpe_inlines = [SlicePrivilegeInline, CordInstanceInline,
1337 TagInline, ReservationInline, SliceNetworkInline]
Scott Baker25881992015-06-12 10:40:15 -07001338
Jeremy Moweryda57d402016-04-15 17:39:49 -07001339 inlines = []
Scott Baker25881992015-06-12 10:40:15 -07001340 for inline_class in cord_vcpe_inlines:
1341 inlines.append(inline_class(self.model, self.admin_site))
1342 else:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001343 inlines = super(SliceAdmin, self).get_inline_instances(
1344 request, obj)
Scott Baker25881992015-06-12 10:40:15 -07001345
1346 return inlines
1347
Jeremy Moweryda57d402016-04-15 17:39:49 -07001348
Scott Baker67db95f2015-02-18 15:50:11 -08001349class SlicePrivilegeAdmin(XOSBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -04001350 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001351 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -04001352 ]
Scott Baker40c00762014-08-21 16:55:59 -07001353 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -07001354 list_display = ('backend_status_icon', 'user', 'slice', 'role')
1355 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -04001356
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001357 user_readonly_fields = ['user', 'slice', 'role']
1358 user_readonly_inlines = []
1359
Tony Mackc2835a92013-05-28 09:18:49 -04001360 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1361 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001362 kwargs['queryset'] = Slice.select_by_user(request.user)
Jeremy Moweryc2e8f162016-01-10 20:36:51 -07001363
Tony Mackc2835a92013-05-28 09:18:49 -04001364 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -05001365 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001366
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001367 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -04001368
Tony Mack04062832013-05-10 08:22:44 -04001369 def queryset(self, request):
1370 # admins can see all memberships. Users can only see memberships of
1371 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -05001372 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001373
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001374 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -04001375 # update openstack connection to use this site/tenant
1376 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -04001377 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -04001378 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001379 obj.save()
1380
1381 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -04001382 # update openstack connection to use this site/tenant
1383 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -04001384 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -04001385 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001386 obj.delete()
1387
Jeremy Moweryda57d402016-04-15 17:39:49 -07001388
Scott Baker67db95f2015-02-18 15:50:11 -08001389class ImageAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001390
Scott Baker36f50872014-08-21 13:01:25 -07001391 fieldsets = [('Image Details',
Jeremy Moweryda57d402016-04-15 17:39:49 -07001392 {'fields': ['backend_status_text', 'name', 'kind', 'disk_format', 'container_format', 'tag', 'path'],
1393 'classes': ['suit-tab suit-tab-general']})
1394 ]
Scott Baker40c00762014-08-21 16:55:59 -07001395 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001396
Jeremy Moweryda57d402016-04-15 17:39:49 -07001397 suit_form_tabs = (('general', 'Image Details'), ('instances', 'Instances'),
1398 ('imagedeployments', 'Deployments'), ('admin-only', 'Admin-Only'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001399
Tony Mackd8515472015-08-19 11:58:18 -04001400 inlines = [InstanceInline, ControllerImagesInline]
Scott Bakerb6f99242014-06-11 11:34:44 -07001401
Jeremy Moweryda57d402016-04-15 17:39:49 -07001402 user_readonly_fields = ['name', 'disk_format',
1403 'container_format', 'tag', 'path']
Scott Bakerb27b62c2014-08-15 16:29:16 -07001404
Scott Bakerdcf9e0d2015-11-09 16:17:11 -08001405 list_display = ['backend_status_icon', 'name', 'kind']
Scott Baker63d1a552014-08-21 15:19:07 -07001406 list_display_links = ('backend_status_icon', 'name', )
1407
Jeremy Moweryda57d402016-04-15 17:39:49 -07001408
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001409class NodeForm(forms.ModelForm):
Scott Bakerb0955d92016-04-06 14:34:49 -07001410 nodelabels = forms.ModelMultipleChoiceField(
Scott Baker7c886d42016-03-04 10:35:32 -08001411 queryset=NodeLabel.objects.all(),
1412 required=False,
1413 help_text="Select which labels apply to this node",
1414 widget=FilteredSelectMultiple(
1415 verbose_name=('Labels'), is_stacked=False
1416 )
1417 )
Jeremy Moweryda57d402016-04-15 17:39:49 -07001418
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001419 class Meta:
Scott Bakerdc5db282015-09-21 15:06:38 -07001420 model = Node
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001421 widgets = {
1422 'site': LinkedSelect,
1423 'deployment': LinkedSelect
1424 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001425
Scott Baker7c886d42016-03-04 10:35:32 -08001426 def __init__(self, *args, **kwargs):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001427 request = kwargs.pop('request', None)
1428 super(NodeForm, self).__init__(*args, **kwargs)
Scott Baker7c886d42016-03-04 10:35:32 -08001429
Jeremy Moweryda57d402016-04-15 17:39:49 -07001430 if self.instance and self.instance.pk:
1431 self.fields['nodelabels'].initial = self.instance.nodelabels.all()
Scott Baker7c886d42016-03-04 10:35:32 -08001432
1433 def save(self, commit=True):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001434 node = super(NodeForm, self).save(commit=False)
Scott Baker7c886d42016-03-04 10:35:32 -08001435
Jeremy Moweryda57d402016-04-15 17:39:49 -07001436 node.nodelabels = self.cleaned_data['nodelabels']
Scott Baker7c886d42016-03-04 10:35:32 -08001437
Jeremy Moweryda57d402016-04-15 17:39:49 -07001438 if commit:
1439 node.save()
Scott Baker7c886d42016-03-04 10:35:32 -08001440
Jeremy Moweryda57d402016-04-15 17:39:49 -07001441 return node
Scott Baker7c886d42016-03-04 10:35:32 -08001442
1443
1444class NodeLabelAdmin(XOSBaseAdmin):
1445 list_display = ('name',)
1446 list_display_links = ('name', )
1447
1448 fields = ('name', )
1449
1450
Scott Baker67db95f2015-02-18 15:50:11 -08001451class NodeAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001452 form = NodeForm
Tony Mack68a1e422014-12-08 16:43:02 -05001453 list_display = ('backend_status_icon', 'name', 'site_deployment')
Scott Baker63d1a552014-08-21 15:19:07 -07001454 list_display_links = ('backend_status_icon', 'name', )
Tony Mack68a1e422014-12-08 16:43:02 -05001455 list_filter = ('site_deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001456
Jeremy Moweryda57d402016-04-15 17:39:49 -07001457 inlines = [TagInline, InstanceInline]
Scott Baker7c886d42016-03-04 10:35:32 -08001458 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name', 'site_deployment'], 'classes':['suit-tab suit-tab-details']}),
Scott Bakerb0955d92016-04-06 14:34:49 -07001459 ('Labels', {'fields': ['nodelabels'], 'classes':['suit-tab suit-tab-labels']})]
Scott Baker40c00762014-08-21 16:55:59 -07001460 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001461
Jeremy Moweryda57d402016-04-15 17:39:49 -07001462 user_readonly_fields = ['name', 'site_deployment']
1463 user_readonly_inlines = [TagInline, InstanceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001464
Jeremy Moweryda57d402016-04-15 17:39:49 -07001465 suit_form_tabs = (('details', 'Node Details'), ('instances',
1466 'Instances'), ('labels', 'Labels'), ('tags', 'Tags'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001467
Tony Mack02b8f142015-08-04 17:32:32 -04001468 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1469 if db_field.name == 'site':
Jeremy Moweryda57d402016-04-15 17:39:49 -07001470 kwargs['queryset'] = Site.select_by_user(
1471 request.user).filter(hosts_nodes=True)
Siobhan Tully567e3e62013-06-21 18:03:16 -04001472
Jeremy Moweryda57d402016-04-15 17:39:49 -07001473 field = super(NodeAdmin, self).formfield_for_foreignkey(
1474 db_field, request, **kwargs)
Scott Bakerdc5db282015-09-21 15:06:38 -07001475
1476 return field
1477
Jeremy Moweryda57d402016-04-15 17:39:49 -07001478
Tony Mackd8515472015-08-19 11:58:18 -04001479class InstanceForm(forms.ModelForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001480
Tony Mackd90cdbf2013-04-16 22:48:40 -04001481 class Meta:
Tony Mackd8515472015-08-19 11:58:18 -04001482 model = Instance
Tony Mackd90cdbf2013-04-16 22:48:40 -04001483 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -04001484 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -04001485 widgets = {
1486 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -04001487 'instance_name': PlainTextWidget(),
Scott Baker9d856052015-01-19 11:32:20 -08001488 'instance_id': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001489 'slice': LinkedSelect,
Tony Mackb2dba4b2014-12-26 13:38:02 -05001490 'deployment': LinkedSelect,
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001491 'node': LinkedSelect,
1492 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -04001493 }
Tony Mackd90cdbf2013-04-16 22:48:40 -04001494
Jeremy Moweryda57d402016-04-15 17:39:49 -07001495
Scott Baker67db95f2015-02-18 15:50:11 -08001496class TagAdmin(XOSBaseAdmin):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001497 list_display = ['backend_status_icon', 'service',
1498 'name', 'value', 'content_type', 'content_object', ]
Scott Baker63d1a552014-08-21 15:19:07 -07001499 list_display_links = list_display
Jeremy Moweryda57d402016-04-15 17:39:49 -07001500 user_readonly_fields = ['service', 'name',
1501 'value', 'content_type', 'content_object', ]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001502 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -04001503
Jeremy Moweryda57d402016-04-15 17:39:49 -07001504
Tony Mack26ea0eb2015-09-01 16:06:52 +00001505class InstancePortInline(XOSTabularInline):
Tony Mackb956a5d2015-09-10 21:58:15 +00001506 fields = ['backend_status_icon', 'network', 'instance', 'ip', 'mac']
Scott Bakere553b472015-09-08 18:22:15 -07001507 readonly_fields = ("backend_status_icon", "ip", "mac")
Scott Baker5a7d9312015-08-26 09:43:33 -07001508 model = Port
Scott Bakerba12a252015-11-16 16:21:47 -08001509 #selflink_fieldname = "network"
Scott Bakerfbade882015-08-25 18:00:15 -07001510 extra = 0
1511 verbose_name_plural = "Ports"
1512 verbose_name = "Port"
1513 suit_classes = 'suit-tab suit-tab-ports'
1514
Jeremy Moweryda57d402016-04-15 17:39:49 -07001515
Tony Mackd8515472015-08-19 11:58:18 -04001516class InstanceAdmin(XOSBaseAdmin):
1517 form = InstanceForm
Tony Mackcdec0902013-04-15 00:38:49 -04001518 fieldsets = [
Jeremy Moweryda57d402016-04-15 17:39:49 -07001519 ('Instance Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'isolation', 'flavor', 'image', 'node',
1520 'parent', 'all_ips_string', 'instance_id', 'instance_name', 'ssh_command', ], 'classes': ['suit-tab suit-tab-general'], }),
1521 ('Container Settings', {'fields': ['volumes'], 'classes': [
1522 'suit-tab suit-tab-container'], }),
Tony Mackcdec0902013-04-15 00:38:49 -04001523 ]
Tony Mackdb8580b2015-01-30 17:20:46 -05001524 readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string')
Jeremy Moweryda57d402016-04-15 17:39:49 -07001525 list_display = ['backend_status_icon', 'all_ips_string', 'instance_id',
1526 'instance_name', 'isolation', 'slice', 'flavor', 'image', 'node', 'deployment']
1527 list_display_links = ('backend_status_icon',
1528 'all_ips_string', 'instance_id', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001529
Jeremy Moweryda57d402016-04-15 17:39:49 -07001530 suit_form_tabs = (('general', 'Instance Details'), ('ports', 'Ports'),
1531 ('container', 'Container Settings'), ('tags', 'Tags'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001532
Tony Mack32010062015-09-13 22:50:39 +00001533 inlines = [TagInline, InstancePortInline]
Tony Mack53106f32013-04-27 16:43:01 -04001534
Jeremy Moweryda57d402016-04-15 17:39:49 -07001535 user_readonly_fields = ['slice', 'deployment',
1536 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001537
Scott Baker7ccc6ad2015-01-25 22:16:13 -08001538 def ssh_command(self, obj):
1539 ssh_command = obj.get_ssh_command()
1540 if ssh_command:
1541 return ssh_command
1542 else:
1543 return "(not available)"
1544
Tony Mackc2835a92013-05-28 09:18:49 -04001545 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1546 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001547 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001548
Tony Mackd8515472015-08-19 11:58:18 -04001549 return super(InstanceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -04001550
Tony Mack04062832013-05-10 08:22:44 -04001551 def queryset(self, request):
Tony Mackd8515472015-08-19 11:58:18 -04001552 # admins can see all instances. Users can only see instances of
Tony Mack04062832013-05-10 08:22:44 -04001553 # the slices they belong to.
Tony Mackd8515472015-08-19 11:58:18 -04001554 return Instance.select_by_user(request.user)
Tony Mack5b061472014-02-04 07:57:10 -05001555
Jeremy Moweryda57d402016-04-15 17:39:49 -07001556 def add_view(self, request, form_url='', extra_context=None):
1557 self.readonly_fields = ('backend_status_text',
1558 'ssh_command', 'all_ips_string')
1559 return super(InstanceAdmin, self).add_view(request, form_url, extra_context)
Tony Mack04062832013-05-10 08:22:44 -04001560
Scott Baker17ee7f82015-09-21 21:42:41 -07001561 def change_view(self, request, object_id, extra_context=None):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001562 self.readonly_fields = ('backend_status_text', 'ssh_command',
1563 'all_ips_string', 'deployment', 'slice', 'flavor', 'image', 'node')
1564 # for XOSAdminMixin.change_view's user_readonly_fields switching code
1565 self.readonly_save = self.readonly_fields
1566 return super(InstanceAdmin, self).change_view(request, object_id, extra_context)
Tony Mack53106f32013-04-27 16:43:01 -04001567
Scott Baker878576f2015-09-21 16:02:54 -07001568 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
1569 deployment_nodes = []
Scott Baker2a5fe502016-03-22 09:37:57 -07001570# for node in Node.objects.all():
1571 for node in Node.objects.order_by("name"):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001572 deployment_nodes.append(
1573 (node.site_deployment.deployment.id, node.id, node.name))
Scott Baker878576f2015-09-21 16:02:54 -07001574
1575 deployment_flavors = []
1576 for flavor in Flavor.objects.all():
1577 for deployment in flavor.deployments.all():
Jeremy Moweryda57d402016-04-15 17:39:49 -07001578 deployment_flavors.append(
1579 (deployment.id, flavor.id, flavor.name))
Scott Baker878576f2015-09-21 16:02:54 -07001580
1581 deployment_images = []
1582 for image in Image.objects.all():
1583 for deployment_image in image.imagedeployments.all():
Jeremy Moweryda57d402016-04-15 17:39:49 -07001584 deployment_images.append(
1585 (deployment_image.deployment.id, image.id, image.name))
Scott Baker878576f2015-09-21 16:02:54 -07001586
1587 site_login_bases = []
1588 for site in Site.objects.all():
1589 site_login_bases.append((site.id, site.login_base))
1590
1591 context["deployment_nodes"] = deployment_nodes
1592 context["deployment_flavors"] = deployment_flavors
1593 context["deployment_images"] = deployment_images
1594 context["site_login_bases"] = site_login_bases
1595 return super(InstanceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
1596
1597 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1598 if db_field.name == 'deployment':
Jeremy Moweryda57d402016-04-15 17:39:49 -07001599 kwargs['queryset'] = Deployment.select_by_acl(request.user).filter(
1600 sitedeployments__nodes__isnull=False).distinct()
1601 kwargs['widget'] = forms.Select(
1602 attrs={'onChange': "instance_deployment_changed(this);"})
Scott Baker878576f2015-09-21 16:02:54 -07001603 if db_field.name == 'flavor':
Jeremy Moweryda57d402016-04-15 17:39:49 -07001604 kwargs['widget'] = forms.Select(
1605 attrs={'onChange': "instance_flavor_changed(this);"})
Scott Baker878576f2015-09-21 16:02:54 -07001606
Jeremy Moweryda57d402016-04-15 17:39:49 -07001607 field = super(InstanceAdmin, self).formfield_for_foreignkey(
1608 db_field, request, **kwargs)
Scott Baker878576f2015-09-21 16:02:54 -07001609
1610 return field
1611
Jeremy Moweryda57d402016-04-15 17:39:49 -07001612 # def save_model(self, request, obj, form, change):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001613 # # update openstack connection to use this site/tenant
1614 # auth = request.session.get('auth', {})
1615 # auth['tenant'] = obj.slice.name
1616 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1617 # obj.creator = request.user
1618 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -04001619
Jeremy Moweryda57d402016-04-15 17:39:49 -07001620 # def delete_model(self, request, obj):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001621 # # update openstack connection to use this site/tenant
1622 # auth = request.session.get('auth', {})
1623 # auth['tenant'] = obj.slice.name
1624 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1625 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -04001626
Jeremy Moweryda57d402016-04-15 17:39:49 -07001627# class ContainerPortInline(XOSTabularInline):
Scott Bakerdcf9e0d2015-11-09 16:17:11 -08001628# fields = ['backend_status_icon', 'network', 'container', 'ip', 'mac', 'segmentation_id']
1629# readonly_fields = ("backend_status_icon", "ip", "mac", "segmentation_id")
1630# model = Port
1631# selflink_fieldname = "network"
1632# extra = 0
1633# verbose_name_plural = "Ports"
1634# verbose_name = "Port"
1635# suit_classes = 'suit-tab suit-tab-ports'
Scott Baker9f457d92015-10-26 19:52:10 -07001636
Jeremy Moweryda57d402016-04-15 17:39:49 -07001637# class ContainerAdmin(XOSBaseAdmin):
Scott Bakerdcf9e0d2015-11-09 16:17:11 -08001638# fieldsets = [
1639# ('Container Details', {'fields': ['backend_status_text', 'slice', 'node', 'docker_image', 'volumes', 'no_sync'], 'classes': ['suit-tab suit-tab-general'], })
1640# ]
1641# readonly_fields = ('backend_status_text', )
1642# list_display = ['backend_status_icon', 'id']
1643# list_display_links = ('backend_status_icon', 'id', )
1644#
1645# suit_form_tabs =(('general', 'Container Details'), ('ports', 'Ports'))
1646#
1647# inlines = [TagInline, ContainerPortInline]
1648#
1649# def formfield_for_foreignkey(self, db_field, request, **kwargs):
1650# if db_field.name == 'slice':
1651# kwargs['queryset'] = Slice.select_by_user(request.user)
1652#
1653# return super(ContainerAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1654#
1655# def queryset(self, request):
1656# # admins can see all instances. Users can only see instances of
1657# # the slices they belong to.
1658# return Container.select_by_user(request.user)
Scott Bakere4ea8952015-10-26 15:12:13 -07001659
Jeremy Moweryda57d402016-04-15 17:39:49 -07001660
Siobhan Tully53437282013-04-26 19:30:27 -04001661class UserCreationForm(forms.ModelForm):
1662 """A form for creating new users. Includes all the required
1663 fields, plus a repeated password."""
1664 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
Jeremy Moweryda57d402016-04-15 17:39:49 -07001665 password2 = forms.CharField(
1666 label='Password confirmation', widget=forms.PasswordInput)
Siobhan Tully53437282013-04-26 19:30:27 -04001667
1668 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001669 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001670 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -04001671
1672 def clean_password2(self):
1673 # Check that the two password entries match
1674 password1 = self.cleaned_data.get("password1")
1675 password2 = self.cleaned_data.get("password2")
1676 if password1 and password2 and password1 != password2:
1677 raise forms.ValidationError("Passwords don't match")
1678 return password2
1679
1680 def save(self, commit=True):
1681 # Save the provided password in hashed format
1682 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -04001683 user.password = self.cleaned_data["password1"]
Jeremy Moweryda57d402016-04-15 17:39:49 -07001684 # user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -04001685 if commit:
1686 user.save()
1687 return user
1688
Siobhan Tully567e3e62013-06-21 18:03:16 -04001689
Siobhan Tully53437282013-04-26 19:30:27 -04001690class UserChangeForm(forms.ModelForm):
1691 """A form for updating users. Includes all the fields on
1692 the user, but replaces the password field with admin's
1693 password hash display field.
1694 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -05001695 password = ReadOnlyPasswordHashField(label='Password',
Jeremy Moweryda57d402016-04-15 17:39:49 -07001696 help_text='<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -04001697
Jeremy Moweryda57d402016-04-15 17:39:49 -07001698 PROFILE_CHOICES = ((None, '------'), ('regular',
1699 'Regular user'), ('cp', 'Content Provider'))
1700 profile = forms.ChoiceField(
1701 choices=PROFILE_CHOICES, required=False, label="Quick Profile")
Scott Bakerb6043c22015-05-19 16:39:48 -07001702
Siobhan Tully53437282013-04-26 19:30:27 -04001703 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001704 model = User
Jeremy Moweryda57d402016-04-15 17:39:49 -07001705 widgets = {'public_key': UploadTextareaWidget, }
Siobhan Tully53437282013-04-26 19:30:27 -04001706
1707 def clean_password(self):
1708 # Regardless of what the user provides, return the initial value.
1709 # This is done here, rather than on the field, because the
1710 # field does not have access to the initial value
1711 return self.initial["password"]
1712
Scott Bakerb6043c22015-05-19 16:39:48 -07001713 def save(self, *args, **kwargs):
1714 if self.cleaned_data['profile']:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001715 self.instance.apply_profile(self.cleaned_data['profile'])
Scott Bakerb6043c22015-05-19 16:39:48 -07001716
1717 return super(UserChangeForm, self).save(*args, **kwargs)
1718
Jeremy Moweryda57d402016-04-15 17:39:49 -07001719
Scott Baker67db95f2015-02-18 15:50:11 -08001720class UserDashboardViewInline(XOSTabularInline):
Scott Baker2c3cb642014-05-19 17:55:56 -07001721 model = UserDashboardView
1722 extra = 0
1723 suit_classes = 'suit-tab suit-tab-dashboards'
1724 fields = ['user', 'dashboardView', 'order']
1725
Jeremy Moweryda57d402016-04-15 17:39:49 -07001726
Scott Baker67db95f2015-02-18 15:50:11 -08001727class ControllerUserInline(XOSTabularInline):
Tony Mack30dfcd72015-01-10 23:08:10 -05001728 model = ControllerUser
1729 extra = 0
1730 suit_classes = 'suit-tab suit-tab-admin-only'
1731 fields = ['controller', 'user', 'kuser_id']
Tony Mack30dfcd72015-01-10 23:08:10 -05001732
1733
Scott Baker3a8aed62015-02-27 12:21:22 -08001734class UserAdmin(XOSAdminMixin, UserAdmin):
1735 # Note: Make sure XOSAdminMixin is listed before
Scott Bakerf4aeedc2014-10-03 13:10:47 -07001736 # admin.ModelAdmin in the class declaration.
1737
Siobhan Tully53437282013-04-26 19:30:27 -04001738 class Meta:
1739 app_label = "core"
1740
1741 # The forms to add and change user instances
1742 form = UserChangeForm
1743 add_form = UserCreationForm
1744
1745 # The fields to be used in displaying the User model.
1746 # These override the definitions on the base UserAdmin
1747 # that reference specific fields on auth.User.
Jeremy Moweryda57d402016-04-15 17:39:49 -07001748 list_display = ('backend_status_icon', 'email',
1749 'firstname', 'lastname', 'site', 'last_login')
Scott Bakera111f442015-01-24 13:33:26 -08001750 list_display_links = ("email",)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001751 list_filter = ('site',)
Jeremy Moweryda57d402016-04-15 17:39:49 -07001752 inlines = [SlicePrivilegeInline, SitePrivilegeInline]
Tony Mack30dfcd72015-01-10 23:08:10 -05001753 admin_inlines = [ControllerUserInline]
Jeremy Moweryda57d402016-04-15 17:39:49 -07001754 fieldListLoginDetails = ['backend_status_text', 'email', 'site', 'password', 'is_active',
1755 'is_readonly', 'is_admin', 'is_appuser', 'public_key', 'login_page', 'profile']
1756 fieldListContactInfo = ['firstname', 'lastname', 'phone', 'timezone']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001757
Siobhan Tully53437282013-04-26 19:30:27 -04001758 fieldsets = (
Jeremy Moweryda57d402016-04-15 17:39:49 -07001759 ('Login Details', {'fields': ['backend_status_text', 'email', 'site', 'password', 'is_active',
1760 'is_readonly', 'is_admin', 'is_appuser', 'public_key'], 'classes': ['suit-tab suit-tab-general']}),
1761 ('Contact Information', {'fields': (
1762 'firstname', 'lastname', 'phone', 'timezone'), 'classes': ['suit-tab suit-tab-contact']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001763 #('Important dates', {'fields': ('last_login',)}),
1764 )
1765 add_fieldsets = (
1766 (None, {
1767 'classes': ('wide',),
Jeremy Moweryda57d402016-04-15 17:39:49 -07001768 'fields': ('site', 'email', 'firstname', 'lastname', 'is_admin', 'is_readonly', 'is_appuser', 'phone', 'public_key', 'password1', 'password2')},
1769 ),
Siobhan Tully53437282013-04-26 19:30:27 -04001770 )
Scott Baker40c00762014-08-21 16:55:59 -07001771 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001772 search_fields = ('email',)
1773 ordering = ('email',)
1774 filter_horizontal = ()
1775
Scott Baker3ca51f62014-05-23 12:05:11 -07001776 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001777
Scott Baker6a995352014-10-06 17:51:20 -07001778 @property
1779 def suit_form_tabs(self):
1780 if getattr(_thread_locals, "obj", None) is None:
1781 return []
1782 else:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001783 tabs = [('general', 'Login Details'),
1784 ('contact', 'Contact Information'),
1785 ('sliceprivileges', 'Slice Privileges'),
1786 ('siteprivileges', 'Site Privileges')]
Tony Mack30dfcd72015-01-10 23:08:10 -05001787
Jeremy Moweryda57d402016-04-15 17:39:49 -07001788 request = getattr(_thread_locals, "request", None)
Tony Mack30dfcd72015-01-10 23:08:10 -05001789 if request and request.user.is_admin:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001790 tabs.append(('admin-only', 'Admin-Only'))
Tony Mack30dfcd72015-01-10 23:08:10 -05001791
1792 return tabs
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001793
Tony Mackc2835a92013-05-28 09:18:49 -04001794 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1795 if db_field.name == 'site':
Jeremy Moweryda57d402016-04-15 17:39:49 -07001796 kwargs['queryset'] = Site.select_by_user(
1797 request.user).filter(hosts_users=True)
Tony Mackc2835a92013-05-28 09:18:49 -04001798
1799 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1800
Tony Mack5b061472014-02-04 07:57:10 -05001801 def queryset(self, request):
1802 return User.select_by_user(request.user)
1803
Tony Mackc8f443d2015-01-25 21:58:30 -05001804 def get_form(self, request, obj=None, **kwargs):
Tony Mack03b92292015-01-28 12:37:12 -05001805 # copy login details list
1806 login_details_fields = list(self.fieldListLoginDetails)
Tony Mack933b2912015-01-28 12:49:58 -05001807 if not request.user.is_admin:
Scott Baker90472612015-01-29 10:55:53 -08001808 # only admins can see 'is_admin' and 'is_readonly' fields
Tony Mack03b92292015-01-28 12:37:12 -05001809 if 'is_admin' in login_details_fields:
1810 login_details_fields.remove('is_admin')
1811 if 'is_readonly' in login_details_fields:
Scott Bakerb6043c22015-05-19 16:39:48 -07001812 login_details_fields.remove('is_readonly')
1813 if 'is_appuser' in login_details_fields:
1814 login_details_fields.remove('is_admin')
1815 if 'profile' in login_details_fields:
1816 login_details_fields.remove('profile')
Jeremy Moweryda57d402016-04-15 17:39:49 -07001817 # if len(request.user.siteprivileges.filter(role__role = 'pi')) > 0:
Scott Bakerb6043c22015-05-19 16:39:48 -07001818 # only admins and pis can change a user's site
Jeremy Moweryc2e8f162016-01-10 20:36:51 -07001819 # self.readonly_fields = ('backend_status_text', 'site')
Tony Mack03b92292015-01-28 12:37:12 -05001820 self.fieldsets = (
Jeremy Moweryda57d402016-04-15 17:39:49 -07001821 ('Login Details', {'fields': login_details_fields,
1822 'classes': ['suit-tab suit-tab-general']}),
1823 ('Contact Information', {
1824 'fields': self.fieldListContactInfo, 'classes': ['suit-tab suit-tab-contact']}),
Tony Mack03b92292015-01-28 12:37:12 -05001825 )
Jeremy Moweryc2e8f162016-01-10 20:36:51 -07001826 return super(UserAdmin, self).get_form(request, obj, **kwargs)
Tony Mackc8f443d2015-01-25 21:58:30 -05001827
Jeremy Moweryda57d402016-04-15 17:39:49 -07001828
Scott Baker67db95f2015-02-18 15:50:11 -08001829class ControllerDashboardViewInline(XOSTabularInline):
Scott Bakerf2c0c512014-12-22 17:35:34 -08001830 model = ControllerDashboardView
Scott Baker786a9c12014-12-19 16:41:12 -08001831 extra = 0
1832 fields = ["controller", "url"]
1833 suit_classes = 'suit-tab suit-tab-controllers'
1834
Jeremy Moweryda57d402016-04-15 17:39:49 -07001835
Scott Baker67db95f2015-02-18 15:50:11 -08001836class DashboardViewAdmin(XOSBaseAdmin):
Scott Baker2c3cb642014-05-19 17:55:56 -07001837 fieldsets = [('Dashboard View Details',
Jeremy Moweryda57d402016-04-15 17:39:49 -07001838 {'fields': ['backend_status_text', 'name', 'url', 'enabled', 'deployments'],
1839 'classes': ['suit-tab suit-tab-general']})
1840 ]
Scott Baker2c44e6e2015-01-18 16:46:26 -08001841 list_display = ["name", "enabled", "url"]
Scott Baker40c00762014-08-21 16:55:59 -07001842 readonly_fields = ('backend_status_text', )
Scott Bakerf2c0c512014-12-22 17:35:34 -08001843 inlines = [ControllerDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001844
Jeremy Moweryda57d402016-04-15 17:39:49 -07001845 suit_form_tabs = (('general', 'Dashboard View Details'),
1846 ('controllers', 'Per-controller Dashboard Details'))
1847
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001848
Scott Baker67db95f2015-02-18 15:50:11 -08001849class ServiceResourceInline(XOSTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001850 model = ServiceResource
1851 extra = 0
1852
Jeremy Moweryda57d402016-04-15 17:39:49 -07001853
Scott Baker67db95f2015-02-18 15:50:11 -08001854class ServiceClassAdmin(XOSBaseAdmin):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001855 list_display = ('backend_status_icon', 'name',
1856 'commitment', 'membershipFee')
Scott Baker63d1a552014-08-21 15:19:07 -07001857 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001858 inlines = [ServiceResourceInline]
1859
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001860 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1861 user_readonly_inlines = []
1862
Jeremy Moweryda57d402016-04-15 17:39:49 -07001863
Scott Baker67db95f2015-02-18 15:50:11 -08001864class ReservedResourceInline(XOSTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001865 model = ReservedResource
1866 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001867 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001868
1869 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001870 field = super(ReservedResourceInline, self).formfield_for_foreignkey(
1871 db_field, request, **kwargs)
Scott Baker133c9212013-05-17 09:09:11 -07001872
1873 if db_field.name == 'resource':
1874 # restrict resources to those that the slice's service class allows
1875 if request._slice is not None:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001876 field.queryset = field.queryset.filter(
1877 serviceClass=request._slice.serviceClass, calendarReservable=True)
Scott Baker133c9212013-05-17 09:09:11 -07001878 if len(field.queryset) > 0:
1879 field.initial = field.queryset.all()[0]
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -05001880 else:
1881 field.queryset = field.queryset.none()
Tony Mackd8515472015-08-19 11:58:18 -04001882 elif db_field.name == 'instance':
1883 # restrict instances to those that belong to the slice
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -05001884 if request._slice is not None:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001885 field.queryset = field.queryset.filter(slice=request._slice)
Scott Baker133c9212013-05-17 09:09:11 -07001886 else:
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -05001887 field.queryset = field.queryset.none()
1888
Scott Baker133c9212013-05-17 09:09:11 -07001889 return field
1890
Tony Mack5b061472014-02-04 07:57:10 -05001891 def queryset(self, request):
1892 return ReservedResource.select_by_user(request.user)
1893
Jeremy Moweryda57d402016-04-15 17:39:49 -07001894
Scott Baker133c9212013-05-17 09:09:11 -07001895class ReservationChangeForm(forms.ModelForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001896
Scott Baker133c9212013-05-17 09:09:11 -07001897 class Meta:
1898 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001899 widgets = {
Jeremy Moweryda57d402016-04-15 17:39:49 -07001900 'slice': LinkedSelect
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001901 }
Scott Baker133c9212013-05-17 09:09:11 -07001902
Jeremy Moweryda57d402016-04-15 17:39:49 -07001903
Scott Baker133c9212013-05-17 09:09:11 -07001904class ReservationAddForm(forms.ModelForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -07001905 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(
1906 attrs={"onChange": "document.getElementById('id_refresh').value=1; submit()"}))
Scott Baker133c9212013-05-17 09:09:11 -07001907 refresh = forms.CharField(widget=forms.HiddenInput())
1908
1909 class Media:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001910 css = {'all': ('xos.css',)} # .field-refresh { display: none; }
Scott Baker133c9212013-05-17 09:09:11 -07001911
1912 def clean_slice(self):
1913 slice = self.cleaned_data.get("slice")
Jeremy Moweryda57d402016-04-15 17:39:49 -07001914 x = ServiceResource.objects.filter(
1915 serviceClass=slice.serviceClass, calendarReservable=True)
Scott Baker133c9212013-05-17 09:09:11 -07001916 if len(x) == 0:
Jeremy Moweryda57d402016-04-15 17:39:49 -07001917 raise forms.ValidationError(
1918 "The slice you selected does not have a service class that allows reservations")
Scott Baker133c9212013-05-17 09:09:11 -07001919 return slice
1920
1921 class Meta:
1922 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001923 widgets = {
Jeremy Moweryda57d402016-04-15 17:39:49 -07001924 'slice': LinkedSelect
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001925 }
1926
Scott Baker133c9212013-05-17 09:09:11 -07001927
1928class ReservationAddRefreshForm(ReservationAddForm):
1929 """ This form is displayed when the Reservation Form receives an update
1930 from the Slice dropdown onChange handler. It doesn't validate the
1931 data and doesn't save the data. This will cause the form to be
1932 redrawn.
1933 """
1934
Scott Baker8737e5f2013-05-17 09:35:32 -07001935 """ don't validate anything other than slice """
1936 dont_validate_fields = ("startTime", "duration")
1937
Scott Baker133c9212013-05-17 09:09:11 -07001938 def full_clean(self):
1939 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001940
1941 for fieldname in self.dont_validate_fields:
1942 if fieldname in self._errors:
1943 del self._errors[fieldname]
1944
Scott Baker133c9212013-05-17 09:09:11 -07001945 return result
1946
1947 """ don't save anything """
Jeremy Moweryda57d402016-04-15 17:39:49 -07001948
Scott Baker133c9212013-05-17 09:09:11 -07001949 def is_valid(self):
1950 return False
1951
Jeremy Moweryda57d402016-04-15 17:39:49 -07001952
Scott Baker67db95f2015-02-18 15:50:11 -08001953class ReservationAdmin(XOSBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001954 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Jeremy Moweryda57d402016-04-15 17:39:49 -07001955 fieldsets = [('Reservation Details', {
1956 'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001957 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001958 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001959 form = ReservationAddForm
1960
Jeremy Moweryda57d402016-04-15 17:39:49 -07001961 suit_form_tabs = (('general', 'Reservation Details'),
1962 ('reservedresources', 'Reserved Resources'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001963
1964 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001965 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001966
Scott Baker133c9212013-05-17 09:09:11 -07001967 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001968 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001969 request._refresh = False
1970 request._slice = None
1971 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001972 # "refresh" will be set to "1" if the form was submitted due to
1973 # a change in the Slice dropdown.
Jeremy Moweryda57d402016-04-15 17:39:49 -07001974 if request.POST.get("refresh", "1") == "1":
Scott Baker133c9212013-05-17 09:09:11 -07001975 request._refresh = True
1976 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001977
1978 # Keep track of the slice that was selected, so the
1979 # reservedResource inline can filter items for the slice.
Jeremy Moweryda57d402016-04-15 17:39:49 -07001980 request._slice = request.POST.get("slice", None)
Scott Baker133c9212013-05-17 09:09:11 -07001981 if (request._slice is not None):
1982 request._slice = Slice.objects.get(id=request._slice)
1983
Jeremy Moweryda57d402016-04-15 17:39:49 -07001984 result = super(ReservationAdmin, self).add_view(
1985 request, form_url, extra_context)
Scott Baker133c9212013-05-17 09:09:11 -07001986 return result
1987
Jeremy Moweryda57d402016-04-15 17:39:49 -07001988 def changelist_view(self, request, extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001989 timezone.activate(request.user.timezone)
1990 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1991
Scott Baker133c9212013-05-17 09:09:11 -07001992 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001993 request._obj_ = obj
1994 if obj is not None:
1995 # For changes, set request._slice to the slice already set in the
1996 # object.
1997 request._slice = obj.slice
1998 self.form = ReservationChangeForm
1999 else:
2000 if getattr(request, "_refresh", False):
2001 self.form = ReservationAddRefreshForm
2002 else:
2003 self.form = ReservationAddForm
2004 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
2005
Scott Baker133c9212013-05-17 09:09:11 -07002006 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04002007 if (obj is not None):
2008 # Prevent slice from being changed after the reservation has been
2009 # created.
2010 return ['slice']
2011 else:
Scott Baker133c9212013-05-17 09:09:11 -07002012 return []
Scott Baker3de3e372013-05-10 16:50:44 -07002013
Tony Mack5b061472014-02-04 07:57:10 -05002014 def queryset(self, request):
2015 return Reservation.select_by_user(request.user)
2016
Jeremy Moweryda57d402016-04-15 17:39:49 -07002017
Scott Baker67db95f2015-02-18 15:50:11 -08002018class NetworkParameterTypeAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07002019 list_display = ("backend_status_icon", "name", )
2020 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002021 user_readonly_fields = ['name']
2022 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07002023
Jeremy Moweryda57d402016-04-15 17:39:49 -07002024
Scott Baker67db95f2015-02-18 15:50:11 -08002025class RouterAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07002026 list_display = ("backend_status_icon", "name", )
2027 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002028 user_readonly_fields = ['name']
2029 user_readonly_inlines = []
2030
Jeremy Moweryda57d402016-04-15 17:39:49 -07002031
Scott Baker67db95f2015-02-18 15:50:11 -08002032class RouterInline(XOSTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07002033 model = Router.networks.through
2034 extra = 0
2035 verbose_name_plural = "Routers"
2036 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04002037 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07002038
Jeremy Moweryda57d402016-04-15 17:39:49 -07002039
Scott Bakerb27b62c2014-08-15 16:29:16 -07002040class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002041 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07002042 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002043 verbose_name_plural = "Parameters"
2044 verbose_name = "Parameter"
2045 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07002046 fields = ['backend_status_icon', 'parameter', 'value']
2047 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002048
Jeremy Moweryda57d402016-04-15 17:39:49 -07002049
Scott Bakerfbade882015-08-25 18:00:15 -07002050class NetworkPortInline(XOSTabularInline):
Scott Bakerdcf9e0d2015-11-09 16:17:11 -08002051 fields = ['backend_status_icon', 'network', 'instance', 'ip', 'mac']
Scott Bakere553b472015-09-08 18:22:15 -07002052 readonly_fields = ("backend_status_icon", "ip", "mac")
Scott Baker5a7d9312015-08-26 09:43:33 -07002053 model = Port
Scott Baker17ff0172015-11-16 13:43:38 -08002054 #selflink_fieldname = "instance"
Scott Baker74d8e622013-07-29 16:04:22 -07002055 extra = 0
Scott Baker5f1068a2015-08-25 17:11:30 -07002056 verbose_name_plural = "Ports"
2057 verbose_name = "Port"
2058 suit_classes = 'suit-tab suit-tab-ports'
Scott Baker74d8e622013-07-29 16:04:22 -07002059
Jeremy Moweryda57d402016-04-15 17:39:49 -07002060
Scott Baker67db95f2015-02-18 15:50:11 -08002061class NetworkSlicesInline(XOSTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07002062 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08002063 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07002064 extra = 0
2065 verbose_name_plural = "Slices"
2066 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04002067 suit_classes = 'suit-tab suit-tab-networkslices'
Jeremy Moweryda57d402016-04-15 17:39:49 -07002068 fields = ['backend_status_icon', 'network', 'slice']
Scott Baker40c00762014-08-21 16:55:59 -07002069 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07002070
Jeremy Moweryda57d402016-04-15 17:39:49 -07002071
Scott Baker67db95f2015-02-18 15:50:11 -08002072class ControllerNetworkInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -05002073 model = ControllerNetwork
Scott Baker8806cdf2014-10-17 16:27:23 -07002074 extra = 0
Tony Mack06c8e472014-11-30 15:53:08 -05002075 verbose_name_plural = "Controller Networks"
2076 verbose_name = "Controller Network"
Scott Baker8806cdf2014-10-17 16:27:23 -07002077 suit_classes = 'suit-tab suit-tab-admin-only'
Jeremy Moweryda57d402016-04-15 17:39:49 -07002078 fields = ['backend_status_icon', 'controller',
Scott Baker39278862016-05-18 08:56:50 -07002079 'net_id', 'subnet_id', 'subnet', 'segmentation_id']
Scott Baker8806cdf2014-10-17 16:27:23 -07002080 readonly_fields = ('backend_status_icon', )
2081
Jeremy Moweryda57d402016-04-15 17:39:49 -07002082
Scott Baker69e045d2014-11-17 23:44:03 -08002083class NetworkForm(forms.ModelForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002084
Scott Baker69e045d2014-11-17 23:44:03 -08002085 class Meta:
2086 model = Network
2087 widgets = {
2088 'topologyParameters': UploadTextareaWidget,
2089 'controllerParameters': UploadTextareaWidget,
2090 }
2091
Jeremy Moweryda57d402016-04-15 17:39:49 -07002092
Scott Baker67db95f2015-02-18 15:50:11 -08002093class NetworkAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07002094 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
2095 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07002096 readonly_fields = ("subnet", )
Jeremy Moweryda57d402016-04-15 17:39:49 -07002097 inlines = [NetworkParameterInline, NetworkPortInline,
2098 NetworkSlicesInline, RouterInline]
Tony Mack3066a952015-01-05 22:48:11 -05002099 admin_inlines = [ControllerNetworkInline]
Scott Baker74d8e622013-07-29 16:04:22 -07002100
Jeremy Moweryda57d402016-04-15 17:39:49 -07002101 form = NetworkForm
Scott Baker69e045d2014-11-17 23:44:03 -08002102
Siobhan Tully2d95e482013-09-06 10:56:06 -04002103 fieldsets = [
Jeremy Moweryda57d402016-04-15 17:39:49 -07002104 (None, {'fields': ['backend_status_text', 'name', 'template', 'ports', 'labels',
2105 'owner', 'guaranteed_bandwidth', 'permit_all_slices',
2106 'permitted_slices', 'network_id', 'router_id', 'subnet_id',
Scott Baker3789cb22015-08-21 16:40:53 -07002107 'subnet', 'autoconnect'],
Scott Baker3e28dd72014-11-17 16:04:45 -08002108 'classes':['suit-tab suit-tab-general']}),
Scott Baker549aa252015-01-03 12:29:29 -08002109 (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
Scott Baker3e28dd72014-11-17 16:04:45 -08002110 'classes':['suit-tab suit-tab-sdn']}),
Jeremy Moweryda57d402016-04-15 17:39:49 -07002111 ]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002112
Scott Baker40c00762014-08-21 16:55:59 -07002113 readonly_fields = ('backend_status_text', )
Jeremy Moweryda57d402016-04-15 17:39:49 -07002114 user_readonly_fields = ['name', 'template', 'ports', 'labels', 'owner', 'guaranteed_bandwidth',
2115 'permit_all_slices', 'permitted_slices', 'network_id', 'router_id',
2116 'subnet_id', 'subnet', 'autoconnect']
Siobhan Tully2d95e482013-09-06 10:56:06 -04002117
Scott Baker8806cdf2014-10-17 16:27:23 -07002118 @property
2119 def suit_form_tabs(self):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002120 tabs = [('general', 'Network Details'),
2121 ('sdn', 'SDN Configuration'),
2122 ('netparams', 'Parameters'),
2123 ('ports', 'Ports'),
2124 ('networkslices', 'Slices'),
2125 ('routers', 'Routers'),
2126 ]
Scott Baker8806cdf2014-10-17 16:27:23 -07002127
Jeremy Moweryda57d402016-04-15 17:39:49 -07002128 request = getattr(_thread_locals, "request", None)
Scott Baker8806cdf2014-10-17 16:27:23 -07002129 if request and request.user.is_admin:
Jeremy Moweryda57d402016-04-15 17:39:49 -07002130 tabs.append(('admin-only', 'Admin-Only'))
Scott Baker8806cdf2014-10-17 16:27:23 -07002131
2132 return tabs
2133
2134
Scott Baker67db95f2015-02-18 15:50:11 -08002135class NetworkTemplateAdmin(XOSBaseAdmin):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002136 list_display = ("backend_status_icon", "name",
2137 "guaranteed_bandwidth", "visibility")
Scott Baker63d1a552014-08-21 15:19:07 -07002138 list_display_links = ('backend_status_icon', 'name', )
Scott Baker369f9b92015-01-03 12:03:38 -08002139 user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002140 user_readonly_inlines = []
Jeremy Moweryda57d402016-04-15 17:39:49 -07002141 inlines = [NetworkParameterInline, ]
Scott Baker3e28dd72014-11-17 16:04:45 -08002142 fieldsets = [
Scott Bakera36a9902015-12-09 15:44:55 -08002143 (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'access', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
Jeremy Moweryda57d402016-04-15 17:39:49 -07002144 'classes':['suit-tab suit-tab-general']}), ]
2145 suit_form_tabs = (('general', 'Network Template Details'),
2146 ('netparams', 'Parameters'))
2147
Scott Baker74d8e622013-07-29 16:04:22 -07002148
Scott Baker17ff0172015-11-16 13:43:38 -08002149class PortAdmin(XOSBaseAdmin):
Scott Baker8a351272016-02-15 17:03:20 -08002150 list_display = ("backend_status_icon", "id", "ip")
Scott Baker17ff0172015-11-16 13:43:38 -08002151 list_display_links = ('backend_status_icon', 'id')
2152 readonly_fields = ("subnet", )
2153 inlines = [NetworkParameterInline]
2154
2155 fieldsets = [
2156 (None, {'fields': ['backend_status_text', 'network', 'instance', 'ip', 'port_id', 'mac'],
2157 'classes':['suit-tab suit-tab-general']}),
Jeremy Moweryda57d402016-04-15 17:39:49 -07002158 ]
Scott Baker17ff0172015-11-16 13:43:38 -08002159
2160 readonly_fields = ('backend_status_text', )
2161 suit_form_tabs = (('general', 'Port Details'), ('netparams', 'Parameters'))
2162
Jeremy Moweryda57d402016-04-15 17:39:49 -07002163
Scott Baker67db95f2015-02-18 15:50:11 -08002164class FlavorAdmin(XOSBaseAdmin):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002165 list_display = ("backend_status_icon", "name",
2166 "flavor", "order", "default")
Scott Baker37b47902014-09-02 14:37:41 -07002167 list_display_links = ("backend_status_icon", "name")
2168 user_readonly_fields = ("name", "flavor")
2169 fields = ("name", "description", "flavor", "order", "default")
2170
Tony Mack31c2b8f2013-04-26 20:01:42 -04002171# register a signal that caches the user's credentials when they log in
Jeremy Moweryda57d402016-04-15 17:39:49 -07002172
2173
Tony Mack31c2b8f2013-04-26 20:01:42 -04002174def cache_credentials(sender, user, request, **kwds):
2175 auth = {'username': request.POST['username'],
2176 'password': request.POST['password']}
2177 request.session['auth'] = auth
2178user_logged_in.connect(cache_credentials)
2179
Jeremy Moweryda57d402016-04-15 17:39:49 -07002180
Scott Baker15cddfa2013-12-09 13:45:19 -08002181def dollar_field(fieldName, short_description):
2182 def newFunc(self, obj):
2183 try:
Jeremy Moweryda57d402016-04-15 17:39:49 -07002184 x = "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
Scott Baker15cddfa2013-12-09 13:45:19 -08002185 except:
Jeremy Moweryda57d402016-04-15 17:39:49 -07002186 x = getattr(obj, fieldName, 0.0)
Scott Baker15cddfa2013-12-09 13:45:19 -08002187 return x
2188 newFunc.short_description = short_description
2189 return newFunc
2190
Jeremy Moweryda57d402016-04-15 17:39:49 -07002191
Scott Baker15cddfa2013-12-09 13:45:19 -08002192def right_dollar_field(fieldName, short_description):
2193 def newFunc(self, obj):
2194 try:
2195 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
Jeremy Moweryda57d402016-04-15 17:39:49 -07002196 x = '<div align=right>$ %0.2f</div>' % float(
2197 getattr(obj, fieldName, 0.0))
Scott Baker15cddfa2013-12-09 13:45:19 -08002198 except:
Jeremy Moweryda57d402016-04-15 17:39:49 -07002199 x = getattr(obj, fieldName, 0.0)
Scott Baker15cddfa2013-12-09 13:45:19 -08002200 return x
2201 newFunc.short_description = short_description
2202 newFunc.allow_tags = True
2203 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08002204
Jeremy Moweryda57d402016-04-15 17:39:49 -07002205
Scott Baker67db95f2015-02-18 15:50:11 -08002206class InvoiceChargeInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08002207 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08002208 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08002209 verbose_name_plural = "Charges"
2210 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002211 exclude = ['account']
Jeremy Moweryda57d402016-04-15 17:39:49 -07002212 fields = ["date", "kind", "state", "object",
2213 "coreHours", "dollar_amount", "slice"]
2214 readonly_fields = ["date", "kind", "state",
2215 "object", "coreHours", "dollar_amount", "slice"]
Scott Baker9cb88a22013-12-09 18:56:00 -08002216 can_delete = False
2217 max_num = 0
2218
2219 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08002220
Jeremy Moweryda57d402016-04-15 17:39:49 -07002221
Scott Baker43105042013-12-06 23:23:36 -08002222class InvoiceAdmin(admin.ModelAdmin):
2223 list_display = ("date", "account")
2224
2225 inlines = [InvoiceChargeInline]
2226
Scott Baker9cb88a22013-12-09 18:56:00 -08002227 fields = ["date", "account", "dollar_amount"]
2228 readonly_fields = ["date", "account", "dollar_amount"]
2229
2230 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08002231
Jeremy Moweryda57d402016-04-15 17:39:49 -07002232
Scott Baker67db95f2015-02-18 15:50:11 -08002233class InvoiceInline(XOSTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08002234 model = Invoice
2235 extra = 0
2236 verbose_name_plural = "Invoices"
2237 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08002238 fields = ["date", "dollar_amount"]
2239 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08002240 suit_classes = 'suit-tab suit-tab-accountinvoice'
Jeremy Moweryda57d402016-04-15 17:39:49 -07002241 can_delete = False
2242 max_num = 0
Scott Baker15cddfa2013-12-09 13:45:19 -08002243
2244 dollar_amount = right_dollar_field("amount", "Amount")
2245
Jeremy Moweryda57d402016-04-15 17:39:49 -07002246
Scott Baker67db95f2015-02-18 15:50:11 -08002247class PendingChargeInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08002248 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08002249 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08002250 verbose_name_plural = "Charges"
2251 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002252 exclude = ["invoice"]
Jeremy Moweryda57d402016-04-15 17:39:49 -07002253 fields = ["date", "kind", "state", "object",
2254 "coreHours", "dollar_amount", "slice"]
2255 readonly_fields = ["date", "kind", "state",
2256 "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08002257 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Jeremy Moweryda57d402016-04-15 17:39:49 -07002258 can_delete = False
2259 max_num = 0
Scott Baker43105042013-12-06 23:23:36 -08002260
2261 def queryset(self, request):
2262 qs = super(PendingChargeInline, self).queryset(request)
2263 qs = qs.filter(state="pending")
2264 return qs
2265
Scott Baker15cddfa2013-12-09 13:45:19 -08002266 dollar_amount = right_dollar_field("amount", "Amount")
2267
Jeremy Moweryda57d402016-04-15 17:39:49 -07002268
Scott Baker67db95f2015-02-18 15:50:11 -08002269class PaymentInline(XOSTabularInline):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002270 model = Payment
Scott Baker43105042013-12-06 23:23:36 -08002271 extra = 1
2272 verbose_name_plural = "Payments"
2273 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08002274 fields = ["date", "dollar_amount"]
2275 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08002276 suit_classes = 'suit-tab suit-tab-accountpayments'
Jeremy Moweryda57d402016-04-15 17:39:49 -07002277 can_delete = False
2278 max_num = 0
Scott Baker15cddfa2013-12-09 13:45:19 -08002279
2280 dollar_amount = right_dollar_field("amount", "Amount")
2281
Jeremy Moweryda57d402016-04-15 17:39:49 -07002282
Scott Baker43105042013-12-06 23:23:36 -08002283class AccountAdmin(admin.ModelAdmin):
2284 list_display = ("site", "balance_due")
2285
2286 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
2287
2288 fieldsets = [
Jeremy Moweryda57d402016-04-15 17:39:49 -07002289 (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 -08002290
Jeremy Moweryda57d402016-04-15 17:39:49 -07002291 readonly_fields = ['site', 'dollar_balance_due',
2292 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08002293
Jeremy Moweryda57d402016-04-15 17:39:49 -07002294 suit_form_tabs = (
2295 ('general', 'Account Details'),
Scott Baker43105042013-12-06 23:23:36 -08002296 ('accountinvoice', 'Invoices'),
2297 ('accountpayments', 'Payments'),
Jeremy Moweryda57d402016-04-15 17:39:49 -07002298 ('accountpendingcharges', 'Pending Charges'),
Scott Baker43105042013-12-06 23:23:36 -08002299 )
2300
Scott Baker15cddfa2013-12-09 13:45:19 -08002301 dollar_balance_due = dollar_field("balance_due", "Balance Due")
2302 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
2303 dollar_total_payments = dollar_field("total_payments", "Total Payments")
2304
Jeremy Moweryda57d402016-04-15 17:39:49 -07002305
Scott Bakerc24f86d2015-08-14 09:10:11 -07002306class ProgramForm(forms.ModelForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002307
Scott Bakerc24f86d2015-08-14 09:10:11 -07002308 class Meta:
2309 model = Program
2310 widgets = {
2311 'contents': UploadTextareaWidget(attrs={'rows': 20, 'cols': 80, 'class': "input-xxlarge"}),
2312 'description': forms.Textarea(attrs={'rows': 3, 'cols': 80, 'class': 'input-xxlarge'}),
2313 'messages': forms.Textarea(attrs={'rows': 20, 'cols': 80, 'class': 'input-xxlarge'}),
2314 'output': forms.Textarea(attrs={'rows': 3, 'cols': 80, 'class': 'input-xxlarge'})
2315 }
2316
Jeremy Moweryda57d402016-04-15 17:39:49 -07002317
Scott Bakerc24f86d2015-08-14 09:10:11 -07002318class ProgramAdmin(XOSBaseAdmin):
2319 list_display = ("name", "status")
2320 list_display_links = ('name', "status")
2321
Jeremy Moweryda57d402016-04-15 17:39:49 -07002322 form = ProgramForm
Scott Bakerc24f86d2015-08-14 09:10:11 -07002323
2324 fieldsets = [
2325 (None, {'fields': ['name', 'command', 'kind', 'description', 'output', 'status'],
2326 'classes':['suit-tab suit-tab-general']}),
2327 (None, {'fields': ['contents'],
2328 'classes':['suit-tab suit-tab-contents']}),
2329 (None, {'fields': ['messages'],
2330 'classes':['suit-tab suit-tab-messages']}),
Jeremy Moweryda57d402016-04-15 17:39:49 -07002331 ]
Scott Bakerc24f86d2015-08-14 09:10:11 -07002332
2333 readonly_fields = ("status",)
2334
2335 @property
2336 def suit_form_tabs(self):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002337 tabs = [('general', 'Program Details'),
2338 ('contents', 'Program Source'),
2339 ('messages', 'Messages'),
2340 ]
Scott Bakerc24f86d2015-08-14 09:10:11 -07002341
Jeremy Moweryda57d402016-04-15 17:39:49 -07002342 request = getattr(_thread_locals, "request", None)
Scott Bakerc24f86d2015-08-14 09:10:11 -07002343 if request and request.user.is_admin:
Jeremy Moweryda57d402016-04-15 17:39:49 -07002344 tabs.append(('admin-only', 'Admin-Only'))
Scott Bakerc24f86d2015-08-14 09:10:11 -07002345
2346 return tabs
2347
Jeremy Moweryda57d402016-04-15 17:39:49 -07002348
Scott Bakerb4d77972016-04-13 15:55:16 -07002349class AddressPoolForm(forms.ModelForm):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002350
Scott Bakerb4d77972016-04-13 15:55:16 -07002351 class Meta:
2352 model = Program
2353 widgets = {
2354 'addresses': UploadTextareaWidget(attrs={'rows': 20, 'cols': 80, 'class': "input-xxlarge"}),
2355 }
2356
Jeremy Moweryda57d402016-04-15 17:39:49 -07002357
Scott Bakerb4d77972016-04-13 15:55:16 -07002358class AddressPoolAdmin(XOSBaseAdmin):
Scott Bakerfca52df2016-04-13 16:40:50 -07002359 list_display = ("name", "cidr")
Scott Bakerb4d77972016-04-13 15:55:16 -07002360 list_display_links = ('name',)
2361
Jeremy Moweryda57d402016-04-15 17:39:49 -07002362 form = AddressPoolForm
Scott Bakerb4d77972016-04-13 15:55:16 -07002363
2364 fieldsets = [
Scott Bakerfca52df2016-04-13 16:40:50 -07002365 (None, {'fields': ['name', 'cidr', 'gateway_ip', 'gateway_mac', 'addresses', 'inuse', 'service'],
Scott Bakerb4d77972016-04-13 15:55:16 -07002366 'classes':['suit-tab suit-tab-general']}),
Jeremy Moweryda57d402016-04-15 17:39:49 -07002367 ]
Scott Bakerb4d77972016-04-13 15:55:16 -07002368
2369 readonly_fields = ("status",)
2370
2371 @property
2372 def suit_form_tabs(self):
Jeremy Moweryda57d402016-04-15 17:39:49 -07002373 tabs = [('general', 'Program Details'),
2374 ('contents', 'Program Source'),
2375 ('messages', 'Messages'),
2376 ]
Scott Bakerb4d77972016-04-13 15:55:16 -07002377
2378# request=getattr(_thread_locals, "request", None)
2379# if request and request.user.is_admin:
2380# tabs.append( ('admin-only', 'Admin-Only') )
2381
2382 return tabs
2383
Scott Baker8cef72b2016-04-29 09:29:42 -07002384class AddressPoolInline(XOSTabularInline):
2385 model = AddressPool
2386 extra = 0
2387 suit_classes = 'suit-tab suit-tab-addresspools'
2388 fields = ['cidr', 'gateway_ip', 'gateway_mac']
2389 readonly_fields = ['cidr',]
2390
2391 # disable the add link
2392 def has_add_permission(self, request):
2393 return False
2394
Siobhan Tully53437282013-04-26 19:30:27 -04002395# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04002396admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04002397# ... and, since we're not using Django's builtin permissions,
2398# unregister the Group model from admin.
Jeremy Moweryda57d402016-04-15 17:39:49 -07002399# admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04002400
Jeremy Moweryc2e8f162016-01-10 20:36:51 -07002401# When debugging it is often easier to see all the classes, but for regular use
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002402# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002403showAll = False
Scott Baker43105042013-12-06 23:23:36 -08002404
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002405admin.site.register(Deployment, DeploymentAdmin)
Tony Mack06c8e472014-11-30 15:53:08 -05002406admin.site.register(Controller, ControllerAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04002407admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04002408admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04002409admin.site.register(Service, ServiceAdmin)
Tony Mack450b6e02015-01-25 12:35:29 -05002410#admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07002411admin.site.register(Network, NetworkAdmin)
Scott Baker17ff0172015-11-16 13:43:38 -08002412admin.site.register(Port, PortAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07002413admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07002414admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Scott Bakerc24f86d2015-08-14 09:10:11 -07002415admin.site.register(Program, ProgramAdmin)
Tony Mack450b6e02015-01-25 12:35:29 -05002416#admin.site.register(Account, AccountAdmin)
2417#admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002418
Siobhan Tullycf04fb62014-01-11 11:25:57 -05002419if True:
2420 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
2421 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullyd3515752013-06-21 16:34:53 -04002422 admin.site.register(Tag, TagAdmin)
Tony Mack06c8e472014-11-30 15:53:08 -05002423 admin.site.register(ControllerRole)
Siobhan Tullyce652d02013-10-08 21:52:35 -04002424 admin.site.register(SiteRole)
2425 admin.site.register(SliceRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002426 admin.site.register(Node, NodeAdmin)
Scott Baker7c886d42016-03-04 10:35:32 -08002427 admin.site.register(NodeLabel, NodeLabelAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04002428 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
2429 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Tony Mackd8515472015-08-19 11:58:18 -04002430 admin.site.register(Instance, InstanceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04002431 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07002432 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07002433 admin.site.register(Flavor, FlavorAdmin)
Scott Bakerb3cf9212015-07-06 14:40:20 -07002434 admin.site.register(TenantRoot, TenantRootAdmin)
2435 admin.site.register(TenantRootRole, TenantRootRoleAdmin)
Jeremy Moweryb52c49a2016-04-05 23:32:10 -07002436 admin.site.register(TenantRole, TenantRoleAdmin)
Scott Baker1e7e3482015-10-15 15:59:19 -07002437 admin.site.register(TenantAttribute, TenantAttributeAdmin)
Scott Bakerb4d77972016-04-13 15:55:16 -07002438 admin.site.register(AddressPool, AddressPoolAdmin)