blob: 66e26ebb634f159f980c2d0efddd1a829814c4f5 [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:
24 return '<img src="/static/admin/img/icon_success.gif">'
25 else:
26 if obj.backend_status == "Provisioning in progress" or obj.backend_status=="":
Scott Bakerbf88e7e2014-08-28 15:15:18 -070027 return '<span title="%s"><img src="/static/admin/img/icon_clock.gif"></span>' % obj.backend_status
Scott Baker63d1a552014-08-21 15:19:07 -070028 else:
Scott Bakerbf88e7e2014-08-28 15:15:18 -070029 return '<span 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 Baker40c00762014-08-21 16:55:59 -0700269 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', '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 Baker34b502f2014-08-05 18:33:31 -0700280 # the inscrutable jquery selector below says:
281 # find the closest parent "tr" to the current element
282 # then find the child with class "field-node"
283 # then find the child with that is a select
284 # then return its id
Scott Bakerdf65d882014-08-05 18:52:14 -0700285 kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"})
Scott Baker34b502f2014-08-05 18:33:31 -0700286 #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"})
Scott Baker3b678742014-06-09 13:11:54 -0700287
288 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700289
290 return field
291
Scott Baker434ca7e2014-08-15 12:29:20 -0700292"""
293 SMBAKER: This is the old code that implemented each network type as a
294 separate column in the sliver table.
Scott Baker74d8e622013-07-29 16:04:22 -0700295
Scott Baker434ca7e2014-08-15 12:29:20 -0700296 def _declared_fieldsets(self):
297 # Return None so django will call get_fieldsets and we can insert our
298 # dynamic fields
299 return None
300
301 def get_readonly_fields(self, request, obj=None):
302 readonly_fields = list(super(SliverInline, self).get_readonly_fields(request, obj))
303
304 # Lookup the networks that are bound to the slivers, and add those
305 # network names to the list of readonly fields.
306
307 for sliver in obj.slivers.all():
308 for nbs in sliver.networksliver_set.all():
309 if nbs.ip:
310 network_name = nbs.network.name
311 if network_name not in [str(x) for x in readonly_fields]:
312 readonly_fields.append(NetworkLookerUpper.get(network_name))
313
314 return readonly_fields
315
316 def get_fieldsets(self, request, obj=None):
317 form = self.get_formset(request, obj).form
318 # fields = the read/write files + the read-only fields
319 fields = list(self.fields)
320 for fieldName in self.get_readonly_fields(request,obj):
321 if not fieldName in fields:
322 fields.append(fieldName)
323
324 return [(None, {'fields': fields})]
325"""
Siobhan Tully567e3e62013-06-21 18:03:16 -0400326
Siobhan Tullyd3515752013-06-21 16:34:53 -0400327class SiteInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400328 model = Site
329 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400330 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400331
Tony Mack5b061472014-02-04 07:57:10 -0500332 def queryset(self, request):
333 return Site.select_by_user(request.user)
334
Siobhan Tullyd3515752013-06-21 16:34:53 -0400335class UserInline(PlStackTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400336 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700337 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
338 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400339 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400340 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400341
Tony Mack5b061472014-02-04 07:57:10 -0500342 def queryset(self, request):
343 return User.select_by_user(request.user)
344
Siobhan Tullyd3515752013-06-21 16:34:53 -0400345class SliceInline(PlStackTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400346 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700347 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
348 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400349 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400350 suit_classes = 'suit-tab suit-tab-slices'
351
Tony Mack5b061472014-02-04 07:57:10 -0500352 def queryset(self, request):
353 return Slice.select_by_user(request.user)
354
Siobhan Tullyd3515752013-06-21 16:34:53 -0400355class NodeInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400356 model = Node
357 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400358 suit_classes = 'suit-tab suit-tab-nodes'
Scott Baker40c00762014-08-21 16:55:59 -0700359 fields = ['backend_status_icon', 'name','deployment','site']
360 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400361
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400362class DeploymentPrivilegeInline(PlStackTabularInline):
363 model = DeploymentPrivilege
364 extra = 0
365 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700366 fields = ['backend_status_icon', 'user','role','deployment']
367 readonly_fields = ('backend_status_icon', )
Tony Mack5b061472014-02-04 07:57:10 -0500368
369 def queryset(self, request):
370 return DeploymentPrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400371
Siobhan Tullyd3515752013-06-21 16:34:53 -0400372class SitePrivilegeInline(PlStackTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400373 model = SitePrivilege
374 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400375 suit_classes = 'suit-tab suit-tab-siteprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700376 fields = ['backend_status_icon', 'user','site', 'role']
377 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400378
Tony Mackc2835a92013-05-28 09:18:49 -0400379 def formfield_for_foreignkey(self, db_field, request, **kwargs):
380 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500381 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400382
383 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500384 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400385 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
386
Tony Mack5b061472014-02-04 07:57:10 -0500387 def queryset(self, request):
388 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400389
Tony Macke4be32f2014-03-11 20:45:25 -0400390class SiteDeploymentInline(PlStackTabularInline):
391 model = SiteDeployments
Tony Macke4be32f2014-03-11 20:45:25 -0400392 extra = 0
393 suit_classes = 'suit-tab suit-tab-deployments'
Scott Baker40c00762014-08-21 16:55:59 -0700394 fields = ['backend_status_icon', 'deployment','site']
395 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400396
397 def formfield_for_foreignkey(self, db_field, request, **kwargs):
398 if db_field.name == 'site':
399 kwargs['queryset'] = Site.select_by_user(request.user)
400
401 if db_field.name == 'deployment':
402 kwargs['queryset'] = Deployment.select_by_user(request.user)
403 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
404
405 def queryset(self, request):
406 return SiteDeployments.select_by_user(request.user)
407
408
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400409class SlicePrivilegeInline(PlStackTabularInline):
410 model = SlicePrivilege
411 suit_classes = 'suit-tab suit-tab-sliceprivileges'
412 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700413 fields = ('backend_status_icon', 'user', 'slice', 'role')
414 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400415
Tony Mackc2835a92013-05-28 09:18:49 -0400416 def formfield_for_foreignkey(self, db_field, request, **kwargs):
417 if db_field.name == 'slice':
Scott Baker36f50872014-08-21 13:01:25 -0700418 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400419 if db_field.name == 'user':
Scott Baker36f50872014-08-21 13:01:25 -0700420 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400421
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400422 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400423
Tony Mack5b061472014-02-04 07:57:10 -0500424 def queryset(self, request):
425 return SlicePrivilege.select_by_user(request.user)
426
Scott Bakera0015eb2013-08-14 17:28:14 -0700427class SliceNetworkInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700428 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800429 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700430 extra = 0
431 verbose_name = "Network Connection"
432 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400433 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700434 fields = ['backend_status_icon', 'network']
435 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700436
437class ImageDeploymentsInline(PlStackTabularInline):
438 model = ImageDeployments
439 extra = 0
440 verbose_name = "Image Deployments"
441 verbose_name_plural = "Image Deployments"
442 suit_classes = 'suit-tab suit-tab-imagedeployments'
Scott Baker40c00762014-08-21 16:55:59 -0700443 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
444 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700445
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500446class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400447 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700448
Tony Mack332ee1d2014-02-04 15:33:45 -0500449 def save_model(self, request, obj, form, change):
Tony Mack3d042792014-03-17 19:18:37 -0400450 obj.caller = request.user
Tony Mack332ee1d2014-02-04 15:33:45 -0500451 # update openstack connection to use this site/tenant
452 obj.save_by_user(request.user)
453
454 def delete_model(self, request, obj):
455 obj.delete_by_user(request.user)
456
457 def save_formset(self, request, form, formset, change):
458 instances = formset.save(commit=False)
459 for instance in instances:
460 instance.save_by_user(request.user)
461 formset.save_m2m()
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400462
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400463class SliceRoleAdmin(PlanetStackBaseAdmin):
464 model = SliceRole
465 pass
466
467class SiteRoleAdmin(PlanetStackBaseAdmin):
468 model = SiteRole
469 pass
470
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400471class DeploymentAdminForm(forms.ModelForm):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400472 sites = forms.ModelMultipleChoiceField(
473 queryset=Site.objects.all(),
474 required=False,
Scott Baker70983182014-06-09 22:10:00 -0700475 help_text="Select which sites are allowed to host nodes in this deployment",
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400476 widget=FilteredSelectMultiple(
477 verbose_name=('Sites'), is_stacked=False
478 )
479 )
Scott Bakerde0f4412014-06-11 15:40:26 -0700480 images = forms.ModelMultipleChoiceField(
481 queryset=Image.objects.all(),
482 required=False,
483 help_text="Select which images should be deployed on this deployment",
484 widget=FilteredSelectMultiple(
485 verbose_name=('Images'), is_stacked=False
486 )
487 )
Scott Baker37b47902014-09-02 14:37:41 -0700488 flavors = forms.ModelMultipleChoiceField(
489 queryset=Flavor.objects.all(),
490 required=False,
491 help_text="Select which flavors should be usable on this deployment",
492 widget=FilteredSelectMultiple(
493 verbose_name=('Flavors'), is_stacked=False
494 )
495 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400496 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400497 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700498 many_to_many = ["flavors",]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400499
Siobhan Tully320b4622014-01-17 15:11:14 -0500500 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700501 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500502 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
503
Scott Baker5380c522014-06-06 14:49:43 -0700504 self.fields['accessControl'].initial = "allow site " + request.user.site.name
505
Siobhan Tully320b4622014-01-17 15:11:14 -0500506 if self.instance and self.instance.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700507 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
Scott Bakerde0f4412014-06-11 15:40:26 -0700508 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
Scott Baker37b47902014-09-02 14:37:41 -0700509 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700510
511 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
512 """ helper function for handling m2m relations from the MultipleChoiceField
513
514 this_obj: the source object we want to link from
515
516 selected_objs: a list of destination objects we want to link to
517
518 all_relations: the full set of relations involving this_obj, including ones we don't want
519
520 relation_class: the class that implements the relation from source to dest
521
522 local_attrname: field name representing this_obj in relation_class
523
524 foreign_attrname: field name representing selected_objs in relation_class
525
526 This function will remove all newobjclass relations from this_obj
527 that are not contained in selected_objs, and add any relations that
528 are in selected_objs but don't exist in the data model yet.
529 """
530
531 existing_dest_objs = []
532 for relation in list(all_relations):
533 if getattr(relation, foreign_attrname) not in selected_objs:
534 #print "deleting site", sdp.site
535 relation.delete()
536 else:
537 existing_dest_objs.append(getattr(relation, foreign_attrname))
538
539 for dest_obj in selected_objs:
540 if dest_obj not in existing_dest_objs:
541 #print "adding site", site
542 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
543 relation = relation_class(**kwargs)
544 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500545
546 def save(self, commit=True):
547 deployment = super(DeploymentAdminForm, self).save(commit=False)
548
Scott Baker37b47902014-09-02 14:37:41 -0700549 deployment.flavors = self.cleaned_data['flavors']
550
Siobhan Tully320b4622014-01-17 15:11:14 -0500551 if commit:
552 deployment.save()
553
554 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700555 # save_m2m() doesn't seem to work with 'through' relations. So we
556 # create/destroy the through models ourselves. There has to be
557 # a better way...
558
Scott Bakerde0f4412014-06-11 15:40:26 -0700559 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
560 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
Scott Bakerc9b14f72014-05-22 13:44:20 -0700561
Scott Baker37b47902014-09-02 14:37:41 -0700562 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500563
564 return deployment
565
Scott Bakerff5e0f32014-05-22 14:40:27 -0700566class DeploymentAdminROForm(DeploymentAdminForm):
567 def save(self, commit=True):
568 raise PermissionDenied
569
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500570class SiteAssocInline(PlStackTabularInline):
571 model = Site.deployments.through
572 extra = 0
573 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400574
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400575class DeploymentAdmin(PlanetStackBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500576 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700577 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'flavors', 'accessControl']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500578 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
Scott Bakerde0f4412014-06-11 15:40:26 -0700579 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700580 list_display = ['backend_status_icon', 'name']
581 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700582 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500583
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500584 user_readonly_fields = ['name']
585
Scott Bakerde0f4412014-06-11 15:40:26 -0700586 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500587
Scott Bakerff5e0f32014-05-22 14:40:27 -0700588 def get_form(self, request, obj=None, **kwargs):
589 if request.user.isReadOnlyUser():
590 kwargs["form"] = DeploymentAdminROForm
591 else:
592 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700593 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
594
595 # from stackexchange: pass the request object into the form
596
597 class AdminFormMetaClass(adminForm):
598 def __new__(cls, *args, **kwargs):
599 kwargs['request'] = request
600 return adminForm(*args, **kwargs)
601
602 return AdminFormMetaClass
603
Siobhan Tullyce652d02013-10-08 21:52:35 -0400604class ServiceAttrAsTabInline(PlStackTabularInline):
605 model = ServiceAttribute
606 fields = ['name','value']
607 extra = 0
608 suit_classes = 'suit-tab suit-tab-serviceattrs'
609
Siobhan Tullyce652d02013-10-08 21:52:35 -0400610class ServiceAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700611 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
612 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700613 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500614 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
615 inlines = [ServiceAttrAsTabInline,SliceInline]
Scott Baker40c00762014-08-21 16:55:59 -0700616 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500617
618 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500619
620 suit_form_tabs =(('general', 'Service Details'),
621 ('slices','Slices'),
622 ('serviceattrs','Additional Attributes'),
623 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400624
Tony Mack0553f282013-06-10 22:54:50 -0400625class SiteAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700626 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400627 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500628 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400629 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400630 ]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400631 suit_form_tabs =(('general', 'Site Details'),
632 ('users','Users'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400633 ('siteprivileges','Privileges'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400634 ('deployments','Deployments'),
635 ('slices','Slices'),
Scott Baker36f50872014-08-21 13:01:25 -0700636 ('nodes','Nodes'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400637 ('tags','Tags'),
638 )
Scott Baker40c00762014-08-21 16:55:59 -0700639 readonly_fields = ['backend_status_text', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500640
641 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500642
Scott Baker63d1a552014-08-21 15:19:07 -0700643 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
644 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400645 filter_horizontal = ('deployments',)
Tony Macke4be32f2014-03-11 20:45:25 -0400646 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400647 search_fields = ['name']
648
Tony Mack04062832013-05-10 08:22:44 -0400649 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500650 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400651
Tony Mack5cd13202013-05-01 21:48:38 -0400652 def get_formsets(self, request, obj=None):
653 for inline in self.get_inline_instances(request, obj):
654 # hide MyInline in the add view
655 if obj is None:
656 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400657 if isinstance(inline, SliceInline):
658 inline.model.caller = request.user
659 yield inline.get_formset(request, obj)
660
661 def get_formsets(self, request, obj=None):
662 for inline in self.get_inline_instances(request, obj):
663 # hide MyInline in the add view
664 if obj is None:
665 continue
666 if isinstance(inline, SliverInline):
667 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -0400668 yield inline.get_formset(request, obj)
669
Scott Baker545db2a2013-12-09 18:44:43 -0800670 def accountLink(self, obj):
671 link_obj = obj.accounts.all()
672 if link_obj:
673 reverse_path = "admin:core_account_change"
674 url = reverse(reverse_path, args =(link_obj[0].id,))
675 return "<a href='%s'>%s</a>" % (url, "view billing details")
676 else:
677 return "no billing data for this site"
678 accountLink.allow_tags = True
679 accountLink.short_description = "Billing"
680
Tony Mack332ee1d2014-02-04 15:33:45 -0500681 def save_model(self, request, obj, form, change):
682 # update openstack connection to use this site/tenant
683 obj.save_by_user(request.user)
684
685 def delete_model(self, request, obj):
686 obj.delete_by_user(request.user)
687
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500688
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400689class SitePrivilegeAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700690 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -0400691 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500692 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -0400693 ]
Scott Baker40c00762014-08-21 16:55:59 -0700694 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700695 list_display = ('backend_status_icon', 'user', 'site', 'role')
696 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500697 user_readonly_fields = fieldList
698 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -0400699
Tony Mackc2835a92013-05-28 09:18:49 -0400700 def formfield_for_foreignkey(self, db_field, request, **kwargs):
701 if db_field.name == 'site':
702 if not request.user.is_admin:
703 # only show sites where user is an admin or pi
704 sites = set()
705 for site_privilege in SitePrivilege.objects.filer(user=request.user):
706 if site_privilege.role.role_type in ['admin', 'pi']:
707 sites.add(site_privilege.site)
708 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
709
710 if db_field.name == 'user':
711 if not request.user.is_admin:
712 # only show users from sites where caller has admin or pi role
713 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
714 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
715 sites = [site_privilege.site for site_privilege in site_privileges]
716 site_privileges = SitePrivilege.objects.filter(site__in=sites)
717 emails = [site_privilege.user.email for site_privilege in site_privileges]
718 users = User.objects.filter(email__in=emails)
719 kwargs['queryset'] = users
720
721 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
722
Tony Mack04062832013-05-10 08:22:44 -0400723 def queryset(self, request):
724 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -0400725 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -0400726 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -0500727 #if not request.user.is_admin:
728 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
729 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
730 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
731 # sites = Site.objects.filter(login_base__in=login_bases)
732 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -0400733 return qs
734
Siobhan Tullyce652d02013-10-08 21:52:35 -0400735class SliceForm(forms.ModelForm):
736 class Meta:
737 model = Slice
738 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -0700739 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -0400740 }
741
Tony Mack2bd5b412013-06-11 21:05:06 -0400742class SliceAdmin(PlanetStackBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400743 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -0400744 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500745 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -0700746 readonly_fields = ('backend_status_text', )
Tony Mack49f75b92014-08-27 11:44:09 -0400747 list_display = ('backend_status_icon', 'slicename', 'site','serviceClass', 'slice_url', 'max_slivers')
748 list_display_links = ('backend_status_icon', 'slicename', )
Siobhan Tully2d95e482013-09-06 10:56:06 -0400749 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400750
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500751 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400752
753 suit_form_tabs =(('general', 'Slice Details'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400754 ('slicenetworks','Networks'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400755 ('sliceprivileges','Privileges'),
756 ('slivers','Slivers'),
757 ('tags','Tags'),
758 ('reservations','Reservations'),
759 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400760
Scott Baker510fdbb2014-08-05 17:19:24 -0700761 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
762 #deployment_nodes = {}
763 #for node in Node.objects.all():
764 # deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
765
766 deployment_nodes = []
767 for node in Node.objects.all():
768 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
769
770 context["deployment_nodes"] = deployment_nodes
771
772 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
773
Tony Mackc2835a92013-05-28 09:18:49 -0400774 def formfield_for_foreignkey(self, db_field, request, **kwargs):
775 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500776 kwargs['queryset'] = Site.select_by_user(request.user)
Scott Baker40c00762014-08-21 16:55:59 -0700777
Tony Mackc2835a92013-05-28 09:18:49 -0400778 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
779
Tony Mack04062832013-05-10 08:22:44 -0400780 def queryset(self, request):
781 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500782 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400783
Tony Mack79748612013-05-01 14:52:03 -0400784 def get_formsets(self, request, obj=None):
785 for inline in self.get_inline_instances(request, obj):
786 # hide MyInline in the add view
787 if obj is None:
788 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400789 if isinstance(inline, SliverInline):
790 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -0400791 yield inline.get_formset(request, obj)
792
Tony Mack2bd5b412013-06-11 21:05:06 -0400793
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400794class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -0400795 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700796 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -0400797 ]
Scott Baker40c00762014-08-21 16:55:59 -0700798 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700799 list_display = ('backend_status_icon', 'user', 'slice', 'role')
800 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -0400801
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500802 user_readonly_fields = ['user', 'slice', 'role']
803 user_readonly_inlines = []
804
Tony Mackc2835a92013-05-28 09:18:49 -0400805 def formfield_for_foreignkey(self, db_field, request, **kwargs):
806 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500807 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400808
809 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500810 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400811
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400812 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400813
Tony Mack04062832013-05-10 08:22:44 -0400814 def queryset(self, request):
815 # admins can see all memberships. Users can only see memberships of
816 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -0500817 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400818
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400819 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -0400820 # update openstack connection to use this site/tenant
821 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400822 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400823 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400824 obj.save()
825
826 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -0400827 # update openstack connection to use this site/tenant
828 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400829 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400830 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400831 obj.delete()
832
Siobhan Tully567e3e62013-06-21 18:03:16 -0400833
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400834class ImageAdmin(PlanetStackBaseAdmin):
835
Scott Baker36f50872014-08-21 13:01:25 -0700836 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -0700837 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400838 'classes': ['suit-tab suit-tab-general']})
839 ]
Scott Baker40c00762014-08-21 16:55:59 -0700840 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400841
Scott Baker2170b972014-06-03 12:14:07 -0700842 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400843
Scott Baker2170b972014-06-03 12:14:07 -0700844 inlines = [SliverInline, ImageDeploymentsInline]
Scott Bakerb6f99242014-06-11 11:34:44 -0700845
Tony Mack32e1ce32014-05-07 13:29:41 -0400846 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -0700847
Scott Baker63d1a552014-08-21 15:19:07 -0700848 list_display = ['backend_status_icon', 'name']
849 list_display_links = ('backend_status_icon', 'name', )
850
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400851class NodeForm(forms.ModelForm):
852 class Meta:
853 widgets = {
854 'site': LinkedSelect,
855 'deployment': LinkedSelect
856 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400857
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500858class NodeAdmin(PlanetStackBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400859 form = NodeForm
Scott Baker63d1a552014-08-21 15:19:07 -0700860 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
861 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400862 list_filter = ('deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500863
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400864 inlines = [TagInline,SliverInline]
Scott Baker40c00762014-08-21 16:55:59 -0700865 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
866 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400867
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500868 user_readonly_fields = ['name','site','deployment']
869 user_readonly_inlines = [TagInline,SliverInline]
870
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400871 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400872
Siobhan Tully567e3e62013-06-21 18:03:16 -0400873
Tony Mackd90cdbf2013-04-16 22:48:40 -0400874class SliverForm(forms.ModelForm):
875 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -0400876 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -0400877 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -0400878 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -0400879 widgets = {
880 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -0400881 'instance_name': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400882 'slice': LinkedSelect,
883 'deploymentNetwork': LinkedSelect,
884 'node': LinkedSelect,
885 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -0400886 }
Tony Mackd90cdbf2013-04-16 22:48:40 -0400887
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500888class TagAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700889 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
890 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500891 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
892 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -0400893
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400894class SliverAdmin(PlanetStackBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -0400895 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -0400896 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700897 ('Sliver Details', {'fields': ['backend_status_text', 'slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image', ], 'classes': ['suit-tab suit-tab-general'], })
Tony Mackcdec0902013-04-15 00:38:49 -0400898 ]
Scott Baker40c00762014-08-21 16:55:59 -0700899 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700900 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
901 list_display_links = ('backend_status_icon', 'ip',)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400902
903 suit_form_tabs =(('general', 'Sliver Details'),
904 ('tags','Tags'),
905 )
906
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400907 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -0400908
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500909 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500910
Tony Mackc2835a92013-05-28 09:18:49 -0400911 def formfield_for_foreignkey(self, db_field, request, **kwargs):
912 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500913 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400914
915 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
916
Tony Mack04062832013-05-10 08:22:44 -0400917 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -0700918 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -0400919 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500920 return Sliver.select_by_user(request.user)
921
Tony Mack04062832013-05-10 08:22:44 -0400922
Tony Mack1d6b85f2013-05-07 18:49:14 -0400923 def get_formsets(self, request, obj=None):
924 # make some fields read only if we are updating an existing record
925 if obj == None:
Scott Baker36f50872014-08-21 13:01:25 -0700926 #self.readonly_fields = ('ip', 'instance_name')
Scott Baker40c00762014-08-21 16:55:59 -0700927 self.readonly_fields = ('backend_status_text')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400928 else:
Scott Baker40c00762014-08-21 16:55:59 -0700929 self.readonly_fields = ('backend_status_text')
Scott Baker36f50872014-08-21 13:01:25 -0700930 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400931
932 for inline in self.get_inline_instances(request, obj):
933 # hide MyInline in the add view
934 if obj is None:
935 continue
Scott Baker526b71e2014-05-13 13:18:01 -0700936 if isinstance(inline, SliverInline):
937 inline.model.caller = request.user
938 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -0400939
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500940 #def save_model(self, request, obj, form, change):
941 # # update openstack connection to use this site/tenant
942 # auth = request.session.get('auth', {})
943 # auth['tenant'] = obj.slice.name
944 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
945 # obj.creator = request.user
946 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -0400947
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500948 #def delete_model(self, request, obj):
949 # # update openstack connection to use this site/tenant
950 # auth = request.session.get('auth', {})
951 # auth['tenant'] = obj.slice.name
952 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
953 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -0400954
Siobhan Tully53437282013-04-26 19:30:27 -0400955class UserCreationForm(forms.ModelForm):
956 """A form for creating new users. Includes all the required
957 fields, plus a repeated password."""
958 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
959 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
960
961 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400962 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400963 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -0400964
965 def clean_password2(self):
966 # Check that the two password entries match
967 password1 = self.cleaned_data.get("password1")
968 password2 = self.cleaned_data.get("password2")
969 if password1 and password2 and password1 != password2:
970 raise forms.ValidationError("Passwords don't match")
971 return password2
972
973 def save(self, commit=True):
974 # Save the provided password in hashed format
975 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -0400976 user.password = self.cleaned_data["password1"]
977 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -0400978 if commit:
979 user.save()
980 return user
981
Siobhan Tully567e3e62013-06-21 18:03:16 -0400982
Siobhan Tully53437282013-04-26 19:30:27 -0400983class UserChangeForm(forms.ModelForm):
984 """A form for updating users. Includes all the fields on
985 the user, but replaces the password field with admin's
986 password hash display field.
987 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -0500988 password = ReadOnlyPasswordHashField(label='Password',
989 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -0400990
991 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400992 model = User
Siobhan Tully53437282013-04-26 19:30:27 -0400993
994 def clean_password(self):
995 # Regardless of what the user provides, return the initial value.
996 # This is done here, rather than on the field, because the
997 # field does not have access to the initial value
998 return self.initial["password"]
999
Scott Baker2c3cb642014-05-19 17:55:56 -07001000class UserDashboardViewInline(PlStackTabularInline):
1001 model = UserDashboardView
1002 extra = 0
1003 suit_classes = 'suit-tab suit-tab-dashboards'
1004 fields = ['user', 'dashboardView', 'order']
1005
Tony Mack2bd5b412013-06-11 21:05:06 -04001006class UserAdmin(UserAdmin):
Siobhan Tully53437282013-04-26 19:30:27 -04001007 class Meta:
1008 app_label = "core"
1009
1010 # The forms to add and change user instances
1011 form = UserChangeForm
1012 add_form = UserCreationForm
1013
1014 # The fields to be used in displaying the User model.
1015 # These override the definitions on the base UserAdmin
1016 # that reference specific fields on auth.User.
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001017 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001018 list_filter = ('site',)
Scott Baker2c3cb642014-05-19 17:55:56 -07001019 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001020
Scott Bakeradae55f2014-08-14 17:32:35 -07001021 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001022 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1023
Siobhan Tully53437282013-04-26 19:30:27 -04001024 fieldsets = (
Scott Baker40c00762014-08-21 16:55:59 -07001025 ('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 -04001026 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Scott Baker2c3cb642014-05-19 17:55:56 -07001027 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001028 #('Important dates', {'fields': ('last_login',)}),
1029 )
1030 add_fieldsets = (
1031 (None, {
1032 'classes': ('wide',),
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001033 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
Siobhan Tully53437282013-04-26 19:30:27 -04001034 ),
1035 )
Scott Baker40c00762014-08-21 16:55:59 -07001036 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001037 search_fields = ('email',)
1038 ordering = ('email',)
1039 filter_horizontal = ()
1040
Scott Baker3ca51f62014-05-23 12:05:11 -07001041 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001042
Scott Baker2c3cb642014-05-19 17:55:56 -07001043 suit_form_tabs =(('general','Login Details'),
1044 ('contact','Contact Information'),
1045 ('sliceprivileges','Slice Privileges'),
1046 ('siteprivileges','Site Privileges'),
1047 ('deploymentprivileges','Deployment Privileges'),
1048 ('dashboards','Dashboard Views'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001049
Tony Mackc2835a92013-05-28 09:18:49 -04001050 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1051 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001052 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001053
1054 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1055
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001056 def has_add_permission(self, request, obj=None):
1057 return (not self.__user_is_readonly(request))
1058
1059 def has_delete_permission(self, request, obj=None):
1060 return (not self.__user_is_readonly(request))
1061
1062 def get_actions(self,request):
1063 actions = super(UserAdmin,self).get_actions(request)
1064
1065 if self.__user_is_readonly(request):
1066 if 'delete_selected' in actions:
1067 del actions['delete_selected']
1068
1069 return actions
1070
1071 def change_view(self,request,object_id, extra_context=None):
1072
1073 if self.__user_is_readonly(request):
Scott Bakerf875eba2014-05-23 12:09:15 -07001074 if not hasattr(self, "readonly_save"):
1075 # save the original readonly fields
1076 self.readonly_save = self.readonly_fields
1077 self.inlines_save = self.inlines
Scott Bakerb27b62c2014-08-15 16:29:16 -07001078 if hasattr(self, "user_readonly_fields"):
1079 self.readonly_fields=self.user_readonly_fields
1080 if hasattr(self, "user_readonly_inlines"):
1081 self.inlines = self.user_readonly_inlines
Scott Bakerf875eba2014-05-23 12:09:15 -07001082 else:
1083 if hasattr(self, "readonly_save"):
1084 # restore the original readonly fields
1085 self.readonly_fields = self.readonly_save
1086 self.inlines = self.inlines_save
1087
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001088 try:
1089 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1090 except PermissionDenied:
1091 pass
1092 if request.method == 'POST':
1093 raise PermissionDenied
1094 request.readonly = True
1095 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1096
1097 def __user_is_readonly(self, request):
1098 #groups = [x.name for x in request.user.groups.all() ]
1099 #return "readonly" in groups
1100 return request.user.isReadOnlyUser()
1101
Tony Mack5b061472014-02-04 07:57:10 -05001102 def queryset(self, request):
1103 return User.select_by_user(request.user)
1104
Scott Baker40c00762014-08-21 16:55:59 -07001105 def backend_status_text(self, obj):
1106 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -07001107
Scott Baker40c00762014-08-21 16:55:59 -07001108 def backend_status_icon(self, obj):
1109 return mark_safe(backend_icon(obj))
1110 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -07001111
Scott Baker2c3cb642014-05-19 17:55:56 -07001112class DashboardViewAdmin(PlanetStackBaseAdmin):
1113 fieldsets = [('Dashboard View Details',
Scott Baker40c00762014-08-21 16:55:59 -07001114 {'fields': ['backend_status_text', 'name', 'url'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001115 'classes': ['suit-tab suit-tab-general']})
1116 ]
Scott Baker40c00762014-08-21 16:55:59 -07001117 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001118
Scott Baker2c3cb642014-05-19 17:55:56 -07001119 suit_form_tabs =(('general','Dashboard View Details'),)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001120
Scott Baker0165fac2014-01-13 11:49:26 -08001121class ServiceResourceInline(PlStackTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001122 model = ServiceResource
1123 extra = 0
1124
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001125class ServiceClassAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001126 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1127 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001128 inlines = [ServiceResourceInline]
1129
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001130 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1131 user_readonly_inlines = []
1132
Scott Baker0165fac2014-01-13 11:49:26 -08001133class ReservedResourceInline(PlStackTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001134 model = ReservedResource
1135 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001136 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001137
1138 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1139 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1140
1141 if db_field.name == 'resource':
1142 # restrict resources to those that the slice's service class allows
1143 if request._slice is not None:
1144 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1145 if len(field.queryset) > 0:
1146 field.initial = field.queryset.all()[0]
1147 else:
1148 field.queryset = field.queryset.none()
1149 elif db_field.name == 'sliver':
1150 # restrict slivers to those that belong to the slice
1151 if request._slice is not None:
1152 field.queryset = field.queryset.filter(slice = request._slice)
1153 else:
1154 field.queryset = field.queryset.none()
1155
1156 return field
1157
Tony Mack5b061472014-02-04 07:57:10 -05001158 def queryset(self, request):
1159 return ReservedResource.select_by_user(request.user)
1160
Scott Baker133c9212013-05-17 09:09:11 -07001161class ReservationChangeForm(forms.ModelForm):
1162 class Meta:
1163 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001164 widgets = {
1165 'slice' : LinkedSelect
1166 }
Scott Baker133c9212013-05-17 09:09:11 -07001167
1168class ReservationAddForm(forms.ModelForm):
1169 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1170 refresh = forms.CharField(widget=forms.HiddenInput())
1171
1172 class Media:
1173 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1174
1175 def clean_slice(self):
1176 slice = self.cleaned_data.get("slice")
1177 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1178 if len(x) == 0:
1179 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1180 return slice
1181
1182 class Meta:
1183 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001184 widgets = {
1185 'slice' : LinkedSelect
1186 }
1187
Scott Baker133c9212013-05-17 09:09:11 -07001188
1189class ReservationAddRefreshForm(ReservationAddForm):
1190 """ This form is displayed when the Reservation Form receives an update
1191 from the Slice dropdown onChange handler. It doesn't validate the
1192 data and doesn't save the data. This will cause the form to be
1193 redrawn.
1194 """
1195
Scott Baker8737e5f2013-05-17 09:35:32 -07001196 """ don't validate anything other than slice """
1197 dont_validate_fields = ("startTime", "duration")
1198
Scott Baker133c9212013-05-17 09:09:11 -07001199 def full_clean(self):
1200 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001201
1202 for fieldname in self.dont_validate_fields:
1203 if fieldname in self._errors:
1204 del self._errors[fieldname]
1205
Scott Baker133c9212013-05-17 09:09:11 -07001206 return result
1207
1208 """ don't save anything """
1209 def is_valid(self):
1210 return False
1211
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001212class ReservationAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001213 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001214 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001215 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001216 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001217 form = ReservationAddForm
1218
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001219 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1220
1221 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001222 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001223
Scott Baker133c9212013-05-17 09:09:11 -07001224 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001225 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001226 request._refresh = False
1227 request._slice = None
1228 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001229 # "refresh" will be set to "1" if the form was submitted due to
1230 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001231 if request.POST.get("refresh","1") == "1":
1232 request._refresh = True
1233 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001234
1235 # Keep track of the slice that was selected, so the
1236 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001237 request._slice = request.POST.get("slice",None)
1238 if (request._slice is not None):
1239 request._slice = Slice.objects.get(id=request._slice)
1240
1241 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1242 return result
1243
Scott Bakeracd45142013-05-19 16:19:16 -07001244 def changelist_view(self, request, extra_context = None):
1245 timezone.activate(request.user.timezone)
1246 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1247
Scott Baker133c9212013-05-17 09:09:11 -07001248 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001249 request._obj_ = obj
1250 if obj is not None:
1251 # For changes, set request._slice to the slice already set in the
1252 # object.
1253 request._slice = obj.slice
1254 self.form = ReservationChangeForm
1255 else:
1256 if getattr(request, "_refresh", False):
1257 self.form = ReservationAddRefreshForm
1258 else:
1259 self.form = ReservationAddForm
1260 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1261
Scott Baker133c9212013-05-17 09:09:11 -07001262 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001263 if (obj is not None):
1264 # Prevent slice from being changed after the reservation has been
1265 # created.
1266 return ['slice']
1267 else:
Scott Baker133c9212013-05-17 09:09:11 -07001268 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001269
Tony Mack5b061472014-02-04 07:57:10 -05001270 def queryset(self, request):
1271 return Reservation.select_by_user(request.user)
1272
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001273class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001274 list_display = ("backend_status_icon", "name", )
1275 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001276 user_readonly_fields = ['name']
1277 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001278
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001279class RouterAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001280 list_display = ("backend_status_icon", "name", )
1281 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001282 user_readonly_fields = ['name']
1283 user_readonly_inlines = []
1284
Scott Baker0165fac2014-01-13 11:49:26 -08001285class RouterInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001286 model = Router.networks.through
1287 extra = 0
1288 verbose_name_plural = "Routers"
1289 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001290 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001291
Scott Bakerb27b62c2014-08-15 16:29:16 -07001292class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001293 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001294 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001295 verbose_name_plural = "Parameters"
1296 verbose_name = "Parameter"
1297 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001298 fields = ['backend_status_icon', 'parameter', 'value']
1299 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001300
Scott Baker0165fac2014-01-13 11:49:26 -08001301class NetworkSliversInline(PlStackTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001302 fields = ['backend_status_icon', 'network','sliver','ip']
1303 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001304 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001305 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001306 extra = 0
1307 verbose_name_plural = "Slivers"
1308 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001309 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001310
Scott Baker0165fac2014-01-13 11:49:26 -08001311class NetworkSlicesInline(PlStackTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001312 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001313 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001314 extra = 0
1315 verbose_name_plural = "Slices"
1316 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001317 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001318 fields = ['backend_status_icon', 'network','slice']
1319 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001320
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001321class NetworkAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001322 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1323 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001324 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001325
Scott Bakerd7d2a392013-08-06 08:57:30 -07001326 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001327
Siobhan Tully2d95e482013-09-06 10:56:06 -04001328 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001329 (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 -05001330
Scott Baker40c00762014-08-21 16:55:59 -07001331 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001332 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 -04001333
1334 suit_form_tabs =(
1335 ('general','Network Details'),
1336 ('netparams', 'Parameters'),
1337 ('networkslivers','Slivers'),
1338 ('networkslices','Slices'),
1339 ('routers','Routers'),
1340 )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001341class NetworkTemplateAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001342 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1343 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001344 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1345 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001346
Scott Baker37b47902014-09-02 14:37:41 -07001347class FlavorAdmin(PlanetStackBaseAdmin):
1348 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1349 list_display_links = ("backend_status_icon", "name")
1350 user_readonly_fields = ("name", "flavor")
1351 fields = ("name", "description", "flavor", "order", "default")
1352
Tony Mack31c2b8f2013-04-26 20:01:42 -04001353# register a signal that caches the user's credentials when they log in
1354def cache_credentials(sender, user, request, **kwds):
1355 auth = {'username': request.POST['username'],
1356 'password': request.POST['password']}
1357 request.session['auth'] = auth
1358user_logged_in.connect(cache_credentials)
1359
Scott Baker15cddfa2013-12-09 13:45:19 -08001360def dollar_field(fieldName, short_description):
1361 def newFunc(self, obj):
1362 try:
1363 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1364 except:
1365 x=getattr(obj, fieldName, 0.0)
1366 return x
1367 newFunc.short_description = short_description
1368 return newFunc
1369
1370def right_dollar_field(fieldName, short_description):
1371 def newFunc(self, obj):
1372 try:
1373 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1374 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1375 except:
1376 x=getattr(obj, fieldName, 0.0)
1377 return x
1378 newFunc.short_description = short_description
1379 newFunc.allow_tags = True
1380 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001381
Scott Baker0165fac2014-01-13 11:49:26 -08001382class InvoiceChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001383 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001384 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001385 verbose_name_plural = "Charges"
1386 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001387 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001388 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1389 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1390 can_delete = False
1391 max_num = 0
1392
1393 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001394
1395class InvoiceAdmin(admin.ModelAdmin):
1396 list_display = ("date", "account")
1397
1398 inlines = [InvoiceChargeInline]
1399
Scott Baker9cb88a22013-12-09 18:56:00 -08001400 fields = ["date", "account", "dollar_amount"]
1401 readonly_fields = ["date", "account", "dollar_amount"]
1402
1403 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001404
Scott Baker0165fac2014-01-13 11:49:26 -08001405class InvoiceInline(PlStackTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001406 model = Invoice
1407 extra = 0
1408 verbose_name_plural = "Invoices"
1409 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001410 fields = ["date", "dollar_amount"]
1411 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001412 suit_classes = 'suit-tab suit-tab-accountinvoice'
1413 can_delete=False
1414 max_num=0
1415
1416 dollar_amount = right_dollar_field("amount", "Amount")
1417
Scott Baker0165fac2014-01-13 11:49:26 -08001418class PendingChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001419 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001420 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001421 verbose_name_plural = "Charges"
1422 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001423 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001424 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1425 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001426 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001427 can_delete=False
1428 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001429
1430 def queryset(self, request):
1431 qs = super(PendingChargeInline, self).queryset(request)
1432 qs = qs.filter(state="pending")
1433 return qs
1434
Scott Baker15cddfa2013-12-09 13:45:19 -08001435 dollar_amount = right_dollar_field("amount", "Amount")
1436
Scott Baker0165fac2014-01-13 11:49:26 -08001437class PaymentInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001438 model=Payment
1439 extra = 1
1440 verbose_name_plural = "Payments"
1441 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001442 fields = ["date", "dollar_amount"]
1443 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001444 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001445 can_delete=False
1446 max_num=0
1447
1448 dollar_amount = right_dollar_field("amount", "Amount")
1449
Scott Baker43105042013-12-06 23:23:36 -08001450class AccountAdmin(admin.ModelAdmin):
1451 list_display = ("site", "balance_due")
1452
1453 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1454
1455 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001456 (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 -08001457
Scott Baker15cddfa2013-12-09 13:45:19 -08001458 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001459
1460 suit_form_tabs =(
1461 ('general','Account Details'),
1462 ('accountinvoice', 'Invoices'),
1463 ('accountpayments', 'Payments'),
1464 ('accountpendingcharges','Pending Charges'),
1465 )
1466
Scott Baker15cddfa2013-12-09 13:45:19 -08001467 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1468 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1469 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1470
Siobhan Tully53437282013-04-26 19:30:27 -04001471# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001472admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001473# ... and, since we're not using Django's builtin permissions,
1474# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001475#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001476
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001477#Do not show django evolution in the admin interface
1478from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001479#admin.site.unregister(Version)
1480#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001481
1482
1483# When debugging it is often easier to see all the classes, but for regular use
1484# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001485showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001486
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001487admin.site.register(Deployment, DeploymentAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001488admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001489admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001490admin.site.register(Service, ServiceAdmin)
smbakera3cf70c2013-06-27 02:01:41 -07001491admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001492admin.site.register(Network, NetworkAdmin)
1493admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001494admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001495admin.site.register(Account, AccountAdmin)
1496admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001497
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001498if True:
1499 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1500 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001501 #admin.site.register(PlanetStack)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001502 admin.site.register(Tag, TagAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001503 admin.site.register(DeploymentRole)
1504 admin.site.register(SiteRole)
1505 admin.site.register(SliceRole)
1506 admin.site.register(PlanetStackRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001507 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001508 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1509 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001510 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001511 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001512 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07001513 admin.site.register(Flavor, FlavorAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001514