blob: 1d64d9c1ad86473c0117ab87449b04697a59060c [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
Scott Baker37b47902014-09-02 14:37:41 -0700538 fieldList = ['backend_status_text', 'name', '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 Mack2bd5b412013-06-11 21:05:06 -0400703class SliceAdmin(PlanetStackBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400704 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -0400705 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500706 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -0700707 readonly_fields = ('backend_status_text', )
Tony Mack7d459902014-09-03 13:18:57 -0400708 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
709 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully2d95e482013-09-06 10:56:06 -0400710 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400711
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500712 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400713
714 suit_form_tabs =(('general', 'Slice Details'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400715 ('slicenetworks','Networks'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400716 ('sliceprivileges','Privileges'),
717 ('slivers','Slivers'),
718 ('tags','Tags'),
719 ('reservations','Reservations'),
720 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400721
Scott Baker510fdbb2014-08-05 17:19:24 -0700722 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -0700723 deployment_nodes = []
724 for node in Node.objects.all():
725 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
726
Scott Baker7a61dc42014-09-02 17:08:20 -0700727 deployment_flavors = []
728 for flavor in Flavor.objects.all():
729 for deployment in flavor.deployments.all():
730 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
731
Scott Baker93e80cd2014-09-09 09:58:49 -0700732 deployment_images = []
733 for image in Image.objects.all():
734 for imageDeployment in image.imagedeployments_set.all():
735 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
736
Tony Mackec23b992014-09-02 21:18:45 -0400737 site_login_bases = []
738 for site in Site.objects.all():
Scott Baker93e80cd2014-09-09 09:58:49 -0700739 site_login_bases.append((site.id, site.login_base))
740
Scott Baker510fdbb2014-08-05 17:19:24 -0700741 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -0700742 context["deployment_flavors"] = deployment_flavors
Scott Baker93e80cd2014-09-09 09:58:49 -0700743 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -0400744 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -0700745 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
746
Tony Mackc2835a92013-05-28 09:18:49 -0400747 def formfield_for_foreignkey(self, db_field, request, **kwargs):
748 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500749 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackec23b992014-09-02 21:18:45 -0400750 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 -0700751
Tony Mackc2835a92013-05-28 09:18:49 -0400752 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
753
Tony Mack04062832013-05-10 08:22:44 -0400754 def queryset(self, request):
755 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500756 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400757
Tony Mack79748612013-05-01 14:52:03 -0400758 def get_formsets(self, request, obj=None):
759 for inline in self.get_inline_instances(request, obj):
760 # hide MyInline in the add view
761 if obj is None:
762 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400763 if isinstance(inline, SliverInline):
764 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -0400765 yield inline.get_formset(request, obj)
766
Tony Mack2bd5b412013-06-11 21:05:06 -0400767
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400768class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -0400769 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700770 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -0400771 ]
Scott Baker40c00762014-08-21 16:55:59 -0700772 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700773 list_display = ('backend_status_icon', 'user', 'slice', 'role')
774 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -0400775
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500776 user_readonly_fields = ['user', 'slice', 'role']
777 user_readonly_inlines = []
778
Tony Mackc2835a92013-05-28 09:18:49 -0400779 def formfield_for_foreignkey(self, db_field, request, **kwargs):
780 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500781 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400782
783 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500784 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400785
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400786 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400787
Tony Mack04062832013-05-10 08:22:44 -0400788 def queryset(self, request):
789 # admins can see all memberships. Users can only see memberships of
790 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -0500791 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400792
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400793 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -0400794 # update openstack connection to use this site/tenant
795 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400796 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400797 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400798 obj.save()
799
800 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -0400801 # update openstack connection to use this site/tenant
802 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400803 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400804 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400805 obj.delete()
806
Siobhan Tully567e3e62013-06-21 18:03:16 -0400807
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400808class ImageAdmin(PlanetStackBaseAdmin):
809
Scott Baker36f50872014-08-21 13:01:25 -0700810 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -0700811 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400812 'classes': ['suit-tab suit-tab-general']})
813 ]
Scott Baker40c00762014-08-21 16:55:59 -0700814 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400815
Scott Baker2170b972014-06-03 12:14:07 -0700816 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400817
Scott Baker2170b972014-06-03 12:14:07 -0700818 inlines = [SliverInline, ImageDeploymentsInline]
Scott Bakerb6f99242014-06-11 11:34:44 -0700819
Tony Mack32e1ce32014-05-07 13:29:41 -0400820 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -0700821
Scott Baker63d1a552014-08-21 15:19:07 -0700822 list_display = ['backend_status_icon', 'name']
823 list_display_links = ('backend_status_icon', 'name', )
824
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400825class NodeForm(forms.ModelForm):
826 class Meta:
827 widgets = {
828 'site': LinkedSelect,
829 'deployment': LinkedSelect
830 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400831
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500832class NodeAdmin(PlanetStackBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400833 form = NodeForm
Scott Baker63d1a552014-08-21 15:19:07 -0700834 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
835 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400836 list_filter = ('deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500837
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400838 inlines = [TagInline,SliverInline]
Scott Baker40c00762014-08-21 16:55:59 -0700839 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
840 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400841
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500842 user_readonly_fields = ['name','site','deployment']
843 user_readonly_inlines = [TagInline,SliverInline]
844
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400845 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400846
Siobhan Tully567e3e62013-06-21 18:03:16 -0400847
Tony Mackd90cdbf2013-04-16 22:48:40 -0400848class SliverForm(forms.ModelForm):
849 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -0400850 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -0400851 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -0400852 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -0400853 widgets = {
854 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -0400855 'instance_name': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400856 'slice': LinkedSelect,
857 'deploymentNetwork': LinkedSelect,
858 'node': LinkedSelect,
859 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -0400860 }
Tony Mackd90cdbf2013-04-16 22:48:40 -0400861
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500862class TagAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700863 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
864 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500865 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
866 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -0400867
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400868class SliverAdmin(PlanetStackBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -0400869 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -0400870 fieldsets = [
Scott Baker7a61dc42014-09-02 17:08:20 -0700871 ('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 -0400872 ]
Scott Baker40c00762014-08-21 16:55:59 -0700873 readonly_fields = ('backend_status_text', )
Scott Baker7a61dc42014-09-02 17:08:20 -0700874 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
Scott Baker63d1a552014-08-21 15:19:07 -0700875 list_display_links = ('backend_status_icon', 'ip',)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400876
877 suit_form_tabs =(('general', 'Sliver Details'),
878 ('tags','Tags'),
879 )
880
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400881 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -0400882
Scott Baker7a61dc42014-09-02 17:08:20 -0700883 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500884
Tony Mackc2835a92013-05-28 09:18:49 -0400885 def formfield_for_foreignkey(self, db_field, request, **kwargs):
886 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500887 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400888
889 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
890
Tony Mack04062832013-05-10 08:22:44 -0400891 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -0700892 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -0400893 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500894 return Sliver.select_by_user(request.user)
895
Tony Mack04062832013-05-10 08:22:44 -0400896
Tony Mack1d6b85f2013-05-07 18:49:14 -0400897 def get_formsets(self, request, obj=None):
898 # make some fields read only if we are updating an existing record
899 if obj == None:
Scott Baker36f50872014-08-21 13:01:25 -0700900 #self.readonly_fields = ('ip', 'instance_name')
Scott Baker40c00762014-08-21 16:55:59 -0700901 self.readonly_fields = ('backend_status_text')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400902 else:
Scott Baker40c00762014-08-21 16:55:59 -0700903 self.readonly_fields = ('backend_status_text')
Scott Baker36f50872014-08-21 13:01:25 -0700904 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400905
906 for inline in self.get_inline_instances(request, obj):
907 # hide MyInline in the add view
908 if obj is None:
909 continue
Scott Baker526b71e2014-05-13 13:18:01 -0700910 if isinstance(inline, SliverInline):
911 inline.model.caller = request.user
912 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -0400913
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500914 #def save_model(self, request, obj, form, change):
915 # # update openstack connection to use this site/tenant
916 # auth = request.session.get('auth', {})
917 # auth['tenant'] = obj.slice.name
918 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
919 # obj.creator = request.user
920 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -0400921
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500922 #def delete_model(self, request, obj):
923 # # update openstack connection to use this site/tenant
924 # auth = request.session.get('auth', {})
925 # auth['tenant'] = obj.slice.name
926 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
927 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -0400928
Siobhan Tully53437282013-04-26 19:30:27 -0400929class UserCreationForm(forms.ModelForm):
930 """A form for creating new users. Includes all the required
931 fields, plus a repeated password."""
932 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
933 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
934
935 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400936 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400937 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -0400938
939 def clean_password2(self):
940 # Check that the two password entries match
941 password1 = self.cleaned_data.get("password1")
942 password2 = self.cleaned_data.get("password2")
943 if password1 and password2 and password1 != password2:
944 raise forms.ValidationError("Passwords don't match")
945 return password2
946
947 def save(self, commit=True):
948 # Save the provided password in hashed format
949 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -0400950 user.password = self.cleaned_data["password1"]
951 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -0400952 if commit:
953 user.save()
954 return user
955
Siobhan Tully567e3e62013-06-21 18:03:16 -0400956
Siobhan Tully53437282013-04-26 19:30:27 -0400957class UserChangeForm(forms.ModelForm):
958 """A form for updating users. Includes all the fields on
959 the user, but replaces the password field with admin's
960 password hash display field.
961 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -0500962 password = ReadOnlyPasswordHashField(label='Password',
963 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -0400964
965 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400966 model = User
Siobhan Tully53437282013-04-26 19:30:27 -0400967
968 def clean_password(self):
969 # Regardless of what the user provides, return the initial value.
970 # This is done here, rather than on the field, because the
971 # field does not have access to the initial value
972 return self.initial["password"]
973
Scott Baker2c3cb642014-05-19 17:55:56 -0700974class UserDashboardViewInline(PlStackTabularInline):
975 model = UserDashboardView
976 extra = 0
977 suit_classes = 'suit-tab suit-tab-dashboards'
978 fields = ['user', 'dashboardView', 'order']
979
Tony Mack2bd5b412013-06-11 21:05:06 -0400980class UserAdmin(UserAdmin):
Siobhan Tully53437282013-04-26 19:30:27 -0400981 class Meta:
982 app_label = "core"
983
984 # The forms to add and change user instances
985 form = UserChangeForm
986 add_form = UserCreationForm
987
988 # The fields to be used in displaying the User model.
989 # These override the definitions on the base UserAdmin
990 # that reference specific fields on auth.User.
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500991 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500992 list_filter = ('site',)
Scott Baker2c3cb642014-05-19 17:55:56 -0700993 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500994
Scott Bakeradae55f2014-08-14 17:32:35 -0700995 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500996 fieldListContactInfo = ['firstname','lastname','phone','timezone']
997
Siobhan Tully53437282013-04-26 19:30:27 -0400998 fieldsets = (
Scott Baker40c00762014-08-21 16:55:59 -0700999 ('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 -04001000 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Scott Baker2c3cb642014-05-19 17:55:56 -07001001 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001002 #('Important dates', {'fields': ('last_login',)}),
1003 )
1004 add_fieldsets = (
1005 (None, {
1006 'classes': ('wide',),
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001007 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
Siobhan Tully53437282013-04-26 19:30:27 -04001008 ),
1009 )
Scott Baker40c00762014-08-21 16:55:59 -07001010 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001011 search_fields = ('email',)
1012 ordering = ('email',)
1013 filter_horizontal = ()
1014
Scott Baker3ca51f62014-05-23 12:05:11 -07001015 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001016
Scott Baker2c3cb642014-05-19 17:55:56 -07001017 suit_form_tabs =(('general','Login Details'),
1018 ('contact','Contact Information'),
1019 ('sliceprivileges','Slice Privileges'),
1020 ('siteprivileges','Site Privileges'),
1021 ('deploymentprivileges','Deployment Privileges'),
1022 ('dashboards','Dashboard Views'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001023
Tony Mackc2835a92013-05-28 09:18:49 -04001024 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1025 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001026 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001027
1028 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1029
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001030 def has_add_permission(self, request, obj=None):
1031 return (not self.__user_is_readonly(request))
1032
1033 def has_delete_permission(self, request, obj=None):
1034 return (not self.__user_is_readonly(request))
1035
1036 def get_actions(self,request):
1037 actions = super(UserAdmin,self).get_actions(request)
1038
1039 if self.__user_is_readonly(request):
1040 if 'delete_selected' in actions:
1041 del actions['delete_selected']
1042
1043 return actions
1044
1045 def change_view(self,request,object_id, extra_context=None):
1046
1047 if self.__user_is_readonly(request):
Scott Bakerf875eba2014-05-23 12:09:15 -07001048 if not hasattr(self, "readonly_save"):
1049 # save the original readonly fields
1050 self.readonly_save = self.readonly_fields
1051 self.inlines_save = self.inlines
Scott Bakerb27b62c2014-08-15 16:29:16 -07001052 if hasattr(self, "user_readonly_fields"):
1053 self.readonly_fields=self.user_readonly_fields
1054 if hasattr(self, "user_readonly_inlines"):
1055 self.inlines = self.user_readonly_inlines
Scott Bakerf875eba2014-05-23 12:09:15 -07001056 else:
1057 if hasattr(self, "readonly_save"):
1058 # restore the original readonly fields
1059 self.readonly_fields = self.readonly_save
1060 self.inlines = self.inlines_save
1061
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001062 try:
1063 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1064 except PermissionDenied:
1065 pass
1066 if request.method == 'POST':
1067 raise PermissionDenied
1068 request.readonly = True
1069 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1070
1071 def __user_is_readonly(self, request):
1072 #groups = [x.name for x in request.user.groups.all() ]
1073 #return "readonly" in groups
1074 return request.user.isReadOnlyUser()
1075
Tony Mack5b061472014-02-04 07:57:10 -05001076 def queryset(self, request):
1077 return User.select_by_user(request.user)
1078
Scott Baker40c00762014-08-21 16:55:59 -07001079 def backend_status_text(self, obj):
1080 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -07001081
Scott Baker40c00762014-08-21 16:55:59 -07001082 def backend_status_icon(self, obj):
1083 return mark_safe(backend_icon(obj))
1084 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -07001085
Scott Baker2c3cb642014-05-19 17:55:56 -07001086class DashboardViewAdmin(PlanetStackBaseAdmin):
1087 fieldsets = [('Dashboard View Details',
Scott Baker40c00762014-08-21 16:55:59 -07001088 {'fields': ['backend_status_text', 'name', 'url'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001089 'classes': ['suit-tab suit-tab-general']})
1090 ]
Scott Baker40c00762014-08-21 16:55:59 -07001091 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001092
Scott Baker2c3cb642014-05-19 17:55:56 -07001093 suit_form_tabs =(('general','Dashboard View Details'),)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001094
Scott Baker0165fac2014-01-13 11:49:26 -08001095class ServiceResourceInline(PlStackTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001096 model = ServiceResource
1097 extra = 0
1098
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001099class ServiceClassAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001100 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1101 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001102 inlines = [ServiceResourceInline]
1103
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001104 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1105 user_readonly_inlines = []
1106
Scott Baker0165fac2014-01-13 11:49:26 -08001107class ReservedResourceInline(PlStackTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001108 model = ReservedResource
1109 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001110 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001111
1112 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1113 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1114
1115 if db_field.name == 'resource':
1116 # restrict resources to those that the slice's service class allows
1117 if request._slice is not None:
1118 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1119 if len(field.queryset) > 0:
1120 field.initial = field.queryset.all()[0]
1121 else:
1122 field.queryset = field.queryset.none()
1123 elif db_field.name == 'sliver':
1124 # restrict slivers to those that belong to the slice
1125 if request._slice is not None:
1126 field.queryset = field.queryset.filter(slice = request._slice)
1127 else:
1128 field.queryset = field.queryset.none()
1129
1130 return field
1131
Tony Mack5b061472014-02-04 07:57:10 -05001132 def queryset(self, request):
1133 return ReservedResource.select_by_user(request.user)
1134
Scott Baker133c9212013-05-17 09:09:11 -07001135class ReservationChangeForm(forms.ModelForm):
1136 class Meta:
1137 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001138 widgets = {
1139 'slice' : LinkedSelect
1140 }
Scott Baker133c9212013-05-17 09:09:11 -07001141
1142class ReservationAddForm(forms.ModelForm):
1143 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1144 refresh = forms.CharField(widget=forms.HiddenInput())
1145
1146 class Media:
1147 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1148
1149 def clean_slice(self):
1150 slice = self.cleaned_data.get("slice")
1151 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1152 if len(x) == 0:
1153 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1154 return slice
1155
1156 class Meta:
1157 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001158 widgets = {
1159 'slice' : LinkedSelect
1160 }
1161
Scott Baker133c9212013-05-17 09:09:11 -07001162
1163class ReservationAddRefreshForm(ReservationAddForm):
1164 """ This form is displayed when the Reservation Form receives an update
1165 from the Slice dropdown onChange handler. It doesn't validate the
1166 data and doesn't save the data. This will cause the form to be
1167 redrawn.
1168 """
1169
Scott Baker8737e5f2013-05-17 09:35:32 -07001170 """ don't validate anything other than slice """
1171 dont_validate_fields = ("startTime", "duration")
1172
Scott Baker133c9212013-05-17 09:09:11 -07001173 def full_clean(self):
1174 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001175
1176 for fieldname in self.dont_validate_fields:
1177 if fieldname in self._errors:
1178 del self._errors[fieldname]
1179
Scott Baker133c9212013-05-17 09:09:11 -07001180 return result
1181
1182 """ don't save anything """
1183 def is_valid(self):
1184 return False
1185
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001186class ReservationAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001187 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001188 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001189 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001190 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001191 form = ReservationAddForm
1192
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001193 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1194
1195 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001196 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001197
Scott Baker133c9212013-05-17 09:09:11 -07001198 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001199 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001200 request._refresh = False
1201 request._slice = None
1202 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001203 # "refresh" will be set to "1" if the form was submitted due to
1204 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001205 if request.POST.get("refresh","1") == "1":
1206 request._refresh = True
1207 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001208
1209 # Keep track of the slice that was selected, so the
1210 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001211 request._slice = request.POST.get("slice",None)
1212 if (request._slice is not None):
1213 request._slice = Slice.objects.get(id=request._slice)
1214
1215 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1216 return result
1217
Scott Bakeracd45142013-05-19 16:19:16 -07001218 def changelist_view(self, request, extra_context = None):
1219 timezone.activate(request.user.timezone)
1220 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1221
Scott Baker133c9212013-05-17 09:09:11 -07001222 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001223 request._obj_ = obj
1224 if obj is not None:
1225 # For changes, set request._slice to the slice already set in the
1226 # object.
1227 request._slice = obj.slice
1228 self.form = ReservationChangeForm
1229 else:
1230 if getattr(request, "_refresh", False):
1231 self.form = ReservationAddRefreshForm
1232 else:
1233 self.form = ReservationAddForm
1234 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1235
Scott Baker133c9212013-05-17 09:09:11 -07001236 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001237 if (obj is not None):
1238 # Prevent slice from being changed after the reservation has been
1239 # created.
1240 return ['slice']
1241 else:
Scott Baker133c9212013-05-17 09:09:11 -07001242 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001243
Tony Mack5b061472014-02-04 07:57:10 -05001244 def queryset(self, request):
1245 return Reservation.select_by_user(request.user)
1246
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001247class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001248 list_display = ("backend_status_icon", "name", )
1249 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001250 user_readonly_fields = ['name']
1251 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001252
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001253class RouterAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001254 list_display = ("backend_status_icon", "name", )
1255 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001256 user_readonly_fields = ['name']
1257 user_readonly_inlines = []
1258
Scott Baker0165fac2014-01-13 11:49:26 -08001259class RouterInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001260 model = Router.networks.through
1261 extra = 0
1262 verbose_name_plural = "Routers"
1263 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001264 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001265
Scott Bakerb27b62c2014-08-15 16:29:16 -07001266class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001267 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001268 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001269 verbose_name_plural = "Parameters"
1270 verbose_name = "Parameter"
1271 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001272 fields = ['backend_status_icon', 'parameter', 'value']
1273 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001274
Scott Baker0165fac2014-01-13 11:49:26 -08001275class NetworkSliversInline(PlStackTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001276 fields = ['backend_status_icon', 'network','sliver','ip']
1277 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001278 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001279 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001280 extra = 0
1281 verbose_name_plural = "Slivers"
1282 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001283 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001284
Scott Baker0165fac2014-01-13 11:49:26 -08001285class NetworkSlicesInline(PlStackTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001286 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001287 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001288 extra = 0
1289 verbose_name_plural = "Slices"
1290 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001291 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001292 fields = ['backend_status_icon', 'network','slice']
1293 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001294
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001295class NetworkAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001296 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1297 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001298 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001299
Scott Bakerd7d2a392013-08-06 08:57:30 -07001300 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001301
Siobhan Tully2d95e482013-09-06 10:56:06 -04001302 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001303 (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 -05001304
Scott Baker40c00762014-08-21 16:55:59 -07001305 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001306 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 -04001307
1308 suit_form_tabs =(
1309 ('general','Network Details'),
1310 ('netparams', 'Parameters'),
1311 ('networkslivers','Slivers'),
1312 ('networkslices','Slices'),
1313 ('routers','Routers'),
1314 )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001315class NetworkTemplateAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001316 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1317 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001318 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1319 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001320
Scott Baker37b47902014-09-02 14:37:41 -07001321class FlavorAdmin(PlanetStackBaseAdmin):
1322 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1323 list_display_links = ("backend_status_icon", "name")
1324 user_readonly_fields = ("name", "flavor")
1325 fields = ("name", "description", "flavor", "order", "default")
1326
Tony Mack31c2b8f2013-04-26 20:01:42 -04001327# register a signal that caches the user's credentials when they log in
1328def cache_credentials(sender, user, request, **kwds):
1329 auth = {'username': request.POST['username'],
1330 'password': request.POST['password']}
1331 request.session['auth'] = auth
1332user_logged_in.connect(cache_credentials)
1333
Scott Baker15cddfa2013-12-09 13:45:19 -08001334def dollar_field(fieldName, short_description):
1335 def newFunc(self, obj):
1336 try:
1337 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1338 except:
1339 x=getattr(obj, fieldName, 0.0)
1340 return x
1341 newFunc.short_description = short_description
1342 return newFunc
1343
1344def right_dollar_field(fieldName, short_description):
1345 def newFunc(self, obj):
1346 try:
1347 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1348 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1349 except:
1350 x=getattr(obj, fieldName, 0.0)
1351 return x
1352 newFunc.short_description = short_description
1353 newFunc.allow_tags = True
1354 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001355
Scott Baker0165fac2014-01-13 11:49:26 -08001356class InvoiceChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001357 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001358 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001359 verbose_name_plural = "Charges"
1360 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001361 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001362 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1363 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1364 can_delete = False
1365 max_num = 0
1366
1367 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001368
1369class InvoiceAdmin(admin.ModelAdmin):
1370 list_display = ("date", "account")
1371
1372 inlines = [InvoiceChargeInline]
1373
Scott Baker9cb88a22013-12-09 18:56:00 -08001374 fields = ["date", "account", "dollar_amount"]
1375 readonly_fields = ["date", "account", "dollar_amount"]
1376
1377 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001378
Scott Baker0165fac2014-01-13 11:49:26 -08001379class InvoiceInline(PlStackTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001380 model = Invoice
1381 extra = 0
1382 verbose_name_plural = "Invoices"
1383 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001384 fields = ["date", "dollar_amount"]
1385 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001386 suit_classes = 'suit-tab suit-tab-accountinvoice'
1387 can_delete=False
1388 max_num=0
1389
1390 dollar_amount = right_dollar_field("amount", "Amount")
1391
Scott Baker0165fac2014-01-13 11:49:26 -08001392class PendingChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001393 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001394 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001395 verbose_name_plural = "Charges"
1396 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001397 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001398 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1399 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001400 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001401 can_delete=False
1402 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001403
1404 def queryset(self, request):
1405 qs = super(PendingChargeInline, self).queryset(request)
1406 qs = qs.filter(state="pending")
1407 return qs
1408
Scott Baker15cddfa2013-12-09 13:45:19 -08001409 dollar_amount = right_dollar_field("amount", "Amount")
1410
Scott Baker0165fac2014-01-13 11:49:26 -08001411class PaymentInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001412 model=Payment
1413 extra = 1
1414 verbose_name_plural = "Payments"
1415 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001416 fields = ["date", "dollar_amount"]
1417 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001418 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001419 can_delete=False
1420 max_num=0
1421
1422 dollar_amount = right_dollar_field("amount", "Amount")
1423
Scott Baker43105042013-12-06 23:23:36 -08001424class AccountAdmin(admin.ModelAdmin):
1425 list_display = ("site", "balance_due")
1426
1427 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1428
1429 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001430 (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 -08001431
Scott Baker15cddfa2013-12-09 13:45:19 -08001432 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001433
1434 suit_form_tabs =(
1435 ('general','Account Details'),
1436 ('accountinvoice', 'Invoices'),
1437 ('accountpayments', 'Payments'),
1438 ('accountpendingcharges','Pending Charges'),
1439 )
1440
Scott Baker15cddfa2013-12-09 13:45:19 -08001441 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1442 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1443 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1444
Siobhan Tully53437282013-04-26 19:30:27 -04001445# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001446admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001447# ... and, since we're not using Django's builtin permissions,
1448# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001449#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001450
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001451#Do not show django evolution in the admin interface
1452from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001453#admin.site.unregister(Version)
1454#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001455
1456
1457# When debugging it is often easier to see all the classes, but for regular use
1458# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001459showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001460
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001461admin.site.register(Deployment, DeploymentAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001462admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001463admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001464admin.site.register(Service, ServiceAdmin)
smbakera3cf70c2013-06-27 02:01:41 -07001465admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001466admin.site.register(Network, NetworkAdmin)
1467admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001468admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001469admin.site.register(Account, AccountAdmin)
1470admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001471
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001472if True:
1473 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1474 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001475 #admin.site.register(PlanetStack)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001476 admin.site.register(Tag, TagAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001477 admin.site.register(DeploymentRole)
1478 admin.site.register(SiteRole)
1479 admin.site.register(SliceRole)
1480 admin.site.register(PlanetStackRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001481 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001482 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1483 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001484 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001485 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001486 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07001487 admin.site.register(Flavor, FlavorAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001488