blob: e2a7adc1b4f74ecb5687bf11c29db234df8944d7 [file] [log] [blame]
Siobhan Tully30fd4292013-05-10 08:59:56 -04001from core.models import Site
2from core.models import *
3from openstack.manager import OpenStackManager
Tony Macke59a7c82013-04-27 11:08:10 -04004
Tony Mack7130ac32013-03-22 21:58:00 -04005from django.contrib import admin
Siobhan Tully53437282013-04-26 19:30:27 -04006from django.contrib.auth.models import Group
Siobhan Tully4bc09f22013-04-10 21:15:21 -04007from django import forms
Tony Mackd90cdbf2013-04-16 22:48:40 -04008from django.utils.safestring import mark_safe
Tony Mack7130ac32013-03-22 21:58:00 -04009from django.contrib.auth.admin import UserAdmin
Scott Baker69e045d2014-11-17 23:44:03 -080010from django.contrib.admin.widgets import FilteredSelectMultiple, AdminTextareaWidget
Scott Baker1a6a3902014-10-03 00:32:37 -070011from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
Scott Bakeracd45142013-05-19 16:19:16 -070012from django.contrib.auth.signals import user_logged_in
13from django.utils import timezone
Siobhan Tullyde5450d2013-06-21 11:35:33 -040014from django.contrib.contenttypes import generic
Siobhan Tullybfd11dc2013-09-03 12:59:24 -040015from suit.widgets import LinkedSelect
Siobhan Tullycf04fb62014-01-11 11:25:57 -050016from django.core.exceptions import PermissionDenied
Tony Mack5817cb42015-02-16 19:54:24 -050017from django.core.urlresolvers import reverse, resolve, NoReverseMatch
Scott Baker69e045d2014-11-17 23:44:03 -080018from django.utils.encoding import force_text, python_2_unicode_compatible
19from django.utils.html import conditional_escape, format_html
Scott Bakera9b8f612015-02-26 20:42:11 -080020from django.utils.text import capfirst
Scott Baker69e045d2014-11-17 23:44:03 -080021from django.forms.utils import flatatt, to_current_timezone
Scott Bakerf41fe2c2015-07-09 19:06:08 -070022from django.core.exceptions import PermissionDenied, ValidationError
Scott Baker92d22172014-10-21 21:03:08 -070023from cgi import escape as html_escape
Scott Bakerf41fe2c2015-07-09 19:06:08 -070024from django.contrib import messages
Tony Mack7130ac32013-03-22 21:58:00 -040025
Scott Baker36f50872014-08-21 13:01:25 -070026import django_evolution
Scott Baker6a995352014-10-06 17:51:20 -070027import threading
28
29# thread locals necessary to work around a django-suit issue
30_thread_locals = threading.local()
Scott Baker36f50872014-08-21 13:01:25 -070031
Scott Bakere5f9d7d2015-02-10 18:24:20 -080032ICON_URLS = {"success": "/static/admin/img/icon_success.gif",
33 "clock": "/static/admin/img/icon_clock.gif",
34 "error": "/static/admin/img/icon_error.gif"}
35
36def backend_icon(obj):
37 (icon, tooltip) = obj.get_backend_icon()
38 icon_url = ICON_URLS.get(icon, "unknown")
39
40 if tooltip:
41 return '<span style="min-width:16px;" title="%s"><img src="%s"></span>' % (tooltip, icon_url)
Scott Baker40c00762014-08-21 16:55:59 -070042 else:
Scott Bakere5f9d7d2015-02-10 18:24:20 -080043 return '<span style="min-width:16px;"><img src="%s"></span>' % icon_url
Scott Baker40c00762014-08-21 16:55:59 -070044
45def backend_text(obj):
Scott Bakere5f9d7d2015-02-10 18:24:20 -080046 (icon, tooltip) = obj.get_backend_icon()
47 icon_url = ICON_URLS.get(icon, "unknown")
48
49 return '<img src="%s"> %s' % (icon_url, tooltip)
Scott Baker63d1a552014-08-21 15:19:07 -070050
Scott Baker69e045d2014-11-17 23:44:03 -080051class UploadTextareaWidget(AdminTextareaWidget):
52 def render(self, name, value, attrs=None):
53 if value is None:
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -050054 value = ''
55 final_attrs = self.build_attrs(attrs, name=name)
56 return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">' \
57 '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>' \
58 '<br><textarea{0}>\r\n{1}</textarea>' % (attrs["id"], attrs["id"], attrs["id"]),
59 flatatt(final_attrs),
Scott Baker69e045d2014-11-17 23:44:03 -080060 force_text(value))
61
Scott Baker50ac4192015-05-11 16:36:58 -070062class SliderWidget(forms.HiddenInput):
63 def render(self, name, value, attrs=None):
64 if value is None:
65 value = '0'
66 final_attrs = self.build_attrs(attrs, name=name)
67 attrs = attrs or attrs[:]
68 attrs["name"] = name
69 attrs["value"] = value
70 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>
71 <script>
72 $(function() {
73 $("#%(id)s_slider").slider({
74 value: %(value)s,
75 slide: function(event, ui) { $("#%(id)s").val( ui.value ); $("#%(id)s_label").html(ui.value); },
76 });
77 });
78 </script>
79 <input type="hidden" id="%(id)s" name="%(name)s" value="%(value)s"></input>
80 """ % attrs
81 html = html.replace("{","{{").replace("}","}}")
82 return format_html(html,
83 flatatt(final_attrs),
84 force_text(value))
85
86
Scott Baker36f50872014-08-21 13:01:25 -070087class PlainTextWidget(forms.HiddenInput):
88 input_type = 'hidden'
89
90 def render(self, name, value, attrs=None):
91 if value is None:
92 value = ''
93 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
94
Scott Baker3a8aed62015-02-27 12:21:22 -080095class XOSAdminMixin(object):
Scott Baker1a6a3902014-10-03 00:32:37 -070096 # call save_by_user and delete_by_user instead of save and delete
Siobhan Tullycf04fb62014-01-11 11:25:57 -050097
98 def has_add_permission(self, request, obj=None):
99 return (not self.__user_is_readonly(request))
Scott Baker36f50872014-08-21 13:01:25 -0700100
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500101 def has_delete_permission(self, request, obj=None):
102 return (not self.__user_is_readonly(request))
103
104 def save_model(self, request, obj, form, change):
105 if self.__user_is_readonly(request):
Scott Baker1a6a3902014-10-03 00:32:37 -0700106 # this 'if' might be redundant if save_by_user is implemented right
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500107 raise PermissionDenied
Scott Baker1a6a3902014-10-03 00:32:37 -0700108
109 obj.caller = request.user
110 # update openstack connection to use this site/tenant
111 obj.save_by_user(request.user)
112
113 def delete_model(self, request, obj):
114 obj.delete_by_user(request.user)
115
116 def save_formset(self, request, form, formset, change):
117 instances = formset.save(commit=False)
118 for instance in instances:
Scott Bakerf8cbac72015-07-08 18:23:17 -0700119 instance.caller = request.user
Scott Baker1a6a3902014-10-03 00:32:37 -0700120 instance.save_by_user(request.user)
121
122 # BUG in django 1.7? Objects are not deleted by formset.save if
123 # commit is False. So let's delete them ourselves.
124 #
125 # code from forms/models.py save_existing_objects()
126 try:
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -0500127 forms_to_delete = formset.deleted_forms
128 except AttributeError:
Scott Baker1a6a3902014-10-03 00:32:37 -0700129 forms_to_delete = []
130 if formset.initial_forms:
131 for form in formset.initial_forms:
132 obj = form.instance
133 if form in forms_to_delete:
134 if obj.pk is None:
135 continue
136 formset.deleted_objects.append(obj)
137 obj.delete()
138
139 formset.save_m2m()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500140
141 def get_actions(self,request):
Scott Baker3a8aed62015-02-27 12:21:22 -0800142 actions = super(XOSAdminMixin,self).get_actions(request)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500143
144 if self.__user_is_readonly(request):
145 if 'delete_selected' in actions:
146 del actions['delete_selected']
147
148 return actions
149
Scott Bakerfbe0f652015-04-03 17:44:31 -0700150 def url_for_model_changelist(self, request, model):
151 # used in add_extra_context
152 return reverse('admin:%s_%s_changelist' % (model._meta.app_label, model._meta.model_name), current_app=model._meta.app_label)
153
Scott Bakera8ef2742015-04-02 22:32:40 -0700154 def add_extra_context(self, request, extra_context):
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800155 # allow custom application breadcrumb url and name
156 extra_context["custom_app_breadcrumb_url"] = getattr(self, "custom_app_breadcrumb_url", None)
157 extra_context["custom_app_breadcrumb_name"] = getattr(self, "custom_app_breadcrumb_name", None)
Scott Bakerf85c0092015-04-02 22:07:18 -0700158 extra_context["custom_changelist_breadcrumb_url"] = getattr(self, "custom_changelist_breadcrumb_url", None)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800159
160 # for Service admins to render their Administration page
161 if getattr(self, "extracontext_registered_admins", False):
162 admins=[]
163 for model, model_admin in admin.site._registry.items():
164 if model == self.model:
165 continue
166 if model._meta.app_label == self.model._meta.app_label:
167 info = {"app": model._meta.app_label,
168 "model": model._meta.model_name,
169 "name": capfirst(model._meta.verbose_name_plural),
Scott Bakerfbe0f652015-04-03 17:44:31 -0700170 "url": self.url_for_model_changelist(request,model) }
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800171 admins.append(info)
172 extra_context["registered_admins"] = admins
173
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500174 def change_view(self,request,object_id, extra_context=None):
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800175 extra_context = extra_context or {}
176
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500177 if self.__user_is_readonly(request):
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -0500178 if not hasattr(self, "readonly_save"):
179 # save the original readonly fields
180 self.readonly_save = self.readonly_fields
181 self.inlines_save = self.inlines
182 if hasattr(self, "user_readonly_fields"):
183 self.readonly_fields=self.user_readonly_fields
184 if hasattr(self, "user_readonly_inlines"):
185 self.inlines = self.user_readonly_inlines
186 else:
187 if hasattr(self, "readonly_save"):
188 # restore the original readonly fields
189 self.readonly_fields = self.readonly_save
190 if hasattr(self, "inlines_save"):
Scott Bakeraf73e102014-04-22 22:40:07 -0700191 self.inlines = self.inlines_save
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500192
Scott Bakera8ef2742015-04-02 22:32:40 -0700193 self.add_extra_context(request, extra_context)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800194
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500195 try:
Scott Baker3a8aed62015-02-27 12:21:22 -0800196 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500197 except PermissionDenied:
198 pass
Scott Bakerf41fe2c2015-07-09 19:06:08 -0700199 except ValidationError as e:
200 if (e.params is None):
201 # Validation errors that don't reference a specific field will
202 # often throw a non-descriptive 500 page to the user. The code
203 # below will cause an error message to be printed and the
204 # page refreshed instead.
205 # As a side-effect it turns the request back into a 'GET' which
206 # may wipe anything the user had changed on the page. But, at
207 # least the user gets a real error message.
208 # TODO: revisit this and display some kind of error view
209 request.method = 'GET'
210 messages.error(request, e.message)
211 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
212 else:
213 raise
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500214 if request.method == 'POST':
215 raise PermissionDenied
216 request.readonly = True
Scott Baker3a8aed62015-02-27 12:21:22 -0800217 return super(XOSAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500218
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800219 def changelist_view(self, request, extra_context = None):
220 extra_context = extra_context or {}
221
Scott Bakera8ef2742015-04-02 22:32:40 -0700222 self.add_extra_context(request, extra_context)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800223
Scott Baker3a8aed62015-02-27 12:21:22 -0800224 return super(XOSAdminMixin, self).changelist_view(request, extra_context=extra_context)
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800225
Scott Baker88ac9d62015-04-14 17:01:18 -0700226 def add_view(self, request, form_url='', extra_context = None):
Scott Bakerfbe0f652015-04-03 17:44:31 -0700227 extra_context = extra_context or {}
228
229 self.add_extra_context(request, extra_context)
230
Scott Baker88ac9d62015-04-14 17:01:18 -0700231 return super(XOSAdminMixin, self).add_view(request, form_url, extra_context=extra_context)
Scott Bakerfbe0f652015-04-03 17:44:31 -0700232
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500233 def __user_is_readonly(self, request):
234 return request.user.isReadOnlyUser()
235
Scott Baker40c00762014-08-21 16:55:59 -0700236 def backend_status_text(self, obj):
237 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -0700238
Scott Baker63d1a552014-08-21 15:19:07 -0700239 def backend_status_icon(self, obj):
Scott Baker40c00762014-08-21 16:55:59 -0700240 return mark_safe(backend_icon(obj))
Scott Baker63d1a552014-08-21 15:19:07 -0700241 backend_status_icon.short_description = ""
242
Scott Bakerdc4724c2014-11-05 09:05:38 -0800243 def get_form(self, request, obj=None, **kwargs):
Scott Baker9b3c1af2014-10-16 00:57:55 -0700244 # Save obj and request in thread-local storage, so suit_form_tabs can
245 # use it to determine whether we're in edit or add mode, and can
246 # determine whether the user is an admin.
247 _thread_locals.request = request
248 _thread_locals.obj = obj
Scott Baker3a8aed62015-02-27 12:21:22 -0800249 return super(XOSAdminMixin, self).get_form(request, obj, **kwargs)
Scott Baker9b3c1af2014-10-16 00:57:55 -0700250
251 def get_inline_instances(self, request, obj=None):
Scott Baker3a8aed62015-02-27 12:21:22 -0800252 inlines = super(XOSAdminMixin, self).get_inline_instances(request, obj)
Scott Baker9b3c1af2014-10-16 00:57:55 -0700253
254 # inlines that should only be shown to an admin user
255 if request.user.is_admin:
256 for inline_class in getattr(self, "admin_inlines", []):
257 inlines.append(inline_class(self.model, self.admin_site))
258
259 return inlines
260
Scott Baker3a8aed62015-02-27 12:21:22 -0800261class ReadOnlyAwareAdmin(XOSAdminMixin, admin.ModelAdmin):
262 # Note: Make sure XOSAdminMixin is listed before
Scott Bakerf4aeedc2014-10-03 13:10:47 -0700263 # admin.ModelAdmin in the class declaration.
264
Scott Baker1a6a3902014-10-03 00:32:37 -0700265 pass
266
Scott Baker67db95f2015-02-18 15:50:11 -0800267class XOSBaseAdmin(ReadOnlyAwareAdmin):
Scott Baker1a6a3902014-10-03 00:32:37 -0700268 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700269
Scott Bakere8859f92014-05-23 12:42:40 -0700270class SingletonAdmin (ReadOnlyAwareAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400271 def has_add_permission(self, request):
Scott Bakere8859f92014-05-23 12:42:40 -0700272 if not super(SingletonAdmin, self).has_add_permission(request):
273 return False
274
Siobhan Tullyce652d02013-10-08 21:52:35 -0400275 num_objects = self.model.objects.count()
276 if num_objects >= 1:
277 return False
278 else:
279 return True
280
Scott Bakera9b8f612015-02-26 20:42:11 -0800281class ServiceAppAdmin (SingletonAdmin):
Scott Bakerfbe38ee2015-02-27 12:12:14 -0800282 extracontext_registered_admins = True
Scott Bakera9b8f612015-02-26 20:42:11 -0800283
Scott Baker67db95f2015-02-18 15:50:11 -0800284class XOSTabularInline(admin.TabularInline):
Scott Baker86568322014-01-12 16:53:31 -0800285 def __init__(self, *args, **kwargs):
Scott Baker67db95f2015-02-18 15:50:11 -0800286 super(XOSTabularInline, self).__init__(*args, **kwargs)
Scott Baker86568322014-01-12 16:53:31 -0800287
288 # InlineModelAdmin as no get_fields() method, so in order to add
289 # the selflink field, we override __init__ to modify self.fields and
290 # self.readonly_fields.
291
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800292 self.setup_selflink()
293
Scott Baker54c9b9b2015-07-24 09:32:14 -0700294 @property
295 def selflink_model(self):
296 if hasattr(self, "selflink_fieldname"):
297 """ self.selflink_model can be defined to punch through a relation
298 to its target object. For example, in SliceNetworkInline, set
299 selflink_model = "network", and the URL will lead to the Network
300 object instead of trying to bring up a change view of the
301 SliceNetwork object.
302 """
303 return getattr(self.model,self.selflink_fieldname).field.rel.to
304 else:
305 return self.model
306
307 @property
308 def selflink_reverse_path(self):
309 return "admin:%s_change" % (self.selflink_model._meta.db_table)
310
311 def get_change_url(self, id):
Scott Baker874936e2014-01-13 18:15:34 -0800312 """ Get the URL to a change form in the admin for this model """
Scott Baker54c9b9b2015-07-24 09:32:14 -0700313 reverse_path = self.selflink_reverse_path # "admin:%s_change" % (model._meta.db_table)
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800314 try:
Scott Baker874936e2014-01-13 18:15:34 -0800315 url = reverse(reverse_path, args=(id,))
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800316 except NoReverseMatch:
Scott Baker874936e2014-01-13 18:15:34 -0800317 return None
318
319 return url
320
321 def setup_selflink(self):
Scott Baker54c9b9b2015-07-24 09:32:14 -0700322 url = self.get_change_url(0)
Scott Baker874936e2014-01-13 18:15:34 -0800323
324 # We don't have an admin for this object, so don't create the
325 # selflink.
326 if (url == None):
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800327 return
328
Scott Baker874936e2014-01-13 18:15:34 -0800329 # Since we need to add "selflink" to the field list, we need to create
330 # self.fields if it is None.
Scott Baker0165fac2014-01-13 11:49:26 -0800331 if (self.fields is None):
332 self.fields = []
333 for f in self.model._meta.fields:
334 if f.editable and f.name != "id":
335 self.fields.append(f.name)
Scott Baker86568322014-01-12 16:53:31 -0800336
Scott Baker874936e2014-01-13 18:15:34 -0800337 self.fields = tuple(self.fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800338
Scott Baker874936e2014-01-13 18:15:34 -0800339 if self.readonly_fields is None:
340 self.readonly_fields = ()
Scott Baker86568322014-01-12 16:53:31 -0800341
Scott Baker874936e2014-01-13 18:15:34 -0800342 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800343
344 def selflink(self, obj):
Scott Baker874936e2014-01-13 18:15:34 -0800345 if hasattr(self, "selflink_fieldname"):
346 obj = getattr(obj, self.selflink_fieldname)
347
Scott Baker86568322014-01-12 16:53:31 -0800348 if obj.id:
Scott Baker54c9b9b2015-07-24 09:32:14 -0700349 url = self.get_change_url(obj.id)
Scott Baker874936e2014-01-13 18:15:34 -0800350 return "<a href='%s'>Details</a>" % str(url)
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -0500351 else:
352 return "Not present"
Scott Baker86568322014-01-12 16:53:31 -0800353
354 selflink.allow_tags = True
355 selflink.short_description = "Details"
Siobhan Tullyd3515752013-06-21 16:34:53 -0400356
Scott Bakerb27b62c2014-08-15 16:29:16 -0700357 def has_add_permission(self, request):
358 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500359
360 def get_readonly_fields(self, request, obj=None):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700361 readonly_fields = list(self.readonly_fields)[:]
362 if request.user.isReadOnlyUser():
363 for field in self.fields:
364 if not field in readonly_fields:
365 readonly_fields.append(field)
366 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500367
Scott Baker40c00762014-08-21 16:55:59 -0700368 def backend_status_icon(self, obj):
369 return mark_safe(backend_icon(obj))
370 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -0700371
Scott Bakerb27b62c2014-08-15 16:29:16 -0700372class PlStackGenericTabularInline(generic.GenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500373 def has_add_permission(self, request):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700374 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500375
Scott Bakerb27b62c2014-08-15 16:29:16 -0700376 def get_readonly_fields(self, request, obj=None):
377 readonly_fields = list(self.readonly_fields)[:]
378 if request.user.isReadOnlyUser():
379 for field in self.fields:
380 if not field in readonly_fields:
381 readonly_fields.append(field)
382 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500383
Scott Baker40c00762014-08-21 16:55:59 -0700384 def backend_status_icon(self, obj):
385 return mark_safe(backend_icon(obj))
386 backend_status_icon.short_description = ""
387
Scott Baker67db95f2015-02-18 15:50:11 -0800388class ReservationInline(XOSTabularInline):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400389 model = Reservation
390 extra = 0
391 suit_classes = 'suit-tab suit-tab-reservations'
Scott Baker36f50872014-08-21 13:01:25 -0700392
Tony Mack5b061472014-02-04 07:57:10 -0500393 def queryset(self, request):
394 return Reservation.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400395
Scott Bakerb27b62c2014-08-15 16:29:16 -0700396class TagInline(PlStackGenericTabularInline):
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400397 model = Tag
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400398 extra = 0
399 suit_classes = 'suit-tab suit-tab-tags'
Tony Mack5b061472014-02-04 07:57:10 -0500400 fields = ['service', 'name', 'value']
401
402 def queryset(self, request):
403 return Tag.select_by_user(request.user)
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400404
Scott Baker74d8e622013-07-29 16:04:22 -0700405class NetworkLookerUpper:
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400406 """ This is a callable that looks up a network name in a sliver and returns
407 the ip address for that network.
408 """
409
Scott Baker434ca7e2014-08-15 12:29:20 -0700410 byNetworkName = {} # class variable
411
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400412 def __init__(self, name):
413 self.short_description = name
414 self.__name__ = name
415 self.network_name = name
416
417 def __call__(self, obj):
418 if obj is not None:
419 for nbs in obj.networksliver_set.all():
420 if (nbs.network.name == self.network_name):
421 return nbs.ip
Scott Baker74d8e622013-07-29 16:04:22 -0700422 return ""
423
424 def __str__(self):
425 return self.network_name
426
Scott Baker434ca7e2014-08-15 12:29:20 -0700427 @staticmethod
428 def get(network_name):
429 """ We want to make sure we alwars return the same NetworkLookerUpper
430 because sometimes django will cause them to be instantiated multiple
431 times (and we don't want different ones in form.fields vs
432 SliverInline.readonly_fields).
433 """
434 if network_name not in NetworkLookerUpper.byNetworkName:
435 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
436 return NetworkLookerUpper.byNetworkName[network_name]
437
Scott Baker67db95f2015-02-18 15:50:11 -0800438class SliverInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400439 model = Sliver
Scott Baker9d856052015-01-19 11:32:20 -0800440 fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'deployment', 'flavor', 'image', 'node']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400441 extra = 0
Scott Baker9d856052015-01-19 11:32:20 -0800442 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name']
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400443 suit_classes = 'suit-tab suit-tab-slivers'
Scott Baker74d8e622013-07-29 16:04:22 -0700444
Tony Mack5b061472014-02-04 07:57:10 -0500445 def queryset(self, request):
446 return Sliver.select_by_user(request.user)
447
Scott Bakerb24cc932014-06-09 10:51:16 -0700448 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Tony Mackb2dba4b2014-12-26 13:38:02 -0500449 if db_field.name == 'deployment':
Tony Mack1b8975c2015-02-24 15:41:49 -0500450
451 kwargs['queryset'] = Deployment.select_by_acl(request.user).filter(sitedeployments__nodes__isnull=False).distinct()
Scott Baker7a61dc42014-09-02 17:08:20 -0700452 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
Tony Mackb2dba4b2014-12-26 13:38:02 -0500453 if db_field.name == 'flavor':
Scott Baker4b6d9442014-09-08 12:14:14 -0700454 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
Scott Baker3b678742014-06-09 13:11:54 -0700455
456 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700457
458 return field
459
Scott Baker25881992015-06-12 10:40:15 -0700460class CordSliverInline(XOSTabularInline):
461 model = Sliver
462 fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'flavor', 'image', 'node']
463 extra = 0
464 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name']
465 suit_classes = 'suit-tab suit-tab-slivers'
466
467 def queryset(self, request):
468 return Sliver.select_by_user(request.user)
469
470 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
471 if db_field.name == 'deployment':
472
473 kwargs['queryset'] = Deployment.select_by_acl(request.user).filter(sitedeployments__nodes__isnull=False).distinct()
474 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
475 if db_field.name == 'flavor':
476 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
477
478 field = super(CordSliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
479
480 return field
481
Scott Baker67db95f2015-02-18 15:50:11 -0800482class SiteInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400483 model = Site
484 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400485 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400486
Tony Mack5b061472014-02-04 07:57:10 -0500487 def queryset(self, request):
488 return Site.select_by_user(request.user)
489
Scott Baker67db95f2015-02-18 15:50:11 -0800490class UserInline(XOSTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400491 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700492 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
493 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400494 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400495 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400496
Tony Mack5b061472014-02-04 07:57:10 -0500497 def queryset(self, request):
498 return User.select_by_user(request.user)
499
Scott Baker67db95f2015-02-18 15:50:11 -0800500class SliceInline(XOSTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400501 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700502 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
503 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400504 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400505 suit_classes = 'suit-tab suit-tab-slices'
506
Tony Mack5b061472014-02-04 07:57:10 -0500507 def queryset(self, request):
508 return Slice.select_by_user(request.user)
509
Scott Baker67db95f2015-02-18 15:50:11 -0800510class NodeInline(XOSTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400511 model = Node
512 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400513 suit_classes = 'suit-tab suit-tab-nodes'
Tony Mack68a1e422014-12-08 16:43:02 -0500514 fields = ['backend_status_icon', 'name', 'site_deployment']
Scott Baker40c00762014-08-21 16:55:59 -0700515 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400516
Scott Baker67db95f2015-02-18 15:50:11 -0800517class DeploymentPrivilegeInline(XOSTabularInline):
Tony Mack68a1e422014-12-08 16:43:02 -0500518 model = DeploymentPrivilege
519 extra = 0
Tony Mack88c89902015-02-09 21:41:57 -0500520 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
Tony Mack68a1e422014-12-08 16:43:02 -0500521 fields = ['backend_status_icon', 'user','role','deployment']
522 readonly_fields = ('backend_status_icon', )
523
524 def queryset(self, request):
525 return DeploymentPrivilege.select_by_user(request.user)
526
Scott Baker67db95f2015-02-18 15:50:11 -0800527class ControllerSiteInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -0500528 model = ControllerSite
529 extra = 0
530 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Macke2363c12015-01-06 15:08:20 -0500531 fields = ['controller', 'site', 'tenant_id']
Tony Mack3066a952015-01-05 22:48:11 -0500532
533
Scott Baker67db95f2015-02-18 15:50:11 -0800534class SitePrivilegeInline(XOSTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400535 model = SitePrivilege
536 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400537 suit_classes = 'suit-tab suit-tab-siteprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700538 fields = ['backend_status_icon', 'user','site', 'role']
539 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400540
Tony Mackc2835a92013-05-28 09:18:49 -0400541 def formfield_for_foreignkey(self, db_field, request, **kwargs):
542 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500543 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400544
545 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500546 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400547 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
548
Tony Mack5b061472014-02-04 07:57:10 -0500549 def queryset(self, request):
550 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400551
Tony Mack60789ac2015-05-11 20:39:32 -0400552
553class ServicePrivilegeInline(XOSTabularInline):
554 model = ServicePrivilege
555 extra = 0
556 suit_classes = 'suit-tab suit-tab-serviceprivileges'
557 fields = ['backend_status_icon', 'user','service', 'role']
558 readonly_fields = ('backend_status_icon', )
559
560 def formfield_for_foreignkey(self, db_field, request, **kwargs):
561 if db_field.name == 'service':
562 kwargs['queryset'] = Service.select_by_user(request.user)
Tony Mack5fa0f402015-05-15 06:33:45 -0400563 if db_field.name == 'user':
564 kwargs['queryset'] = User.select_by_user(request.user)
565 return super(ServicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mack60789ac2015-05-11 20:39:32 -0400566
567 def queryset(self, request):
568 return ServicePrivilege.select_by_user(request.user)
569
Scott Baker67db95f2015-02-18 15:50:11 -0800570class SiteDeploymentInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -0500571 model = SiteDeployment
Tony Macke4be32f2014-03-11 20:45:25 -0400572 extra = 0
Tony Mackb81d5e42015-01-30 10:58:29 -0500573 suit_classes = 'suit-tab suit-tab-sitedeployments'
Tony Mackd14d48f2014-12-05 17:13:08 -0500574 fields = ['backend_status_icon', 'deployment','site', 'controller']
Scott Baker40c00762014-08-21 16:55:59 -0700575 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400576
577 def formfield_for_foreignkey(self, db_field, request, **kwargs):
578 if db_field.name == 'site':
579 kwargs['queryset'] = Site.select_by_user(request.user)
580
581 if db_field.name == 'deployment':
582 kwargs['queryset'] = Deployment.select_by_user(request.user)
Tony Mackd14d48f2014-12-05 17:13:08 -0500583
584 if db_field.name == 'controller':
Tony Mack5817cb42015-02-16 19:54:24 -0500585 kwargs['queryset'] = Controller.select_by_user(request.user).filter(deployment__id=int(resolve(request.path).args[0]))
Tony Mackd14d48f2014-12-05 17:13:08 -0500586
Tony Mack3066a952015-01-05 22:48:11 -0500587 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Macke4be32f2014-03-11 20:45:25 -0400588
589 def queryset(self, request):
Tony Mack3066a952015-01-05 22:48:11 -0500590 return SiteDeployment.select_by_user(request.user)
Tony Macke4be32f2014-03-11 20:45:25 -0400591
592
Scott Baker67db95f2015-02-18 15:50:11 -0800593class SlicePrivilegeInline(XOSTabularInline):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400594 model = SlicePrivilege
595 suit_classes = 'suit-tab suit-tab-sliceprivileges'
596 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700597 fields = ('backend_status_icon', 'user', 'slice', 'role')
598 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400599
Tony Mackc2835a92013-05-28 09:18:49 -0400600 def formfield_for_foreignkey(self, db_field, request, **kwargs):
601 if db_field.name == 'slice':
Scott Baker36f50872014-08-21 13:01:25 -0700602 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400603 if db_field.name == 'user':
Scott Baker31ba9ca2015-07-16 12:40:07 -0700604 # all users are available to be granted SlicePrivilege
605 kwargs['queryset'] = User.objects.all()
Tony Mackc2835a92013-05-28 09:18:49 -0400606
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400607 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400608
Tony Mack5b061472014-02-04 07:57:10 -0500609 def queryset(self, request):
610 return SlicePrivilege.select_by_user(request.user)
611
Scott Baker67db95f2015-02-18 15:50:11 -0800612class SliceNetworkInline(XOSTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700613 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800614 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700615 extra = 0
616 verbose_name = "Network Connection"
617 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400618 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700619 fields = ['backend_status_icon', 'network']
620 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700621
Scott Baker67db95f2015-02-18 15:50:11 -0800622class ImageDeploymentsInline(XOSTabularInline):
Sapan Bhatia1b6bba22014-11-19 15:10:16 -0500623 model = ImageDeployments
Scott Baker2170b972014-06-03 12:14:07 -0700624 extra = 0
625 verbose_name = "Image Deployments"
626 verbose_name_plural = "Image Deployments"
627 suit_classes = 'suit-tab suit-tab-imagedeployments'
Tony Mack06c8e472014-11-30 15:53:08 -0500628 fields = ['backend_status_icon', 'image', 'deployment']
629 readonly_fields = ['backend_status_icon']
630
Scott Baker67db95f2015-02-18 15:50:11 -0800631class ControllerImagesInline(XOSTabularInline):
Tony Mack06c8e472014-11-30 15:53:08 -0500632 model = ControllerImages
633 extra = 0
634 verbose_name = "Controller Images"
635 verbose_name_plural = "Controller Images"
636 suit_classes = 'suit-tab suit-tab-admin-only'
637 fields = ['backend_status_icon', 'image', 'controller', 'glance_image_id']
Scott Baker40c00762014-08-21 16:55:59 -0700638 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700639
Scott Baker67db95f2015-02-18 15:50:11 -0800640class SliceRoleAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400641 model = SliceRole
642 pass
643
Scott Baker67db95f2015-02-18 15:50:11 -0800644class SiteRoleAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400645 model = SiteRole
646 pass
647
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400648class DeploymentAdminForm(forms.ModelForm):
Scott Bakerde0f4412014-06-11 15:40:26 -0700649 images = forms.ModelMultipleChoiceField(
650 queryset=Image.objects.all(),
651 required=False,
652 help_text="Select which images should be deployed on this deployment",
653 widget=FilteredSelectMultiple(
654 verbose_name=('Images'), is_stacked=False
655 )
656 )
Scott Baker37b47902014-09-02 14:37:41 -0700657 flavors = forms.ModelMultipleChoiceField(
658 queryset=Flavor.objects.all(),
659 required=False,
660 help_text="Select which flavors should be usable on this deployment",
661 widget=FilteredSelectMultiple(
662 verbose_name=('Flavors'), is_stacked=False
663 )
664 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400665 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400666 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700667 many_to_many = ["flavors",]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400668
Siobhan Tully320b4622014-01-17 15:11:14 -0500669 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700670 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500671 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
672
Scott Baker5380c522014-06-06 14:49:43 -0700673 self.fields['accessControl'].initial = "allow site " + request.user.site.name
674
Siobhan Tully320b4622014-01-17 15:11:14 -0500675 if self.instance and self.instance.pk:
Scott Baker69e045d2014-11-17 23:44:03 -0800676 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
Scott Baker37b47902014-09-02 14:37:41 -0700677 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700678
679 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
680 """ helper function for handling m2m relations from the MultipleChoiceField
681
682 this_obj: the source object we want to link from
683
684 selected_objs: a list of destination objects we want to link to
685
686 all_relations: the full set of relations involving this_obj, including ones we don't want
687
688 relation_class: the class that implements the relation from source to dest
689
690 local_attrname: field name representing this_obj in relation_class
691
692 foreign_attrname: field name representing selected_objs in relation_class
693
694 This function will remove all newobjclass relations from this_obj
695 that are not contained in selected_objs, and add any relations that
696 are in selected_objs but don't exist in the data model yet.
697 """
698
699 existing_dest_objs = []
700 for relation in list(all_relations):
701 if getattr(relation, foreign_attrname) not in selected_objs:
702 #print "deleting site", sdp.site
703 relation.delete()
704 else:
705 existing_dest_objs.append(getattr(relation, foreign_attrname))
706
707 for dest_obj in selected_objs:
708 if dest_obj not in existing_dest_objs:
709 #print "adding site", site
710 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
711 relation = relation_class(**kwargs)
712 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500713
714 def save(self, commit=True):
715 deployment = super(DeploymentAdminForm, self).save(commit=False)
716
717 if commit:
718 deployment.save()
Scott Baker0057d052014-10-06 17:17:40 -0700719 # this has to be done after save() if/when a deployment is first created
720 deployment.flavors = self.cleaned_data['flavors']
Siobhan Tully320b4622014-01-17 15:11:14 -0500721
722 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700723 # save_m2m() doesn't seem to work with 'through' relations. So we
724 # create/destroy the through models ourselves. There has to be
725 # a better way...
726
Tony Mackb2fde612014-12-15 11:45:02 -0500727 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
728 # manipulate_m2m_objs doesn't work for Flavor/Deployment relationship
729 # so well handle that manually here
730 for flavor in deployment.flavors.all():
731 if getattr(flavor, 'name') not in self.cleaned_data['flavors']:
Tony Mackd4ab7822014-12-15 12:37:59 -0500732 deployment.flavors.remove(flavor)
Tony Mackb2fde612014-12-15 11:45:02 -0500733 for flavor in self.cleaned_data['flavors']:
734 if flavor not in deployment.flavors.all():
735 flavor.deployments.add(deployment)
Scott Bakerc9b14f72014-05-22 13:44:20 -0700736
Scott Baker37b47902014-09-02 14:37:41 -0700737 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500738
739 return deployment
740
Scott Bakerff5e0f32014-05-22 14:40:27 -0700741class DeploymentAdminROForm(DeploymentAdminForm):
742 def save(self, commit=True):
743 raise PermissionDenied
744
Scott Baker67db95f2015-02-18 15:50:11 -0800745class SiteAssocInline(XOSTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500746 model = Site.deployments.through
747 extra = 0
748 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400749
Scott Baker67db95f2015-02-18 15:50:11 -0800750class DeploymentAdmin(XOSBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500751 model = Deployment
Scott Baker622bcf02015-02-10 08:40:34 -0800752 fieldList = ['backend_status_text', 'name', 'images', 'flavors', 'accessControl']
753 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Mack68a1e422014-12-08 16:43:02 -0500754 # node no longer directly connected to deployment
755 #inlines = [DeploymentPrivilegeInline,NodeInline,TagInline,ImageDeploymentsInline]
Tony Mackb81d5e42015-01-30 10:58:29 -0500756 inlines = [DeploymentPrivilegeInline,TagInline,ImageDeploymentsInline,SiteDeploymentInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700757 list_display = ['backend_status_icon', 'name']
758 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700759 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500760
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500761 user_readonly_fields = ['name']
762
Tony Mack68a1e422014-12-08 16:43:02 -0500763 # nodes no longer direclty connected to deployments
Scott Baker622bcf02015-02-10 08:40:34 -0800764 suit_form_tabs =(('general','Deployment Details'),('deploymentprivileges','Privileges'), ('sitedeployments', 'Sites'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500765
Scott Bakerff5e0f32014-05-22 14:40:27 -0700766 def get_form(self, request, obj=None, **kwargs):
Tony Mackcf29cfa2015-02-05 06:13:04 -0500767 if request.user.isReadOnlyUser() or not request.user.is_admin:
Scott Bakerff5e0f32014-05-22 14:40:27 -0700768 kwargs["form"] = DeploymentAdminROForm
769 else:
770 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700771 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
772
773 # from stackexchange: pass the request object into the form
774
775 class AdminFormMetaClass(adminForm):
776 def __new__(cls, *args, **kwargs):
777 kwargs['request'] = request
778 return adminForm(*args, **kwargs)
779
780 return AdminFormMetaClass
781
Scott Baker67db95f2015-02-18 15:50:11 -0800782class ControllerAdmin(XOSBaseAdmin):
Scott Baker622bcf02015-02-10 08:40:34 -0800783 model = Controller
Scott Baker180148a2015-02-16 11:55:09 -0800784 fieldList = ['deployment', 'name', 'backend_type', 'version', 'auth_url', 'admin_user', 'admin_tenant','admin_password', 'domain']
Scott Baker622bcf02015-02-10 08:40:34 -0800785 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Mack3066a952015-01-05 22:48:11 -0500786 inlines = [ControllerSiteInline] # ,ControllerImagesInline]
Tony Mackd14d48f2014-12-05 17:13:08 -0500787 list_display = ['backend_status_icon', 'name', 'version', 'backend_type']
788 list_display_links = ('backend_status_icon', 'name', )
789 readonly_fields = ('backend_status_text',)
790
791 user_readonly_fields = []
792
Tony Mack2e897fa2015-01-13 17:33:08 -0500793 def save_model(self, request, obj, form, change):
794 # update openstack connection to use this site/tenant
795 obj.save_by_user(request.user)
796
797 def delete_model(self, request, obj):
Scott Baker622bcf02015-02-10 08:40:34 -0800798 obj.delete_by_user(request.user)
799
Tony Mack78fc1362015-02-18 11:41:36 -0500800 def queryset(self, request):
801 return Controller.select_by_user(request.user)
802
Scott Baker622bcf02015-02-10 08:40:34 -0800803 @property
804 def suit_form_tabs(self):
805 tabs = [('general', 'Controller Details'),
806 ]
807
808 request=getattr(_thread_locals, "request", None)
809 if request and request.user.is_admin:
810 tabs.append( ('admin-only', 'Admin-Only') )
811
812 return tabs
Tony Mack2e897fa2015-01-13 17:33:08 -0500813
Scott Bakerb3cf9212015-07-06 14:40:20 -0700814class TenantRootRoleAdmin(XOSBaseAdmin):
815 model = TenantRootRole
816 fields = ('role',)
817
818class TenantRootTenantInline(XOSTabularInline):
819 model = Tenant
820 fields = ['provider_service', 'subscriber_root']
821 extra = 0
822 suit_classes = 'suit-tab suit-tab-tenantroots'
823 fk_name = 'subscriber_root'
824 verbose_name = 'subscribed tenant'
825 verbose_name_plural = 'subscribed tenants'
826
827 #def queryset(self, request):
828 # qs = super(TenantRootTenantInline, self).queryset(request)
829 # return qs.filter(kind="coarse")
830
831class TenantRootPrivilegeInline(XOSTabularInline):
832 model = TenantRootPrivilege
833 extra = 0
834 suit_classes = 'suit-tab suit-tab-tenantrootprivileges'
835 fields = ['backend_status_icon', 'user', 'role', 'tenant_root']
836 readonly_fields = ('backend_status_icon', )
837
838 def queryset(self, request):
839 return TenantRootPrivilege.select_by_user(request.user)
840
841class TenantRootAdmin(XOSBaseAdmin):
842 model = TenantRoot
843 list_display = ('backend_status_icon', 'name', 'kind')
844 list_display_links = ('backend_status_icon', 'name')
845 fieldList = ('backend_status_text', 'name', 'kind', )
846 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
847 inlines = (TenantRootTenantInline, TenantRootPrivilegeInline)
848 readonly_fields = ('backend_status_text', )
849
850 suit_form_tabs =(('general', 'Tenant Root Details'),
851 ('tenantroots','Tenancy'),
852 ('tenantrootprivileges','Privileges')
853 )
854
Scott Bakeref58a842015-04-26 20:30:40 -0700855class ProviderTenantInline(XOSTabularInline):
856 model = CoarseTenant
857 fields = ['provider_service', 'subscriber_service', 'connect_method']
858 extra = 0
859 suit_classes = 'suit-tab suit-tab-servicetenants'
860 fk_name = 'provider_service'
861 verbose_name = 'provided tenant'
862 verbose_name_plural = 'provided tenants'
863
864 def queryset(self, request):
865 qs = super(ProviderTenantInline, self).queryset(request)
866 return qs.filter(kind="coarse")
867
868class SubscriberTenantInline(XOSTabularInline):
869 model = CoarseTenant
870 fields = ['provider_service', 'subscriber_service', 'connect_method']
871 extra = 0
872 suit_classes = 'suit-tab suit-tab-servicetenants'
873 fk_name = 'subscriber_service'
874 verbose_name = 'subscribed tenant'
875 verbose_name_plural = 'subscribed tenants'
876
877 def queryset(self, request):
878 qs = super(SubscriberTenantInline, self).queryset(request)
879 return qs.filter(kind="coarse")
880
Scott Baker67db95f2015-02-18 15:50:11 -0800881class ServiceAttrAsTabInline(XOSTabularInline):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400882 model = ServiceAttribute
883 fields = ['name','value']
884 extra = 0
885 suit_classes = 'suit-tab suit-tab-serviceattrs'
886
Scott Baker67db95f2015-02-18 15:50:11 -0800887class ServiceAdmin(XOSBaseAdmin):
Scott Baker0d306722015-04-15 20:58:20 -0700888 list_display = ("backend_status_icon","name","kind","versionNumber","enabled","published")
Scott Baker63d1a552014-08-21 15:19:07 -0700889 list_display_links = ('backend_status_icon', 'name', )
Scott Bakerb9040e92015-07-13 12:33:28 -0700890 fieldList = ["backend_status_text","name","kind","description","versionNumber","enabled","published","view_url","icon_url","public_key","service_specific_attribute","service_specific_id"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500891 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
Tony Mack60789ac2015-05-11 20:39:32 -0400892 inlines = [ServiceAttrAsTabInline,SliceInline,ProviderTenantInline,SubscriberTenantInline,ServicePrivilegeInline]
Scott Baker40c00762014-08-21 16:55:59 -0700893 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500894
895 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500896
897 suit_form_tabs =(('general', 'Service Details'),
898 ('slices','Slices'),
899 ('serviceattrs','Additional Attributes'),
Scott Bakeref58a842015-04-26 20:30:40 -0700900 ('servicetenants','Tenancy'),
Scott Bakerb3cf9212015-07-06 14:40:20 -0700901 ('serviceprivileges','Privileges')
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500902 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400903
Scott Baker67db95f2015-02-18 15:50:11 -0800904class SiteNodeInline(XOSTabularInline):
Tony Mack4f134e62015-01-14 20:58:38 -0500905 model = Node
906 fields = ['name', 'site_deployment']
907 extra = 0
908 suit_classes = 'suit-tab suit-tab-nodes'
909
Tony Mackc2a0d312015-02-25 11:39:34 -0500910 def formfield_for_foreignkey(self, db_field, request, **kwargs):
911 # only display site deployments associated with this site
912 if db_field.name == 'site_deployment':
913 kwargs['queryset'] = SiteDeployment.objects.filter(site__id=int(request.path.split('/')[-2]))
914
915 return super(SiteNodeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
916
Scott Baker67db95f2015-02-18 15:50:11 -0800917class SiteAdmin(XOSBaseAdmin):
Tony Mack450b6e02015-01-25 12:35:29 -0500918 #fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
919 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'location']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400920 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500921 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400922 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400923 ]
Tony Mack450b6e02015-01-25 12:35:29 -0500924 #readonly_fields = ['backend_status_text', 'accountLink']
925 readonly_fields = ['backend_status_text']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500926
Tony Mack450b6e02015-01-25 12:35:29 -0500927 #user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
928 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500929
Scott Baker63d1a552014-08-21 15:19:07 -0700930 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
931 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400932 filter_horizontal = ('deployments',)
Tony Mackb81d5e42015-01-30 10:58:29 -0500933 inlines = [SliceInline,UserInline,TagInline, SitePrivilegeInline, SiteNodeInline]
Tony Mackde100182015-01-14 12:11:05 -0500934 admin_inlines = [ControllerSiteInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400935 search_fields = ['name']
936
Tony Mack30dfcd72015-01-10 23:08:10 -0500937 @property
938 def suit_form_tabs(self):
939 tabs = [('general', 'Site Details'),
940 ('users','Users'),
941 ('siteprivileges','Privileges'),
Tony Mack30dfcd72015-01-10 23:08:10 -0500942 ('slices','Slices'),
Tony Mack4f134e62015-01-14 20:58:38 -0500943 ('nodes','Nodes'),
Tony Mack30dfcd72015-01-10 23:08:10 -0500944 ]
945
946 request=getattr(_thread_locals, "request", None)
947 if request and request.user.is_admin:
948 tabs.append( ('admin-only', 'Admin-Only') )
949
950 return tabs
951
Tony Mack04062832013-05-10 08:22:44 -0400952 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500953 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400954
Tony Mack5cd13202013-05-01 21:48:38 -0400955 def get_formsets(self, request, obj=None):
956 for inline in self.get_inline_instances(request, obj):
957 # hide MyInline in the add view
958 if obj is None:
959 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400960 if isinstance(inline, SliverInline):
961 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -0400962 yield inline.get_formset(request, obj)
963
Scott Baker545db2a2013-12-09 18:44:43 -0800964 def accountLink(self, obj):
965 link_obj = obj.accounts.all()
966 if link_obj:
967 reverse_path = "admin:core_account_change"
968 url = reverse(reverse_path, args =(link_obj[0].id,))
969 return "<a href='%s'>%s</a>" % (url, "view billing details")
970 else:
971 return "no billing data for this site"
972 accountLink.allow_tags = True
973 accountLink.short_description = "Billing"
974
Tony Mack332ee1d2014-02-04 15:33:45 -0500975 def save_model(self, request, obj, form, change):
976 # update openstack connection to use this site/tenant
977 obj.save_by_user(request.user)
978
979 def delete_model(self, request, obj):
980 obj.delete_by_user(request.user)
981
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500982
Scott Baker67db95f2015-02-18 15:50:11 -0800983class SitePrivilegeAdmin(XOSBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700984 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -0400985 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500986 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -0400987 ]
Scott Baker40c00762014-08-21 16:55:59 -0700988 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700989 list_display = ('backend_status_icon', 'user', 'site', 'role')
990 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500991 user_readonly_fields = fieldList
992 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -0400993
Tony Mackc2835a92013-05-28 09:18:49 -0400994 def formfield_for_foreignkey(self, db_field, request, **kwargs):
995 if db_field.name == 'site':
996 if not request.user.is_admin:
997 # only show sites where user is an admin or pi
998 sites = set()
999 for site_privilege in SitePrivilege.objects.filer(user=request.user):
1000 if site_privilege.role.role_type in ['admin', 'pi']:
1001 sites.add(site_privilege.site)
1002 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
1003
1004 if db_field.name == 'user':
1005 if not request.user.is_admin:
1006 # only show users from sites where caller has admin or pi role
1007 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
1008 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
1009 sites = [site_privilege.site for site_privilege in site_privileges]
1010 site_privileges = SitePrivilege.objects.filter(site__in=sites)
1011 emails = [site_privilege.user.email for site_privilege in site_privileges]
1012 users = User.objects.filter(email__in=emails)
1013 kwargs['queryset'] = users
1014
1015 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1016
Tony Mack04062832013-05-10 08:22:44 -04001017 def queryset(self, request):
1018 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -04001019 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -04001020 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -05001021 #if not request.user.is_admin:
1022 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
1023 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
1024 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
1025 # sites = Site.objects.filter(login_base__in=login_bases)
1026 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -04001027 return qs
1028
Siobhan Tullyce652d02013-10-08 21:52:35 -04001029class SliceForm(forms.ModelForm):
1030 class Meta:
1031 model = Slice
1032 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -07001033 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -04001034 }
1035
Tony Macke75841e2014-09-29 16:10:52 -04001036 def clean(self):
1037 cleaned_data = super(SliceForm, self).clean()
1038 name = cleaned_data.get('name')
Scott Baker3cb382c2014-10-06 23:09:59 -07001039 site = cleaned_data.get('site')
Tony Mackcc9e2592014-10-22 12:54:19 -04001040 slice_id = self.instance.id
1041 if not site and slice_id:
1042 site = Slice.objects.get(id=slice_id).site
Scott Baker3cb382c2014-10-06 23:09:59 -07001043 if (not isinstance(site,Site)):
1044 # previous code indicates 'site' could be a site_id and not a site?
1045 site = Slice.objects.get(id=site.id)
Tony Macke75841e2014-09-29 16:10:52 -04001046 if not name.startswith(site.login_base):
1047 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
1048 return cleaned_data
1049
Scott Baker67db95f2015-02-18 15:50:11 -08001050class ControllerSliceInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -05001051 model = ControllerSlice
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001052 extra = 0
Tony Mack06c8e472014-11-30 15:53:08 -05001053 verbose_name = "Controller Slices"
1054 verbose_name_plural = "Controller Slices"
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001055 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack06c8e472014-11-30 15:53:08 -05001056 fields = ['backend_status_icon', 'controller', 'tenant_id']
Tony Mack30dfcd72015-01-10 23:08:10 -05001057 readonly_fields = ('backend_status_icon', 'controller' )
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001058
Scott Baker67db95f2015-02-18 15:50:11 -08001059class SliceAdmin(XOSBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -04001060 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -04001061 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001062 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -07001063 readonly_fields = ('backend_status_text', )
Tony Mack7d459902014-09-03 13:18:57 -04001064 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
1065 list_display_links = ('backend_status_icon', 'name', )
Scott Baker25881992015-06-12 10:40:15 -07001066 normal_inlines = [SlicePrivilegeInline, SliverInline, TagInline, ReservationInline, SliceNetworkInline]
1067 inlines = normal_inlines
Tony Mack3066a952015-01-05 22:48:11 -05001068 admin_inlines = [ControllerSliceInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001069
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001070 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001071
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001072 @property
1073 def suit_form_tabs(self):
1074 tabs =[('general', 'Slice Details'),
1075 ('slicenetworks','Networks'),
1076 ('sliceprivileges','Privileges'),
1077 ('slivers','Slivers'),
Tony Mack450b6e02015-01-25 12:35:29 -05001078 #('reservations','Reservations'),
Tony Mackd2433382015-01-15 14:44:06 -05001079 ('tags','Tags'),
Scott Bakerf9f1ef42014-10-15 16:54:04 -07001080 ]
1081
1082 request=getattr(_thread_locals, "request", None)
1083 if request and request.user.is_admin:
1084 tabs.append( ('admin-only', 'Admin-Only') )
1085
1086 return tabs
Tony Mack0aa732a2014-10-22 11:54:29 -04001087
1088 def add_view(self, request, form_url='', extra_context=None):
Scott Baker25881992015-06-12 10:40:15 -07001089 # Ugly hack for CORD
1090 self.inlines = self.normal_inlines
Tony Mack0aa732a2014-10-22 11:54:29 -04001091 # revert to default read-only fields
1092 self.readonly_fields = ('backend_status_text',)
1093 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
1094
1095 def change_view(self, request, object_id, form_url='', extra_context=None):
Tony Mack0aa732a2014-10-22 11:54:29 -04001096 # cannot change the site of an existing slice so make the site field read only
1097 if object_id:
1098 self.readonly_fields = ('backend_status_text','site')
Scott Baker25881992015-06-12 10:40:15 -07001099
Tony Mack0aa732a2014-10-22 11:54:29 -04001100 return super(SliceAdmin, self).change_view(request, object_id, form_url)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001101
Scott Baker510fdbb2014-08-05 17:19:24 -07001102 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -07001103 deployment_nodes = []
1104 for node in Node.objects.all():
Scott Baker66b11e22015-01-21 16:24:07 -08001105 deployment_nodes.append( (node.site_deployment.deployment.id, node.id, node.name) )
Scott Baker510fdbb2014-08-05 17:19:24 -07001106
Scott Baker7a61dc42014-09-02 17:08:20 -07001107 deployment_flavors = []
1108 for flavor in Flavor.objects.all():
1109 for deployment in flavor.deployments.all():
1110 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
1111
Tony Mack68a1e422014-12-08 16:43:02 -05001112 deployment_images = []
Scott Bakeraf36c4d2014-09-09 09:58:49 -07001113 for image in Image.objects.all():
Tony Mack68a1e422014-12-08 16:43:02 -05001114 for deployment_image in image.imagedeployments.all():
Scott Bakerf2c0c512014-12-22 17:35:34 -08001115 deployment_images.append( (deployment_image.deployment.id, image.id, image.name) )
Scott Bakeraf36c4d2014-09-09 09:58:49 -07001116
Tony Mackec23b992014-09-02 21:18:45 -04001117 site_login_bases = []
1118 for site in Site.objects.all():
Scott Bakeraf36c4d2014-09-09 09:58:49 -07001119 site_login_bases.append((site.id, site.login_base))
1120
Scott Baker510fdbb2014-08-05 17:19:24 -07001121 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -07001122 context["deployment_flavors"] = deployment_flavors
Scott Bakeraf36c4d2014-09-09 09:58:49 -07001123 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -04001124 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -07001125 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
1126
Tony Mackc2835a92013-05-28 09:18:49 -04001127 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1128 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001129 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackec23b992014-09-02 21:18:45 -04001130 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_prefix(this, $($(this).closest('fieldset')[0]).find('.field-name input')[0].id)"})
Scott Baker40c00762014-08-21 16:55:59 -07001131
Tony Mackc2835a92013-05-28 09:18:49 -04001132 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1133
Tony Mack04062832013-05-10 08:22:44 -04001134 def queryset(self, request):
1135 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -05001136 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -04001137
Tony Mack79748612013-05-01 14:52:03 -04001138 def get_formsets(self, request, obj=None):
1139 for inline in self.get_inline_instances(request, obj):
1140 # hide MyInline in the add view
1141 if obj is None:
1142 continue
Tony Mack2bd5b412013-06-11 21:05:06 -04001143 if isinstance(inline, SliverInline):
1144 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -04001145 yield inline.get_formset(request, obj)
1146
Scott Baker25881992015-06-12 10:40:15 -07001147 def UNUSED_get_inline_instances(self, request, obj=None):
1148 # HACK for CORD to do something special on vcpe slice page
1149 # this was a good idea, but failed miserably, as something still
1150 # expects there to be a deployment field.
1151 # XXX this approach is better than clobbering self.inlines, so
1152 # try to make this work post-demo.
1153 if (obj is not None) and (obj.name == "mysite_vcpe"):
1154 cord_vcpe_inlines = [ SlicePrivilegeInline, CordSliverInline, TagInline, ReservationInline,SliceNetworkInline]
1155
1156 inlines=[]
1157 for inline_class in cord_vcpe_inlines:
1158 inlines.append(inline_class(self.model, self.admin_site))
1159 else:
1160 inlines = super(SliceAdmin, self).get_inline_instances(request, obj)
1161
1162 return inlines
1163
Scott Baker67db95f2015-02-18 15:50:11 -08001164class SlicePrivilegeAdmin(XOSBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -04001165 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001166 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -04001167 ]
Scott Baker40c00762014-08-21 16:55:59 -07001168 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -07001169 list_display = ('backend_status_icon', 'user', 'slice', 'role')
1170 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -04001171
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001172 user_readonly_fields = ['user', 'slice', 'role']
1173 user_readonly_inlines = []
1174
Tony Mackc2835a92013-05-28 09:18:49 -04001175 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1176 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001177 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001178
1179 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -05001180 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001181
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001182 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -04001183
Tony Mack04062832013-05-10 08:22:44 -04001184 def queryset(self, request):
1185 # admins can see all memberships. Users can only see memberships of
1186 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -05001187 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001188
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001189 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -04001190 # update openstack connection to use this site/tenant
1191 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -04001192 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -04001193 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001194 obj.save()
1195
1196 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -04001197 # update openstack connection to use this site/tenant
1198 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -04001199 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -04001200 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -04001201 obj.delete()
1202
Scott Baker67db95f2015-02-18 15:50:11 -08001203class ImageAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001204
Scott Baker36f50872014-08-21 13:01:25 -07001205 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -07001206 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001207 'classes': ['suit-tab suit-tab-general']})
1208 ]
Scott Baker40c00762014-08-21 16:55:59 -07001209 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001210
Tony Mack06c8e472014-11-30 15:53:08 -05001211 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'), ('controllerimages', 'Controllers'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001212
Tony Mack06c8e472014-11-30 15:53:08 -05001213 inlines = [SliverInline, ControllerImagesInline]
Scott Bakerb6f99242014-06-11 11:34:44 -07001214
Tony Mack32e1ce32014-05-07 13:29:41 -04001215 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -07001216
Scott Baker63d1a552014-08-21 15:19:07 -07001217 list_display = ['backend_status_icon', 'name']
1218 list_display_links = ('backend_status_icon', 'name', )
1219
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001220class NodeForm(forms.ModelForm):
1221 class Meta:
1222 widgets = {
1223 'site': LinkedSelect,
1224 'deployment': LinkedSelect
1225 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001226
Scott Baker67db95f2015-02-18 15:50:11 -08001227class NodeAdmin(XOSBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001228 form = NodeForm
Tony Mack68a1e422014-12-08 16:43:02 -05001229 list_display = ('backend_status_icon', 'name', 'site_deployment')
Scott Baker63d1a552014-08-21 15:19:07 -07001230 list_display_links = ('backend_status_icon', 'name', )
Tony Mack68a1e422014-12-08 16:43:02 -05001231 list_filter = ('site_deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001232
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001233 inlines = [TagInline,SliverInline]
Tony Mack68a1e422014-12-08 16:43:02 -05001234 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site_deployment'], 'classes':['suit-tab suit-tab-details']})]
Scott Baker40c00762014-08-21 16:55:59 -07001235 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001236
Tony Mack68a1e422014-12-08 16:43:02 -05001237 user_readonly_fields = ['name','site_deployment']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001238 user_readonly_inlines = [TagInline,SliverInline]
1239
Tony Mack5fecf712015-01-12 21:40:09 -05001240 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001241
Siobhan Tully567e3e62013-06-21 18:03:16 -04001242
Tony Mackd90cdbf2013-04-16 22:48:40 -04001243class SliverForm(forms.ModelForm):
1244 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -04001245 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -04001246 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -04001247 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -04001248 widgets = {
1249 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -04001250 'instance_name': PlainTextWidget(),
Scott Baker9d856052015-01-19 11:32:20 -08001251 'instance_id': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001252 'slice': LinkedSelect,
Tony Mackb2dba4b2014-12-26 13:38:02 -05001253 'deployment': LinkedSelect,
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001254 'node': LinkedSelect,
1255 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -04001256 }
Tony Mackd90cdbf2013-04-16 22:48:40 -04001257
Scott Baker67db95f2015-02-18 15:50:11 -08001258class TagAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001259 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
1260 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001261 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
1262 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -04001263
Scott Baker67db95f2015-02-18 15:50:11 -08001264class SliverAdmin(XOSBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -04001265 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -04001266 fieldsets = [
Tony Mack707f7d72015-01-30 12:52:46 -05001267 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deployment', 'node', 'all_ips_string', 'instance_id', 'instance_name', 'flavor', 'image', 'ssh_command'], 'classes': ['suit-tab suit-tab-general'], })
Tony Mackcdec0902013-04-15 00:38:49 -04001268 ]
Tony Mackdb8580b2015-01-30 17:20:46 -05001269 readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string')
Tony Mack707f7d72015-01-30 12:52:46 -05001270 list_display = ['backend_status_icon', 'all_ips_string', 'instance_id', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deployment']
Scott Baker2f295402015-02-13 14:38:21 -08001271 list_display_links = ('backend_status_icon', 'all_ips_string', 'instance_id', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001272
Scott Bakerc72997a2015-01-19 08:24:08 -08001273 suit_form_tabs =(('general', 'Sliver Details'),)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001274
Siobhan Tullyde5450d2013-06-21 11:35:33 -04001275 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -04001276
Tony Mackb2dba4b2014-12-26 13:38:02 -05001277 user_readonly_fields = ['slice', 'deployment', 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001278
Scott Baker7ccc6ad2015-01-25 22:16:13 -08001279 def ssh_command(self, obj):
1280 ssh_command = obj.get_ssh_command()
1281 if ssh_command:
1282 return ssh_command
1283 else:
1284 return "(not available)"
1285
Tony Mackc2835a92013-05-28 09:18:49 -04001286 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1287 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -05001288 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001289
1290 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1291
Tony Mack04062832013-05-10 08:22:44 -04001292 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -07001293 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -04001294 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -05001295 return Sliver.select_by_user(request.user)
1296
Tony Mack04062832013-05-10 08:22:44 -04001297
Tony Mack1d6b85f2013-05-07 18:49:14 -04001298 def get_formsets(self, request, obj=None):
1299 # make some fields read only if we are updating an existing record
1300 if obj == None:
Tony Mackb428feb2015-01-30 17:42:10 -05001301 self.readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string')
Tony Mack1d6b85f2013-05-07 18:49:14 -04001302 else:
Tony Mackb428feb2015-01-30 17:42:10 -05001303 self.readonly_fields = ('backend_status_text', 'ssh_command', 'all_ips_string', 'slice', 'flavor', 'image', 'node')
Tony Mack1d6b85f2013-05-07 18:49:14 -04001304
1305 for inline in self.get_inline_instances(request, obj):
1306 # hide MyInline in the add view
1307 if obj is None:
1308 continue
Scott Baker526b71e2014-05-13 13:18:01 -07001309 if isinstance(inline, SliverInline):
1310 inline.model.caller = request.user
1311 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -04001312
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001313 #def save_model(self, request, obj, form, change):
1314 # # update openstack connection to use this site/tenant
1315 # auth = request.session.get('auth', {})
1316 # auth['tenant'] = obj.slice.name
1317 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1318 # obj.creator = request.user
1319 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -04001320
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001321 #def delete_model(self, request, obj):
1322 # # update openstack connection to use this site/tenant
1323 # auth = request.session.get('auth', {})
1324 # auth['tenant'] = obj.slice.name
1325 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1326 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -04001327
Siobhan Tully53437282013-04-26 19:30:27 -04001328class UserCreationForm(forms.ModelForm):
1329 """A form for creating new users. Includes all the required
1330 fields, plus a repeated password."""
1331 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1332 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1333
1334 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001335 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001336 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -04001337
1338 def clean_password2(self):
1339 # Check that the two password entries match
1340 password1 = self.cleaned_data.get("password1")
1341 password2 = self.cleaned_data.get("password2")
1342 if password1 and password2 and password1 != password2:
1343 raise forms.ValidationError("Passwords don't match")
1344 return password2
1345
1346 def save(self, commit=True):
1347 # Save the provided password in hashed format
1348 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -04001349 user.password = self.cleaned_data["password1"]
1350 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -04001351 if commit:
1352 user.save()
1353 return user
1354
Siobhan Tully567e3e62013-06-21 18:03:16 -04001355
Siobhan Tully53437282013-04-26 19:30:27 -04001356class UserChangeForm(forms.ModelForm):
1357 """A form for updating users. Includes all the fields on
1358 the user, but replaces the password field with admin's
1359 password hash display field.
1360 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -05001361 password = ReadOnlyPasswordHashField(label='Password',
1362 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -04001363
Scott Bakerb6043c22015-05-19 16:39:48 -07001364 PROFILE_CHOICES = ((None, '------'), ('regular', 'Regular user'), ('cp', 'Content Provider'))
1365 profile = forms.ChoiceField(choices=PROFILE_CHOICES, required=False, label="Quick Profile")
1366
Siobhan Tully53437282013-04-26 19:30:27 -04001367 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001368 model = User
Scott Baker69e045d2014-11-17 23:44:03 -08001369 widgets = { 'public_key': UploadTextareaWidget, }
Siobhan Tully53437282013-04-26 19:30:27 -04001370
1371 def clean_password(self):
1372 # Regardless of what the user provides, return the initial value.
1373 # This is done here, rather than on the field, because the
1374 # field does not have access to the initial value
1375 return self.initial["password"]
1376
Scott Bakerb6043c22015-05-19 16:39:48 -07001377 def save(self, *args, **kwargs):
1378 if self.cleaned_data['profile']:
1379 self.instance.apply_profile(self.cleaned_data['profile'])
1380
1381 return super(UserChangeForm, self).save(*args, **kwargs)
1382
Scott Baker67db95f2015-02-18 15:50:11 -08001383class UserDashboardViewInline(XOSTabularInline):
Scott Baker2c3cb642014-05-19 17:55:56 -07001384 model = UserDashboardView
1385 extra = 0
1386 suit_classes = 'suit-tab suit-tab-dashboards'
1387 fields = ['user', 'dashboardView', 'order']
1388
Scott Baker67db95f2015-02-18 15:50:11 -08001389class ControllerUserInline(XOSTabularInline):
Tony Mack30dfcd72015-01-10 23:08:10 -05001390 model = ControllerUser
1391 extra = 0
1392 suit_classes = 'suit-tab suit-tab-admin-only'
1393 fields = ['controller', 'user', 'kuser_id']
1394 readonly_fields=['controller']
1395
1396
Scott Baker3a8aed62015-02-27 12:21:22 -08001397class UserAdmin(XOSAdminMixin, UserAdmin):
1398 # Note: Make sure XOSAdminMixin is listed before
Scott Bakerf4aeedc2014-10-03 13:10:47 -07001399 # admin.ModelAdmin in the class declaration.
1400
Siobhan Tully53437282013-04-26 19:30:27 -04001401 class Meta:
1402 app_label = "core"
1403
1404 # The forms to add and change user instances
1405 form = UserChangeForm
1406 add_form = UserCreationForm
1407
1408 # The fields to be used in displaying the User model.
1409 # These override the definitions on the base UserAdmin
1410 # that reference specific fields on auth.User.
Scott Bakera111f442015-01-24 13:33:26 -08001411 list_display = ('backend_status_icon', 'email', 'firstname', 'lastname', 'site', 'last_login')
1412 list_display_links = ("email",)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001413 list_filter = ('site',)
Scott Baker90472612015-01-29 10:55:53 -08001414 inlines = [SlicePrivilegeInline,SitePrivilegeInline]
Tony Mack30dfcd72015-01-10 23:08:10 -05001415 admin_inlines = [ControllerUserInline]
Scott Baker6da26a52015-06-10 16:14:58 -07001416 fieldListLoginDetails = ['backend_status_text', 'email', 'site','password','is_active','is_readonly','is_admin','is_appuser', 'public_key', 'login_page', 'profile']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001417 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1418
Siobhan Tully53437282013-04-26 19:30:27 -04001419 fieldsets = (
Scott Baker034232d2015-05-15 09:19:36 -07001420 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'is_appuser', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001421 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001422 #('Important dates', {'fields': ('last_login',)}),
1423 )
1424 add_fieldsets = (
1425 (None, {
1426 'classes': ('wide',),
Scott Baker034232d2015-05-15 09:19:36 -07001427 'fields': ('site', 'email', 'firstname', 'lastname', 'is_admin', 'is_readonly', 'is_appuser', 'phone', 'public_key','password1', 'password2')},
Siobhan Tully53437282013-04-26 19:30:27 -04001428 ),
1429 )
Scott Baker40c00762014-08-21 16:55:59 -07001430 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001431 search_fields = ('email',)
1432 ordering = ('email',)
1433 filter_horizontal = ()
1434
Scott Baker3ca51f62014-05-23 12:05:11 -07001435 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001436
Scott Baker6a995352014-10-06 17:51:20 -07001437 @property
1438 def suit_form_tabs(self):
1439 if getattr(_thread_locals, "obj", None) is None:
1440 return []
1441 else:
Tony Mack30dfcd72015-01-10 23:08:10 -05001442 tabs = [('general','Login Details'),
Scott Baker6a995352014-10-06 17:51:20 -07001443 ('contact','Contact Information'),
1444 ('sliceprivileges','Slice Privileges'),
Scott Baker90472612015-01-29 10:55:53 -08001445 ('siteprivileges','Site Privileges')]
Tony Mack30dfcd72015-01-10 23:08:10 -05001446
1447 request=getattr(_thread_locals, "request", None)
1448 if request and request.user.is_admin:
1449 tabs.append( ('admin-only', 'Admin-Only') )
1450
1451 return tabs
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001452
Tony Mackc2835a92013-05-28 09:18:49 -04001453 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1454 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001455 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001456
1457 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1458
Tony Mack5b061472014-02-04 07:57:10 -05001459 def queryset(self, request):
1460 return User.select_by_user(request.user)
1461
Tony Mackc8f443d2015-01-25 21:58:30 -05001462 def get_form(self, request, obj=None, **kwargs):
Tony Mack03b92292015-01-28 12:37:12 -05001463 # copy login details list
1464 login_details_fields = list(self.fieldListLoginDetails)
Tony Mack933b2912015-01-28 12:49:58 -05001465 if not request.user.is_admin:
Scott Baker90472612015-01-29 10:55:53 -08001466 # only admins can see 'is_admin' and 'is_readonly' fields
Tony Mack03b92292015-01-28 12:37:12 -05001467 if 'is_admin' in login_details_fields:
1468 login_details_fields.remove('is_admin')
1469 if 'is_readonly' in login_details_fields:
Scott Bakerb6043c22015-05-19 16:39:48 -07001470 login_details_fields.remove('is_readonly')
1471 if 'is_appuser' in login_details_fields:
1472 login_details_fields.remove('is_admin')
1473 if 'profile' in login_details_fields:
1474 login_details_fields.remove('profile')
Tony Mack933b2912015-01-28 12:49:58 -05001475 #if len(request.user.siteprivileges.filter(role__role = 'pi')) > 0:
Scott Bakerb6043c22015-05-19 16:39:48 -07001476 # only admins and pis can change a user's site
Tony Mack933b2912015-01-28 12:49:58 -05001477 # self.readonly_fields = ('backend_status_text', 'site')
Tony Mack03b92292015-01-28 12:37:12 -05001478 self.fieldsets = (
1479 ('Login Details', {'fields': login_details_fields, 'classes':['suit-tab suit-tab-general']}),
1480 ('Contact Information', {'fields': self.fieldListContactInfo, 'classes':['suit-tab suit-tab-contact']}),
1481 )
Tony Mackc8f443d2015-01-25 21:58:30 -05001482 return super(UserAdmin, self).get_form(request, obj, **kwargs)
1483
Scott Baker67db95f2015-02-18 15:50:11 -08001484class ControllerDashboardViewInline(XOSTabularInline):
Scott Bakerf2c0c512014-12-22 17:35:34 -08001485 model = ControllerDashboardView
Scott Baker786a9c12014-12-19 16:41:12 -08001486 extra = 0
1487 fields = ["controller", "url"]
1488 suit_classes = 'suit-tab suit-tab-controllers'
1489
Scott Baker67db95f2015-02-18 15:50:11 -08001490class DashboardViewAdmin(XOSBaseAdmin):
Scott Baker2c3cb642014-05-19 17:55:56 -07001491 fieldsets = [('Dashboard View Details',
Scott Baker59248182015-02-17 13:34:32 -08001492 {'fields': ['backend_status_text', 'name', 'url', 'enabled', 'deployments'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001493 'classes': ['suit-tab suit-tab-general']})
1494 ]
Scott Baker2c44e6e2015-01-18 16:46:26 -08001495 list_display = ["name", "enabled", "url"]
Scott Baker40c00762014-08-21 16:55:59 -07001496 readonly_fields = ('backend_status_text', )
Scott Bakerf2c0c512014-12-22 17:35:34 -08001497 inlines = [ControllerDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001498
Scott Baker786a9c12014-12-19 16:41:12 -08001499 suit_form_tabs =(('general','Dashboard View Details'),
1500 ('controllers', 'Per-controller Dashboard Details'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001501
Scott Baker67db95f2015-02-18 15:50:11 -08001502class ServiceResourceInline(XOSTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001503 model = ServiceResource
1504 extra = 0
1505
Scott Baker67db95f2015-02-18 15:50:11 -08001506class ServiceClassAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001507 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1508 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001509 inlines = [ServiceResourceInline]
1510
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001511 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1512 user_readonly_inlines = []
1513
Scott Baker67db95f2015-02-18 15:50:11 -08001514class ReservedResourceInline(XOSTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001515 model = ReservedResource
1516 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001517 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001518
1519 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1520 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1521
1522 if db_field.name == 'resource':
1523 # restrict resources to those that the slice's service class allows
1524 if request._slice is not None:
1525 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1526 if len(field.queryset) > 0:
1527 field.initial = field.queryset.all()[0]
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -05001528 else:
1529 field.queryset = field.queryset.none()
1530 elif db_field.name == 'sliver':
1531 # restrict slivers to those that belong to the slice
1532 if request._slice is not None:
Scott Baker133c9212013-05-17 09:09:11 -07001533 field.queryset = field.queryset.filter(slice = request._slice)
1534 else:
S.Çağlar Onurb6e63f02015-02-24 17:28:09 -05001535 field.queryset = field.queryset.none()
1536
Scott Baker133c9212013-05-17 09:09:11 -07001537 return field
1538
Tony Mack5b061472014-02-04 07:57:10 -05001539 def queryset(self, request):
1540 return ReservedResource.select_by_user(request.user)
1541
Scott Baker133c9212013-05-17 09:09:11 -07001542class ReservationChangeForm(forms.ModelForm):
1543 class Meta:
1544 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001545 widgets = {
1546 'slice' : LinkedSelect
1547 }
Scott Baker133c9212013-05-17 09:09:11 -07001548
1549class ReservationAddForm(forms.ModelForm):
1550 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1551 refresh = forms.CharField(widget=forms.HiddenInput())
1552
1553 class Media:
Scott Baker06868952015-02-18 15:15:58 -08001554 css = {'all': ('xos.css',)} # .field-refresh { display: none; }
Scott Baker133c9212013-05-17 09:09:11 -07001555
1556 def clean_slice(self):
1557 slice = self.cleaned_data.get("slice")
1558 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1559 if len(x) == 0:
1560 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1561 return slice
1562
1563 class Meta:
1564 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001565 widgets = {
1566 'slice' : LinkedSelect
1567 }
1568
Scott Baker133c9212013-05-17 09:09:11 -07001569
1570class ReservationAddRefreshForm(ReservationAddForm):
1571 """ This form is displayed when the Reservation Form receives an update
1572 from the Slice dropdown onChange handler. It doesn't validate the
1573 data and doesn't save the data. This will cause the form to be
1574 redrawn.
1575 """
1576
Scott Baker8737e5f2013-05-17 09:35:32 -07001577 """ don't validate anything other than slice """
1578 dont_validate_fields = ("startTime", "duration")
1579
Scott Baker133c9212013-05-17 09:09:11 -07001580 def full_clean(self):
1581 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001582
1583 for fieldname in self.dont_validate_fields:
1584 if fieldname in self._errors:
1585 del self._errors[fieldname]
1586
Scott Baker133c9212013-05-17 09:09:11 -07001587 return result
1588
1589 """ don't save anything """
1590 def is_valid(self):
1591 return False
1592
Scott Baker67db95f2015-02-18 15:50:11 -08001593class ReservationAdmin(XOSBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001594 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001595 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001596 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001597 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001598 form = ReservationAddForm
1599
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001600 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1601
1602 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001603 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001604
Scott Baker133c9212013-05-17 09:09:11 -07001605 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001606 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001607 request._refresh = False
1608 request._slice = None
1609 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001610 # "refresh" will be set to "1" if the form was submitted due to
1611 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001612 if request.POST.get("refresh","1") == "1":
1613 request._refresh = True
1614 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001615
1616 # Keep track of the slice that was selected, so the
1617 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001618 request._slice = request.POST.get("slice",None)
1619 if (request._slice is not None):
1620 request._slice = Slice.objects.get(id=request._slice)
1621
1622 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1623 return result
1624
Scott Bakeracd45142013-05-19 16:19:16 -07001625 def changelist_view(self, request, extra_context = None):
1626 timezone.activate(request.user.timezone)
1627 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1628
Scott Baker133c9212013-05-17 09:09:11 -07001629 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001630 request._obj_ = obj
1631 if obj is not None:
1632 # For changes, set request._slice to the slice already set in the
1633 # object.
1634 request._slice = obj.slice
1635 self.form = ReservationChangeForm
1636 else:
1637 if getattr(request, "_refresh", False):
1638 self.form = ReservationAddRefreshForm
1639 else:
1640 self.form = ReservationAddForm
1641 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1642
Scott Baker133c9212013-05-17 09:09:11 -07001643 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001644 if (obj is not None):
1645 # Prevent slice from being changed after the reservation has been
1646 # created.
1647 return ['slice']
1648 else:
Scott Baker133c9212013-05-17 09:09:11 -07001649 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001650
Tony Mack5b061472014-02-04 07:57:10 -05001651 def queryset(self, request):
1652 return Reservation.select_by_user(request.user)
1653
Scott Baker67db95f2015-02-18 15:50:11 -08001654class NetworkParameterTypeAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001655 list_display = ("backend_status_icon", "name", )
1656 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001657 user_readonly_fields = ['name']
1658 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001659
Scott Baker67db95f2015-02-18 15:50:11 -08001660class RouterAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001661 list_display = ("backend_status_icon", "name", )
1662 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001663 user_readonly_fields = ['name']
1664 user_readonly_inlines = []
1665
Scott Baker67db95f2015-02-18 15:50:11 -08001666class RouterInline(XOSTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001667 model = Router.networks.through
1668 extra = 0
1669 verbose_name_plural = "Routers"
1670 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001671 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001672
Scott Bakerb27b62c2014-08-15 16:29:16 -07001673class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001674 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001675 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001676 verbose_name_plural = "Parameters"
1677 verbose_name = "Parameter"
1678 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001679 fields = ['backend_status_icon', 'parameter', 'value']
1680 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001681
Scott Baker67db95f2015-02-18 15:50:11 -08001682class NetworkSliversInline(XOSTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001683 fields = ['backend_status_icon', 'network','sliver','ip']
1684 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001685 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001686 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001687 extra = 0
1688 verbose_name_plural = "Slivers"
1689 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001690 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001691
Scott Baker67db95f2015-02-18 15:50:11 -08001692class NetworkSlicesInline(XOSTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001693 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001694 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001695 extra = 0
1696 verbose_name_plural = "Slices"
1697 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001698 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001699 fields = ['backend_status_icon', 'network','slice']
1700 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001701
Scott Baker67db95f2015-02-18 15:50:11 -08001702class ControllerNetworkInline(XOSTabularInline):
Tony Mack3066a952015-01-05 22:48:11 -05001703 model = ControllerNetwork
Scott Baker8806cdf2014-10-17 16:27:23 -07001704 extra = 0
Tony Mack06c8e472014-11-30 15:53:08 -05001705 verbose_name_plural = "Controller Networks"
1706 verbose_name = "Controller Network"
Scott Baker8806cdf2014-10-17 16:27:23 -07001707 suit_classes = 'suit-tab suit-tab-admin-only'
Tony Mack06c8e472014-11-30 15:53:08 -05001708 fields = ['backend_status_icon', 'controller','net_id','subnet_id']
Scott Baker8806cdf2014-10-17 16:27:23 -07001709 readonly_fields = ('backend_status_icon', )
1710
Scott Baker69e045d2014-11-17 23:44:03 -08001711class NetworkForm(forms.ModelForm):
1712 class Meta:
1713 model = Network
1714 widgets = {
1715 'topologyParameters': UploadTextareaWidget,
1716 'controllerParameters': UploadTextareaWidget,
1717 }
1718
Scott Baker67db95f2015-02-18 15:50:11 -08001719class NetworkAdmin(XOSBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001720 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1721 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001722 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001723
Scott Bakerd7d2a392013-08-06 08:57:30 -07001724 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Tony Mack3066a952015-01-05 22:48:11 -05001725 admin_inlines = [ControllerNetworkInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001726
Scott Baker69e045d2014-11-17 23:44:03 -08001727 form=NetworkForm
1728
Siobhan Tully2d95e482013-09-06 10:56:06 -04001729 fieldsets = [
Scott Baker549aa252015-01-03 12:29:29 -08001730 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet'],
Scott Baker3e28dd72014-11-17 16:04:45 -08001731 'classes':['suit-tab suit-tab-general']}),
Scott Baker549aa252015-01-03 12:29:29 -08001732 (None, {'fields': ['topology_parameters', 'controller_url', 'controller_parameters'],
Scott Baker3e28dd72014-11-17 16:04:45 -08001733 'classes':['suit-tab suit-tab-sdn']}),
1734 ]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001735
Scott Baker40c00762014-08-21 16:55:59 -07001736 readonly_fields = ('backend_status_text', )
Scott Baker549aa252015-01-03 12:29:29 -08001737 user_readonly_fields = ['name','template','ports','labels','owner','guaranteed_bandwidth', 'permit_all_slices','permitted_slices','network_id','router_id','subnet_id','subnet']
Siobhan Tully2d95e482013-09-06 10:56:06 -04001738
Scott Baker8806cdf2014-10-17 16:27:23 -07001739 @property
1740 def suit_form_tabs(self):
1741 tabs=[('general','Network Details'),
Scott Baker3e28dd72014-11-17 16:04:45 -08001742 ('sdn', 'SDN Configuration'),
Scott Baker8806cdf2014-10-17 16:27:23 -07001743 ('netparams', 'Parameters'),
1744 ('networkslivers','Slivers'),
1745 ('networkslices','Slices'),
1746 ('routers','Routers'),
1747 ]
1748
1749 request=getattr(_thread_locals, "request", None)
1750 if request and request.user.is_admin:
1751 tabs.append( ('admin-only', 'Admin-Only') )
1752
1753 return tabs
1754
1755
Scott Baker67db95f2015-02-18 15:50:11 -08001756class NetworkTemplateAdmin(XOSBaseAdmin):
Scott Baker369f9b92015-01-03 12:03:38 -08001757 list_display = ("backend_status_icon", "name", "guaranteed_bandwidth", "visibility")
Scott Baker63d1a552014-08-21 15:19:07 -07001758 list_display_links = ('backend_status_icon', 'name', )
Scott Baker369f9b92015-01-03 12:03:38 -08001759 user_readonly_fields = ["name", "guaranteed_bandwidth", "visibility"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001760 user_readonly_inlines = []
Scott Baker3e28dd72014-11-17 16:04:45 -08001761 fieldsets = [
Scott Baker369f9b92015-01-03 12:03:38 -08001762 (None, {'fields': ['name', 'description', 'guaranteed_bandwidth', 'visibility', 'translation', 'shared_network_name', 'shared_network_id', 'topology_kind', 'controller_kind'],
Scott Baker3e28dd72014-11-17 16:04:45 -08001763 'classes':['suit-tab suit-tab-general']}),]
1764 suit_form_tabs = (('general','Network Template Details'), )
Scott Baker74d8e622013-07-29 16:04:22 -07001765
Scott Baker67db95f2015-02-18 15:50:11 -08001766class FlavorAdmin(XOSBaseAdmin):
Scott Baker37b47902014-09-02 14:37:41 -07001767 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1768 list_display_links = ("backend_status_icon", "name")
1769 user_readonly_fields = ("name", "flavor")
1770 fields = ("name", "description", "flavor", "order", "default")
1771
Tony Mack31c2b8f2013-04-26 20:01:42 -04001772# register a signal that caches the user's credentials when they log in
1773def cache_credentials(sender, user, request, **kwds):
1774 auth = {'username': request.POST['username'],
1775 'password': request.POST['password']}
1776 request.session['auth'] = auth
1777user_logged_in.connect(cache_credentials)
1778
Scott Baker15cddfa2013-12-09 13:45:19 -08001779def dollar_field(fieldName, short_description):
1780 def newFunc(self, obj):
1781 try:
1782 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1783 except:
1784 x=getattr(obj, fieldName, 0.0)
1785 return x
1786 newFunc.short_description = short_description
1787 return newFunc
1788
1789def right_dollar_field(fieldName, short_description):
1790 def newFunc(self, obj):
1791 try:
1792 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1793 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1794 except:
1795 x=getattr(obj, fieldName, 0.0)
1796 return x
1797 newFunc.short_description = short_description
1798 newFunc.allow_tags = True
1799 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001800
Scott Baker67db95f2015-02-18 15:50:11 -08001801class InvoiceChargeInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001802 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001803 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001804 verbose_name_plural = "Charges"
1805 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001806 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001807 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1808 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1809 can_delete = False
1810 max_num = 0
1811
1812 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001813
1814class InvoiceAdmin(admin.ModelAdmin):
1815 list_display = ("date", "account")
1816
1817 inlines = [InvoiceChargeInline]
1818
Scott Baker9cb88a22013-12-09 18:56:00 -08001819 fields = ["date", "account", "dollar_amount"]
1820 readonly_fields = ["date", "account", "dollar_amount"]
1821
1822 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001823
Scott Baker67db95f2015-02-18 15:50:11 -08001824class InvoiceInline(XOSTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001825 model = Invoice
1826 extra = 0
1827 verbose_name_plural = "Invoices"
1828 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001829 fields = ["date", "dollar_amount"]
1830 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001831 suit_classes = 'suit-tab suit-tab-accountinvoice'
1832 can_delete=False
1833 max_num=0
1834
1835 dollar_amount = right_dollar_field("amount", "Amount")
1836
Scott Baker67db95f2015-02-18 15:50:11 -08001837class PendingChargeInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001838 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001839 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001840 verbose_name_plural = "Charges"
1841 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001842 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001843 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1844 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001845 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001846 can_delete=False
1847 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001848
1849 def queryset(self, request):
1850 qs = super(PendingChargeInline, self).queryset(request)
1851 qs = qs.filter(state="pending")
1852 return qs
1853
Scott Baker15cddfa2013-12-09 13:45:19 -08001854 dollar_amount = right_dollar_field("amount", "Amount")
1855
Scott Baker67db95f2015-02-18 15:50:11 -08001856class PaymentInline(XOSTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001857 model=Payment
1858 extra = 1
1859 verbose_name_plural = "Payments"
1860 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001861 fields = ["date", "dollar_amount"]
1862 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001863 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001864 can_delete=False
1865 max_num=0
1866
1867 dollar_amount = right_dollar_field("amount", "Amount")
1868
Scott Baker43105042013-12-06 23:23:36 -08001869class AccountAdmin(admin.ModelAdmin):
1870 list_display = ("site", "balance_due")
1871
1872 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1873
1874 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001875 (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 -08001876
Scott Baker15cddfa2013-12-09 13:45:19 -08001877 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001878
1879 suit_form_tabs =(
1880 ('general','Account Details'),
1881 ('accountinvoice', 'Invoices'),
1882 ('accountpayments', 'Payments'),
1883 ('accountpendingcharges','Pending Charges'),
1884 )
1885
Scott Baker15cddfa2013-12-09 13:45:19 -08001886 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1887 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1888 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1889
Siobhan Tully53437282013-04-26 19:30:27 -04001890# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001891admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001892# ... and, since we're not using Django's builtin permissions,
1893# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001894#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001895
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001896#Do not show django evolution in the admin interface
1897from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001898#admin.site.unregister(Version)
1899#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001900
1901
1902# When debugging it is often easier to see all the classes, but for regular use
1903# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001904showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001905
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001906admin.site.register(Deployment, DeploymentAdmin)
Tony Mack06c8e472014-11-30 15:53:08 -05001907admin.site.register(Controller, ControllerAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001908admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001909admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001910admin.site.register(Service, ServiceAdmin)
Tony Mack450b6e02015-01-25 12:35:29 -05001911#admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001912admin.site.register(Network, NetworkAdmin)
1913admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001914admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Tony Mack450b6e02015-01-25 12:35:29 -05001915#admin.site.register(Account, AccountAdmin)
1916#admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001917
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001918if True:
1919 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1920 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001921 admin.site.register(Tag, TagAdmin)
Tony Mack06c8e472014-11-30 15:53:08 -05001922 admin.site.register(ControllerRole)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001923 admin.site.register(SiteRole)
1924 admin.site.register(SliceRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001925 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001926 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1927 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001928 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001929 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001930 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07001931 admin.site.register(Flavor, FlavorAdmin)
Scott Bakerb3cf9212015-07-06 14:40:20 -07001932 admin.site.register(TenantRoot, TenantRootAdmin)
1933 admin.site.register(TenantRootRole, TenantRootRoleAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001934