blob: 6fcc3f963ed8f4267550d4de9911590ddecc6dad [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
Siobhan Tully4bc09f22013-04-10 21:15:21 -040010from django.contrib.admin.widgets import FilteredSelectMultiple
Siobhan Tully53437282013-04-26 19:30:27 -040011from django.contrib.auth.forms import ReadOnlyPasswordHashField
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
Tony Mack7130ac32013-03-22 21:58:00 -040018
Scott Baker36f50872014-08-21 13:01:25 -070019import django_evolution
20
Scott Baker40c00762014-08-21 16:55:59 -070021def backend_icon(obj): # backend_status, enacted, updated):
22 #return "%s %s %s" % (str(obj.updated), str(obj.enacted), str(obj.backend_status))
23 if (obj.enacted is not None) and obj.enacted >= obj.updated:
Scott Bakerfbf06642014-09-09 10:38:15 -070024 return '<span style="min-width:16px;"><img src="/static/admin/img/icon_success.gif"></span>'
Scott Baker40c00762014-08-21 16:55:59 -070025 else:
26 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
Scott Bakerfbf06642014-09-09 10:38:15 -070027 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 -070028 else:
Scott Bakerfbf06642014-09-09 10:38:15 -070029 return '<span style="min-width:16px;" title="%s"><img src="/static/admin/img/icon_error.gif"></span>' % obj.backend_status
Scott Baker40c00762014-08-21 16:55:59 -070030
31def backend_text(obj):
32 icon = backend_icon(obj)
33 if (obj.enacted is not None) and obj.enacted >= obj.updated:
34 return "%s %s" % (icon, "successfully enacted") # enacted on %s" % str(obj.enacted))
35 else:
36 return "%s %s" % (icon, obj.backend_status)
Scott Baker63d1a552014-08-21 15:19:07 -070037
Scott Baker36f50872014-08-21 13:01:25 -070038class PlainTextWidget(forms.HiddenInput):
39 input_type = 'hidden'
40
41 def render(self, name, value, attrs=None):
42 if value is None:
43 value = ''
44 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
45
Siobhan Tullycf04fb62014-01-11 11:25:57 -050046class ReadOnlyAwareAdmin(admin.ModelAdmin):
47
48 def has_add_permission(self, request, obj=None):
49 return (not self.__user_is_readonly(request))
Scott Baker36f50872014-08-21 13:01:25 -070050
Siobhan Tullycf04fb62014-01-11 11:25:57 -050051 def has_delete_permission(self, request, obj=None):
52 return (not self.__user_is_readonly(request))
53
54 def save_model(self, request, obj, form, change):
55 if self.__user_is_readonly(request):
56 raise PermissionDenied
57 #pass
58 else:
59 return super(ReadOnlyAwareAdmin, self).save_model(request, obj, form, change)
60
61 def get_actions(self,request):
62 actions = super(ReadOnlyAwareAdmin,self).get_actions(request)
63
64 if self.__user_is_readonly(request):
65 if 'delete_selected' in actions:
66 del actions['delete_selected']
67
68 return actions
69
70 def change_view(self,request,object_id, extra_context=None):
Siobhan Tullycf04fb62014-01-11 11:25:57 -050071 if self.__user_is_readonly(request):
Scott Bakeraf73e102014-04-22 22:40:07 -070072 if not hasattr(self, "readonly_save"):
73 # save the original readonly fields
74 self.readonly_save = self.readonly_fields
75 self.inlines_save = self.inlines
Scott Bakere8859f92014-05-23 12:42:40 -070076 if hasattr(self, "user_readonly_fields"):
77 self.readonly_fields=self.user_readonly_fields
78 if hasattr(self, "user_readonly_inlines"):
79 self.inlines = self.user_readonly_inlines
Scott Bakeraf73e102014-04-22 22:40:07 -070080 else:
81 if hasattr(self, "readonly_save"):
82 # restore the original readonly fields
83 self.readonly_fields = self.readonly_save
Scott Bakere8859f92014-05-23 12:42:40 -070084 if hasattr(self, "inlines_save"):
Scott Bakeraf73e102014-04-22 22:40:07 -070085 self.inlines = self.inlines_save
Siobhan Tullycf04fb62014-01-11 11:25:57 -050086
87 try:
88 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
89 except PermissionDenied:
90 pass
91 if request.method == 'POST':
92 raise PermissionDenied
93 request.readonly = True
94 return super(ReadOnlyAwareAdmin, self).change_view(request, object_id, extra_context=extra_context)
95
Siobhan Tullycf04fb62014-01-11 11:25:57 -050096 def __user_is_readonly(self, request):
97 return request.user.isReadOnlyUser()
98
Scott Baker40c00762014-08-21 16:55:59 -070099 def backend_status_text(self, obj):
100 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -0700101
Scott Baker63d1a552014-08-21 15:19:07 -0700102 def backend_status_icon(self, obj):
Scott Baker40c00762014-08-21 16:55:59 -0700103 return mark_safe(backend_icon(obj))
Scott Baker63d1a552014-08-21 15:19:07 -0700104 backend_status_icon.short_description = ""
105
Scott Baker36f50872014-08-21 13:01:25 -0700106
Scott Bakere8859f92014-05-23 12:42:40 -0700107class SingletonAdmin (ReadOnlyAwareAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400108 def has_add_permission(self, request):
Scott Bakere8859f92014-05-23 12:42:40 -0700109 if not super(SingletonAdmin, self).has_add_permission(request):
110 return False
111
Siobhan Tullyce652d02013-10-08 21:52:35 -0400112 num_objects = self.model.objects.count()
113 if num_objects >= 1:
114 return False
115 else:
116 return True
117
118
Siobhan Tullyd3515752013-06-21 16:34:53 -0400119class PlStackTabularInline(admin.TabularInline):
Scott Baker86568322014-01-12 16:53:31 -0800120 def __init__(self, *args, **kwargs):
121 super(PlStackTabularInline, self).__init__(*args, **kwargs)
122
123 # InlineModelAdmin as no get_fields() method, so in order to add
124 # the selflink field, we override __init__ to modify self.fields and
125 # self.readonly_fields.
126
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800127 self.setup_selflink()
128
Scott Baker874936e2014-01-13 18:15:34 -0800129 def get_change_url(self, model, id):
130 """ Get the URL to a change form in the admin for this model """
131 reverse_path = "admin:%s_change" % (model._meta.db_table)
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800132 try:
Scott Baker874936e2014-01-13 18:15:34 -0800133 url = reverse(reverse_path, args=(id,))
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800134 except NoReverseMatch:
Scott Baker874936e2014-01-13 18:15:34 -0800135 return None
136
137 return url
138
139 def setup_selflink(self):
140 if hasattr(self, "selflink_fieldname"):
141 """ self.selflink_model can be defined to punch through a relation
142 to its target object. For example, in SliceNetworkInline, set
143 selflink_model = "network", and the URL will lead to the Network
144 object instead of trying to bring up a change view of the
145 SliceNetwork object.
146 """
147 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
148 else:
149 self.selflink_model = self.model
150
151 url = self.get_change_url(self.selflink_model, 0)
152
153 # We don't have an admin for this object, so don't create the
154 # selflink.
155 if (url == None):
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800156 return
157
Scott Baker874936e2014-01-13 18:15:34 -0800158 # Since we need to add "selflink" to the field list, we need to create
159 # self.fields if it is None.
Scott Baker0165fac2014-01-13 11:49:26 -0800160 if (self.fields is None):
161 self.fields = []
162 for f in self.model._meta.fields:
163 if f.editable and f.name != "id":
164 self.fields.append(f.name)
Scott Baker86568322014-01-12 16:53:31 -0800165
Scott Baker874936e2014-01-13 18:15:34 -0800166 self.fields = tuple(self.fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800167
Scott Baker874936e2014-01-13 18:15:34 -0800168 if self.readonly_fields is None:
169 self.readonly_fields = ()
Scott Baker86568322014-01-12 16:53:31 -0800170
Scott Baker874936e2014-01-13 18:15:34 -0800171 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800172
173 def selflink(self, obj):
Scott Baker874936e2014-01-13 18:15:34 -0800174 if hasattr(self, "selflink_fieldname"):
175 obj = getattr(obj, self.selflink_fieldname)
176
Scott Baker86568322014-01-12 16:53:31 -0800177 if obj.id:
Scott Baker874936e2014-01-13 18:15:34 -0800178 url = self.get_change_url(self.selflink_model, obj.id)
179 return "<a href='%s'>Details</a>" % str(url)
Scott Baker86568322014-01-12 16:53:31 -0800180 else:
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800181 return "Not present"
Scott Baker86568322014-01-12 16:53:31 -0800182
183 selflink.allow_tags = True
184 selflink.short_description = "Details"
Siobhan Tullyd3515752013-06-21 16:34:53 -0400185
Scott Bakerb27b62c2014-08-15 16:29:16 -0700186 def has_add_permission(self, request):
187 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500188
189 def get_readonly_fields(self, request, obj=None):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700190 readonly_fields = list(self.readonly_fields)[:]
191 if request.user.isReadOnlyUser():
192 for field in self.fields:
193 if not field in readonly_fields:
194 readonly_fields.append(field)
195 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500196
Scott Baker40c00762014-08-21 16:55:59 -0700197 def backend_status_icon(self, obj):
198 return mark_safe(backend_icon(obj))
199 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -0700200
Scott Bakerb27b62c2014-08-15 16:29:16 -0700201class PlStackGenericTabularInline(generic.GenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500202 def has_add_permission(self, request):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700203 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500204
Scott Bakerb27b62c2014-08-15 16:29:16 -0700205 def get_readonly_fields(self, request, obj=None):
206 readonly_fields = list(self.readonly_fields)[:]
207 if request.user.isReadOnlyUser():
208 for field in self.fields:
209 if not field in readonly_fields:
210 readonly_fields.append(field)
211 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500212
Scott Baker40c00762014-08-21 16:55:59 -0700213 def backend_status_icon(self, obj):
214 return mark_safe(backend_icon(obj))
215 backend_status_icon.short_description = ""
216
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400217class ReservationInline(PlStackTabularInline):
218 model = Reservation
219 extra = 0
220 suit_classes = 'suit-tab suit-tab-reservations'
Scott Baker36f50872014-08-21 13:01:25 -0700221
Tony Mack5b061472014-02-04 07:57:10 -0500222 def queryset(self, request):
223 return Reservation.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400224
Scott Bakerb27b62c2014-08-15 16:29:16 -0700225class TagInline(PlStackGenericTabularInline):
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400226 model = Tag
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400227 extra = 0
228 suit_classes = 'suit-tab suit-tab-tags'
Tony Mack5b061472014-02-04 07:57:10 -0500229 fields = ['service', 'name', 'value']
230
231 def queryset(self, request):
232 return Tag.select_by_user(request.user)
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400233
Scott Baker74d8e622013-07-29 16:04:22 -0700234class NetworkLookerUpper:
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400235 """ This is a callable that looks up a network name in a sliver and returns
236 the ip address for that network.
237 """
238
Scott Baker434ca7e2014-08-15 12:29:20 -0700239 byNetworkName = {} # class variable
240
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400241 def __init__(self, name):
242 self.short_description = name
243 self.__name__ = name
244 self.network_name = name
245
246 def __call__(self, obj):
247 if obj is not None:
248 for nbs in obj.networksliver_set.all():
249 if (nbs.network.name == self.network_name):
250 return nbs.ip
Scott Baker74d8e622013-07-29 16:04:22 -0700251 return ""
252
253 def __str__(self):
254 return self.network_name
255
Scott Baker434ca7e2014-08-15 12:29:20 -0700256 @staticmethod
257 def get(network_name):
258 """ We want to make sure we alwars return the same NetworkLookerUpper
259 because sometimes django will cause them to be instantiated multiple
260 times (and we don't want different ones in form.fields vs
261 SliverInline.readonly_fields).
262 """
263 if network_name not in NetworkLookerUpper.byNetworkName:
264 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
265 return NetworkLookerUpper.byNetworkName[network_name]
266
Siobhan Tullyd3515752013-06-21 16:34:53 -0400267class SliverInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400268 model = Sliver
Scott Baker7a61dc42014-09-02 17:08:20 -0700269 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400270 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700271 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400272 suit_classes = 'suit-tab suit-tab-slivers'
Scott Baker74d8e622013-07-29 16:04:22 -0700273
Tony Mack5b061472014-02-04 07:57:10 -0500274 def queryset(self, request):
275 return Sliver.select_by_user(request.user)
276
Scott Bakerb24cc932014-06-09 10:51:16 -0700277 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Scott Bakerb24cc932014-06-09 10:51:16 -0700278 if db_field.name == 'deploymentNetwork':
Scott Baker3b678742014-06-09 13:11:54 -0700279 kwargs['queryset'] = Deployment.select_by_acl(request.user)
Scott Baker7a61dc42014-09-02 17:08:20 -0700280 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
Scott Baker32481312014-09-08 12:14:14 -0700281 elif db_field.name == 'flavor':
282 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
Scott Baker3b678742014-06-09 13:11:54 -0700283
284 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700285
286 return field
287
Siobhan Tullyd3515752013-06-21 16:34:53 -0400288class SiteInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400289 model = Site
290 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400291 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400292
Tony Mack5b061472014-02-04 07:57:10 -0500293 def queryset(self, request):
294 return Site.select_by_user(request.user)
295
Siobhan Tullyd3515752013-06-21 16:34:53 -0400296class UserInline(PlStackTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400297 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700298 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
299 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400300 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400301 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400302
Tony Mack5b061472014-02-04 07:57:10 -0500303 def queryset(self, request):
304 return User.select_by_user(request.user)
305
Siobhan Tullyd3515752013-06-21 16:34:53 -0400306class SliceInline(PlStackTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400307 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700308 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
309 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400310 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400311 suit_classes = 'suit-tab suit-tab-slices'
312
Tony Mack5b061472014-02-04 07:57:10 -0500313 def queryset(self, request):
314 return Slice.select_by_user(request.user)
315
Siobhan Tullyd3515752013-06-21 16:34:53 -0400316class NodeInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400317 model = Node
318 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400319 suit_classes = 'suit-tab suit-tab-nodes'
Scott Baker40c00762014-08-21 16:55:59 -0700320 fields = ['backend_status_icon', 'name','deployment','site']
321 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400322
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400323class DeploymentPrivilegeInline(PlStackTabularInline):
324 model = DeploymentPrivilege
325 extra = 0
326 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700327 fields = ['backend_status_icon', 'user','role','deployment']
328 readonly_fields = ('backend_status_icon', )
Tony Mack5b061472014-02-04 07:57:10 -0500329
330 def queryset(self, request):
331 return DeploymentPrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400332
Siobhan Tullyd3515752013-06-21 16:34:53 -0400333class SitePrivilegeInline(PlStackTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400334 model = SitePrivilege
335 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400336 suit_classes = 'suit-tab suit-tab-siteprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700337 fields = ['backend_status_icon', 'user','site', 'role']
338 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400339
Tony Mackc2835a92013-05-28 09:18:49 -0400340 def formfield_for_foreignkey(self, db_field, request, **kwargs):
341 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500342 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400343
344 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500345 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400346 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
347
Tony Mack5b061472014-02-04 07:57:10 -0500348 def queryset(self, request):
349 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400350
Tony Macke4be32f2014-03-11 20:45:25 -0400351class SiteDeploymentInline(PlStackTabularInline):
352 model = SiteDeployments
Tony Macke4be32f2014-03-11 20:45:25 -0400353 extra = 0
354 suit_classes = 'suit-tab suit-tab-deployments'
Scott Baker40c00762014-08-21 16:55:59 -0700355 fields = ['backend_status_icon', 'deployment','site']
356 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400357
358 def formfield_for_foreignkey(self, db_field, request, **kwargs):
359 if db_field.name == 'site':
360 kwargs['queryset'] = Site.select_by_user(request.user)
361
362 if db_field.name == 'deployment':
363 kwargs['queryset'] = Deployment.select_by_user(request.user)
364 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
365
366 def queryset(self, request):
367 return SiteDeployments.select_by_user(request.user)
368
369
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400370class SlicePrivilegeInline(PlStackTabularInline):
371 model = SlicePrivilege
372 suit_classes = 'suit-tab suit-tab-sliceprivileges'
373 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700374 fields = ('backend_status_icon', 'user', 'slice', 'role')
375 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400376
Tony Mackc2835a92013-05-28 09:18:49 -0400377 def formfield_for_foreignkey(self, db_field, request, **kwargs):
378 if db_field.name == 'slice':
Scott Baker36f50872014-08-21 13:01:25 -0700379 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400380 if db_field.name == 'user':
Scott Baker36f50872014-08-21 13:01:25 -0700381 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400382
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400383 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400384
Tony Mack5b061472014-02-04 07:57:10 -0500385 def queryset(self, request):
386 return SlicePrivilege.select_by_user(request.user)
387
Scott Bakera0015eb2013-08-14 17:28:14 -0700388class SliceNetworkInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700389 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800390 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700391 extra = 0
392 verbose_name = "Network Connection"
393 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400394 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700395 fields = ['backend_status_icon', 'network']
396 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700397
398class ImageDeploymentsInline(PlStackTabularInline):
399 model = ImageDeployments
400 extra = 0
401 verbose_name = "Image Deployments"
402 verbose_name_plural = "Image Deployments"
403 suit_classes = 'suit-tab suit-tab-imagedeployments'
Scott Baker40c00762014-08-21 16:55:59 -0700404 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
405 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700406
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500407class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400408 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700409
Tony Mack332ee1d2014-02-04 15:33:45 -0500410 def save_model(self, request, obj, form, change):
Tony Mack3d042792014-03-17 19:18:37 -0400411 obj.caller = request.user
Tony Mack332ee1d2014-02-04 15:33:45 -0500412 # update openstack connection to use this site/tenant
413 obj.save_by_user(request.user)
414
415 def delete_model(self, request, obj):
416 obj.delete_by_user(request.user)
417
418 def save_formset(self, request, form, formset, change):
419 instances = formset.save(commit=False)
420 for instance in instances:
421 instance.save_by_user(request.user)
422 formset.save_m2m()
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400423
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400424class SliceRoleAdmin(PlanetStackBaseAdmin):
425 model = SliceRole
426 pass
427
428class SiteRoleAdmin(PlanetStackBaseAdmin):
429 model = SiteRole
430 pass
431
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400432class DeploymentAdminForm(forms.ModelForm):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400433 sites = forms.ModelMultipleChoiceField(
434 queryset=Site.objects.all(),
435 required=False,
Scott Baker70983182014-06-09 22:10:00 -0700436 help_text="Select which sites are allowed to host nodes in this deployment",
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400437 widget=FilteredSelectMultiple(
438 verbose_name=('Sites'), is_stacked=False
439 )
440 )
Scott Bakerde0f4412014-06-11 15:40:26 -0700441 images = forms.ModelMultipleChoiceField(
442 queryset=Image.objects.all(),
443 required=False,
444 help_text="Select which images should be deployed on this deployment",
445 widget=FilteredSelectMultiple(
446 verbose_name=('Images'), is_stacked=False
447 )
448 )
Scott Baker37b47902014-09-02 14:37:41 -0700449 flavors = forms.ModelMultipleChoiceField(
450 queryset=Flavor.objects.all(),
451 required=False,
452 help_text="Select which flavors should be usable on this deployment",
453 widget=FilteredSelectMultiple(
454 verbose_name=('Flavors'), is_stacked=False
455 )
456 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400457 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400458 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700459 many_to_many = ["flavors",]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400460
Siobhan Tully320b4622014-01-17 15:11:14 -0500461 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700462 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500463 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
464
Scott Baker5380c522014-06-06 14:49:43 -0700465 self.fields['accessControl'].initial = "allow site " + request.user.site.name
466
Siobhan Tully320b4622014-01-17 15:11:14 -0500467 if self.instance and self.instance.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700468 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
Scott Bakerde0f4412014-06-11 15:40:26 -0700469 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
Scott Baker37b47902014-09-02 14:37:41 -0700470 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700471
472 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
473 """ helper function for handling m2m relations from the MultipleChoiceField
474
475 this_obj: the source object we want to link from
476
477 selected_objs: a list of destination objects we want to link to
478
479 all_relations: the full set of relations involving this_obj, including ones we don't want
480
481 relation_class: the class that implements the relation from source to dest
482
483 local_attrname: field name representing this_obj in relation_class
484
485 foreign_attrname: field name representing selected_objs in relation_class
486
487 This function will remove all newobjclass relations from this_obj
488 that are not contained in selected_objs, and add any relations that
489 are in selected_objs but don't exist in the data model yet.
490 """
491
492 existing_dest_objs = []
493 for relation in list(all_relations):
494 if getattr(relation, foreign_attrname) not in selected_objs:
495 #print "deleting site", sdp.site
496 relation.delete()
497 else:
498 existing_dest_objs.append(getattr(relation, foreign_attrname))
499
500 for dest_obj in selected_objs:
501 if dest_obj not in existing_dest_objs:
502 #print "adding site", site
503 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
504 relation = relation_class(**kwargs)
505 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500506
507 def save(self, commit=True):
508 deployment = super(DeploymentAdminForm, self).save(commit=False)
509
Scott Baker37b47902014-09-02 14:37:41 -0700510 deployment.flavors = self.cleaned_data['flavors']
511
Siobhan Tully320b4622014-01-17 15:11:14 -0500512 if commit:
513 deployment.save()
514
515 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700516 # save_m2m() doesn't seem to work with 'through' relations. So we
517 # create/destroy the through models ourselves. There has to be
518 # a better way...
519
Scott Bakerde0f4412014-06-11 15:40:26 -0700520 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
521 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
Scott Bakerc9b14f72014-05-22 13:44:20 -0700522
Scott Baker37b47902014-09-02 14:37:41 -0700523 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500524
525 return deployment
526
Scott Bakerff5e0f32014-05-22 14:40:27 -0700527class DeploymentAdminROForm(DeploymentAdminForm):
528 def save(self, commit=True):
529 raise PermissionDenied
530
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500531class SiteAssocInline(PlStackTabularInline):
532 model = Site.deployments.through
533 extra = 0
534 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400535
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400536class DeploymentAdmin(PlanetStackBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500537 model = Deployment
Tony Mack2cbd3802014-09-29 16:10:52 -0400538 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500539 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
Scott Bakerde0f4412014-06-11 15:40:26 -0700540 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700541 list_display = ['backend_status_icon', 'name']
542 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700543 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500544
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500545 user_readonly_fields = ['name']
546
Scott Bakerde0f4412014-06-11 15:40:26 -0700547 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500548
Scott Bakerff5e0f32014-05-22 14:40:27 -0700549 def get_form(self, request, obj=None, **kwargs):
550 if request.user.isReadOnlyUser():
551 kwargs["form"] = DeploymentAdminROForm
552 else:
553 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700554 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
555
556 # from stackexchange: pass the request object into the form
557
558 class AdminFormMetaClass(adminForm):
559 def __new__(cls, *args, **kwargs):
560 kwargs['request'] = request
561 return adminForm(*args, **kwargs)
562
563 return AdminFormMetaClass
564
Siobhan Tullyce652d02013-10-08 21:52:35 -0400565class ServiceAttrAsTabInline(PlStackTabularInline):
566 model = ServiceAttribute
567 fields = ['name','value']
568 extra = 0
569 suit_classes = 'suit-tab suit-tab-serviceattrs'
570
Siobhan Tullyce652d02013-10-08 21:52:35 -0400571class ServiceAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700572 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
573 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700574 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500575 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
576 inlines = [ServiceAttrAsTabInline,SliceInline]
Scott Baker40c00762014-08-21 16:55:59 -0700577 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500578
579 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500580
581 suit_form_tabs =(('general', 'Service Details'),
582 ('slices','Slices'),
583 ('serviceattrs','Additional Attributes'),
584 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400585
Tony Mack0553f282013-06-10 22:54:50 -0400586class SiteAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700587 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400588 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500589 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400590 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400591 ]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400592 suit_form_tabs =(('general', 'Site Details'),
593 ('users','Users'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400594 ('siteprivileges','Privileges'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400595 ('deployments','Deployments'),
596 ('slices','Slices'),
Scott Baker36f50872014-08-21 13:01:25 -0700597 ('nodes','Nodes'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400598 ('tags','Tags'),
599 )
Scott Baker40c00762014-08-21 16:55:59 -0700600 readonly_fields = ['backend_status_text', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500601
602 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500603
Scott Baker63d1a552014-08-21 15:19:07 -0700604 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
605 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400606 filter_horizontal = ('deployments',)
Tony Macke4be32f2014-03-11 20:45:25 -0400607 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400608 search_fields = ['name']
609
Tony Mack04062832013-05-10 08:22:44 -0400610 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500611 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400612
Tony Mack5cd13202013-05-01 21:48:38 -0400613 def get_formsets(self, request, obj=None):
614 for inline in self.get_inline_instances(request, obj):
615 # hide MyInline in the add view
616 if obj is None:
617 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400618 if isinstance(inline, SliceInline):
619 inline.model.caller = request.user
620 yield inline.get_formset(request, obj)
621
622 def get_formsets(self, request, obj=None):
623 for inline in self.get_inline_instances(request, obj):
624 # hide MyInline in the add view
625 if obj is None:
626 continue
627 if isinstance(inline, SliverInline):
628 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -0400629 yield inline.get_formset(request, obj)
630
Scott Baker545db2a2013-12-09 18:44:43 -0800631 def accountLink(self, obj):
632 link_obj = obj.accounts.all()
633 if link_obj:
634 reverse_path = "admin:core_account_change"
635 url = reverse(reverse_path, args =(link_obj[0].id,))
636 return "<a href='%s'>%s</a>" % (url, "view billing details")
637 else:
638 return "no billing data for this site"
639 accountLink.allow_tags = True
640 accountLink.short_description = "Billing"
641
Tony Mack332ee1d2014-02-04 15:33:45 -0500642 def save_model(self, request, obj, form, change):
643 # update openstack connection to use this site/tenant
644 obj.save_by_user(request.user)
645
646 def delete_model(self, request, obj):
647 obj.delete_by_user(request.user)
648
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500649
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400650class SitePrivilegeAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700651 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -0400652 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500653 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -0400654 ]
Scott Baker40c00762014-08-21 16:55:59 -0700655 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700656 list_display = ('backend_status_icon', 'user', 'site', 'role')
657 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500658 user_readonly_fields = fieldList
659 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -0400660
Tony Mackc2835a92013-05-28 09:18:49 -0400661 def formfield_for_foreignkey(self, db_field, request, **kwargs):
662 if db_field.name == 'site':
663 if not request.user.is_admin:
664 # only show sites where user is an admin or pi
665 sites = set()
666 for site_privilege in SitePrivilege.objects.filer(user=request.user):
667 if site_privilege.role.role_type in ['admin', 'pi']:
668 sites.add(site_privilege.site)
669 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
670
671 if db_field.name == 'user':
672 if not request.user.is_admin:
673 # only show users from sites where caller has admin or pi role
674 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
675 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
676 sites = [site_privilege.site for site_privilege in site_privileges]
677 site_privileges = SitePrivilege.objects.filter(site__in=sites)
678 emails = [site_privilege.user.email for site_privilege in site_privileges]
679 users = User.objects.filter(email__in=emails)
680 kwargs['queryset'] = users
681
682 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
683
Tony Mack04062832013-05-10 08:22:44 -0400684 def queryset(self, request):
685 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -0400686 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -0400687 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -0500688 #if not request.user.is_admin:
689 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
690 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
691 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
692 # sites = Site.objects.filter(login_base__in=login_bases)
693 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -0400694 return qs
695
Siobhan Tullyce652d02013-10-08 21:52:35 -0400696class SliceForm(forms.ModelForm):
697 class Meta:
698 model = Slice
699 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -0700700 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -0400701 }
702
Tony Mack2cbd3802014-09-29 16:10:52 -0400703 def clean(self):
704 cleaned_data = super(SliceForm, self).clean()
705 name = cleaned_data.get('name')
706 site_id = cleaned_data.get('site')
707 site = Slice.objects.get(id=site_id)
708 if not name.startswith(site.login_base):
709 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
710 return cleaned_data
711
Tony Mack2bd5b412013-06-11 21:05:06 -0400712class SliceAdmin(PlanetStackBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400713 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -0400714 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500715 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -0700716 readonly_fields = ('backend_status_text', )
Tony Mack7d459902014-09-03 13:18:57 -0400717 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
718 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully2d95e482013-09-06 10:56:06 -0400719 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400720
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500721 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400722
723 suit_form_tabs =(('general', 'Slice Details'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400724 ('slicenetworks','Networks'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400725 ('sliceprivileges','Privileges'),
726 ('slivers','Slivers'),
727 ('tags','Tags'),
728 ('reservations','Reservations'),
729 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400730
Scott Baker510fdbb2014-08-05 17:19:24 -0700731 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -0700732 deployment_nodes = []
733 for node in Node.objects.all():
734 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
735
Scott Baker7a61dc42014-09-02 17:08:20 -0700736 deployment_flavors = []
737 for flavor in Flavor.objects.all():
738 for deployment in flavor.deployments.all():
739 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
740
Scott Baker93e80cd2014-09-09 09:58:49 -0700741 deployment_images = []
742 for image in Image.objects.all():
743 for imageDeployment in image.imagedeployments_set.all():
744 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
745
Tony Mackec23b992014-09-02 21:18:45 -0400746 site_login_bases = []
747 for site in Site.objects.all():
Scott Baker93e80cd2014-09-09 09:58:49 -0700748 site_login_bases.append((site.id, site.login_base))
749
Scott Baker510fdbb2014-08-05 17:19:24 -0700750 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -0700751 context["deployment_flavors"] = deployment_flavors
Scott Baker93e80cd2014-09-09 09:58:49 -0700752 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -0400753 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -0700754 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
755
Tony Mackc2835a92013-05-28 09:18:49 -0400756 def formfield_for_foreignkey(self, db_field, request, **kwargs):
757 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500758 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackec23b992014-09-02 21:18:45 -0400759 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 -0700760
Tony Mackc2835a92013-05-28 09:18:49 -0400761 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
762
Tony Mack04062832013-05-10 08:22:44 -0400763 def queryset(self, request):
764 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500765 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400766
Tony Mack79748612013-05-01 14:52:03 -0400767 def get_formsets(self, request, obj=None):
768 for inline in self.get_inline_instances(request, obj):
769 # hide MyInline in the add view
770 if obj is None:
771 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400772 if isinstance(inline, SliverInline):
773 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -0400774 yield inline.get_formset(request, obj)
775
Tony Mack2bd5b412013-06-11 21:05:06 -0400776
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400777class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -0400778 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700779 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -0400780 ]
Scott Baker40c00762014-08-21 16:55:59 -0700781 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700782 list_display = ('backend_status_icon', 'user', 'slice', 'role')
783 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -0400784
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500785 user_readonly_fields = ['user', 'slice', 'role']
786 user_readonly_inlines = []
787
Tony Mackc2835a92013-05-28 09:18:49 -0400788 def formfield_for_foreignkey(self, db_field, request, **kwargs):
789 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500790 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400791
792 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500793 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400794
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400795 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400796
Tony Mack04062832013-05-10 08:22:44 -0400797 def queryset(self, request):
798 # admins can see all memberships. Users can only see memberships of
799 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -0500800 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400801
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400802 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -0400803 # update openstack connection to use this site/tenant
804 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400805 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400806 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400807 obj.save()
808
809 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -0400810 # update openstack connection to use this site/tenant
811 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400812 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400813 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400814 obj.delete()
815
Siobhan Tully567e3e62013-06-21 18:03:16 -0400816
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400817class ImageAdmin(PlanetStackBaseAdmin):
818
Scott Baker36f50872014-08-21 13:01:25 -0700819 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -0700820 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400821 'classes': ['suit-tab suit-tab-general']})
822 ]
Scott Baker40c00762014-08-21 16:55:59 -0700823 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400824
Scott Baker2170b972014-06-03 12:14:07 -0700825 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400826
Scott Baker2170b972014-06-03 12:14:07 -0700827 inlines = [SliverInline, ImageDeploymentsInline]
Scott Bakerb6f99242014-06-11 11:34:44 -0700828
Tony Mack32e1ce32014-05-07 13:29:41 -0400829 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -0700830
Scott Baker63d1a552014-08-21 15:19:07 -0700831 list_display = ['backend_status_icon', 'name']
832 list_display_links = ('backend_status_icon', 'name', )
833
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400834class NodeForm(forms.ModelForm):
835 class Meta:
836 widgets = {
837 'site': LinkedSelect,
838 'deployment': LinkedSelect
839 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400840
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500841class NodeAdmin(PlanetStackBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400842 form = NodeForm
Scott Baker63d1a552014-08-21 15:19:07 -0700843 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
844 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400845 list_filter = ('deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500846
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400847 inlines = [TagInline,SliverInline]
Scott Baker40c00762014-08-21 16:55:59 -0700848 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
849 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400850
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500851 user_readonly_fields = ['name','site','deployment']
852 user_readonly_inlines = [TagInline,SliverInline]
853
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400854 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400855
Siobhan Tully567e3e62013-06-21 18:03:16 -0400856
Tony Mackd90cdbf2013-04-16 22:48:40 -0400857class SliverForm(forms.ModelForm):
858 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -0400859 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -0400860 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -0400861 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -0400862 widgets = {
863 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -0400864 'instance_name': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400865 'slice': LinkedSelect,
866 'deploymentNetwork': LinkedSelect,
867 'node': LinkedSelect,
868 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -0400869 }
Tony Mackd90cdbf2013-04-16 22:48:40 -0400870
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500871class TagAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700872 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
873 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500874 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
875 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -0400876
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400877class SliverAdmin(PlanetStackBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -0400878 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -0400879 fieldsets = [
Scott Baker7a61dc42014-09-02 17:08:20 -0700880 ('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 -0400881 ]
Scott Baker40c00762014-08-21 16:55:59 -0700882 readonly_fields = ('backend_status_text', )
Scott Baker7a61dc42014-09-02 17:08:20 -0700883 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
Scott Baker63d1a552014-08-21 15:19:07 -0700884 list_display_links = ('backend_status_icon', 'ip',)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400885
886 suit_form_tabs =(('general', 'Sliver Details'),
887 ('tags','Tags'),
888 )
889
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400890 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -0400891
Scott Baker7a61dc42014-09-02 17:08:20 -0700892 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500893
Tony Mackc2835a92013-05-28 09:18:49 -0400894 def formfield_for_foreignkey(self, db_field, request, **kwargs):
895 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500896 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400897
898 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
899
Tony Mack04062832013-05-10 08:22:44 -0400900 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -0700901 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -0400902 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500903 return Sliver.select_by_user(request.user)
904
Tony Mack04062832013-05-10 08:22:44 -0400905
Tony Mack1d6b85f2013-05-07 18:49:14 -0400906 def get_formsets(self, request, obj=None):
907 # make some fields read only if we are updating an existing record
908 if obj == None:
Scott Baker36f50872014-08-21 13:01:25 -0700909 #self.readonly_fields = ('ip', 'instance_name')
Scott Baker40c00762014-08-21 16:55:59 -0700910 self.readonly_fields = ('backend_status_text')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400911 else:
Scott Baker40c00762014-08-21 16:55:59 -0700912 self.readonly_fields = ('backend_status_text')
Scott Baker36f50872014-08-21 13:01:25 -0700913 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400914
915 for inline in self.get_inline_instances(request, obj):
916 # hide MyInline in the add view
917 if obj is None:
918 continue
Scott Baker526b71e2014-05-13 13:18:01 -0700919 if isinstance(inline, SliverInline):
920 inline.model.caller = request.user
921 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -0400922
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500923 #def save_model(self, request, obj, form, change):
924 # # update openstack connection to use this site/tenant
925 # auth = request.session.get('auth', {})
926 # auth['tenant'] = obj.slice.name
927 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
928 # obj.creator = request.user
929 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -0400930
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500931 #def delete_model(self, request, obj):
932 # # update openstack connection to use this site/tenant
933 # auth = request.session.get('auth', {})
934 # auth['tenant'] = obj.slice.name
935 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
936 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -0400937
Siobhan Tully53437282013-04-26 19:30:27 -0400938class UserCreationForm(forms.ModelForm):
939 """A form for creating new users. Includes all the required
940 fields, plus a repeated password."""
941 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
942 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
943
944 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400945 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400946 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -0400947
948 def clean_password2(self):
949 # Check that the two password entries match
950 password1 = self.cleaned_data.get("password1")
951 password2 = self.cleaned_data.get("password2")
952 if password1 and password2 and password1 != password2:
953 raise forms.ValidationError("Passwords don't match")
954 return password2
955
956 def save(self, commit=True):
957 # Save the provided password in hashed format
958 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -0400959 user.password = self.cleaned_data["password1"]
960 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -0400961 if commit:
962 user.save()
963 return user
964
Siobhan Tully567e3e62013-06-21 18:03:16 -0400965
Siobhan Tully53437282013-04-26 19:30:27 -0400966class UserChangeForm(forms.ModelForm):
967 """A form for updating users. Includes all the fields on
968 the user, but replaces the password field with admin's
969 password hash display field.
970 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -0500971 password = ReadOnlyPasswordHashField(label='Password',
972 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -0400973
974 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400975 model = User
Siobhan Tully53437282013-04-26 19:30:27 -0400976
977 def clean_password(self):
978 # Regardless of what the user provides, return the initial value.
979 # This is done here, rather than on the field, because the
980 # field does not have access to the initial value
981 return self.initial["password"]
982
Scott Baker2c3cb642014-05-19 17:55:56 -0700983class UserDashboardViewInline(PlStackTabularInline):
984 model = UserDashboardView
985 extra = 0
986 suit_classes = 'suit-tab suit-tab-dashboards'
987 fields = ['user', 'dashboardView', 'order']
988
Tony Mack2bd5b412013-06-11 21:05:06 -0400989class UserAdmin(UserAdmin):
Siobhan Tully53437282013-04-26 19:30:27 -0400990 class Meta:
991 app_label = "core"
992
993 # The forms to add and change user instances
994 form = UserChangeForm
995 add_form = UserCreationForm
996
997 # The fields to be used in displaying the User model.
998 # These override the definitions on the base UserAdmin
999 # that reference specific fields on auth.User.
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001000 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001001 list_filter = ('site',)
Scott Baker2c3cb642014-05-19 17:55:56 -07001002 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001003
Scott Bakeradae55f2014-08-14 17:32:35 -07001004 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001005 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1006
Siobhan Tully53437282013-04-26 19:30:27 -04001007 fieldsets = (
Scott Baker40c00762014-08-21 16:55:59 -07001008 ('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 -04001009 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Scott Baker2c3cb642014-05-19 17:55:56 -07001010 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001011 #('Important dates', {'fields': ('last_login',)}),
1012 )
1013 add_fieldsets = (
1014 (None, {
1015 'classes': ('wide',),
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001016 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
Siobhan Tully53437282013-04-26 19:30:27 -04001017 ),
1018 )
Scott Baker40c00762014-08-21 16:55:59 -07001019 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001020 search_fields = ('email',)
1021 ordering = ('email',)
1022 filter_horizontal = ()
1023
Scott Baker3ca51f62014-05-23 12:05:11 -07001024 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001025
Scott Baker2c3cb642014-05-19 17:55:56 -07001026 suit_form_tabs =(('general','Login Details'),
1027 ('contact','Contact Information'),
1028 ('sliceprivileges','Slice Privileges'),
1029 ('siteprivileges','Site Privileges'),
1030 ('deploymentprivileges','Deployment Privileges'),
1031 ('dashboards','Dashboard Views'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001032
Tony Mackc2835a92013-05-28 09:18:49 -04001033 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1034 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001035 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001036
1037 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1038
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001039 def has_add_permission(self, request, obj=None):
1040 return (not self.__user_is_readonly(request))
1041
1042 def has_delete_permission(self, request, obj=None):
1043 return (not self.__user_is_readonly(request))
1044
1045 def get_actions(self,request):
1046 actions = super(UserAdmin,self).get_actions(request)
1047
1048 if self.__user_is_readonly(request):
1049 if 'delete_selected' in actions:
1050 del actions['delete_selected']
1051
1052 return actions
1053
1054 def change_view(self,request,object_id, extra_context=None):
1055
1056 if self.__user_is_readonly(request):
Scott Bakerf875eba2014-05-23 12:09:15 -07001057 if not hasattr(self, "readonly_save"):
1058 # save the original readonly fields
1059 self.readonly_save = self.readonly_fields
1060 self.inlines_save = self.inlines
Scott Bakerb27b62c2014-08-15 16:29:16 -07001061 if hasattr(self, "user_readonly_fields"):
1062 self.readonly_fields=self.user_readonly_fields
1063 if hasattr(self, "user_readonly_inlines"):
1064 self.inlines = self.user_readonly_inlines
Scott Bakerf875eba2014-05-23 12:09:15 -07001065 else:
1066 if hasattr(self, "readonly_save"):
1067 # restore the original readonly fields
1068 self.readonly_fields = self.readonly_save
1069 self.inlines = self.inlines_save
1070
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001071 try:
1072 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1073 except PermissionDenied:
1074 pass
1075 if request.method == 'POST':
1076 raise PermissionDenied
1077 request.readonly = True
1078 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1079
1080 def __user_is_readonly(self, request):
1081 #groups = [x.name for x in request.user.groups.all() ]
1082 #return "readonly" in groups
1083 return request.user.isReadOnlyUser()
1084
Tony Mack5b061472014-02-04 07:57:10 -05001085 def queryset(self, request):
1086 return User.select_by_user(request.user)
1087
Scott Baker40c00762014-08-21 16:55:59 -07001088 def backend_status_text(self, obj):
1089 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -07001090
Scott Baker40c00762014-08-21 16:55:59 -07001091 def backend_status_icon(self, obj):
1092 return mark_safe(backend_icon(obj))
1093 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -07001094
Scott Baker2c3cb642014-05-19 17:55:56 -07001095class DashboardViewAdmin(PlanetStackBaseAdmin):
1096 fieldsets = [('Dashboard View Details',
Scott Baker40c00762014-08-21 16:55:59 -07001097 {'fields': ['backend_status_text', 'name', 'url'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001098 'classes': ['suit-tab suit-tab-general']})
1099 ]
Scott Baker40c00762014-08-21 16:55:59 -07001100 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001101
Scott Baker2c3cb642014-05-19 17:55:56 -07001102 suit_form_tabs =(('general','Dashboard View Details'),)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001103
Scott Baker0165fac2014-01-13 11:49:26 -08001104class ServiceResourceInline(PlStackTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001105 model = ServiceResource
1106 extra = 0
1107
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001108class ServiceClassAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001109 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1110 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001111 inlines = [ServiceResourceInline]
1112
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001113 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1114 user_readonly_inlines = []
1115
Scott Baker0165fac2014-01-13 11:49:26 -08001116class ReservedResourceInline(PlStackTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001117 model = ReservedResource
1118 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001119 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001120
1121 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1122 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1123
1124 if db_field.name == 'resource':
1125 # restrict resources to those that the slice's service class allows
1126 if request._slice is not None:
1127 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1128 if len(field.queryset) > 0:
1129 field.initial = field.queryset.all()[0]
1130 else:
1131 field.queryset = field.queryset.none()
1132 elif db_field.name == 'sliver':
1133 # restrict slivers to those that belong to the slice
1134 if request._slice is not None:
1135 field.queryset = field.queryset.filter(slice = request._slice)
1136 else:
1137 field.queryset = field.queryset.none()
1138
1139 return field
1140
Tony Mack5b061472014-02-04 07:57:10 -05001141 def queryset(self, request):
1142 return ReservedResource.select_by_user(request.user)
1143
Scott Baker133c9212013-05-17 09:09:11 -07001144class ReservationChangeForm(forms.ModelForm):
1145 class Meta:
1146 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001147 widgets = {
1148 'slice' : LinkedSelect
1149 }
Scott Baker133c9212013-05-17 09:09:11 -07001150
1151class ReservationAddForm(forms.ModelForm):
1152 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1153 refresh = forms.CharField(widget=forms.HiddenInput())
1154
1155 class Media:
1156 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1157
1158 def clean_slice(self):
1159 slice = self.cleaned_data.get("slice")
1160 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1161 if len(x) == 0:
1162 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1163 return slice
1164
1165 class Meta:
1166 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001167 widgets = {
1168 'slice' : LinkedSelect
1169 }
1170
Scott Baker133c9212013-05-17 09:09:11 -07001171
1172class ReservationAddRefreshForm(ReservationAddForm):
1173 """ This form is displayed when the Reservation Form receives an update
1174 from the Slice dropdown onChange handler. It doesn't validate the
1175 data and doesn't save the data. This will cause the form to be
1176 redrawn.
1177 """
1178
Scott Baker8737e5f2013-05-17 09:35:32 -07001179 """ don't validate anything other than slice """
1180 dont_validate_fields = ("startTime", "duration")
1181
Scott Baker133c9212013-05-17 09:09:11 -07001182 def full_clean(self):
1183 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001184
1185 for fieldname in self.dont_validate_fields:
1186 if fieldname in self._errors:
1187 del self._errors[fieldname]
1188
Scott Baker133c9212013-05-17 09:09:11 -07001189 return result
1190
1191 """ don't save anything """
1192 def is_valid(self):
1193 return False
1194
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001195class ReservationAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001196 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001197 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001198 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001199 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001200 form = ReservationAddForm
1201
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001202 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1203
1204 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001205 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001206
Scott Baker133c9212013-05-17 09:09:11 -07001207 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001208 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001209 request._refresh = False
1210 request._slice = None
1211 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001212 # "refresh" will be set to "1" if the form was submitted due to
1213 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001214 if request.POST.get("refresh","1") == "1":
1215 request._refresh = True
1216 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001217
1218 # Keep track of the slice that was selected, so the
1219 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001220 request._slice = request.POST.get("slice",None)
1221 if (request._slice is not None):
1222 request._slice = Slice.objects.get(id=request._slice)
1223
1224 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1225 return result
1226
Scott Bakeracd45142013-05-19 16:19:16 -07001227 def changelist_view(self, request, extra_context = None):
1228 timezone.activate(request.user.timezone)
1229 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1230
Scott Baker133c9212013-05-17 09:09:11 -07001231 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001232 request._obj_ = obj
1233 if obj is not None:
1234 # For changes, set request._slice to the slice already set in the
1235 # object.
1236 request._slice = obj.slice
1237 self.form = ReservationChangeForm
1238 else:
1239 if getattr(request, "_refresh", False):
1240 self.form = ReservationAddRefreshForm
1241 else:
1242 self.form = ReservationAddForm
1243 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1244
Scott Baker133c9212013-05-17 09:09:11 -07001245 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001246 if (obj is not None):
1247 # Prevent slice from being changed after the reservation has been
1248 # created.
1249 return ['slice']
1250 else:
Scott Baker133c9212013-05-17 09:09:11 -07001251 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001252
Tony Mack5b061472014-02-04 07:57:10 -05001253 def queryset(self, request):
1254 return Reservation.select_by_user(request.user)
1255
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001256class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001257 list_display = ("backend_status_icon", "name", )
1258 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001259 user_readonly_fields = ['name']
1260 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001261
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001262class RouterAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001263 list_display = ("backend_status_icon", "name", )
1264 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001265 user_readonly_fields = ['name']
1266 user_readonly_inlines = []
1267
Scott Baker0165fac2014-01-13 11:49:26 -08001268class RouterInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001269 model = Router.networks.through
1270 extra = 0
1271 verbose_name_plural = "Routers"
1272 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001273 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001274
Scott Bakerb27b62c2014-08-15 16:29:16 -07001275class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001276 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001277 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001278 verbose_name_plural = "Parameters"
1279 verbose_name = "Parameter"
1280 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001281 fields = ['backend_status_icon', 'parameter', 'value']
1282 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001283
Scott Baker0165fac2014-01-13 11:49:26 -08001284class NetworkSliversInline(PlStackTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001285 fields = ['backend_status_icon', 'network','sliver','ip']
1286 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001287 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001288 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001289 extra = 0
1290 verbose_name_plural = "Slivers"
1291 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001292 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001293
Scott Baker0165fac2014-01-13 11:49:26 -08001294class NetworkSlicesInline(PlStackTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001295 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001296 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001297 extra = 0
1298 verbose_name_plural = "Slices"
1299 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001300 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001301 fields = ['backend_status_icon', 'network','slice']
1302 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001303
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001304class NetworkAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001305 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1306 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001307 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001308
Scott Bakerd7d2a392013-08-06 08:57:30 -07001309 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001310
Siobhan Tully2d95e482013-09-06 10:56:06 -04001311 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001312 (None, {'fields': ['backend_status_text', 'name','template','ports','labels','owner','guaranteedBandwidth', 'permitAllSlices','permittedSlices','network_id','router_id','subnet_id','subnet'], 'classes':['suit-tab suit-tab-general']}),]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001313
Scott Baker40c00762014-08-21 16:55:59 -07001314 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001315 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 -04001316
1317 suit_form_tabs =(
1318 ('general','Network Details'),
1319 ('netparams', 'Parameters'),
1320 ('networkslivers','Slivers'),
1321 ('networkslices','Slices'),
1322 ('routers','Routers'),
1323 )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001324class NetworkTemplateAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001325 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1326 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001327 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1328 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001329
Scott Baker37b47902014-09-02 14:37:41 -07001330class FlavorAdmin(PlanetStackBaseAdmin):
1331 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1332 list_display_links = ("backend_status_icon", "name")
1333 user_readonly_fields = ("name", "flavor")
1334 fields = ("name", "description", "flavor", "order", "default")
1335
Tony Mack31c2b8f2013-04-26 20:01:42 -04001336# register a signal that caches the user's credentials when they log in
1337def cache_credentials(sender, user, request, **kwds):
1338 auth = {'username': request.POST['username'],
1339 'password': request.POST['password']}
1340 request.session['auth'] = auth
1341user_logged_in.connect(cache_credentials)
1342
Scott Baker15cddfa2013-12-09 13:45:19 -08001343def dollar_field(fieldName, short_description):
1344 def newFunc(self, obj):
1345 try:
1346 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1347 except:
1348 x=getattr(obj, fieldName, 0.0)
1349 return x
1350 newFunc.short_description = short_description
1351 return newFunc
1352
1353def right_dollar_field(fieldName, short_description):
1354 def newFunc(self, obj):
1355 try:
1356 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1357 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1358 except:
1359 x=getattr(obj, fieldName, 0.0)
1360 return x
1361 newFunc.short_description = short_description
1362 newFunc.allow_tags = True
1363 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001364
Scott Baker0165fac2014-01-13 11:49:26 -08001365class InvoiceChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001366 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001367 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001368 verbose_name_plural = "Charges"
1369 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001370 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001371 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1372 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1373 can_delete = False
1374 max_num = 0
1375
1376 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001377
1378class InvoiceAdmin(admin.ModelAdmin):
1379 list_display = ("date", "account")
1380
1381 inlines = [InvoiceChargeInline]
1382
Scott Baker9cb88a22013-12-09 18:56:00 -08001383 fields = ["date", "account", "dollar_amount"]
1384 readonly_fields = ["date", "account", "dollar_amount"]
1385
1386 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001387
Scott Baker0165fac2014-01-13 11:49:26 -08001388class InvoiceInline(PlStackTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001389 model = Invoice
1390 extra = 0
1391 verbose_name_plural = "Invoices"
1392 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001393 fields = ["date", "dollar_amount"]
1394 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001395 suit_classes = 'suit-tab suit-tab-accountinvoice'
1396 can_delete=False
1397 max_num=0
1398
1399 dollar_amount = right_dollar_field("amount", "Amount")
1400
Scott Baker0165fac2014-01-13 11:49:26 -08001401class PendingChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001402 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001403 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001404 verbose_name_plural = "Charges"
1405 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001406 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001407 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1408 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001409 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001410 can_delete=False
1411 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001412
1413 def queryset(self, request):
1414 qs = super(PendingChargeInline, self).queryset(request)
1415 qs = qs.filter(state="pending")
1416 return qs
1417
Scott Baker15cddfa2013-12-09 13:45:19 -08001418 dollar_amount = right_dollar_field("amount", "Amount")
1419
Scott Baker0165fac2014-01-13 11:49:26 -08001420class PaymentInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001421 model=Payment
1422 extra = 1
1423 verbose_name_plural = "Payments"
1424 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001425 fields = ["date", "dollar_amount"]
1426 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001427 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001428 can_delete=False
1429 max_num=0
1430
1431 dollar_amount = right_dollar_field("amount", "Amount")
1432
Scott Baker43105042013-12-06 23:23:36 -08001433class AccountAdmin(admin.ModelAdmin):
1434 list_display = ("site", "balance_due")
1435
1436 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1437
1438 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001439 (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 -08001440
Scott Baker15cddfa2013-12-09 13:45:19 -08001441 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001442
1443 suit_form_tabs =(
1444 ('general','Account Details'),
1445 ('accountinvoice', 'Invoices'),
1446 ('accountpayments', 'Payments'),
1447 ('accountpendingcharges','Pending Charges'),
1448 )
1449
Scott Baker15cddfa2013-12-09 13:45:19 -08001450 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1451 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1452 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1453
Siobhan Tully53437282013-04-26 19:30:27 -04001454# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001455admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001456# ... and, since we're not using Django's builtin permissions,
1457# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001458#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001459
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001460#Do not show django evolution in the admin interface
1461from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001462#admin.site.unregister(Version)
1463#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001464
1465
1466# When debugging it is often easier to see all the classes, but for regular use
1467# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001468showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001469
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001470admin.site.register(Deployment, DeploymentAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001471admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001472admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001473admin.site.register(Service, ServiceAdmin)
smbakera3cf70c2013-06-27 02:01:41 -07001474admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001475admin.site.register(Network, NetworkAdmin)
1476admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001477admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001478admin.site.register(Account, AccountAdmin)
1479admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001480
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001481if True:
1482 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1483 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001484 #admin.site.register(PlanetStack)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001485 admin.site.register(Tag, TagAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001486 admin.site.register(DeploymentRole)
1487 admin.site.register(SiteRole)
1488 admin.site.register(SliceRole)
1489 admin.site.register(PlanetStackRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001490 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001491 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1492 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001493 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001494 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001495 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07001496 admin.site.register(Flavor, FlavorAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001497