blob: bd71db90e8ad4b3f14b1555d6903e7c8cb807756 [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 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400488 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400489 model = Deployment
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400490
Siobhan Tully320b4622014-01-17 15:11:14 -0500491 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700492 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500493 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
494
Scott Baker5380c522014-06-06 14:49:43 -0700495 self.fields['accessControl'].initial = "allow site " + request.user.site.name
496
Siobhan Tully320b4622014-01-17 15:11:14 -0500497 if self.instance and self.instance.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700498 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
Scott Bakerde0f4412014-06-11 15:40:26 -0700499 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
500
501 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
502 """ helper function for handling m2m relations from the MultipleChoiceField
503
504 this_obj: the source object we want to link from
505
506 selected_objs: a list of destination objects we want to link to
507
508 all_relations: the full set of relations involving this_obj, including ones we don't want
509
510 relation_class: the class that implements the relation from source to dest
511
512 local_attrname: field name representing this_obj in relation_class
513
514 foreign_attrname: field name representing selected_objs in relation_class
515
516 This function will remove all newobjclass relations from this_obj
517 that are not contained in selected_objs, and add any relations that
518 are in selected_objs but don't exist in the data model yet.
519 """
520
521 existing_dest_objs = []
522 for relation in list(all_relations):
523 if getattr(relation, foreign_attrname) not in selected_objs:
524 #print "deleting site", sdp.site
525 relation.delete()
526 else:
527 existing_dest_objs.append(getattr(relation, foreign_attrname))
528
529 for dest_obj in selected_objs:
530 if dest_obj not in existing_dest_objs:
531 #print "adding site", site
532 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
533 relation = relation_class(**kwargs)
534 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500535
536 def save(self, commit=True):
537 deployment = super(DeploymentAdminForm, self).save(commit=False)
538
539 if commit:
540 deployment.save()
541
542 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700543 # save_m2m() doesn't seem to work with 'through' relations. So we
544 # create/destroy the through models ourselves. There has to be
545 # a better way...
546
Scott Bakerde0f4412014-06-11 15:40:26 -0700547 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
548 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
Scott Bakerc9b14f72014-05-22 13:44:20 -0700549
Siobhan Tully320b4622014-01-17 15:11:14 -0500550 self.save_m2m()
551
552 return deployment
553
Scott Bakerff5e0f32014-05-22 14:40:27 -0700554class DeploymentAdminROForm(DeploymentAdminForm):
555 def save(self, commit=True):
556 raise PermissionDenied
557
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500558class SiteAssocInline(PlStackTabularInline):
559 model = Site.deployments.through
560 extra = 0
561 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400562
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400563class DeploymentAdmin(PlanetStackBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500564 model = Deployment
Scott Baker40c00762014-08-21 16:55:59 -0700565 fieldList = ['backend_status_text', 'name', 'sites', 'images', 'accessControl']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500566 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
Scott Bakerde0f4412014-06-11 15:40:26 -0700567 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700568 list_display = ['backend_status_icon', 'name']
569 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700570 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500571
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500572 user_readonly_fields = ['name']
573
Scott Bakerde0f4412014-06-11 15:40:26 -0700574 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500575
Scott Bakerff5e0f32014-05-22 14:40:27 -0700576 def get_form(self, request, obj=None, **kwargs):
577 if request.user.isReadOnlyUser():
578 kwargs["form"] = DeploymentAdminROForm
579 else:
580 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700581 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
582
583 # from stackexchange: pass the request object into the form
584
585 class AdminFormMetaClass(adminForm):
586 def __new__(cls, *args, **kwargs):
587 kwargs['request'] = request
588 return adminForm(*args, **kwargs)
589
590 return AdminFormMetaClass
591
Siobhan Tullyce652d02013-10-08 21:52:35 -0400592class ServiceAttrAsTabInline(PlStackTabularInline):
593 model = ServiceAttribute
594 fields = ['name','value']
595 extra = 0
596 suit_classes = 'suit-tab suit-tab-serviceattrs'
597
Siobhan Tullyce652d02013-10-08 21:52:35 -0400598class ServiceAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700599 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
600 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700601 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500602 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
603 inlines = [ServiceAttrAsTabInline,SliceInline]
Scott Baker40c00762014-08-21 16:55:59 -0700604 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500605
606 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500607
608 suit_form_tabs =(('general', 'Service Details'),
609 ('slices','Slices'),
610 ('serviceattrs','Additional Attributes'),
611 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400612
Tony Mack0553f282013-06-10 22:54:50 -0400613class SiteAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700614 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400615 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500616 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400617 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400618 ]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400619 suit_form_tabs =(('general', 'Site Details'),
620 ('users','Users'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400621 ('siteprivileges','Privileges'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400622 ('deployments','Deployments'),
623 ('slices','Slices'),
Scott Baker36f50872014-08-21 13:01:25 -0700624 ('nodes','Nodes'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400625 ('tags','Tags'),
626 )
Scott Baker40c00762014-08-21 16:55:59 -0700627 readonly_fields = ['backend_status_text', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500628
629 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500630
Scott Baker63d1a552014-08-21 15:19:07 -0700631 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
632 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400633 filter_horizontal = ('deployments',)
Tony Macke4be32f2014-03-11 20:45:25 -0400634 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400635 search_fields = ['name']
636
Tony Mack04062832013-05-10 08:22:44 -0400637 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500638 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400639
Tony Mack5cd13202013-05-01 21:48:38 -0400640 def get_formsets(self, request, obj=None):
641 for inline in self.get_inline_instances(request, obj):
642 # hide MyInline in the add view
643 if obj is None:
644 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400645 if isinstance(inline, SliceInline):
646 inline.model.caller = request.user
647 yield inline.get_formset(request, obj)
648
649 def get_formsets(self, request, obj=None):
650 for inline in self.get_inline_instances(request, obj):
651 # hide MyInline in the add view
652 if obj is None:
653 continue
654 if isinstance(inline, SliverInline):
655 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -0400656 yield inline.get_formset(request, obj)
657
Scott Baker545db2a2013-12-09 18:44:43 -0800658 def accountLink(self, obj):
659 link_obj = obj.accounts.all()
660 if link_obj:
661 reverse_path = "admin:core_account_change"
662 url = reverse(reverse_path, args =(link_obj[0].id,))
663 return "<a href='%s'>%s</a>" % (url, "view billing details")
664 else:
665 return "no billing data for this site"
666 accountLink.allow_tags = True
667 accountLink.short_description = "Billing"
668
Tony Mack332ee1d2014-02-04 15:33:45 -0500669 def save_model(self, request, obj, form, change):
670 # update openstack connection to use this site/tenant
671 obj.save_by_user(request.user)
672
673 def delete_model(self, request, obj):
674 obj.delete_by_user(request.user)
675
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500676
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400677class SitePrivilegeAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700678 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -0400679 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500680 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -0400681 ]
Scott Baker40c00762014-08-21 16:55:59 -0700682 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700683 list_display = ('backend_status_icon', 'user', 'site', 'role')
684 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500685 user_readonly_fields = fieldList
686 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -0400687
Tony Mackc2835a92013-05-28 09:18:49 -0400688 def formfield_for_foreignkey(self, db_field, request, **kwargs):
689 if db_field.name == 'site':
690 if not request.user.is_admin:
691 # only show sites where user is an admin or pi
692 sites = set()
693 for site_privilege in SitePrivilege.objects.filer(user=request.user):
694 if site_privilege.role.role_type in ['admin', 'pi']:
695 sites.add(site_privilege.site)
696 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
697
698 if db_field.name == 'user':
699 if not request.user.is_admin:
700 # only show users from sites where caller has admin or pi role
701 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
702 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
703 sites = [site_privilege.site for site_privilege in site_privileges]
704 site_privileges = SitePrivilege.objects.filter(site__in=sites)
705 emails = [site_privilege.user.email for site_privilege in site_privileges]
706 users = User.objects.filter(email__in=emails)
707 kwargs['queryset'] = users
708
709 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
710
Tony Mack04062832013-05-10 08:22:44 -0400711 def queryset(self, request):
712 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -0400713 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -0400714 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -0500715 #if not request.user.is_admin:
716 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
717 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
718 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
719 # sites = Site.objects.filter(login_base__in=login_bases)
720 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -0400721 return qs
722
Siobhan Tullyce652d02013-10-08 21:52:35 -0400723class SliceForm(forms.ModelForm):
724 class Meta:
725 model = Slice
726 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -0700727 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -0400728 }
729
Tony Mack2bd5b412013-06-11 21:05:06 -0400730class SliceAdmin(PlanetStackBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400731 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -0400732 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500733 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -0700734 readonly_fields = ('backend_status_text', )
Tony Mack49f75b92014-08-27 11:44:09 -0400735 list_display = ('backend_status_icon', 'slicename', 'site','serviceClass', 'slice_url', 'max_slivers')
736 list_display_links = ('backend_status_icon', 'slicename', )
Siobhan Tully2d95e482013-09-06 10:56:06 -0400737 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400738
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500739 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400740
741 suit_form_tabs =(('general', 'Slice Details'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400742 ('slicenetworks','Networks'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400743 ('sliceprivileges','Privileges'),
744 ('slivers','Slivers'),
745 ('tags','Tags'),
746 ('reservations','Reservations'),
747 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400748
Scott Baker510fdbb2014-08-05 17:19:24 -0700749 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
750 #deployment_nodes = {}
751 #for node in Node.objects.all():
752 # deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) )
753
754 deployment_nodes = []
755 for node in Node.objects.all():
756 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
757
Tony Mack7283fdf2014-09-02 00:37:36 -0400758 sites = {}
759 for site in Site.objects.all():
760 sites[site.id] = site.login_base
761
Scott Baker510fdbb2014-08-05 17:19:24 -0700762 context["deployment_nodes"] = deployment_nodes
Tony Mack7283fdf2014-09-02 00:37:36 -0400763 context["sites"] = sites
Scott Baker510fdbb2014-08-05 17:19:24 -0700764
765 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
766
Tony Mackc2835a92013-05-28 09:18:49 -0400767 def formfield_for_foreignkey(self, db_field, request, **kwargs):
768 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500769 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mack7283fdf2014-09-02 00:37:36 -0400770 kwargs['widget'] = forms.Select(attrs={'onChange': "update_slice_name(this, $($(this).closest('div')[0]).find('.field-name input')[0].id)"})
Scott Baker40c00762014-08-21 16:55:59 -0700771
Tony Mackc2835a92013-05-28 09:18:49 -0400772 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
773
Tony Mack04062832013-05-10 08:22:44 -0400774 def queryset(self, request):
775 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500776 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400777
Tony Mack79748612013-05-01 14:52:03 -0400778 def get_formsets(self, request, obj=None):
779 for inline in self.get_inline_instances(request, obj):
780 # hide MyInline in the add view
781 if obj is None:
782 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400783 if isinstance(inline, SliverInline):
784 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -0400785 yield inline.get_formset(request, obj)
786
Tony Mack2bd5b412013-06-11 21:05:06 -0400787
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400788class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -0400789 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700790 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -0400791 ]
Scott Baker40c00762014-08-21 16:55:59 -0700792 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700793 list_display = ('backend_status_icon', 'user', 'slice', 'role')
794 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -0400795
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500796 user_readonly_fields = ['user', 'slice', 'role']
797 user_readonly_inlines = []
798
Tony Mackc2835a92013-05-28 09:18:49 -0400799 def formfield_for_foreignkey(self, db_field, request, **kwargs):
800 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500801 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400802
803 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500804 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400805
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400806 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400807
Tony Mack04062832013-05-10 08:22:44 -0400808 def queryset(self, request):
809 # admins can see all memberships. Users can only see memberships of
810 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -0500811 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400812
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400813 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -0400814 # update openstack connection to use this site/tenant
815 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400816 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400817 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400818 obj.save()
819
820 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -0400821 # update openstack connection to use this site/tenant
822 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400823 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400824 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400825 obj.delete()
826
Siobhan Tully567e3e62013-06-21 18:03:16 -0400827
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400828class ImageAdmin(PlanetStackBaseAdmin):
829
Scott Baker36f50872014-08-21 13:01:25 -0700830 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -0700831 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400832 'classes': ['suit-tab suit-tab-general']})
833 ]
Scott Baker40c00762014-08-21 16:55:59 -0700834 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400835
Scott Baker2170b972014-06-03 12:14:07 -0700836 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400837
Scott Baker2170b972014-06-03 12:14:07 -0700838 inlines = [SliverInline, ImageDeploymentsInline]
Scott Bakerb6f99242014-06-11 11:34:44 -0700839
Tony Mack32e1ce32014-05-07 13:29:41 -0400840 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -0700841
Scott Baker63d1a552014-08-21 15:19:07 -0700842 list_display = ['backend_status_icon', 'name']
843 list_display_links = ('backend_status_icon', 'name', )
844
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400845class NodeForm(forms.ModelForm):
846 class Meta:
847 widgets = {
848 'site': LinkedSelect,
849 'deployment': LinkedSelect
850 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400851
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500852class NodeAdmin(PlanetStackBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400853 form = NodeForm
Scott Baker63d1a552014-08-21 15:19:07 -0700854 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
855 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400856 list_filter = ('deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500857
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400858 inlines = [TagInline,SliverInline]
Scott Baker40c00762014-08-21 16:55:59 -0700859 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
860 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400861
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500862 user_readonly_fields = ['name','site','deployment']
863 user_readonly_inlines = [TagInline,SliverInline]
864
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400865 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400866
Siobhan Tully567e3e62013-06-21 18:03:16 -0400867
Tony Mackd90cdbf2013-04-16 22:48:40 -0400868class SliverForm(forms.ModelForm):
869 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -0400870 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -0400871 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -0400872 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -0400873 widgets = {
874 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -0400875 'instance_name': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400876 'slice': LinkedSelect,
877 'deploymentNetwork': LinkedSelect,
878 'node': LinkedSelect,
879 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -0400880 }
Tony Mackd90cdbf2013-04-16 22:48:40 -0400881
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500882class TagAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700883 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
884 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500885 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
886 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -0400887
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400888class SliverAdmin(PlanetStackBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -0400889 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -0400890 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700891 ('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 -0400892 ]
Scott Baker40c00762014-08-21 16:55:59 -0700893 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700894 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
895 list_display_links = ('backend_status_icon', 'ip',)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400896
897 suit_form_tabs =(('general', 'Sliver Details'),
898 ('tags','Tags'),
899 )
900
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400901 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -0400902
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500903 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'numberCores', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500904
Tony Mackc2835a92013-05-28 09:18:49 -0400905 def formfield_for_foreignkey(self, db_field, request, **kwargs):
906 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500907 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400908
909 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
910
Tony Mack04062832013-05-10 08:22:44 -0400911 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -0700912 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -0400913 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500914 return Sliver.select_by_user(request.user)
915
Tony Mack04062832013-05-10 08:22:44 -0400916
Tony Mack1d6b85f2013-05-07 18:49:14 -0400917 def get_formsets(self, request, obj=None):
918 # make some fields read only if we are updating an existing record
919 if obj == None:
Scott Baker36f50872014-08-21 13:01:25 -0700920 #self.readonly_fields = ('ip', 'instance_name')
Scott Baker40c00762014-08-21 16:55:59 -0700921 self.readonly_fields = ('backend_status_text')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400922 else:
Scott Baker40c00762014-08-21 16:55:59 -0700923 self.readonly_fields = ('backend_status_text')
Scott Baker36f50872014-08-21 13:01:25 -0700924 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400925
926 for inline in self.get_inline_instances(request, obj):
927 # hide MyInline in the add view
928 if obj is None:
929 continue
Scott Baker526b71e2014-05-13 13:18:01 -0700930 if isinstance(inline, SliverInline):
931 inline.model.caller = request.user
932 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -0400933
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500934 #def save_model(self, request, obj, form, change):
935 # # update openstack connection to use this site/tenant
936 # auth = request.session.get('auth', {})
937 # auth['tenant'] = obj.slice.name
938 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
939 # obj.creator = request.user
940 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -0400941
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500942 #def delete_model(self, request, obj):
943 # # update openstack connection to use this site/tenant
944 # auth = request.session.get('auth', {})
945 # auth['tenant'] = obj.slice.name
946 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
947 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -0400948
Siobhan Tully53437282013-04-26 19:30:27 -0400949class UserCreationForm(forms.ModelForm):
950 """A form for creating new users. Includes all the required
951 fields, plus a repeated password."""
952 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
953 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
954
955 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400956 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400957 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -0400958
959 def clean_password2(self):
960 # Check that the two password entries match
961 password1 = self.cleaned_data.get("password1")
962 password2 = self.cleaned_data.get("password2")
963 if password1 and password2 and password1 != password2:
964 raise forms.ValidationError("Passwords don't match")
965 return password2
966
967 def save(self, commit=True):
968 # Save the provided password in hashed format
969 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -0400970 user.password = self.cleaned_data["password1"]
971 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -0400972 if commit:
973 user.save()
974 return user
975
Siobhan Tully567e3e62013-06-21 18:03:16 -0400976
Siobhan Tully53437282013-04-26 19:30:27 -0400977class UserChangeForm(forms.ModelForm):
978 """A form for updating users. Includes all the fields on
979 the user, but replaces the password field with admin's
980 password hash display field.
981 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -0500982 password = ReadOnlyPasswordHashField(label='Password',
983 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -0400984
985 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400986 model = User
Siobhan Tully53437282013-04-26 19:30:27 -0400987
988 def clean_password(self):
989 # Regardless of what the user provides, return the initial value.
990 # This is done here, rather than on the field, because the
991 # field does not have access to the initial value
992 return self.initial["password"]
993
Scott Baker2c3cb642014-05-19 17:55:56 -0700994class UserDashboardViewInline(PlStackTabularInline):
995 model = UserDashboardView
996 extra = 0
997 suit_classes = 'suit-tab suit-tab-dashboards'
998 fields = ['user', 'dashboardView', 'order']
999
Tony Mack2bd5b412013-06-11 21:05:06 -04001000class UserAdmin(UserAdmin):
Siobhan Tully53437282013-04-26 19:30:27 -04001001 class Meta:
1002 app_label = "core"
1003
1004 # The forms to add and change user instances
1005 form = UserChangeForm
1006 add_form = UserCreationForm
1007
1008 # The fields to be used in displaying the User model.
1009 # These override the definitions on the base UserAdmin
1010 # that reference specific fields on auth.User.
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001011 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001012 list_filter = ('site',)
Scott Baker2c3cb642014-05-19 17:55:56 -07001013 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001014
Scott Bakeradae55f2014-08-14 17:32:35 -07001015 fieldListLoginDetails = ['email','site','password','is_active','is_readonly','is_admin','public_key']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001016 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1017
Siobhan Tully53437282013-04-26 19:30:27 -04001018 fieldsets = (
Scott Baker40c00762014-08-21 16:55:59 -07001019 ('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 -04001020 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Scott Baker2c3cb642014-05-19 17:55:56 -07001021 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001022 #('Important dates', {'fields': ('last_login',)}),
1023 )
1024 add_fieldsets = (
1025 (None, {
1026 'classes': ('wide',),
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001027 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
Siobhan Tully53437282013-04-26 19:30:27 -04001028 ),
1029 )
Scott Baker40c00762014-08-21 16:55:59 -07001030 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001031 search_fields = ('email',)
1032 ordering = ('email',)
1033 filter_horizontal = ()
1034
Scott Baker3ca51f62014-05-23 12:05:11 -07001035 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001036
Scott Baker2c3cb642014-05-19 17:55:56 -07001037 suit_form_tabs =(('general','Login Details'),
1038 ('contact','Contact Information'),
1039 ('sliceprivileges','Slice Privileges'),
1040 ('siteprivileges','Site Privileges'),
1041 ('deploymentprivileges','Deployment Privileges'),
1042 ('dashboards','Dashboard Views'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001043
Tony Mackc2835a92013-05-28 09:18:49 -04001044 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1045 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001046 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001047
1048 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1049
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001050 def has_add_permission(self, request, obj=None):
1051 return (not self.__user_is_readonly(request))
1052
1053 def has_delete_permission(self, request, obj=None):
1054 return (not self.__user_is_readonly(request))
1055
1056 def get_actions(self,request):
1057 actions = super(UserAdmin,self).get_actions(request)
1058
1059 if self.__user_is_readonly(request):
1060 if 'delete_selected' in actions:
1061 del actions['delete_selected']
1062
1063 return actions
1064
1065 def change_view(self,request,object_id, extra_context=None):
1066
1067 if self.__user_is_readonly(request):
Scott Bakerf875eba2014-05-23 12:09:15 -07001068 if not hasattr(self, "readonly_save"):
1069 # save the original readonly fields
1070 self.readonly_save = self.readonly_fields
1071 self.inlines_save = self.inlines
Scott Bakerb27b62c2014-08-15 16:29:16 -07001072 if hasattr(self, "user_readonly_fields"):
1073 self.readonly_fields=self.user_readonly_fields
1074 if hasattr(self, "user_readonly_inlines"):
1075 self.inlines = self.user_readonly_inlines
Scott Bakerf875eba2014-05-23 12:09:15 -07001076 else:
1077 if hasattr(self, "readonly_save"):
1078 # restore the original readonly fields
1079 self.readonly_fields = self.readonly_save
1080 self.inlines = self.inlines_save
1081
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001082 try:
1083 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1084 except PermissionDenied:
1085 pass
1086 if request.method == 'POST':
1087 raise PermissionDenied
1088 request.readonly = True
1089 return super(UserAdmin, self).change_view(request, object_id, extra_context=extra_context)
1090
1091 def __user_is_readonly(self, request):
1092 #groups = [x.name for x in request.user.groups.all() ]
1093 #return "readonly" in groups
1094 return request.user.isReadOnlyUser()
1095
Tony Mack5b061472014-02-04 07:57:10 -05001096 def queryset(self, request):
1097 return User.select_by_user(request.user)
1098
Scott Baker40c00762014-08-21 16:55:59 -07001099 def backend_status_text(self, obj):
1100 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -07001101
Scott Baker40c00762014-08-21 16:55:59 -07001102 def backend_status_icon(self, obj):
1103 return mark_safe(backend_icon(obj))
1104 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -07001105
Scott Baker2c3cb642014-05-19 17:55:56 -07001106class DashboardViewAdmin(PlanetStackBaseAdmin):
1107 fieldsets = [('Dashboard View Details',
Scott Baker40c00762014-08-21 16:55:59 -07001108 {'fields': ['backend_status_text', 'name', 'url'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001109 'classes': ['suit-tab suit-tab-general']})
1110 ]
Scott Baker40c00762014-08-21 16:55:59 -07001111 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001112
Scott Baker2c3cb642014-05-19 17:55:56 -07001113 suit_form_tabs =(('general','Dashboard View Details'),)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001114
Scott Baker0165fac2014-01-13 11:49:26 -08001115class ServiceResourceInline(PlStackTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001116 model = ServiceResource
1117 extra = 0
1118
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001119class ServiceClassAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001120 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1121 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001122 inlines = [ServiceResourceInline]
1123
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001124 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1125 user_readonly_inlines = []
1126
Scott Baker0165fac2014-01-13 11:49:26 -08001127class ReservedResourceInline(PlStackTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001128 model = ReservedResource
1129 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001130 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001131
1132 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1133 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1134
1135 if db_field.name == 'resource':
1136 # restrict resources to those that the slice's service class allows
1137 if request._slice is not None:
1138 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1139 if len(field.queryset) > 0:
1140 field.initial = field.queryset.all()[0]
1141 else:
1142 field.queryset = field.queryset.none()
1143 elif db_field.name == 'sliver':
1144 # restrict slivers to those that belong to the slice
1145 if request._slice is not None:
1146 field.queryset = field.queryset.filter(slice = request._slice)
1147 else:
1148 field.queryset = field.queryset.none()
1149
1150 return field
1151
Tony Mack5b061472014-02-04 07:57:10 -05001152 def queryset(self, request):
1153 return ReservedResource.select_by_user(request.user)
1154
Scott Baker133c9212013-05-17 09:09:11 -07001155class ReservationChangeForm(forms.ModelForm):
1156 class Meta:
1157 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001158 widgets = {
1159 'slice' : LinkedSelect
1160 }
Scott Baker133c9212013-05-17 09:09:11 -07001161
1162class ReservationAddForm(forms.ModelForm):
1163 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1164 refresh = forms.CharField(widget=forms.HiddenInput())
1165
1166 class Media:
1167 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1168
1169 def clean_slice(self):
1170 slice = self.cleaned_data.get("slice")
1171 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1172 if len(x) == 0:
1173 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1174 return slice
1175
1176 class Meta:
1177 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001178 widgets = {
1179 'slice' : LinkedSelect
1180 }
1181
Scott Baker133c9212013-05-17 09:09:11 -07001182
1183class ReservationAddRefreshForm(ReservationAddForm):
1184 """ This form is displayed when the Reservation Form receives an update
1185 from the Slice dropdown onChange handler. It doesn't validate the
1186 data and doesn't save the data. This will cause the form to be
1187 redrawn.
1188 """
1189
Scott Baker8737e5f2013-05-17 09:35:32 -07001190 """ don't validate anything other than slice """
1191 dont_validate_fields = ("startTime", "duration")
1192
Scott Baker133c9212013-05-17 09:09:11 -07001193 def full_clean(self):
1194 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001195
1196 for fieldname in self.dont_validate_fields:
1197 if fieldname in self._errors:
1198 del self._errors[fieldname]
1199
Scott Baker133c9212013-05-17 09:09:11 -07001200 return result
1201
1202 """ don't save anything """
1203 def is_valid(self):
1204 return False
1205
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001206class ReservationAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001207 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001208 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001209 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001210 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001211 form = ReservationAddForm
1212
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001213 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1214
1215 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001216 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001217
Scott Baker133c9212013-05-17 09:09:11 -07001218 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001219 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001220 request._refresh = False
1221 request._slice = None
1222 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001223 # "refresh" will be set to "1" if the form was submitted due to
1224 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001225 if request.POST.get("refresh","1") == "1":
1226 request._refresh = True
1227 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001228
1229 # Keep track of the slice that was selected, so the
1230 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001231 request._slice = request.POST.get("slice",None)
1232 if (request._slice is not None):
1233 request._slice = Slice.objects.get(id=request._slice)
1234
1235 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1236 return result
1237
Scott Bakeracd45142013-05-19 16:19:16 -07001238 def changelist_view(self, request, extra_context = None):
1239 timezone.activate(request.user.timezone)
1240 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1241
Scott Baker133c9212013-05-17 09:09:11 -07001242 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001243 request._obj_ = obj
1244 if obj is not None:
1245 # For changes, set request._slice to the slice already set in the
1246 # object.
1247 request._slice = obj.slice
1248 self.form = ReservationChangeForm
1249 else:
1250 if getattr(request, "_refresh", False):
1251 self.form = ReservationAddRefreshForm
1252 else:
1253 self.form = ReservationAddForm
1254 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1255
Scott Baker133c9212013-05-17 09:09:11 -07001256 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001257 if (obj is not None):
1258 # Prevent slice from being changed after the reservation has been
1259 # created.
1260 return ['slice']
1261 else:
Scott Baker133c9212013-05-17 09:09:11 -07001262 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001263
Tony Mack5b061472014-02-04 07:57:10 -05001264 def queryset(self, request):
1265 return Reservation.select_by_user(request.user)
1266
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001267class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001268 list_display = ("backend_status_icon", "name", )
1269 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001270 user_readonly_fields = ['name']
1271 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001272
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001273class RouterAdmin(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 = []
1278
Scott Baker0165fac2014-01-13 11:49:26 -08001279class RouterInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001280 model = Router.networks.through
1281 extra = 0
1282 verbose_name_plural = "Routers"
1283 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001284 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001285
Scott Bakerb27b62c2014-08-15 16:29:16 -07001286class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001287 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001288 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001289 verbose_name_plural = "Parameters"
1290 verbose_name = "Parameter"
1291 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001292 fields = ['backend_status_icon', 'parameter', 'value']
1293 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001294
Scott Baker0165fac2014-01-13 11:49:26 -08001295class NetworkSliversInline(PlStackTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001296 fields = ['backend_status_icon', 'network','sliver','ip']
1297 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001298 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001299 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001300 extra = 0
1301 verbose_name_plural = "Slivers"
1302 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001303 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001304
Scott Baker0165fac2014-01-13 11:49:26 -08001305class NetworkSlicesInline(PlStackTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001306 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001307 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001308 extra = 0
1309 verbose_name_plural = "Slices"
1310 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001311 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001312 fields = ['backend_status_icon', 'network','slice']
1313 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001314
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001315class NetworkAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001316 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1317 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001318 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001319
Scott Bakerd7d2a392013-08-06 08:57:30 -07001320 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001321
Siobhan Tully2d95e482013-09-06 10:56:06 -04001322 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001323 (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 -05001324
Scott Baker40c00762014-08-21 16:55:59 -07001325 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001326 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 -04001327
1328 suit_form_tabs =(
1329 ('general','Network Details'),
1330 ('netparams', 'Parameters'),
1331 ('networkslivers','Slivers'),
1332 ('networkslices','Slices'),
1333 ('routers','Routers'),
1334 )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001335class NetworkTemplateAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001336 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1337 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001338 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1339 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001340
Tony Mack31c2b8f2013-04-26 20:01:42 -04001341# register a signal that caches the user's credentials when they log in
1342def cache_credentials(sender, user, request, **kwds):
1343 auth = {'username': request.POST['username'],
1344 'password': request.POST['password']}
1345 request.session['auth'] = auth
1346user_logged_in.connect(cache_credentials)
1347
Scott Baker15cddfa2013-12-09 13:45:19 -08001348def dollar_field(fieldName, short_description):
1349 def newFunc(self, obj):
1350 try:
1351 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1352 except:
1353 x=getattr(obj, fieldName, 0.0)
1354 return x
1355 newFunc.short_description = short_description
1356 return newFunc
1357
1358def right_dollar_field(fieldName, short_description):
1359 def newFunc(self, obj):
1360 try:
1361 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1362 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1363 except:
1364 x=getattr(obj, fieldName, 0.0)
1365 return x
1366 newFunc.short_description = short_description
1367 newFunc.allow_tags = True
1368 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001369
Scott Baker0165fac2014-01-13 11:49:26 -08001370class InvoiceChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001371 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001372 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001373 verbose_name_plural = "Charges"
1374 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001375 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001376 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1377 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1378 can_delete = False
1379 max_num = 0
1380
1381 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001382
1383class InvoiceAdmin(admin.ModelAdmin):
1384 list_display = ("date", "account")
1385
1386 inlines = [InvoiceChargeInline]
1387
Scott Baker9cb88a22013-12-09 18:56:00 -08001388 fields = ["date", "account", "dollar_amount"]
1389 readonly_fields = ["date", "account", "dollar_amount"]
1390
1391 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001392
Scott Baker0165fac2014-01-13 11:49:26 -08001393class InvoiceInline(PlStackTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001394 model = Invoice
1395 extra = 0
1396 verbose_name_plural = "Invoices"
1397 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001398 fields = ["date", "dollar_amount"]
1399 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001400 suit_classes = 'suit-tab suit-tab-accountinvoice'
1401 can_delete=False
1402 max_num=0
1403
1404 dollar_amount = right_dollar_field("amount", "Amount")
1405
Scott Baker0165fac2014-01-13 11:49:26 -08001406class PendingChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001407 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001408 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001409 verbose_name_plural = "Charges"
1410 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001411 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001412 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1413 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001414 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001415 can_delete=False
1416 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001417
1418 def queryset(self, request):
1419 qs = super(PendingChargeInline, self).queryset(request)
1420 qs = qs.filter(state="pending")
1421 return qs
1422
Scott Baker15cddfa2013-12-09 13:45:19 -08001423 dollar_amount = right_dollar_field("amount", "Amount")
1424
Scott Baker0165fac2014-01-13 11:49:26 -08001425class PaymentInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001426 model=Payment
1427 extra = 1
1428 verbose_name_plural = "Payments"
1429 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001430 fields = ["date", "dollar_amount"]
1431 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001432 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001433 can_delete=False
1434 max_num=0
1435
1436 dollar_amount = right_dollar_field("amount", "Amount")
1437
Scott Baker43105042013-12-06 23:23:36 -08001438class AccountAdmin(admin.ModelAdmin):
1439 list_display = ("site", "balance_due")
1440
1441 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1442
1443 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001444 (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 -08001445
Scott Baker15cddfa2013-12-09 13:45:19 -08001446 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001447
1448 suit_form_tabs =(
1449 ('general','Account Details'),
1450 ('accountinvoice', 'Invoices'),
1451 ('accountpayments', 'Payments'),
1452 ('accountpendingcharges','Pending Charges'),
1453 )
1454
Scott Baker15cddfa2013-12-09 13:45:19 -08001455 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1456 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1457 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1458
Siobhan Tullyce652d02013-10-08 21:52:35 -04001459
Siobhan Tully53437282013-04-26 19:30:27 -04001460# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001461admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001462# ... and, since we're not using Django's builtin permissions,
1463# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001464#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001465
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001466#Do not show django evolution in the admin interface
1467from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001468#admin.site.unregister(Version)
1469#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001470
1471
1472# When debugging it is often easier to see all the classes, but for regular use
1473# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001474showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001475
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001476admin.site.register(Deployment, DeploymentAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001477admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001478admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001479admin.site.register(Service, ServiceAdmin)
smbakera3cf70c2013-06-27 02:01:41 -07001480admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001481admin.site.register(Network, NetworkAdmin)
1482admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001483admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001484admin.site.register(Account, AccountAdmin)
1485admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001486
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001487if True:
1488 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1489 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001490 #admin.site.register(PlanetStack)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001491 admin.site.register(Tag, TagAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001492 admin.site.register(DeploymentRole)
1493 admin.site.register(SiteRole)
1494 admin.site.register(SliceRole)
1495 admin.site.register(PlanetStackRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001496 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001497 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1498 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001499 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001500 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001501 admin.site.register(DashboardView, DashboardViewAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001502