blob: 9ea1c361a1cf97fc7e6f8c1dd73a138b669ea14d [file] [log] [blame]
Siobhan Tully30fd4292013-05-10 08:59:56 -04001from core.models import Site
2from core.models import *
3from openstack.manager import OpenStackManager
Tony Macke59a7c82013-04-27 11:08:10 -04004
Tony Mack7130ac32013-03-22 21:58:00 -04005from django.contrib import admin
Siobhan Tully53437282013-04-26 19:30:27 -04006from django.contrib.auth.models import Group
Siobhan Tully4bc09f22013-04-10 21:15:21 -04007from django import forms
Tony Mackd90cdbf2013-04-16 22:48:40 -04008from django.utils.safestring import mark_safe
Tony Mack7130ac32013-03-22 21:58:00 -04009from django.contrib.auth.admin import UserAdmin
Scott Baker9f6b8ed2014-11-17 23:44:03 -080010from django.contrib.admin.widgets import FilteredSelectMultiple, AdminTextareaWidget
Scott Bakercbfb6002014-10-03 00:32:37 -070011from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
Scott Bakeracd45142013-05-19 16:19:16 -070012from django.contrib.auth.signals import user_logged_in
13from django.utils import timezone
Siobhan Tullyde5450d2013-06-21 11:35:33 -040014from django.contrib.contenttypes import generic
Siobhan Tullybfd11dc2013-09-03 12:59:24 -040015from suit.widgets import LinkedSelect
Siobhan Tullycf04fb62014-01-11 11:25:57 -050016from django.core.exceptions import PermissionDenied
Scott Bakere2bbf7e2014-01-13 12:09:31 -080017from django.core.urlresolvers import reverse, NoReverseMatch
Scott Baker9f6b8ed2014-11-17 23:44:03 -080018from django.utils.encoding import force_text, python_2_unicode_compatible
19from django.utils.html import conditional_escape, format_html
20from django.forms.utils import flatatt, to_current_timezone
Scott Baker3cde7372014-10-21 21:03:08 -070021from cgi import escape as html_escape
Tony Mack7130ac32013-03-22 21:58:00 -040022
Scott Baker36f50872014-08-21 13:01:25 -070023import django_evolution
Scott Baker0a5633b2014-10-06 17:51:20 -070024import threading
25
26# thread locals necessary to work around a django-suit issue
27_thread_locals = threading.local()
Scott Baker36f50872014-08-21 13:01:25 -070028
Scott Baker40c00762014-08-21 16:55:59 -070029def backend_icon(obj): # backend_status, enacted, updated):
30 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
31 if (obj.enacted is not None) and obj.enacted >= obj.updated:
Scott Bakerfbf06642014-09-09 10:38:15 -070032 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
Scott Baker40c00762014-08-21 16:55:59 -070033 else:
34 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
Scott Bakerfbf06642014-09-09 10:38:15 -070035 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
Scott Baker63d1a552014-08-21 15:19:07 -070036 else:
Scott Baker3cde7372014-10-21 21:03:08 -070037 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % html_escape(obj.backend_status, quote=True)
Scott Baker40c00762014-08-21 16:55:59 -070038
39def backend_text(obj):
40 icon = backend_icon(obj)
41 if (obj.enacted is not None) and obj.enacted >= obj.updated:
Scott Baker3cde7372014-10-21 21:03:08 -070042 return "%s %s" % (icon, "successfully enacted")
Scott Baker40c00762014-08-21 16:55:59 -070043 else:
Scott Baker3cde7372014-10-21 21:03:08 -070044 return "%s %s" % (icon, html_escape(obj.backend_status, quote=True))
Scott Baker63d1a552014-08-21 15:19:07 -070045
Scott Baker9f6b8ed2014-11-17 23:44:03 -080046class UploadTextareaWidget(AdminTextareaWidget):
47 def render(self, name, value, attrs=None):
48 if value is None:
49 value = ''
50 final_attrs = self.build_attrs(attrs, name=name)
51 return format_html('<input type="file" style="width: 0; height: 0" id="btn_upload_%s" onChange="uploadTextarea(event,\'%s\');">' \
52 '<button onClick="$(\'#btn_upload_%s\').click(); return false;">Upload</button>' \
53 '<br><textarea{0}>\r\n{1}</textarea>' % (attrs["id"], attrs["id"], attrs["id"]),
54 flatatt(final_attrs),
55 force_text(value))
56
Scott Baker36f50872014-08-21 13:01:25 -070057class PlainTextWidget(forms.HiddenInput):
58 input_type = 'hidden'
59
60 def render(self, name, value, attrs=None):
61 if value is None:
62 value = ''
63 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
64
Scott Baker86c83ab2014-10-03 13:10:47 -070065class PermissionCheckingAdminMixin(object):
Scott Bakercbfb6002014-10-03 00:32:37 -070066 # call save_by_user and delete_by_user instead of save and delete
Siobhan Tullycf04fb62014-01-11 11:25:57 -050067
68 def has_add_permission(self, request, obj=None):
69 return (not self.__user_is_readonly(request))
Scott Baker36f50872014-08-21 13:01:25 -070070
Siobhan Tullycf04fb62014-01-11 11:25:57 -050071 def has_delete_permission(self, request, obj=None):
72 return (not self.__user_is_readonly(request))
73
74 def save_model(self, request, obj, form, change):
75 if self.__user_is_readonly(request):
Scott Bakercbfb6002014-10-03 00:32:37 -070076 # this 'if' might be redundant if save_by_user is implemented right
Siobhan Tullycf04fb62014-01-11 11:25:57 -050077 raise PermissionDenied
Scott Bakercbfb6002014-10-03 00:32:37 -070078
79 obj.caller = request.user
80 # update openstack connection to use this site/tenant
81 obj.save_by_user(request.user)
82
83 def delete_model(self, request, obj):
84 obj.delete_by_user(request.user)
85
86 def save_formset(self, request, form, formset, change):
87 instances = formset.save(commit=False)
88 for instance in instances:
89 instance.save_by_user(request.user)
90
91 # BUG in django 1.7? Objects are not deleted by formset.save if
92 # commit is False. So let's delete them ourselves.
93 #
94 # code from forms/models.py save_existing_objects()
95 try:
96 forms_to_delete = formset.deleted_forms
97 except AttributeError:
98 forms_to_delete = []
99 if formset.initial_forms:
100 for form in formset.initial_forms:
101 obj = form.instance
102 if form in forms_to_delete:
103 if obj.pk is None:
104 continue
105 formset.deleted_objects.append(obj)
106 obj.delete()
107
108 formset.save_m2m()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500109
110 def get_actions(self,request):
Scott Baker86c83ab2014-10-03 13:10:47 -0700111 actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500112
113 if self.__user_is_readonly(request):
114 if 'delete_selected' in actions:
115 del actions['delete_selected']
116
117 return actions
118
119 def change_view(self,request,object_id, extra_context=None):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500120 if self.__user_is_readonly(request):
Scott Bakeraf73e102014-04-22 22:40:07 -0700121 if not hasattr(self, "readonly_save"):
122 # save the original readonly fields
123 self.readonly_save = self.readonly_fields
124 self.inlines_save = self.inlines
Scott Bakere8859f92014-05-23 12:42:40 -0700125 if hasattr(self, "user_readonly_fields"):
126 self.readonly_fields=self.user_readonly_fields
127 if hasattr(self, "user_readonly_inlines"):
128 self.inlines = self.user_readonly_inlines
Scott Bakeraf73e102014-04-22 22:40:07 -0700129 else:
130 if hasattr(self, "readonly_save"):
131 # restore the original readonly fields
132 self.readonly_fields = self.readonly_save
Scott Bakere8859f92014-05-23 12:42:40 -0700133 if hasattr(self, "inlines_save"):
Scott Bakeraf73e102014-04-22 22:40:07 -0700134 self.inlines = self.inlines_save
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500135
136 try:
Scott Baker86c83ab2014-10-03 13:10:47 -0700137 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500138 except PermissionDenied:
139 pass
140 if request.method == 'POST':
141 raise PermissionDenied
142 request.readonly = True
Scott Baker86c83ab2014-10-03 13:10:47 -0700143 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500144
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500145 def __user_is_readonly(self, request):
146 return request.user.isReadOnlyUser()
147
Scott Baker40c00762014-08-21 16:55:59 -0700148 def backend_status_text(self, obj):
149 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -0700150
Scott Baker63d1a552014-08-21 15:19:07 -0700151 def backend_status_icon(self, obj):
Scott Baker40c00762014-08-21 16:55:59 -0700152 return mark_safe(backend_icon(obj))
Scott Baker63d1a552014-08-21 15:19:07 -0700153 backend_status_icon.short_description = ""
154
Scott Baker24ded6a2014-11-05 09:05:38 -0800155 def get_form(self, request, obj=None, **kwargs):
Scott Baker5c432692014-10-16 00:57:55 -0700156 # Save obj and request in thread-local storage, so suit_form_tabs can
157 # use it to determine whether we're in edit or add mode, and can
158 # determine whether the user is an admin.
159 _thread_locals.request = request
160 _thread_locals.obj = obj
Scott Baker24ded6a2014-11-05 09:05:38 -0800161 return super(PermissionCheckingAdminMixin, self).get_form(request, obj, **kwargs)
Scott Baker5c432692014-10-16 00:57:55 -0700162
163 def get_inline_instances(self, request, obj=None):
164 inlines = super(PermissionCheckingAdminMixin, self).get_inline_instances(request, obj)
165
166 # inlines that should only be shown to an admin user
167 if request.user.is_admin:
168 for inline_class in getattr(self, "admin_inlines", []):
169 inlines.append(inline_class(self.model, self.admin_site))
170
171 return inlines
172
Scott Baker86c83ab2014-10-03 13:10:47 -0700173class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
174 # Note: Make sure PermissionCheckingAdminMixin is listed before
175 # admin.ModelAdmin in the class declaration.
176
Scott Bakercbfb6002014-10-03 00:32:37 -0700177 pass
178
179class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
180 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700181
Scott Bakere8859f92014-05-23 12:42:40 -0700182class SingletonAdmin (ReadOnlyAwareAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400183 def has_add_permission(self, request):
Scott Bakere8859f92014-05-23 12:42:40 -0700184 if not super(SingletonAdmin, self).has_add_permission(request):
185 return False
186
Siobhan Tullyce652d02013-10-08 21:52:35 -0400187 num_objects = self.model.objects.count()
188 if num_objects >= 1:
189 return False
190 else:
191 return True
192
Siobhan Tullyd3515752013-06-21 16:34:53 -0400193class PlStackTabularInline(admin.TabularInline):
Scott Baker86568322014-01-12 16:53:31 -0800194 def __init__(self, *args, **kwargs):
195 super(PlStackTabularInline, self).__init__(*args, **kwargs)
196
197 # InlineModelAdmin as no get_fields() method, so in order to add
198 # the selflink field, we override __init__ to modify self.fields and
199 # self.readonly_fields.
200
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800201 self.setup_selflink()
202
Scott Baker874936e2014-01-13 18:15:34 -0800203 def get_change_url(self, model, id):
204 """ Get the URL to a change form in the admin for this model """
205 reverse_path = "admin:%s_change" % (model._meta.db_table)
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800206 try:
Scott Baker874936e2014-01-13 18:15:34 -0800207 url = reverse(reverse_path, args=(id,))
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800208 except NoReverseMatch:
Scott Baker874936e2014-01-13 18:15:34 -0800209 return None
210
211 return url
212
213 def setup_selflink(self):
214 if hasattr(self, "selflink_fieldname"):
215 """ self.selflink_model can be defined to punch through a relation
216 to its target object. For example, in SliceNetworkInline, set
217 selflink_model = "network", and the URL will lead to the Network
218 object instead of trying to bring up a change view of the
219 SliceNetwork object.
220 """
221 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
222 else:
223 self.selflink_model = self.model
224
225 url = self.get_change_url(self.selflink_model, 0)
226
227 # We don't have an admin for this object, so don't create the
228 # selflink.
229 if (url == None):
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800230 return
231
Scott Baker874936e2014-01-13 18:15:34 -0800232 # Since we need to add "selflink" to the field list, we need to create
233 # self.fields if it is None.
Scott Baker0165fac2014-01-13 11:49:26 -0800234 if (self.fields is None):
235 self.fields = []
236 for f in self.model._meta.fields:
237 if f.editable and f.name != "id":
238 self.fields.append(f.name)
Scott Baker86568322014-01-12 16:53:31 -0800239
Scott Baker874936e2014-01-13 18:15:34 -0800240 self.fields = tuple(self.fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800241
Scott Baker874936e2014-01-13 18:15:34 -0800242 if self.readonly_fields is None:
243 self.readonly_fields = ()
Scott Baker86568322014-01-12 16:53:31 -0800244
Scott Baker874936e2014-01-13 18:15:34 -0800245 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800246
247 def selflink(self, obj):
Scott Baker874936e2014-01-13 18:15:34 -0800248 if hasattr(self, "selflink_fieldname"):
249 obj = getattr(obj, self.selflink_fieldname)
250
Scott Baker86568322014-01-12 16:53:31 -0800251 if obj.id:
Scott Baker874936e2014-01-13 18:15:34 -0800252 url = self.get_change_url(self.selflink_model, obj.id)
253 return "<a href='%s'>Details</a>" % str(url)
Scott Baker86568322014-01-12 16:53:31 -0800254 else:
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800255 return "Not present"
Scott Baker86568322014-01-12 16:53:31 -0800256
257 selflink.allow_tags = True
258 selflink.short_description = "Details"
Siobhan Tullyd3515752013-06-21 16:34:53 -0400259
Scott Bakerb27b62c2014-08-15 16:29:16 -0700260 def has_add_permission(self, request):
261 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500262
263 def get_readonly_fields(self, request, obj=None):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700264 readonly_fields = list(self.readonly_fields)[:]
265 if request.user.isReadOnlyUser():
266 for field in self.fields:
267 if not field in readonly_fields:
268 readonly_fields.append(field)
269 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500270
Scott Baker40c00762014-08-21 16:55:59 -0700271 def backend_status_icon(self, obj):
272 return mark_safe(backend_icon(obj))
273 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -0700274
Scott Bakerb27b62c2014-08-15 16:29:16 -0700275class PlStackGenericTabularInline(generic.GenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500276 def has_add_permission(self, request):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700277 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500278
Scott Bakerb27b62c2014-08-15 16:29:16 -0700279 def get_readonly_fields(self, request, obj=None):
280 readonly_fields = list(self.readonly_fields)[:]
281 if request.user.isReadOnlyUser():
282 for field in self.fields:
283 if not field in readonly_fields:
284 readonly_fields.append(field)
285 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500286
Scott Baker40c00762014-08-21 16:55:59 -0700287 def backend_status_icon(self, obj):
288 return mark_safe(backend_icon(obj))
289 backend_status_icon.short_description = ""
290
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400291class ReservationInline(PlStackTabularInline):
292 model = Reservation
293 extra = 0
294 suit_classes = 'suit-tab suit-tab-reservations'
Scott Baker36f50872014-08-21 13:01:25 -0700295
Tony Mack5b061472014-02-04 07:57:10 -0500296 def queryset(self, request):
297 return Reservation.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400298
Scott Bakerb27b62c2014-08-15 16:29:16 -0700299class TagInline(PlStackGenericTabularInline):
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400300 model = Tag
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400301 extra = 0
302 suit_classes = 'suit-tab suit-tab-tags'
Tony Mack5b061472014-02-04 07:57:10 -0500303 fields = ['service', 'name', 'value']
304
305 def queryset(self, request):
306 return Tag.select_by_user(request.user)
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400307
Scott Baker74d8e622013-07-29 16:04:22 -0700308class NetworkLookerUpper:
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400309 """ This is a callable that looks up a network name in a sliver and returns
310 the ip address for that network.
311 """
312
Scott Baker434ca7e2014-08-15 12:29:20 -0700313 byNetworkName = {} # class variable
314
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400315 def __init__(self, name):
316 self.short_description = name
317 self.__name__ = name
318 self.network_name = name
319
320 def __call__(self, obj):
321 if obj is not None:
322 for nbs in obj.networksliver_set.all():
323 if (nbs.network.name == self.network_name):
324 return nbs.ip
Scott Baker74d8e622013-07-29 16:04:22 -0700325 return ""
326
327 def __str__(self):
328 return self.network_name
329
Scott Baker434ca7e2014-08-15 12:29:20 -0700330 @staticmethod
331 def get(network_name):
332 """ We want to make sure we alwars return the same NetworkLookerUpper
333 because sometimes django will cause them to be instantiated multiple
334 times (and we don't want different ones in form.fields vs
335 SliverInline.readonly_fields).
336 """
337 if network_name not in NetworkLookerUpper.byNetworkName:
338 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
339 return NetworkLookerUpper.byNetworkName[network_name]
340
Siobhan Tullyd3515752013-06-21 16:34:53 -0400341class SliverInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400342 model = Sliver
Scott Baker7a61dc42014-09-02 17:08:20 -0700343 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400344 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700345 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400346 suit_classes = 'suit-tab suit-tab-slivers'
Scott Baker74d8e622013-07-29 16:04:22 -0700347
Tony Mack5b061472014-02-04 07:57:10 -0500348 def queryset(self, request):
349 return Sliver.select_by_user(request.user)
350
Scott Bakerb24cc932014-06-09 10:51:16 -0700351 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Scott Bakerb24cc932014-06-09 10:51:16 -0700352 if db_field.name == 'deploymentNetwork':
Scott Baker3b678742014-06-09 13:11:54 -0700353 kwargs['queryset'] = Deployment.select_by_acl(request.user)
Scott Baker7a61dc42014-09-02 17:08:20 -0700354 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
Scott Baker32481312014-09-08 12:14:14 -0700355 elif db_field.name == 'flavor':
356 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
Scott Baker3b678742014-06-09 13:11:54 -0700357
358 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700359
360 return field
361
Siobhan Tullyd3515752013-06-21 16:34:53 -0400362class SiteInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400363 model = Site
364 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400365 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400366
Tony Mack5b061472014-02-04 07:57:10 -0500367 def queryset(self, request):
368 return Site.select_by_user(request.user)
369
Siobhan Tullyd3515752013-06-21 16:34:53 -0400370class UserInline(PlStackTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400371 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700372 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
373 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400374 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400375 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400376
Tony Mack5b061472014-02-04 07:57:10 -0500377 def queryset(self, request):
378 return User.select_by_user(request.user)
379
Siobhan Tullyd3515752013-06-21 16:34:53 -0400380class SliceInline(PlStackTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400381 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700382 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
383 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400384 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400385 suit_classes = 'suit-tab suit-tab-slices'
386
Tony Mack5b061472014-02-04 07:57:10 -0500387 def queryset(self, request):
388 return Slice.select_by_user(request.user)
389
Siobhan Tullyd3515752013-06-21 16:34:53 -0400390class NodeInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400391 model = Node
392 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400393 suit_classes = 'suit-tab suit-tab-nodes'
Scott Baker40c00762014-08-21 16:55:59 -0700394 fields = ['backend_status_icon', 'name','deployment','site']
395 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400396
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400397class DeploymentPrivilegeInline(PlStackTabularInline):
398 model = DeploymentPrivilege
399 extra = 0
400 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700401 fields = ['backend_status_icon', 'user','role','deployment']
402 readonly_fields = ('backend_status_icon', )
Tony Mack5b061472014-02-04 07:57:10 -0500403
404 def queryset(self, request):
405 return DeploymentPrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400406
Siobhan Tullyd3515752013-06-21 16:34:53 -0400407class SitePrivilegeInline(PlStackTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400408 model = SitePrivilege
409 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400410 suit_classes = 'suit-tab suit-tab-siteprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700411 fields = ['backend_status_icon', 'user','site', 'role']
412 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400413
Tony Mackc2835a92013-05-28 09:18:49 -0400414 def formfield_for_foreignkey(self, db_field, request, **kwargs):
415 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500416 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400417
418 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500419 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400420 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
421
Tony Mack5b061472014-02-04 07:57:10 -0500422 def queryset(self, request):
423 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400424
Sapan Bhatia39e85582014-11-19 15:07:36 -0500425class SiteDeploymentsInline(PlStackTabularInline):
426 model = SiteDeployments
Tony Macke4be32f2014-03-11 20:45:25 -0400427 extra = 0
428 suit_classes = 'suit-tab suit-tab-deployments'
Scott Baker40c00762014-08-21 16:55:59 -0700429 fields = ['backend_status_icon', 'deployment','site']
430 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400431
432 def formfield_for_foreignkey(self, db_field, request, **kwargs):
433 if db_field.name == 'site':
434 kwargs['queryset'] = Site.select_by_user(request.user)
435
436 if db_field.name == 'deployment':
437 kwargs['queryset'] = Deployment.select_by_user(request.user)
Sapan Bhatia39e85582014-11-19 15:07:36 -0500438 return super(SiteDeploymentsInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Macke4be32f2014-03-11 20:45:25 -0400439
440 def queryset(self, request):
Sapan Bhatia39e85582014-11-19 15:07:36 -0500441 return SiteDeployments.select_by_user(request.user)
Tony Macke4be32f2014-03-11 20:45:25 -0400442
443
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400444class SlicePrivilegeInline(PlStackTabularInline):
445 model = SlicePrivilege
446 suit_classes = 'suit-tab suit-tab-sliceprivileges'
447 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700448 fields = ('backend_status_icon', 'user', 'slice', 'role')
449 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400450
Tony Mackc2835a92013-05-28 09:18:49 -0400451 def formfield_for_foreignkey(self, db_field, request, **kwargs):
452 if db_field.name == 'slice':
Scott Baker36f50872014-08-21 13:01:25 -0700453 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400454 if db_field.name == 'user':
Scott Baker36f50872014-08-21 13:01:25 -0700455 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400456
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400457 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400458
Tony Mack5b061472014-02-04 07:57:10 -0500459 def queryset(self, request):
460 return SlicePrivilege.select_by_user(request.user)
461
Scott Bakera0015eb2013-08-14 17:28:14 -0700462class SliceNetworkInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700463 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800464 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700465 extra = 0
466 verbose_name = "Network Connection"
467 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400468 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700469 fields = ['backend_status_icon', 'network']
470 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700471
Sapan Bhatiae9f96f62014-11-19 15:10:16 -0500472class ImageDeploymentsInline(PlStackTabularInline):
473 model = ImageDeployments
Scott Baker2170b972014-06-03 12:14:07 -0700474 extra = 0
475 verbose_name = "Image Deployments"
476 verbose_name_plural = "Image Deployments"
477 suit_classes = 'suit-tab suit-tab-imagedeployments'
Scott Baker40c00762014-08-21 16:55:59 -0700478 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
479 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700480
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400481class SliceRoleAdmin(PlanetStackBaseAdmin):
482 model = SliceRole
483 pass
484
485class SiteRoleAdmin(PlanetStackBaseAdmin):
486 model = SiteRole
487 pass
488
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400489class DeploymentAdminForm(forms.ModelForm):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400490 sites = forms.ModelMultipleChoiceField(
491 queryset=Site.objects.all(),
492 required=False,
Scott Baker70983182014-06-09 22:10:00 -0700493 help_text="Select which sites are allowed to host nodes in this deployment",
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400494 widget=FilteredSelectMultiple(
495 verbose_name=('Sites'), is_stacked=False
496 )
497 )
Scott Bakerde0f4412014-06-11 15:40:26 -0700498 images = forms.ModelMultipleChoiceField(
499 queryset=Image.objects.all(),
500 required=False,
501 help_text="Select which images should be deployed on this deployment",
502 widget=FilteredSelectMultiple(
503 verbose_name=('Images'), is_stacked=False
504 )
505 )
Scott Baker37b47902014-09-02 14:37:41 -0700506 flavors = forms.ModelMultipleChoiceField(
507 queryset=Flavor.objects.all(),
508 required=False,
509 help_text="Select which flavors should be usable on this deployment",
510 widget=FilteredSelectMultiple(
511 verbose_name=('Flavors'), is_stacked=False
512 )
513 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400514 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400515 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700516 many_to_many = ["flavors",]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400517
Siobhan Tully320b4622014-01-17 15:11:14 -0500518 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700519 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500520 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
521
Scott Baker5380c522014-06-06 14:49:43 -0700522 self.fields['accessControl'].initial = "allow site " + request.user.site.name
523
Siobhan Tully320b4622014-01-17 15:11:14 -0500524 if self.instance and self.instance.pk:
Scott Baker9f6b8ed2014-11-17 23:44:03 -0800525 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments.all()]
526 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments.all()]
Scott Baker37b47902014-09-02 14:37:41 -0700527 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700528
529 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
530 """ helper function for handling m2m relations from the MultipleChoiceField
531
532 this_obj: the source object we want to link from
533
534 selected_objs: a list of destination objects we want to link to
535
536 all_relations: the full set of relations involving this_obj, including ones we don't want
537
538 relation_class: the class that implements the relation from source to dest
539
540 local_attrname: field name representing this_obj in relation_class
541
542 foreign_attrname: field name representing selected_objs in relation_class
543
544 This function will remove all newobjclass relations from this_obj
545 that are not contained in selected_objs, and add any relations that
546 are in selected_objs but don't exist in the data model yet.
547 """
548
549 existing_dest_objs = []
550 for relation in list(all_relations):
551 if getattr(relation, foreign_attrname) not in selected_objs:
552 #print "deleting site", sdp.site
553 relation.delete()
554 else:
555 existing_dest_objs.append(getattr(relation, foreign_attrname))
556
557 for dest_obj in selected_objs:
558 if dest_obj not in existing_dest_objs:
559 #print "adding site", site
560 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
561 relation = relation_class(**kwargs)
562 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500563
564 def save(self, commit=True):
565 deployment = super(DeploymentAdminForm, self).save(commit=False)
566
567 if commit:
568 deployment.save()
Scott Baker61b6aec2014-10-06 17:17:40 -0700569 # this has to be done after save() if/when a deployment is first created
570 deployment.flavors = self.cleaned_data['flavors']
Siobhan Tully320b4622014-01-17 15:11:14 -0500571
572 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700573 # save_m2m() doesn't seem to work with 'through' relations. So we
574 # create/destroy the through models ourselves. There has to be
575 # a better way...
576
Sapan Bhatia39e85582014-11-19 15:07:36 -0500577 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments.all(), SiteDeployments, "deployment", "site")
Sapan Bhatiae9f96f62014-11-19 15:10:16 -0500578 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments.all(), ImageDeployments, "deployment", "image")
Scott Bakerc9b14f72014-05-22 13:44:20 -0700579
Scott Baker37b47902014-09-02 14:37:41 -0700580 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500581
582 return deployment
583
Scott Bakerff5e0f32014-05-22 14:40:27 -0700584class DeploymentAdminROForm(DeploymentAdminForm):
585 def save(self, commit=True):
586 raise PermissionDenied
587
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500588class SiteAssocInline(PlStackTabularInline):
589 model = Site.deployments.through
590 extra = 0
591 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400592
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400593class DeploymentAdmin(PlanetStackBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500594 model = Deployment
Tony Mack2cbd3802014-09-29 16:10:52 -0400595 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500596 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
Sapan Bhatiae9f96f62014-11-19 15:10:16 -0500597 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700598 list_display = ['backend_status_icon', 'name']
599 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700600 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500601
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500602 user_readonly_fields = ['name']
603
Scott Bakerde0f4412014-06-11 15:40:26 -0700604 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500605
Scott Bakerff5e0f32014-05-22 14:40:27 -0700606 def get_form(self, request, obj=None, **kwargs):
607 if request.user.isReadOnlyUser():
608 kwargs["form"] = DeploymentAdminROForm
609 else:
610 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700611 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
612
613 # from stackexchange: pass the request object into the form
614
615 class AdminFormMetaClass(adminForm):
616 def __new__(cls, *args, **kwargs):
617 kwargs['request'] = request
618 return adminForm(*args, **kwargs)
619
620 return AdminFormMetaClass
621
Siobhan Tullyce652d02013-10-08 21:52:35 -0400622class ServiceAttrAsTabInline(PlStackTabularInline):
623 model = ServiceAttribute
624 fields = ['name','value']
625 extra = 0
626 suit_classes = 'suit-tab suit-tab-serviceattrs'
627
Siobhan Tullyce652d02013-10-08 21:52:35 -0400628class ServiceAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700629 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
630 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700631 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500632 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
633 inlines = [ServiceAttrAsTabInline,SliceInline]
Scott Baker40c00762014-08-21 16:55:59 -0700634 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500635
636 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500637
638 suit_form_tabs =(('general', 'Service Details'),
639 ('slices','Slices'),
640 ('serviceattrs','Additional Attributes'),
641 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400642
Tony Mack0553f282013-06-10 22:54:50 -0400643class SiteAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700644 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400645 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500646 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400647 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400648 ]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400649 suit_form_tabs =(('general', 'Site Details'),
650 ('users','Users'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400651 ('siteprivileges','Privileges'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400652 ('deployments','Deployments'),
653 ('slices','Slices'),
Scott Baker36f50872014-08-21 13:01:25 -0700654 ('nodes','Nodes'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400655 ('tags','Tags'),
656 )
Scott Baker40c00762014-08-21 16:55:59 -0700657 readonly_fields = ['backend_status_text', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500658
659 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500660
Scott Baker63d1a552014-08-21 15:19:07 -0700661 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
662 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400663 filter_horizontal = ('deployments',)
Sapan Bhatia39e85582014-11-19 15:07:36 -0500664 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentsInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400665 search_fields = ['name']
666
Tony Mack04062832013-05-10 08:22:44 -0400667 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500668 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400669
Tony Mack5cd13202013-05-01 21:48:38 -0400670 def get_formsets(self, request, obj=None):
671 for inline in self.get_inline_instances(request, obj):
672 # hide MyInline in the add view
673 if obj is None:
674 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400675 if isinstance(inline, SliverInline):
676 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -0400677 yield inline.get_formset(request, obj)
678
Scott Baker545db2a2013-12-09 18:44:43 -0800679 def accountLink(self, obj):
680 link_obj = obj.accounts.all()
681 if link_obj:
682 reverse_path = "admin:core_account_change"
683 url = reverse(reverse_path, args =(link_obj[0].id,))
684 return "<a href='%s'>%s</a>" % (url, "view billing details")
685 else:
686 return "no billing data for this site"
687 accountLink.allow_tags = True
688 accountLink.short_description = "Billing"
689
Tony Mack332ee1d2014-02-04 15:33:45 -0500690 def save_model(self, request, obj, form, change):
691 # update openstack connection to use this site/tenant
692 obj.save_by_user(request.user)
693
694 def delete_model(self, request, obj):
695 obj.delete_by_user(request.user)
696
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500697
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400698class SitePrivilegeAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700699 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -0400700 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500701 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -0400702 ]
Scott Baker40c00762014-08-21 16:55:59 -0700703 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700704 list_display = ('backend_status_icon', 'user', 'site', 'role')
705 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500706 user_readonly_fields = fieldList
707 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -0400708
Tony Mackc2835a92013-05-28 09:18:49 -0400709 def formfield_for_foreignkey(self, db_field, request, **kwargs):
710 if db_field.name == 'site':
711 if not request.user.is_admin:
712 # only show sites where user is an admin or pi
713 sites = set()
714 for site_privilege in SitePrivilege.objects.filer(user=request.user):
715 if site_privilege.role.role_type in ['admin', 'pi']:
716 sites.add(site_privilege.site)
717 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
718
719 if db_field.name == 'user':
720 if not request.user.is_admin:
721 # only show users from sites where caller has admin or pi role
722 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
723 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
724 sites = [site_privilege.site for site_privilege in site_privileges]
725 site_privileges = SitePrivilege.objects.filter(site__in=sites)
726 emails = [site_privilege.user.email for site_privilege in site_privileges]
727 users = User.objects.filter(email__in=emails)
728 kwargs['queryset'] = users
729
730 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
731
Tony Mack04062832013-05-10 08:22:44 -0400732 def queryset(self, request):
733 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -0400734 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -0400735 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -0500736 #if not request.user.is_admin:
737 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
738 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
739 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
740 # sites = Site.objects.filter(login_base__in=login_bases)
741 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -0400742 return qs
743
Siobhan Tullyce652d02013-10-08 21:52:35 -0400744class SliceForm(forms.ModelForm):
745 class Meta:
746 model = Slice
747 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -0700748 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -0400749 }
750
Tony Mack2cbd3802014-09-29 16:10:52 -0400751 def clean(self):
752 cleaned_data = super(SliceForm, self).clean()
753 name = cleaned_data.get('name')
Scott Baker6efad462014-10-06 23:09:59 -0700754 site = cleaned_data.get('site')
Tony Mack585cb192014-10-22 12:54:19 -0400755 slice_id = self.instance.id
756 if not site and slice_id:
757 site = Slice.objects.get(id=slice_id).site
Scott Baker6efad462014-10-06 23:09:59 -0700758 if (not isinstance(site,Site)):
759 # previous code indicates 'site' could be a site_id and not a site?
760 site = Slice.objects.get(id=site.id)
Tony Mack2cbd3802014-09-29 16:10:52 -0400761 if not name.startswith(site.login_base):
762 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
763 return cleaned_data
764
Sapan Bhatiabf7856b2014-11-19 15:09:38 -0500765class SliceDeploymentsInline(PlStackTabularInline):
766 model = SliceDeployments
Scott Bakerc4efdc72014-10-15 16:54:04 -0700767 extra = 0
768 verbose_name = "Slice Deployment"
769 verbose_name_plural = "Slice Deployments"
770 suit_classes = 'suit-tab suit-tab-admin-only'
771 fields = ['backend_status_icon', 'deployment', 'tenant_id']
772 readonly_fields = ('backend_status_icon', )
773
Tony Mack2bd5b412013-06-11 21:05:06 -0400774class SliceAdmin(PlanetStackBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400775 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -0400776 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500777 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -0700778 readonly_fields = ('backend_status_text', )
Tony Mack7d459902014-09-03 13:18:57 -0400779 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
780 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully2d95e482013-09-06 10:56:06 -0400781 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
Sapan Bhatiabf7856b2014-11-19 15:09:38 -0500782 admin_inlines = [SliceDeploymentsInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400783
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500784 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400785
Scott Bakerc4efdc72014-10-15 16:54:04 -0700786 @property
787 def suit_form_tabs(self):
788 tabs =[('general', 'Slice Details'),
789 ('slicenetworks','Networks'),
790 ('sliceprivileges','Privileges'),
791 ('slivers','Slivers'),
792 ('tags','Tags'),
793 ('reservations','Reservations'),
794 ]
795
796 request=getattr(_thread_locals, "request", None)
797 if request and request.user.is_admin:
798 tabs.append( ('admin-only', 'Admin-Only') )
799
800 return tabs
Tony Mack7b8505a2014-10-22 11:54:29 -0400801
802 def add_view(self, request, form_url='', extra_context=None):
803 # revert to default read-only fields
804 self.readonly_fields = ('backend_status_text',)
805 return super(SliceAdmin, self).add_view(request, form_url, extra_context=extra_context)
806
807 def change_view(self, request, object_id, form_url='', extra_context=None):
808 print object_id
809 # cannot change the site of an existing slice so make the site field read only
810 if object_id:
811 self.readonly_fields = ('backend_status_text','site')
812 return super(SliceAdmin, self).change_view(request, object_id, form_url)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400813
Scott Baker510fdbb2014-08-05 17:19:24 -0700814 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -0700815 deployment_nodes = []
816 for node in Node.objects.all():
817 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
818
Scott Baker7a61dc42014-09-02 17:08:20 -0700819 deployment_flavors = []
820 for flavor in Flavor.objects.all():
821 for deployment in flavor.deployments.all():
822 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
823
Scott Baker93e80cd2014-09-09 09:58:49 -0700824 deployment_images = []
825 for image in Image.objects.all():
Sapan Bhatia400f5b52014-11-20 15:08:18 -0500826 for imageDeployment in image.imagedeployments.all():
Scott Baker93e80cd2014-09-09 09:58:49 -0700827 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
828
Tony Mackec23b992014-09-02 21:18:45 -0400829 site_login_bases = []
830 for site in Site.objects.all():
Scott Baker93e80cd2014-09-09 09:58:49 -0700831 site_login_bases.append((site.id, site.login_base))
832
Scott Baker510fdbb2014-08-05 17:19:24 -0700833 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -0700834 context["deployment_flavors"] = deployment_flavors
Scott Baker93e80cd2014-09-09 09:58:49 -0700835 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -0400836 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -0700837 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
838
Tony Mackc2835a92013-05-28 09:18:49 -0400839 def formfield_for_foreignkey(self, db_field, request, **kwargs):
840 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500841 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackec23b992014-09-02 21:18:45 -0400842 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 -0700843
Tony Mackc2835a92013-05-28 09:18:49 -0400844 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
845
Tony Mack04062832013-05-10 08:22:44 -0400846 def queryset(self, request):
847 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500848 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400849
Tony Mack79748612013-05-01 14:52:03 -0400850 def get_formsets(self, request, obj=None):
851 for inline in self.get_inline_instances(request, obj):
852 # hide MyInline in the add view
853 if obj is None:
854 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400855 if isinstance(inline, SliverInline):
856 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -0400857 yield inline.get_formset(request, obj)
858
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400859class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -0400860 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700861 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -0400862 ]
Scott Baker40c00762014-08-21 16:55:59 -0700863 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700864 list_display = ('backend_status_icon', 'user', 'slice', 'role')
865 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -0400866
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500867 user_readonly_fields = ['user', 'slice', 'role']
868 user_readonly_inlines = []
869
Tony Mackc2835a92013-05-28 09:18:49 -0400870 def formfield_for_foreignkey(self, db_field, request, **kwargs):
871 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500872 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400873
874 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500875 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400876
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400877 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400878
Tony Mack04062832013-05-10 08:22:44 -0400879 def queryset(self, request):
880 # admins can see all memberships. Users can only see memberships of
881 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -0500882 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400883
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400884 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -0400885 # update openstack connection to use this site/tenant
886 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400887 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400888 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400889 obj.save()
890
891 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -0400892 # update openstack connection to use this site/tenant
893 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400894 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400895 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400896 obj.delete()
897
Siobhan Tully567e3e62013-06-21 18:03:16 -0400898
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400899class ImageAdmin(PlanetStackBaseAdmin):
900
Scott Baker36f50872014-08-21 13:01:25 -0700901 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -0700902 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400903 'classes': ['suit-tab suit-tab-general']})
904 ]
Scott Baker40c00762014-08-21 16:55:59 -0700905 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400906
Scott Baker2170b972014-06-03 12:14:07 -0700907 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400908
Sapan Bhatiae9f96f62014-11-19 15:10:16 -0500909 inlines = [SliverInline, ImageDeploymentsInline]
Scott Bakerb6f99242014-06-11 11:34:44 -0700910
Tony Mack32e1ce32014-05-07 13:29:41 -0400911 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -0700912
Scott Baker63d1a552014-08-21 15:19:07 -0700913 list_display = ['backend_status_icon', 'name']
914 list_display_links = ('backend_status_icon', 'name', )
915
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400916class NodeForm(forms.ModelForm):
917 class Meta:
918 widgets = {
919 'site': LinkedSelect,
920 'deployment': LinkedSelect
921 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400922
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500923class NodeAdmin(PlanetStackBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400924 form = NodeForm
Scott Baker63d1a552014-08-21 15:19:07 -0700925 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
926 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400927 list_filter = ('deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500928
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400929 inlines = [TagInline,SliverInline]
Scott Baker40c00762014-08-21 16:55:59 -0700930 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
931 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400932
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500933 user_readonly_fields = ['name','site','deployment']
934 user_readonly_inlines = [TagInline,SliverInline]
935
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400936 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400937
Siobhan Tully567e3e62013-06-21 18:03:16 -0400938
Tony Mackd90cdbf2013-04-16 22:48:40 -0400939class SliverForm(forms.ModelForm):
940 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -0400941 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -0400942 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -0400943 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -0400944 widgets = {
945 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -0400946 'instance_name': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400947 'slice': LinkedSelect,
948 'deploymentNetwork': LinkedSelect,
949 'node': LinkedSelect,
950 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -0400951 }
Tony Mackd90cdbf2013-04-16 22:48:40 -0400952
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500953class TagAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700954 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
955 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500956 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
957 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -0400958
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400959class SliverAdmin(PlanetStackBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -0400960 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -0400961 fieldsets = [
Scott Baker7a61dc42014-09-02 17:08:20 -0700962 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
Tony Mackcdec0902013-04-15 00:38:49 -0400963 ]
Scott Baker40c00762014-08-21 16:55:59 -0700964 readonly_fields = ('backend_status_text', )
Scott Baker7a61dc42014-09-02 17:08:20 -0700965 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
Scott Baker63d1a552014-08-21 15:19:07 -0700966 list_display_links = ('backend_status_icon', 'ip',)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400967
968 suit_form_tabs =(('general', 'Sliver Details'),
969 ('tags','Tags'),
970 )
971
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400972 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -0400973
Scott Baker7a61dc42014-09-02 17:08:20 -0700974 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500975
Tony Mackc2835a92013-05-28 09:18:49 -0400976 def formfield_for_foreignkey(self, db_field, request, **kwargs):
977 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500978 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400979
980 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
981
Tony Mack04062832013-05-10 08:22:44 -0400982 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -0700983 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -0400984 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500985 return Sliver.select_by_user(request.user)
986
Tony Mack04062832013-05-10 08:22:44 -0400987
Tony Mack1d6b85f2013-05-07 18:49:14 -0400988 def get_formsets(self, request, obj=None):
989 # make some fields read only if we are updating an existing record
990 if obj == None:
Scott Baker36f50872014-08-21 13:01:25 -0700991 #self.readonly_fields = ('ip', 'instance_name')
Scott Bakerfbb45862014-10-17 16:27:23 -0700992 self.readonly_fields = ('backend_status_text',)
Tony Mack1d6b85f2013-05-07 18:49:14 -0400993 else:
Scott Bakerfbb45862014-10-17 16:27:23 -0700994 self.readonly_fields = ('backend_status_text',)
Scott Baker36f50872014-08-21 13:01:25 -0700995 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400996
997 for inline in self.get_inline_instances(request, obj):
998 # hide MyInline in the add view
999 if obj is None:
1000 continue
Scott Baker526b71e2014-05-13 13:18:01 -07001001 if isinstance(inline, SliverInline):
1002 inline.model.caller = request.user
1003 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -04001004
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001005 #def save_model(self, request, obj, form, change):
1006 # # update openstack connection to use this site/tenant
1007 # auth = request.session.get('auth', {})
1008 # auth['tenant'] = obj.slice.name
1009 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1010 # obj.creator = request.user
1011 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -04001012
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001013 #def delete_model(self, request, obj):
1014 # # update openstack connection to use this site/tenant
1015 # auth = request.session.get('auth', {})
1016 # auth['tenant'] = obj.slice.name
1017 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
1018 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -04001019
Siobhan Tully53437282013-04-26 19:30:27 -04001020class UserCreationForm(forms.ModelForm):
1021 """A form for creating new users. Includes all the required
1022 fields, plus a repeated password."""
1023 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
1024 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
1025
1026 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001027 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001028 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -04001029
1030 def clean_password2(self):
1031 # Check that the two password entries match
1032 password1 = self.cleaned_data.get("password1")
1033 password2 = self.cleaned_data.get("password2")
1034 if password1 and password2 and password1 != password2:
1035 raise forms.ValidationError("Passwords don't match")
1036 return password2
1037
1038 def save(self, commit=True):
1039 # Save the provided password in hashed format
1040 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -04001041 user.password = self.cleaned_data["password1"]
1042 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -04001043 if commit:
1044 user.save()
1045 return user
1046
Siobhan Tully567e3e62013-06-21 18:03:16 -04001047
Siobhan Tully53437282013-04-26 19:30:27 -04001048class UserChangeForm(forms.ModelForm):
1049 """A form for updating users. Includes all the fields on
1050 the user, but replaces the password field with admin's
1051 password hash display field.
1052 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -05001053 password = ReadOnlyPasswordHashField(label='Password',
1054 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -04001055
1056 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -04001057 model = User
Scott Baker9f6b8ed2014-11-17 23:44:03 -08001058 widgets = { 'public_key': UploadTextareaWidget, }
Siobhan Tully53437282013-04-26 19:30:27 -04001059
1060 def clean_password(self):
1061 # Regardless of what the user provides, return the initial value.
1062 # This is done here, rather than on the field, because the
1063 # field does not have access to the initial value
1064 return self.initial["password"]
1065
Scott Baker2c3cb642014-05-19 17:55:56 -07001066class UserDashboardViewInline(PlStackTabularInline):
1067 model = UserDashboardView
1068 extra = 0
1069 suit_classes = 'suit-tab suit-tab-dashboards'
1070 fields = ['user', 'dashboardView', 'order']
1071
Scott Baker86c83ab2014-10-03 13:10:47 -07001072class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1073 # Note: Make sure PermissionCheckingAdminMixin is listed before
1074 # admin.ModelAdmin in the class declaration.
1075
Siobhan Tully53437282013-04-26 19:30:27 -04001076 class Meta:
1077 app_label = "core"
1078
1079 # The forms to add and change user instances
1080 form = UserChangeForm
1081 add_form = UserCreationForm
1082
1083 # The fields to be used in displaying the User model.
1084 # These override the definitions on the base UserAdmin
1085 # that reference specific fields on auth.User.
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001086 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001087 list_filter = ('site',)
Scott Baker2c3cb642014-05-19 17:55:56 -07001088 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001089
Scott Bakercbfb6002014-10-03 00:32:37 -07001090 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001091 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1092
Siobhan Tully53437282013-04-26 19:30:27 -04001093 fieldsets = (
Scott Baker40c00762014-08-21 16:55:59 -07001094 ('Login Details', {'fields': ['backend_status_text', 'email', 'site','password', 'is_active', 'is_readonly', 'is_admin', 'public_key'], 'classes':['suit-tab suit-tab-general']}),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001095 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Scott Baker2c3cb642014-05-19 17:55:56 -07001096 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001097 #('Important dates', {'fields': ('last_login',)}),
1098 )
1099 add_fieldsets = (
1100 (None, {
1101 'classes': ('wide',),
Scott Baker0a5633b2014-10-06 17:51:20 -07001102 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')},
Siobhan Tully53437282013-04-26 19:30:27 -04001103 ),
1104 )
Scott Baker40c00762014-08-21 16:55:59 -07001105 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001106 search_fields = ('email',)
1107 ordering = ('email',)
1108 filter_horizontal = ()
1109
Scott Baker3ca51f62014-05-23 12:05:11 -07001110 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001111
Scott Baker0a5633b2014-10-06 17:51:20 -07001112 @property
1113 def suit_form_tabs(self):
1114 if getattr(_thread_locals, "obj", None) is None:
1115 return []
1116 else:
1117 return (('general','Login Details'),
1118 ('contact','Contact Information'),
1119 ('sliceprivileges','Slice Privileges'),
1120 ('siteprivileges','Site Privileges'),
1121 ('deploymentprivileges','Deployment Privileges'),
1122 ('dashboards','Dashboard Views'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001123
Tony Mackc2835a92013-05-28 09:18:49 -04001124 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1125 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001126 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001127
1128 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1129
Tony Mack5b061472014-02-04 07:57:10 -05001130 def queryset(self, request):
1131 return User.select_by_user(request.user)
1132
Scott Baker2c3cb642014-05-19 17:55:56 -07001133class DashboardViewAdmin(PlanetStackBaseAdmin):
1134 fieldsets = [('Dashboard View Details',
Scott Baker40c00762014-08-21 16:55:59 -07001135 {'fields': ['backend_status_text', 'name', 'url'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001136 'classes': ['suit-tab suit-tab-general']})
1137 ]
Scott Baker40c00762014-08-21 16:55:59 -07001138 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001139
Scott Baker2c3cb642014-05-19 17:55:56 -07001140 suit_form_tabs =(('general','Dashboard View Details'),)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001141
Scott Baker0165fac2014-01-13 11:49:26 -08001142class ServiceResourceInline(PlStackTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001143 model = ServiceResource
1144 extra = 0
1145
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001146class ServiceClassAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001147 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1148 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001149 inlines = [ServiceResourceInline]
1150
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001151 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1152 user_readonly_inlines = []
1153
Scott Baker0165fac2014-01-13 11:49:26 -08001154class ReservedResourceInline(PlStackTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001155 model = ReservedResource
1156 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001157 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001158
1159 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1160 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1161
1162 if db_field.name == 'resource':
1163 # restrict resources to those that the slice's service class allows
1164 if request._slice is not None:
1165 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1166 if len(field.queryset) > 0:
1167 field.initial = field.queryset.all()[0]
1168 else:
1169 field.queryset = field.queryset.none()
1170 elif db_field.name == 'sliver':
1171 # restrict slivers to those that belong to the slice
1172 if request._slice is not None:
1173 field.queryset = field.queryset.filter(slice = request._slice)
1174 else:
1175 field.queryset = field.queryset.none()
1176
1177 return field
1178
Tony Mack5b061472014-02-04 07:57:10 -05001179 def queryset(self, request):
1180 return ReservedResource.select_by_user(request.user)
1181
Scott Baker133c9212013-05-17 09:09:11 -07001182class ReservationChangeForm(forms.ModelForm):
1183 class Meta:
1184 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001185 widgets = {
1186 'slice' : LinkedSelect
1187 }
Scott Baker133c9212013-05-17 09:09:11 -07001188
1189class ReservationAddForm(forms.ModelForm):
1190 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1191 refresh = forms.CharField(widget=forms.HiddenInput())
1192
1193 class Media:
1194 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1195
1196 def clean_slice(self):
1197 slice = self.cleaned_data.get("slice")
1198 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1199 if len(x) == 0:
1200 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1201 return slice
1202
1203 class Meta:
1204 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001205 widgets = {
1206 'slice' : LinkedSelect
1207 }
1208
Scott Baker133c9212013-05-17 09:09:11 -07001209
1210class ReservationAddRefreshForm(ReservationAddForm):
1211 """ This form is displayed when the Reservation Form receives an update
1212 from the Slice dropdown onChange handler. It doesn't validate the
1213 data and doesn't save the data. This will cause the form to be
1214 redrawn.
1215 """
1216
Scott Baker8737e5f2013-05-17 09:35:32 -07001217 """ don't validate anything other than slice """
1218 dont_validate_fields = ("startTime", "duration")
1219
Scott Baker133c9212013-05-17 09:09:11 -07001220 def full_clean(self):
1221 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001222
1223 for fieldname in self.dont_validate_fields:
1224 if fieldname in self._errors:
1225 del self._errors[fieldname]
1226
Scott Baker133c9212013-05-17 09:09:11 -07001227 return result
1228
1229 """ don't save anything """
1230 def is_valid(self):
1231 return False
1232
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001233class ReservationAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001234 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001235 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001236 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001237 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001238 form = ReservationAddForm
1239
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001240 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1241
1242 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001243 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001244
Scott Baker133c9212013-05-17 09:09:11 -07001245 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001246 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001247 request._refresh = False
1248 request._slice = None
1249 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001250 # "refresh" will be set to "1" if the form was submitted due to
1251 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001252 if request.POST.get("refresh","1") == "1":
1253 request._refresh = True
1254 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001255
1256 # Keep track of the slice that was selected, so the
1257 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001258 request._slice = request.POST.get("slice",None)
1259 if (request._slice is not None):
1260 request._slice = Slice.objects.get(id=request._slice)
1261
1262 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1263 return result
1264
Scott Bakeracd45142013-05-19 16:19:16 -07001265 def changelist_view(self, request, extra_context = None):
1266 timezone.activate(request.user.timezone)
1267 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1268
Scott Baker133c9212013-05-17 09:09:11 -07001269 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001270 request._obj_ = obj
1271 if obj is not None:
1272 # For changes, set request._slice to the slice already set in the
1273 # object.
1274 request._slice = obj.slice
1275 self.form = ReservationChangeForm
1276 else:
1277 if getattr(request, "_refresh", False):
1278 self.form = ReservationAddRefreshForm
1279 else:
1280 self.form = ReservationAddForm
1281 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1282
Scott Baker133c9212013-05-17 09:09:11 -07001283 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001284 if (obj is not None):
1285 # Prevent slice from being changed after the reservation has been
1286 # created.
1287 return ['slice']
1288 else:
Scott Baker133c9212013-05-17 09:09:11 -07001289 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001290
Tony Mack5b061472014-02-04 07:57:10 -05001291 def queryset(self, request):
1292 return Reservation.select_by_user(request.user)
1293
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001294class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001295 list_display = ("backend_status_icon", "name", )
1296 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001297 user_readonly_fields = ['name']
1298 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001299
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001300class RouterAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001301 list_display = ("backend_status_icon", "name", )
1302 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001303 user_readonly_fields = ['name']
1304 user_readonly_inlines = []
1305
Scott Baker0165fac2014-01-13 11:49:26 -08001306class RouterInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001307 model = Router.networks.through
1308 extra = 0
1309 verbose_name_plural = "Routers"
1310 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001311 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001312
Scott Bakerb27b62c2014-08-15 16:29:16 -07001313class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001314 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001315 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001316 verbose_name_plural = "Parameters"
1317 verbose_name = "Parameter"
1318 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001319 fields = ['backend_status_icon', 'parameter', 'value']
1320 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001321
Scott Baker0165fac2014-01-13 11:49:26 -08001322class NetworkSliversInline(PlStackTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001323 fields = ['backend_status_icon', 'network','sliver','ip']
1324 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001325 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001326 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001327 extra = 0
1328 verbose_name_plural = "Slivers"
1329 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001330 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001331
Scott Baker0165fac2014-01-13 11:49:26 -08001332class NetworkSlicesInline(PlStackTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001333 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001334 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001335 extra = 0
1336 verbose_name_plural = "Slices"
1337 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001338 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001339 fields = ['backend_status_icon', 'network','slice']
1340 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001341
Scott Bakerfbb45862014-10-17 16:27:23 -07001342class NetworkDeploymentsInline(PlStackTabularInline):
1343 model = NetworkDeployments
1344 extra = 0
1345 verbose_name_plural = "Network Deployments"
1346 verbose_name = "Network Deployment"
1347 suit_classes = 'suit-tab suit-tab-admin-only'
1348 fields = ['backend_status_icon', 'deployment','net_id','subnet_id']
1349 readonly_fields = ('backend_status_icon', )
1350
Scott Baker9f6b8ed2014-11-17 23:44:03 -08001351class NetworkForm(forms.ModelForm):
1352 class Meta:
1353 model = Network
1354 widgets = {
1355 'topologyParameters': UploadTextareaWidget,
1356 'controllerParameters': UploadTextareaWidget,
1357 }
1358
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001359class NetworkAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001360 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1361 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001362 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001363
Scott Bakerd7d2a392013-08-06 08:57:30 -07001364 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Scott Bakerfbb45862014-10-17 16:27:23 -07001365 admin_inlines = [NetworkDeploymentsInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001366
Scott Baker9f6b8ed2014-11-17 23:44:03 -08001367 form=NetworkForm
1368
Siobhan Tully2d95e482013-09-06 10:56:06 -04001369 fieldsets = [
Scott Baker40248712014-11-17 16:04:45 -08001370 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'],
1371 'classes':['suit-tab suit-tab-general']}),
Scott Baker7b6722e2014-11-17 16:18:00 -08001372 (None, {'fields': ['topologyParameters', 'controllerUrl', 'controllerParameters'],
Scott Baker40248712014-11-17 16:04:45 -08001373 'classes':['suit-tab suit-tab-sdn']}),
1374 ]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001375
Scott Baker40c00762014-08-21 16:55:59 -07001376 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001377 user_readonly_fields = ['name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet']
Siobhan Tully2d95e482013-09-06 10:56:06 -04001378
Scott Bakerfbb45862014-10-17 16:27:23 -07001379 @property
1380 def suit_form_tabs(self):
1381 tabs=[('general','Network Details'),
Scott Baker40248712014-11-17 16:04:45 -08001382 ('sdn', 'SDN Configuration'),
Scott Bakerfbb45862014-10-17 16:27:23 -07001383 ('netparams', 'Parameters'),
1384 ('networkslivers','Slivers'),
1385 ('networkslices','Slices'),
1386 ('routers','Routers'),
1387 ]
1388
1389 request=getattr(_thread_locals, "request", None)
1390 if request and request.user.is_admin:
1391 tabs.append( ('admin-only', 'Admin-Only') )
1392
1393 return tabs
1394
1395
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001396class NetworkTemplateAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001397 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1398 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001399 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1400 user_readonly_inlines = []
Scott Baker40248712014-11-17 16:04:45 -08001401 fieldsets = [
1402 (None, {'fields': ['name', 'description', 'guaranteedBandwidth', 'visibility', 'translation', 'sharedNetworkName', 'sharedNetworkId', 'topologyKind', 'controllerKind'],
1403 'classes':['suit-tab suit-tab-general']}),]
1404 suit_form_tabs = (('general','Network Template Details'), )
Scott Baker74d8e622013-07-29 16:04:22 -07001405
Scott Baker37b47902014-09-02 14:37:41 -07001406class FlavorAdmin(PlanetStackBaseAdmin):
1407 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1408 list_display_links = ("backend_status_icon", "name")
1409 user_readonly_fields = ("name", "flavor")
1410 fields = ("name", "description", "flavor", "order", "default")
1411
Tony Mack31c2b8f2013-04-26 20:01:42 -04001412# register a signal that caches the user's credentials when they log in
1413def cache_credentials(sender, user, request, **kwds):
1414 auth = {'username': request.POST['username'],
1415 'password': request.POST['password']}
1416 request.session['auth'] = auth
1417user_logged_in.connect(cache_credentials)
1418
Scott Baker15cddfa2013-12-09 13:45:19 -08001419def dollar_field(fieldName, short_description):
1420 def newFunc(self, obj):
1421 try:
1422 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1423 except:
1424 x=getattr(obj, fieldName, 0.0)
1425 return x
1426 newFunc.short_description = short_description
1427 return newFunc
1428
1429def right_dollar_field(fieldName, short_description):
1430 def newFunc(self, obj):
1431 try:
1432 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1433 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1434 except:
1435 x=getattr(obj, fieldName, 0.0)
1436 return x
1437 newFunc.short_description = short_description
1438 newFunc.allow_tags = True
1439 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001440
Scott Baker0165fac2014-01-13 11:49:26 -08001441class InvoiceChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001442 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001443 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001444 verbose_name_plural = "Charges"
1445 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001446 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001447 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1448 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1449 can_delete = False
1450 max_num = 0
1451
1452 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001453
1454class InvoiceAdmin(admin.ModelAdmin):
1455 list_display = ("date", "account")
1456
1457 inlines = [InvoiceChargeInline]
1458
Scott Baker9cb88a22013-12-09 18:56:00 -08001459 fields = ["date", "account", "dollar_amount"]
1460 readonly_fields = ["date", "account", "dollar_amount"]
1461
1462 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001463
Scott Baker0165fac2014-01-13 11:49:26 -08001464class InvoiceInline(PlStackTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001465 model = Invoice
1466 extra = 0
1467 verbose_name_plural = "Invoices"
1468 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001469 fields = ["date", "dollar_amount"]
1470 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001471 suit_classes = 'suit-tab suit-tab-accountinvoice'
1472 can_delete=False
1473 max_num=0
1474
1475 dollar_amount = right_dollar_field("amount", "Amount")
1476
Scott Baker0165fac2014-01-13 11:49:26 -08001477class PendingChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001478 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001479 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001480 verbose_name_plural = "Charges"
1481 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001482 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001483 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1484 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001485 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001486 can_delete=False
1487 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001488
1489 def queryset(self, request):
1490 qs = super(PendingChargeInline, self).queryset(request)
1491 qs = qs.filter(state="pending")
1492 return qs
1493
Scott Baker15cddfa2013-12-09 13:45:19 -08001494 dollar_amount = right_dollar_field("amount", "Amount")
1495
Scott Baker0165fac2014-01-13 11:49:26 -08001496class PaymentInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001497 model=Payment
1498 extra = 1
1499 verbose_name_plural = "Payments"
1500 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001501 fields = ["date", "dollar_amount"]
1502 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001503 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001504 can_delete=False
1505 max_num=0
1506
1507 dollar_amount = right_dollar_field("amount", "Amount")
1508
Scott Baker43105042013-12-06 23:23:36 -08001509class AccountAdmin(admin.ModelAdmin):
1510 list_display = ("site", "balance_due")
1511
1512 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1513
1514 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001515 (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 -08001516
Scott Baker15cddfa2013-12-09 13:45:19 -08001517 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001518
1519 suit_form_tabs =(
1520 ('general','Account Details'),
1521 ('accountinvoice', 'Invoices'),
1522 ('accountpayments', 'Payments'),
1523 ('accountpendingcharges','Pending Charges'),
1524 )
1525
Scott Baker15cddfa2013-12-09 13:45:19 -08001526 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1527 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1528 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1529
Siobhan Tully53437282013-04-26 19:30:27 -04001530# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001531admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001532# ... and, since we're not using Django's builtin permissions,
1533# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001534#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001535
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001536#Do not show django evolution in the admin interface
1537from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001538#admin.site.unregister(Version)
1539#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001540
1541
1542# When debugging it is often easier to see all the classes, but for regular use
1543# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001544showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001545
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001546admin.site.register(Deployment, DeploymentAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001547admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001548admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001549admin.site.register(Service, ServiceAdmin)
smbakera3cf70c2013-06-27 02:01:41 -07001550admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001551admin.site.register(Network, NetworkAdmin)
1552admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001553admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001554admin.site.register(Account, AccountAdmin)
1555admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001556
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001557if True:
1558 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1559 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001560 #admin.site.register(PlanetStack)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001561 admin.site.register(Tag, TagAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001562 admin.site.register(DeploymentRole)
1563 admin.site.register(SiteRole)
1564 admin.site.register(SliceRole)
1565 admin.site.register(PlanetStackRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001566 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001567 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1568 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001569 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001570 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001571 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07001572 admin.site.register(Flavor, FlavorAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001573