blob: 11a2c59faeaabddb096fc88d4d4da5beeed30062 [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
Scott Bakercbfb6002014-10-03 00:32:37 -070011from django.contrib.auth.forms import ReadOnlyPasswordHashField, AdminPasswordChangeForm
Scott Bakeracd45142013-05-19 16:19:16 -070012from django.contrib.auth.signals import user_logged_in
13from django.utils import timezone
Siobhan Tullyde5450d2013-06-21 11:35:33 -040014from django.contrib.contenttypes import generic
Siobhan Tullybfd11dc2013-09-03 12:59:24 -040015from suit.widgets import LinkedSelect
Siobhan Tullycf04fb62014-01-11 11:25:57 -050016from django.core.exceptions import PermissionDenied
Scott Bakere2bbf7e2014-01-13 12:09:31 -080017from django.core.urlresolvers import reverse, NoReverseMatch
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
Scott Baker86c83ab2014-10-03 13:10:47 -070046class PermissionCheckingAdminMixin(object):
Scott Bakercbfb6002014-10-03 00:32:37 -070047 # call save_by_user and delete_by_user instead of save and delete
Siobhan Tullycf04fb62014-01-11 11:25:57 -050048
49 def has_add_permission(self, request, obj=None):
50 return (not self.__user_is_readonly(request))
Scott Baker36f50872014-08-21 13:01:25 -070051
Siobhan Tullycf04fb62014-01-11 11:25:57 -050052 def has_delete_permission(self, request, obj=None):
53 return (not self.__user_is_readonly(request))
54
55 def save_model(self, request, obj, form, change):
56 if self.__user_is_readonly(request):
Scott Bakercbfb6002014-10-03 00:32:37 -070057 # this 'if' might be redundant if save_by_user is implemented right
Siobhan Tullycf04fb62014-01-11 11:25:57 -050058 raise PermissionDenied
Scott Bakercbfb6002014-10-03 00:32:37 -070059
60 obj.caller = request.user
61 # update openstack connection to use this site/tenant
62 obj.save_by_user(request.user)
63
64 def delete_model(self, request, obj):
65 obj.delete_by_user(request.user)
66
67 def save_formset(self, request, form, formset, change):
68 instances = formset.save(commit=False)
69 for instance in instances:
70 instance.save_by_user(request.user)
71
72 # BUG in django 1.7? Objects are not deleted by formset.save if
73 # commit is False. So let's delete them ourselves.
74 #
75 # code from forms/models.py save_existing_objects()
76 try:
77 forms_to_delete = formset.deleted_forms
78 except AttributeError:
79 forms_to_delete = []
80 if formset.initial_forms:
81 for form in formset.initial_forms:
82 obj = form.instance
83 if form in forms_to_delete:
84 if obj.pk is None:
85 continue
86 formset.deleted_objects.append(obj)
87 obj.delete()
88
89 formset.save_m2m()
Siobhan Tullycf04fb62014-01-11 11:25:57 -050090
91 def get_actions(self,request):
Scott Baker86c83ab2014-10-03 13:10:47 -070092 actions = super(PermissionCheckingAdminMixin,self).get_actions(request)
Siobhan Tullycf04fb62014-01-11 11:25:57 -050093
94 if self.__user_is_readonly(request):
95 if 'delete_selected' in actions:
96 del actions['delete_selected']
97
98 return actions
99
100 def change_view(self,request,object_id, extra_context=None):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500101 if self.__user_is_readonly(request):
Scott Bakeraf73e102014-04-22 22:40:07 -0700102 if not hasattr(self, "readonly_save"):
103 # save the original readonly fields
104 self.readonly_save = self.readonly_fields
105 self.inlines_save = self.inlines
Scott Bakere8859f92014-05-23 12:42:40 -0700106 if hasattr(self, "user_readonly_fields"):
107 self.readonly_fields=self.user_readonly_fields
108 if hasattr(self, "user_readonly_inlines"):
109 self.inlines = self.user_readonly_inlines
Scott Bakeraf73e102014-04-22 22:40:07 -0700110 else:
111 if hasattr(self, "readonly_save"):
112 # restore the original readonly fields
113 self.readonly_fields = self.readonly_save
Scott Bakere8859f92014-05-23 12:42:40 -0700114 if hasattr(self, "inlines_save"):
Scott Bakeraf73e102014-04-22 22:40:07 -0700115 self.inlines = self.inlines_save
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500116
117 try:
Scott Baker86c83ab2014-10-03 13:10:47 -0700118 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500119 except PermissionDenied:
120 pass
121 if request.method == 'POST':
122 raise PermissionDenied
123 request.readonly = True
Scott Baker86c83ab2014-10-03 13:10:47 -0700124 return super(PermissionCheckingAdminMixin, self).change_view(request, object_id, extra_context=extra_context)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500125
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500126 def __user_is_readonly(self, request):
127 return request.user.isReadOnlyUser()
128
Scott Baker40c00762014-08-21 16:55:59 -0700129 def backend_status_text(self, obj):
130 return mark_safe(backend_text(obj))
Scott Baker36f50872014-08-21 13:01:25 -0700131
Scott Baker63d1a552014-08-21 15:19:07 -0700132 def backend_status_icon(self, obj):
Scott Baker40c00762014-08-21 16:55:59 -0700133 return mark_safe(backend_icon(obj))
Scott Baker63d1a552014-08-21 15:19:07 -0700134 backend_status_icon.short_description = ""
135
Scott Baker86c83ab2014-10-03 13:10:47 -0700136class ReadOnlyAwareAdmin(PermissionCheckingAdminMixin, admin.ModelAdmin):
137 # Note: Make sure PermissionCheckingAdminMixin is listed before
138 # admin.ModelAdmin in the class declaration.
139
Scott Bakercbfb6002014-10-03 00:32:37 -0700140 pass
141
142class PlanetStackBaseAdmin(ReadOnlyAwareAdmin):
143 save_on_top = False
Scott Baker36f50872014-08-21 13:01:25 -0700144
Scott Bakere8859f92014-05-23 12:42:40 -0700145class SingletonAdmin (ReadOnlyAwareAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400146 def has_add_permission(self, request):
Scott Bakere8859f92014-05-23 12:42:40 -0700147 if not super(SingletonAdmin, self).has_add_permission(request):
148 return False
149
Siobhan Tullyce652d02013-10-08 21:52:35 -0400150 num_objects = self.model.objects.count()
151 if num_objects >= 1:
152 return False
153 else:
154 return True
155
Siobhan Tullyd3515752013-06-21 16:34:53 -0400156class PlStackTabularInline(admin.TabularInline):
Scott Baker86568322014-01-12 16:53:31 -0800157 def __init__(self, *args, **kwargs):
158 super(PlStackTabularInline, self).__init__(*args, **kwargs)
159
160 # InlineModelAdmin as no get_fields() method, so in order to add
161 # the selflink field, we override __init__ to modify self.fields and
162 # self.readonly_fields.
163
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800164 self.setup_selflink()
165
Scott Baker874936e2014-01-13 18:15:34 -0800166 def get_change_url(self, model, id):
167 """ Get the URL to a change form in the admin for this model """
168 reverse_path = "admin:%s_change" % (model._meta.db_table)
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800169 try:
Scott Baker874936e2014-01-13 18:15:34 -0800170 url = reverse(reverse_path, args=(id,))
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800171 except NoReverseMatch:
Scott Baker874936e2014-01-13 18:15:34 -0800172 return None
173
174 return url
175
176 def setup_selflink(self):
177 if hasattr(self, "selflink_fieldname"):
178 """ self.selflink_model can be defined to punch through a relation
179 to its target object. For example, in SliceNetworkInline, set
180 selflink_model = "network", and the URL will lead to the Network
181 object instead of trying to bring up a change view of the
182 SliceNetwork object.
183 """
184 self.selflink_model = getattr(self.model,self.selflink_fieldname).field.rel.to
185 else:
186 self.selflink_model = self.model
187
188 url = self.get_change_url(self.selflink_model, 0)
189
190 # We don't have an admin for this object, so don't create the
191 # selflink.
192 if (url == None):
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800193 return
194
Scott Baker874936e2014-01-13 18:15:34 -0800195 # Since we need to add "selflink" to the field list, we need to create
196 # self.fields if it is None.
Scott Baker0165fac2014-01-13 11:49:26 -0800197 if (self.fields is None):
198 self.fields = []
199 for f in self.model._meta.fields:
200 if f.editable and f.name != "id":
201 self.fields.append(f.name)
Scott Baker86568322014-01-12 16:53:31 -0800202
Scott Baker874936e2014-01-13 18:15:34 -0800203 self.fields = tuple(self.fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800204
Scott Baker874936e2014-01-13 18:15:34 -0800205 if self.readonly_fields is None:
206 self.readonly_fields = ()
Scott Baker86568322014-01-12 16:53:31 -0800207
Scott Baker874936e2014-01-13 18:15:34 -0800208 self.readonly_fields = tuple(self.readonly_fields) + ("selflink", )
Scott Baker86568322014-01-12 16:53:31 -0800209
210 def selflink(self, obj):
Scott Baker874936e2014-01-13 18:15:34 -0800211 if hasattr(self, "selflink_fieldname"):
212 obj = getattr(obj, self.selflink_fieldname)
213
Scott Baker86568322014-01-12 16:53:31 -0800214 if obj.id:
Scott Baker874936e2014-01-13 18:15:34 -0800215 url = self.get_change_url(self.selflink_model, obj.id)
216 return "<a href='%s'>Details</a>" % str(url)
Scott Baker86568322014-01-12 16:53:31 -0800217 else:
Scott Bakere2bbf7e2014-01-13 12:09:31 -0800218 return "Not present"
Scott Baker86568322014-01-12 16:53:31 -0800219
220 selflink.allow_tags = True
221 selflink.short_description = "Details"
Siobhan Tullyd3515752013-06-21 16:34:53 -0400222
Scott Bakerb27b62c2014-08-15 16:29:16 -0700223 def has_add_permission(self, request):
224 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500225
226 def get_readonly_fields(self, request, obj=None):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700227 readonly_fields = list(self.readonly_fields)[:]
228 if request.user.isReadOnlyUser():
229 for field in self.fields:
230 if not field in readonly_fields:
231 readonly_fields.append(field)
232 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500233
Scott Baker40c00762014-08-21 16:55:59 -0700234 def backend_status_icon(self, obj):
235 return mark_safe(backend_icon(obj))
236 backend_status_icon.short_description = ""
Scott Baker36f50872014-08-21 13:01:25 -0700237
Scott Bakerb27b62c2014-08-15 16:29:16 -0700238class PlStackGenericTabularInline(generic.GenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500239 def has_add_permission(self, request):
Scott Bakerb27b62c2014-08-15 16:29:16 -0700240 return not request.user.isReadOnlyUser()
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500241
Scott Bakerb27b62c2014-08-15 16:29:16 -0700242 def get_readonly_fields(self, request, obj=None):
243 readonly_fields = list(self.readonly_fields)[:]
244 if request.user.isReadOnlyUser():
245 for field in self.fields:
246 if not field in readonly_fields:
247 readonly_fields.append(field)
248 return readonly_fields
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500249
Scott Baker40c00762014-08-21 16:55:59 -0700250 def backend_status_icon(self, obj):
251 return mark_safe(backend_icon(obj))
252 backend_status_icon.short_description = ""
253
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400254class ReservationInline(PlStackTabularInline):
255 model = Reservation
256 extra = 0
257 suit_classes = 'suit-tab suit-tab-reservations'
Scott Baker36f50872014-08-21 13:01:25 -0700258
Tony Mack5b061472014-02-04 07:57:10 -0500259 def queryset(self, request):
260 return Reservation.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400261
Scott Bakerb27b62c2014-08-15 16:29:16 -0700262class TagInline(PlStackGenericTabularInline):
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400263 model = Tag
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400264 extra = 0
265 suit_classes = 'suit-tab suit-tab-tags'
Tony Mack5b061472014-02-04 07:57:10 -0500266 fields = ['service', 'name', 'value']
267
268 def queryset(self, request):
269 return Tag.select_by_user(request.user)
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400270
Scott Baker74d8e622013-07-29 16:04:22 -0700271class NetworkLookerUpper:
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400272 """ This is a callable that looks up a network name in a sliver and returns
273 the ip address for that network.
274 """
275
Scott Baker434ca7e2014-08-15 12:29:20 -0700276 byNetworkName = {} # class variable
277
Siobhan Tully2c780ad2013-09-06 11:22:40 -0400278 def __init__(self, name):
279 self.short_description = name
280 self.__name__ = name
281 self.network_name = name
282
283 def __call__(self, obj):
284 if obj is not None:
285 for nbs in obj.networksliver_set.all():
286 if (nbs.network.name == self.network_name):
287 return nbs.ip
Scott Baker74d8e622013-07-29 16:04:22 -0700288 return ""
289
290 def __str__(self):
291 return self.network_name
292
Scott Baker434ca7e2014-08-15 12:29:20 -0700293 @staticmethod
294 def get(network_name):
295 """ We want to make sure we alwars return the same NetworkLookerUpper
296 because sometimes django will cause them to be instantiated multiple
297 times (and we don't want different ones in form.fields vs
298 SliverInline.readonly_fields).
299 """
300 if network_name not in NetworkLookerUpper.byNetworkName:
301 NetworkLookerUpper.byNetworkName[network_name] = NetworkLookerUpper(network_name)
302 return NetworkLookerUpper.byNetworkName[network_name]
303
Siobhan Tullyd3515752013-06-21 16:34:53 -0400304class SliverInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400305 model = Sliver
Scott Baker7a61dc42014-09-02 17:08:20 -0700306 fields = ['backend_status_icon', 'all_ips_string', 'instance_name', 'slice', 'deploymentNetwork', 'flavor', 'image', 'node']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400307 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700308 readonly_fields = ['backend_status_icon', 'all_ips_string', 'instance_name']
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400309 suit_classes = 'suit-tab suit-tab-slivers'
Scott Baker74d8e622013-07-29 16:04:22 -0700310
Tony Mack5b061472014-02-04 07:57:10 -0500311 def queryset(self, request):
312 return Sliver.select_by_user(request.user)
313
Scott Bakerb24cc932014-06-09 10:51:16 -0700314 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Scott Bakerb24cc932014-06-09 10:51:16 -0700315 if db_field.name == 'deploymentNetwork':
Scott Baker3b678742014-06-09 13:11:54 -0700316 kwargs['queryset'] = Deployment.select_by_acl(request.user)
Scott Baker7a61dc42014-09-02 17:08:20 -0700317 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_deployment_changed(this);"})
Scott Baker32481312014-09-08 12:14:14 -0700318 elif db_field.name == 'flavor':
319 kwargs['widget'] = forms.Select(attrs={'onChange': "sliver_flavor_changed(this);"})
Scott Baker3b678742014-06-09 13:11:54 -0700320
321 field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Scott Bakerb24cc932014-06-09 10:51:16 -0700322
323 return field
324
Siobhan Tullyd3515752013-06-21 16:34:53 -0400325class SiteInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400326 model = Site
327 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400328 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400329
Tony Mack5b061472014-02-04 07:57:10 -0500330 def queryset(self, request):
331 return Site.select_by_user(request.user)
332
Siobhan Tullyd3515752013-06-21 16:34:53 -0400333class UserInline(PlStackTabularInline):
Siobhan Tully30fd4292013-05-10 08:59:56 -0400334 model = User
Scott Baker40c00762014-08-21 16:55:59 -0700335 fields = ['backend_status_icon', 'email', 'firstname', 'lastname']
336 readonly_fields = ('backend_status_icon', )
Siobhan Tully30fd4292013-05-10 08:59:56 -0400337 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400338 suit_classes = 'suit-tab suit-tab-users'
Siobhan Tully30fd4292013-05-10 08:59:56 -0400339
Tony Mack5b061472014-02-04 07:57:10 -0500340 def queryset(self, request):
341 return User.select_by_user(request.user)
342
Siobhan Tullyd3515752013-06-21 16:34:53 -0400343class SliceInline(PlStackTabularInline):
Tony Mack00d361f2013-04-28 10:28:42 -0400344 model = Slice
Scott Baker40c00762014-08-21 16:55:59 -0700345 fields = ['backend_status_icon', 'name', 'site', 'serviceClass', 'service']
346 readonly_fields = ('backend_status_icon', )
Tony Mack00d361f2013-04-28 10:28:42 -0400347 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400348 suit_classes = 'suit-tab suit-tab-slices'
349
Tony Mack5b061472014-02-04 07:57:10 -0500350 def queryset(self, request):
351 return Slice.select_by_user(request.user)
352
Siobhan Tullyd3515752013-06-21 16:34:53 -0400353class NodeInline(PlStackTabularInline):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400354 model = Node
355 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400356 suit_classes = 'suit-tab suit-tab-nodes'
Scott Baker40c00762014-08-21 16:55:59 -0700357 fields = ['backend_status_icon', 'name','deployment','site']
358 readonly_fields = ('backend_status_icon', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400359
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400360class DeploymentPrivilegeInline(PlStackTabularInline):
361 model = DeploymentPrivilege
362 extra = 0
363 suit_classes = 'suit-tab suit-tab-deploymentprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700364 fields = ['backend_status_icon', 'user','role','deployment']
365 readonly_fields = ('backend_status_icon', )
Tony Mack5b061472014-02-04 07:57:10 -0500366
367 def queryset(self, request):
368 return DeploymentPrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400369
Siobhan Tullyd3515752013-06-21 16:34:53 -0400370class SitePrivilegeInline(PlStackTabularInline):
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400371 model = SitePrivilege
372 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400373 suit_classes = 'suit-tab suit-tab-siteprivileges'
Scott Baker40c00762014-08-21 16:55:59 -0700374 fields = ['backend_status_icon', 'user','site', '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 == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500379 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400380
381 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500382 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400383 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
384
Tony Mack5b061472014-02-04 07:57:10 -0500385 def queryset(self, request):
386 return SitePrivilege.select_by_user(request.user)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400387
Tony Macke4be32f2014-03-11 20:45:25 -0400388class SiteDeploymentInline(PlStackTabularInline):
389 model = SiteDeployments
Tony Macke4be32f2014-03-11 20:45:25 -0400390 extra = 0
391 suit_classes = 'suit-tab suit-tab-deployments'
Scott Baker40c00762014-08-21 16:55:59 -0700392 fields = ['backend_status_icon', 'deployment','site']
393 readonly_fields = ('backend_status_icon', )
Tony Macke4be32f2014-03-11 20:45:25 -0400394
395 def formfield_for_foreignkey(self, db_field, request, **kwargs):
396 if db_field.name == 'site':
397 kwargs['queryset'] = Site.select_by_user(request.user)
398
399 if db_field.name == 'deployment':
400 kwargs['queryset'] = Deployment.select_by_user(request.user)
401 return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
402
403 def queryset(self, request):
404 return SiteDeployments.select_by_user(request.user)
405
406
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400407class SlicePrivilegeInline(PlStackTabularInline):
408 model = SlicePrivilege
409 suit_classes = 'suit-tab suit-tab-sliceprivileges'
410 extra = 0
Scott Baker40c00762014-08-21 16:55:59 -0700411 fields = ('backend_status_icon', 'user', 'slice', 'role')
412 readonly_fields = ('backend_status_icon', )
Siobhan Tullyaa1bcd52013-05-10 12:43:09 -0400413
Tony Mackc2835a92013-05-28 09:18:49 -0400414 def formfield_for_foreignkey(self, db_field, request, **kwargs):
415 if db_field.name == 'slice':
Scott Baker36f50872014-08-21 13:01:25 -0700416 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400417 if db_field.name == 'user':
Scott Baker36f50872014-08-21 13:01:25 -0700418 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400419
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400420 return super(SlicePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400421
Tony Mack5b061472014-02-04 07:57:10 -0500422 def queryset(self, request):
423 return SlicePrivilege.select_by_user(request.user)
424
Scott Bakera0015eb2013-08-14 17:28:14 -0700425class SliceNetworkInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -0700426 model = Network.slices.through
Scott Baker874936e2014-01-13 18:15:34 -0800427 selflink_fieldname = "network"
Scott Baker74d8e622013-07-29 16:04:22 -0700428 extra = 0
429 verbose_name = "Network Connection"
430 verbose_name_plural = "Network Connections"
Siobhan Tully2d95e482013-09-06 10:56:06 -0400431 suit_classes = 'suit-tab suit-tab-slicenetworks'
Scott Baker40c00762014-08-21 16:55:59 -0700432 fields = ['backend_status_icon', 'network']
433 readonly_fields = ('backend_status_icon', )
Scott Baker2170b972014-06-03 12:14:07 -0700434
435class ImageDeploymentsInline(PlStackTabularInline):
436 model = ImageDeployments
437 extra = 0
438 verbose_name = "Image Deployments"
439 verbose_name_plural = "Image Deployments"
440 suit_classes = 'suit-tab suit-tab-imagedeployments'
Scott Baker40c00762014-08-21 16:55:59 -0700441 fields = ['backend_status_icon', 'image', 'deployment', 'glance_image_id']
442 readonly_fields = ['backend_status_icon', 'glance_image_id']
Scott Baker74d8e622013-07-29 16:04:22 -0700443
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400444class SliceRoleAdmin(PlanetStackBaseAdmin):
445 model = SliceRole
446 pass
447
448class SiteRoleAdmin(PlanetStackBaseAdmin):
449 model = SiteRole
450 pass
451
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400452class DeploymentAdminForm(forms.ModelForm):
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400453 sites = forms.ModelMultipleChoiceField(
454 queryset=Site.objects.all(),
455 required=False,
Scott Baker70983182014-06-09 22:10:00 -0700456 help_text="Select which sites are allowed to host nodes in this deployment",
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400457 widget=FilteredSelectMultiple(
458 verbose_name=('Sites'), is_stacked=False
459 )
460 )
Scott Bakerde0f4412014-06-11 15:40:26 -0700461 images = forms.ModelMultipleChoiceField(
462 queryset=Image.objects.all(),
463 required=False,
464 help_text="Select which images should be deployed on this deployment",
465 widget=FilteredSelectMultiple(
466 verbose_name=('Images'), is_stacked=False
467 )
468 )
Scott Baker37b47902014-09-02 14:37:41 -0700469 flavors = forms.ModelMultipleChoiceField(
470 queryset=Flavor.objects.all(),
471 required=False,
472 help_text="Select which flavors should be usable on this deployment",
473 widget=FilteredSelectMultiple(
474 verbose_name=('Flavors'), is_stacked=False
475 )
476 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400477 class Meta:
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400478 model = Deployment
Scott Baker37b47902014-09-02 14:37:41 -0700479 many_to_many = ["flavors",]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400480
Siobhan Tully320b4622014-01-17 15:11:14 -0500481 def __init__(self, *args, **kwargs):
Scott Baker5380c522014-06-06 14:49:43 -0700482 request = kwargs.pop('request', None)
Siobhan Tully320b4622014-01-17 15:11:14 -0500483 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
484
Scott Baker5380c522014-06-06 14:49:43 -0700485 self.fields['accessControl'].initial = "allow site " + request.user.site.name
486
Siobhan Tully320b4622014-01-17 15:11:14 -0500487 if self.instance and self.instance.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700488 self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()]
Scott Bakerde0f4412014-06-11 15:40:26 -0700489 self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()]
Scott Baker37b47902014-09-02 14:37:41 -0700490 self.fields['flavors'].initial = self.instance.flavors.all()
Scott Bakerde0f4412014-06-11 15:40:26 -0700491
492 def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname):
493 """ helper function for handling m2m relations from the MultipleChoiceField
494
495 this_obj: the source object we want to link from
496
497 selected_objs: a list of destination objects we want to link to
498
499 all_relations: the full set of relations involving this_obj, including ones we don't want
500
501 relation_class: the class that implements the relation from source to dest
502
503 local_attrname: field name representing this_obj in relation_class
504
505 foreign_attrname: field name representing selected_objs in relation_class
506
507 This function will remove all newobjclass relations from this_obj
508 that are not contained in selected_objs, and add any relations that
509 are in selected_objs but don't exist in the data model yet.
510 """
511
512 existing_dest_objs = []
513 for relation in list(all_relations):
514 if getattr(relation, foreign_attrname) not in selected_objs:
515 #print "deleting site", sdp.site
516 relation.delete()
517 else:
518 existing_dest_objs.append(getattr(relation, foreign_attrname))
519
520 for dest_obj in selected_objs:
521 if dest_obj not in existing_dest_objs:
522 #print "adding site", site
523 kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj}
524 relation = relation_class(**kwargs)
525 relation.save()
Siobhan Tully320b4622014-01-17 15:11:14 -0500526
527 def save(self, commit=True):
528 deployment = super(DeploymentAdminForm, self).save(commit=False)
529
Scott Baker37b47902014-09-02 14:37:41 -0700530 deployment.flavors = self.cleaned_data['flavors']
531
Siobhan Tully320b4622014-01-17 15:11:14 -0500532 if commit:
533 deployment.save()
534
535 if deployment.pk:
Scott Bakerc9b14f72014-05-22 13:44:20 -0700536 # save_m2m() doesn't seem to work with 'through' relations. So we
537 # create/destroy the through models ourselves. There has to be
538 # a better way...
539
Scott Bakerde0f4412014-06-11 15:40:26 -0700540 self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site")
541 self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image")
Scott Bakerc9b14f72014-05-22 13:44:20 -0700542
Scott Baker37b47902014-09-02 14:37:41 -0700543 self.save_m2m()
Siobhan Tully320b4622014-01-17 15:11:14 -0500544
545 return deployment
546
Scott Bakerff5e0f32014-05-22 14:40:27 -0700547class DeploymentAdminROForm(DeploymentAdminForm):
548 def save(self, commit=True):
549 raise PermissionDenied
550
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500551class SiteAssocInline(PlStackTabularInline):
552 model = Site.deployments.through
553 extra = 0
554 suit_classes = 'suit-tab suit-tab-sites'
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400555
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400556class DeploymentAdmin(PlanetStackBaseAdmin):
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500557 model = Deployment
Tony Mack2cbd3802014-09-29 16:10:52 -0400558 fieldList = ['backend_status_text', 'name', 'availability_zone', 'sites', 'images', 'flavors', 'accessControl']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500559 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})]
Scott Bakerde0f4412014-06-11 15:40:26 -0700560 inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline]
Scott Baker63d1a552014-08-21 15:19:07 -0700561 list_display = ['backend_status_icon', 'name']
562 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700563 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500564
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500565 user_readonly_fields = ['name']
566
Scott Bakerde0f4412014-06-11 15:40:26 -0700567 suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images'))
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500568
Scott Bakerff5e0f32014-05-22 14:40:27 -0700569 def get_form(self, request, obj=None, **kwargs):
570 if request.user.isReadOnlyUser():
571 kwargs["form"] = DeploymentAdminROForm
572 else:
573 kwargs["form"] = DeploymentAdminForm
Scott Baker5380c522014-06-06 14:49:43 -0700574 adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs)
575
576 # from stackexchange: pass the request object into the form
577
578 class AdminFormMetaClass(adminForm):
579 def __new__(cls, *args, **kwargs):
580 kwargs['request'] = request
581 return adminForm(*args, **kwargs)
582
583 return AdminFormMetaClass
584
Siobhan Tullyce652d02013-10-08 21:52:35 -0400585class ServiceAttrAsTabInline(PlStackTabularInline):
586 model = ServiceAttribute
587 fields = ['name','value']
588 extra = 0
589 suit_classes = 'suit-tab suit-tab-serviceattrs'
590
Siobhan Tullyce652d02013-10-08 21:52:35 -0400591class ServiceAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700592 list_display = ("backend_status_icon","name","description","versionNumber","enabled","published")
593 list_display_links = ('backend_status_icon', 'name', )
Scott Baker40c00762014-08-21 16:55:59 -0700594 fieldList = ["backend_status_text","name","description","versionNumber","enabled","published"]
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500595 fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']})]
596 inlines = [ServiceAttrAsTabInline,SliceInline]
Scott Baker40c00762014-08-21 16:55:59 -0700597 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500598
599 user_readonly_fields = fieldList
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500600
601 suit_form_tabs =(('general', 'Service Details'),
602 ('slices','Slices'),
603 ('serviceattrs','Additional Attributes'),
604 )
Siobhan Tullyce652d02013-10-08 21:52:35 -0400605
Tony Mack0553f282013-06-10 22:54:50 -0400606class SiteAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700607 fieldList = ['backend_status_text', 'name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400608 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500609 (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
Tony Macke4be32f2014-03-11 20:45:25 -0400610 #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400611 ]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400612 suit_form_tabs =(('general', 'Site Details'),
613 ('users','Users'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400614 ('siteprivileges','Privileges'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400615 ('deployments','Deployments'),
616 ('slices','Slices'),
Scott Baker36f50872014-08-21 13:01:25 -0700617 ('nodes','Nodes'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400618 ('tags','Tags'),
619 )
Scott Baker40c00762014-08-21 16:55:59 -0700620 readonly_fields = ['backend_status_text', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500621
622 user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500623
Scott Baker63d1a552014-08-21 15:19:07 -0700624 list_display = ('backend_status_icon', 'name', 'login_base','site_url', 'enabled')
625 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400626 filter_horizontal = ('deployments',)
Tony Macke4be32f2014-03-11 20:45:25 -0400627 inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400628 search_fields = ['name']
629
Tony Mack04062832013-05-10 08:22:44 -0400630 def queryset(self, request):
Tony Mack5b061472014-02-04 07:57:10 -0500631 return Site.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400632
Tony Mack5cd13202013-05-01 21:48:38 -0400633 def get_formsets(self, request, obj=None):
634 for inline in self.get_inline_instances(request, obj):
635 # hide MyInline in the add view
636 if obj is None:
637 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400638 if isinstance(inline, SliceInline):
639 inline.model.caller = request.user
640 yield inline.get_formset(request, obj)
641
642 def get_formsets(self, request, obj=None):
643 for inline in self.get_inline_instances(request, obj):
644 # hide MyInline in the add view
645 if obj is None:
646 continue
647 if isinstance(inline, SliverInline):
648 inline.model.caller = request.user
Tony Mack5cd13202013-05-01 21:48:38 -0400649 yield inline.get_formset(request, obj)
650
Scott Baker545db2a2013-12-09 18:44:43 -0800651 def accountLink(self, obj):
652 link_obj = obj.accounts.all()
653 if link_obj:
654 reverse_path = "admin:core_account_change"
655 url = reverse(reverse_path, args =(link_obj[0].id,))
656 return "<a href='%s'>%s</a>" % (url, "view billing details")
657 else:
658 return "no billing data for this site"
659 accountLink.allow_tags = True
660 accountLink.short_description = "Billing"
661
Tony Mack332ee1d2014-02-04 15:33:45 -0500662 def save_model(self, request, obj, form, change):
663 # update openstack connection to use this site/tenant
664 obj.save_by_user(request.user)
665
666 def delete_model(self, request, obj):
667 obj.delete_by_user(request.user)
668
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500669
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400670class SitePrivilegeAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -0700671 fieldList = ['backend_status_text', 'user', 'site', 'role']
Tony Mack00d361f2013-04-28 10:28:42 -0400672 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500673 (None, {'fields': fieldList, 'classes':['collapse']})
Tony Mack00d361f2013-04-28 10:28:42 -0400674 ]
Scott Baker40c00762014-08-21 16:55:59 -0700675 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700676 list_display = ('backend_status_icon', 'user', 'site', 'role')
677 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500678 user_readonly_fields = fieldList
679 user_readonly_inlines = []
Tony Mack00d361f2013-04-28 10:28:42 -0400680
Tony Mackc2835a92013-05-28 09:18:49 -0400681 def formfield_for_foreignkey(self, db_field, request, **kwargs):
682 if db_field.name == 'site':
683 if not request.user.is_admin:
684 # only show sites where user is an admin or pi
685 sites = set()
686 for site_privilege in SitePrivilege.objects.filer(user=request.user):
687 if site_privilege.role.role_type in ['admin', 'pi']:
688 sites.add(site_privilege.site)
689 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
690
691 if db_field.name == 'user':
692 if not request.user.is_admin:
693 # only show users from sites where caller has admin or pi role
694 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
695 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
696 sites = [site_privilege.site for site_privilege in site_privileges]
697 site_privileges = SitePrivilege.objects.filter(site__in=sites)
698 emails = [site_privilege.user.email for site_privilege in site_privileges]
699 users = User.objects.filter(email__in=emails)
700 kwargs['queryset'] = users
701
702 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
703
Tony Mack04062832013-05-10 08:22:44 -0400704 def queryset(self, request):
705 # admins can see all privileges. Users can only see privileges at sites
Tony Mackc2835a92013-05-28 09:18:49 -0400706 # where they have the admin role or pi role.
Tony Mack04062832013-05-10 08:22:44 -0400707 qs = super(SitePrivilegeAdmin, self).queryset(request)
Tony Mack5b061472014-02-04 07:57:10 -0500708 #if not request.user.is_admin:
709 # roles = Role.objects.filter(role_type__in=['admin', 'pi'])
710 # site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
711 # login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
712 # sites = Site.objects.filter(login_base__in=login_bases)
713 # qs = qs.filter(site__in=sites)
Tony Mack04062832013-05-10 08:22:44 -0400714 return qs
715
Siobhan Tullyce652d02013-10-08 21:52:35 -0400716class SliceForm(forms.ModelForm):
717 class Meta:
718 model = Slice
719 widgets = {
Scott Baker36f50872014-08-21 13:01:25 -0700720 'service': LinkedSelect
Siobhan Tullyce652d02013-10-08 21:52:35 -0400721 }
722
Tony Mack2cbd3802014-09-29 16:10:52 -0400723 def clean(self):
724 cleaned_data = super(SliceForm, self).clean()
725 name = cleaned_data.get('name')
726 site_id = cleaned_data.get('site')
727 site = Slice.objects.get(id=site_id)
728 if not name.startswith(site.login_base):
729 raise forms.ValidationError('slice name must begin with %s' % site.login_base)
730 return cleaned_data
731
Tony Mack2bd5b412013-06-11 21:05:06 -0400732class SliceAdmin(PlanetStackBaseAdmin):
Siobhan Tullyce652d02013-10-08 21:52:35 -0400733 form = SliceForm
Tony Mackfbb26fc2014-09-02 07:03:27 -0400734 fieldList = ['backend_status_text', 'site', 'name', 'serviceClass', 'enabled','description', 'service', 'slice_url', 'max_slivers']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500735 fieldsets = [('Slice Details', {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),]
Scott Baker40c00762014-08-21 16:55:59 -0700736 readonly_fields = ('backend_status_text', )
Tony Mack7d459902014-09-03 13:18:57 -0400737 list_display = ('backend_status_icon', 'name', 'site','serviceClass', 'slice_url', 'max_slivers')
738 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tully2d95e482013-09-06 10:56:06 -0400739 inlines = [SlicePrivilegeInline,SliverInline, TagInline, ReservationInline,SliceNetworkInline]
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400740
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500741 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400742
743 suit_form_tabs =(('general', 'Slice Details'),
Siobhan Tully2d95e482013-09-06 10:56:06 -0400744 ('slicenetworks','Networks'),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400745 ('sliceprivileges','Privileges'),
746 ('slivers','Slivers'),
747 ('tags','Tags'),
748 ('reservations','Reservations'),
749 )
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400750
Scott Baker510fdbb2014-08-05 17:19:24 -0700751 def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
Scott Baker510fdbb2014-08-05 17:19:24 -0700752 deployment_nodes = []
753 for node in Node.objects.all():
754 deployment_nodes.append( (node.deployment.id, node.id, node.name) )
755
Scott Baker7a61dc42014-09-02 17:08:20 -0700756 deployment_flavors = []
757 for flavor in Flavor.objects.all():
758 for deployment in flavor.deployments.all():
759 deployment_flavors.append( (deployment.id, flavor.id, flavor.name) )
760
Scott Baker93e80cd2014-09-09 09:58:49 -0700761 deployment_images = []
762 for image in Image.objects.all():
763 for imageDeployment in image.imagedeployments_set.all():
764 deployment_images.append( (imageDeployment.deployment.id, image.id, image.name) )
765
Tony Mackec23b992014-09-02 21:18:45 -0400766 site_login_bases = []
767 for site in Site.objects.all():
Scott Baker93e80cd2014-09-09 09:58:49 -0700768 site_login_bases.append((site.id, site.login_base))
769
Scott Baker510fdbb2014-08-05 17:19:24 -0700770 context["deployment_nodes"] = deployment_nodes
Scott Baker7a61dc42014-09-02 17:08:20 -0700771 context["deployment_flavors"] = deployment_flavors
Scott Baker93e80cd2014-09-09 09:58:49 -0700772 context["deployment_images"] = deployment_images
Tony Mackec23b992014-09-02 21:18:45 -0400773 context["site_login_bases"] = site_login_bases
Scott Baker510fdbb2014-08-05 17:19:24 -0700774 return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj)
775
Tony Mackc2835a92013-05-28 09:18:49 -0400776 def formfield_for_foreignkey(self, db_field, request, **kwargs):
777 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -0500778 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackec23b992014-09-02 21:18:45 -0400779 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 -0700780
Tony Mackc2835a92013-05-28 09:18:49 -0400781 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
782
Tony Mack04062832013-05-10 08:22:44 -0400783 def queryset(self, request):
784 # admins can see all keys. Users can only see slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500785 return Slice.select_by_user(request.user)
Tony Mack04062832013-05-10 08:22:44 -0400786
Tony Mack79748612013-05-01 14:52:03 -0400787 def get_formsets(self, request, obj=None):
788 for inline in self.get_inline_instances(request, obj):
789 # hide MyInline in the add view
790 if obj is None:
791 continue
Tony Mack2bd5b412013-06-11 21:05:06 -0400792 if isinstance(inline, SliverInline):
793 inline.model.caller = request.user
Tony Mack79748612013-05-01 14:52:03 -0400794 yield inline.get_formset(request, obj)
795
Tony Mack2bd5b412013-06-11 21:05:06 -0400796
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400797class SlicePrivilegeAdmin(PlanetStackBaseAdmin):
Tony Mack00d361f2013-04-28 10:28:42 -0400798 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -0700799 (None, {'fields': ['backend_status_text', 'user', 'slice', 'role']})
Tony Mack00d361f2013-04-28 10:28:42 -0400800 ]
Scott Baker40c00762014-08-21 16:55:59 -0700801 readonly_fields = ('backend_status_text', )
Scott Baker63d1a552014-08-21 15:19:07 -0700802 list_display = ('backend_status_icon', 'user', 'slice', 'role')
803 list_display_links = list_display
Tony Mack00d361f2013-04-28 10:28:42 -0400804
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500805 user_readonly_fields = ['user', 'slice', 'role']
806 user_readonly_inlines = []
807
Tony Mackc2835a92013-05-28 09:18:49 -0400808 def formfield_for_foreignkey(self, db_field, request, **kwargs):
809 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500810 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400811
812 if db_field.name == 'user':
Tony Mack5b061472014-02-04 07:57:10 -0500813 kwargs['queryset'] = User.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400814
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400815 return super(SlicePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
Tony Mackc2835a92013-05-28 09:18:49 -0400816
Tony Mack04062832013-05-10 08:22:44 -0400817 def queryset(self, request):
818 # admins can see all memberships. Users can only see memberships of
819 # slices where they have the admin role.
Tony Mack5b061472014-02-04 07:57:10 -0500820 return SlicePrivilege.select_by_user(request.user)
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400821
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400822 def save_model(self, request, obj, form, change):
Tony Mack951dab42013-05-02 19:51:45 -0400823 # update openstack connection to use this site/tenant
824 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400825 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400826 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400827 obj.save()
828
829 def delete_model(self, request, obj):
Tony Mack951dab42013-05-02 19:51:45 -0400830 # update openstack connection to use this site/tenant
831 auth = request.session.get('auth', {})
Tony Mackf7f79a12014-08-11 11:21:42 -0400832 auth['tenant'] = obj.slice.slicename
Tony Mack951dab42013-05-02 19:51:45 -0400833 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400834 obj.delete()
835
Siobhan Tully567e3e62013-06-21 18:03:16 -0400836
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400837class ImageAdmin(PlanetStackBaseAdmin):
838
Scott Baker36f50872014-08-21 13:01:25 -0700839 fieldsets = [('Image Details',
Scott Baker40c00762014-08-21 16:55:59 -0700840 {'fields': ['backend_status_text', 'name', 'disk_format', 'container_format'],
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400841 'classes': ['suit-tab suit-tab-general']})
842 ]
Scott Baker40c00762014-08-21 16:55:59 -0700843 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400844
Scott Baker2170b972014-06-03 12:14:07 -0700845 suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400846
Scott Baker2170b972014-06-03 12:14:07 -0700847 inlines = [SliverInline, ImageDeploymentsInline]
Scott Bakerb6f99242014-06-11 11:34:44 -0700848
Tony Mack32e1ce32014-05-07 13:29:41 -0400849 user_readonly_fields = ['name', 'disk_format', 'container_format']
Scott Bakerb27b62c2014-08-15 16:29:16 -0700850
Scott Baker63d1a552014-08-21 15:19:07 -0700851 list_display = ['backend_status_icon', 'name']
852 list_display_links = ('backend_status_icon', 'name', )
853
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400854class NodeForm(forms.ModelForm):
855 class Meta:
856 widgets = {
857 'site': LinkedSelect,
858 'deployment': LinkedSelect
859 }
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400860
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500861class NodeAdmin(PlanetStackBaseAdmin):
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400862 form = NodeForm
Scott Baker63d1a552014-08-21 15:19:07 -0700863 list_display = ('backend_status_icon', 'name', 'site', 'deployment')
864 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullybf1153a2013-05-27 20:53:48 -0400865 list_filter = ('deployment',)
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500866
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400867 inlines = [TagInline,SliverInline]
Scott Baker40c00762014-08-21 16:55:59 -0700868 fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name','site','deployment'], 'classes':['suit-tab suit-tab-details']})]
869 readonly_fields = ('backend_status_text', )
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400870
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500871 user_readonly_fields = ['name','site','deployment']
872 user_readonly_inlines = [TagInline,SliverInline]
873
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400874 suit_form_tabs =(('details','Node Details'),('slivers','Slivers'),('tags','Tags'))
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400875
Siobhan Tully567e3e62013-06-21 18:03:16 -0400876
Tony Mackd90cdbf2013-04-16 22:48:40 -0400877class SliverForm(forms.ModelForm):
878 class Meta:
Tony Mack1d6b85f2013-05-07 18:49:14 -0400879 model = Sliver
Tony Mackd90cdbf2013-04-16 22:48:40 -0400880 ip = forms.CharField(widget=PlainTextWidget)
Tony Mack18261812013-05-02 16:39:20 -0400881 instance_name = forms.CharField(widget=PlainTextWidget)
Tony Mackd90cdbf2013-04-16 22:48:40 -0400882 widgets = {
883 'ip': PlainTextWidget(),
Tony Mack18261812013-05-02 16:39:20 -0400884 'instance_name': PlainTextWidget(),
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400885 'slice': LinkedSelect,
886 'deploymentNetwork': LinkedSelect,
887 'node': LinkedSelect,
888 'image': LinkedSelect
Siobhan Tully53437282013-04-26 19:30:27 -0400889 }
Tony Mackd90cdbf2013-04-16 22:48:40 -0400890
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500891class TagAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -0700892 list_display = ['backend_status_icon', 'service', 'name', 'value', 'content_type', 'content_object',]
893 list_display_links = list_display
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500894 user_readonly_fields = ['service', 'name', 'value', 'content_type', 'content_object',]
895 user_readonly_inlines = []
Siobhan Tullyd3515752013-06-21 16:34:53 -0400896
Tony Mack9bcbe4f2013-04-29 08:13:27 -0400897class SliverAdmin(PlanetStackBaseAdmin):
Tony Mackd90cdbf2013-04-16 22:48:40 -0400898 form = SliverForm
Tony Mackcdec0902013-04-15 00:38:49 -0400899 fieldsets = [
Scott Baker7a61dc42014-09-02 17:08:20 -0700900 ('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 -0400901 ]
Scott Baker40c00762014-08-21 16:55:59 -0700902 readonly_fields = ('backend_status_text', )
Scott Baker7a61dc42014-09-02 17:08:20 -0700903 list_display = ['backend_status_icon', 'ip', 'instance_name', 'slice', 'flavor', 'image', 'node', 'deploymentNetwork']
Scott Baker63d1a552014-08-21 15:19:07 -0700904 list_display_links = ('backend_status_icon', 'ip',)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400905
906 suit_form_tabs =(('general', 'Sliver Details'),
907 ('tags','Tags'),
908 )
909
Siobhan Tullyde5450d2013-06-21 11:35:33 -0400910 inlines = [TagInline]
Tony Mack53106f32013-04-27 16:43:01 -0400911
Scott Baker7a61dc42014-09-02 17:08:20 -0700912 user_readonly_fields = ['slice', 'deploymentNetwork', 'node', 'ip', 'instance_name', 'flavor', 'image']
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500913
Tony Mackc2835a92013-05-28 09:18:49 -0400914 def formfield_for_foreignkey(self, db_field, request, **kwargs):
915 if db_field.name == 'slice':
Tony Mack5b061472014-02-04 07:57:10 -0500916 kwargs['queryset'] = Slice.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -0400917
918 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
919
Tony Mack04062832013-05-10 08:22:44 -0400920 def queryset(self, request):
Scott Baker36f50872014-08-21 13:01:25 -0700921 # admins can see all slivers. Users can only see slivers of
Tony Mack04062832013-05-10 08:22:44 -0400922 # the slices they belong to.
Tony Mack5b061472014-02-04 07:57:10 -0500923 return Sliver.select_by_user(request.user)
924
Tony Mack04062832013-05-10 08:22:44 -0400925
Tony Mack1d6b85f2013-05-07 18:49:14 -0400926 def get_formsets(self, request, obj=None):
927 # make some fields read only if we are updating an existing record
928 if obj == None:
Scott Baker36f50872014-08-21 13:01:25 -0700929 #self.readonly_fields = ('ip', 'instance_name')
Scott Baker40c00762014-08-21 16:55:59 -0700930 self.readonly_fields = ('backend_status_text')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400931 else:
Scott Baker40c00762014-08-21 16:55:59 -0700932 self.readonly_fields = ('backend_status_text')
Scott Baker36f50872014-08-21 13:01:25 -0700933 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
Tony Mack1d6b85f2013-05-07 18:49:14 -0400934
935 for inline in self.get_inline_instances(request, obj):
936 # hide MyInline in the add view
937 if obj is None:
938 continue
Scott Baker526b71e2014-05-13 13:18:01 -0700939 if isinstance(inline, SliverInline):
940 inline.model.caller = request.user
941 yield inline.get_formset(request, obj)
Tony Mack53106f32013-04-27 16:43:01 -0400942
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500943 #def save_model(self, request, obj, form, change):
944 # # update openstack connection to use this site/tenant
945 # auth = request.session.get('auth', {})
946 # auth['tenant'] = obj.slice.name
947 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
948 # obj.creator = request.user
949 # obj.save()
Tony Mack53106f32013-04-27 16:43:01 -0400950
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500951 #def delete_model(self, request, obj):
952 # # update openstack connection to use this site/tenant
953 # auth = request.session.get('auth', {})
954 # auth['tenant'] = obj.slice.name
955 # obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
956 # obj.delete()
Tony Mackcdec0902013-04-15 00:38:49 -0400957
Siobhan Tully53437282013-04-26 19:30:27 -0400958class UserCreationForm(forms.ModelForm):
959 """A form for creating new users. Includes all the required
960 fields, plus a repeated password."""
961 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
962 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
963
964 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400965 model = User
Siobhan Tullybfd11dc2013-09-03 12:59:24 -0400966 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key')
Siobhan Tully53437282013-04-26 19:30:27 -0400967
968 def clean_password2(self):
969 # Check that the two password entries match
970 password1 = self.cleaned_data.get("password1")
971 password2 = self.cleaned_data.get("password2")
972 if password1 and password2 and password1 != password2:
973 raise forms.ValidationError("Passwords don't match")
974 return password2
975
976 def save(self, commit=True):
977 # Save the provided password in hashed format
978 user = super(UserCreationForm, self).save(commit=False)
Tony Mackf9f4afb2013-05-01 21:02:12 -0400979 user.password = self.cleaned_data["password1"]
980 #user.set_password(self.cleaned_data["password1"])
Siobhan Tully53437282013-04-26 19:30:27 -0400981 if commit:
982 user.save()
983 return user
984
Siobhan Tully567e3e62013-06-21 18:03:16 -0400985
Siobhan Tully53437282013-04-26 19:30:27 -0400986class UserChangeForm(forms.ModelForm):
987 """A form for updating users. Includes all the fields on
988 the user, but replaces the password field with admin's
989 password hash display field.
990 """
Siobhan Tully63b7ba42014-01-12 10:35:11 -0500991 password = ReadOnlyPasswordHashField(label='Password',
992 help_text= '<a href=\"password/\">Change Password</a>.')
Siobhan Tully53437282013-04-26 19:30:27 -0400993
994 class Meta:
Siobhan Tully30fd4292013-05-10 08:59:56 -0400995 model = User
Siobhan Tully53437282013-04-26 19:30:27 -0400996
997 def clean_password(self):
998 # Regardless of what the user provides, return the initial value.
999 # This is done here, rather than on the field, because the
1000 # field does not have access to the initial value
1001 return self.initial["password"]
1002
Scott Baker2c3cb642014-05-19 17:55:56 -07001003class UserDashboardViewInline(PlStackTabularInline):
1004 model = UserDashboardView
1005 extra = 0
1006 suit_classes = 'suit-tab suit-tab-dashboards'
1007 fields = ['user', 'dashboardView', 'order']
1008
Scott Baker86c83ab2014-10-03 13:10:47 -07001009class UserAdmin(PermissionCheckingAdminMixin, UserAdmin):
1010 # Note: Make sure PermissionCheckingAdminMixin is listed before
1011 # admin.ModelAdmin in the class declaration.
1012
Siobhan Tully53437282013-04-26 19:30:27 -04001013 class Meta:
1014 app_label = "core"
1015
1016 # The forms to add and change user instances
1017 form = UserChangeForm
1018 add_form = UserCreationForm
1019
1020 # The fields to be used in displaying the User model.
1021 # These override the definitions on the base UserAdmin
1022 # that reference specific fields on auth.User.
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001023 list_display = ('email', 'firstname', 'lastname', 'site', 'last_login')
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001024 list_filter = ('site',)
Scott Baker2c3cb642014-05-19 17:55:56 -07001025 inlines = [SlicePrivilegeInline,SitePrivilegeInline,DeploymentPrivilegeInline,UserDashboardViewInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001026
Scott Bakercbfb6002014-10-03 00:32:37 -07001027 fieldListLoginDetails = ['backend_status_text', 'email','site','password','is_active','is_readonly','is_admin','public_key']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001028 fieldListContactInfo = ['firstname','lastname','phone','timezone']
1029
Siobhan Tully53437282013-04-26 19:30:27 -04001030 fieldsets = (
Scott Baker40c00762014-08-21 16:55:59 -07001031 ('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 -04001032 ('Contact Information', {'fields': ('firstname','lastname','phone', 'timezone'), 'classes':['suit-tab suit-tab-contact']}),
Scott Baker2c3cb642014-05-19 17:55:56 -07001033 #('Dashboard Views', {'fields': ('dashboards',), 'classes':['suit-tab suit-tab-dashboards']}),
Siobhan Tully53437282013-04-26 19:30:27 -04001034 #('Important dates', {'fields': ('last_login',)}),
1035 )
1036 add_fieldsets = (
1037 (None, {
1038 'classes': ('wide',),
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001039 'fields': ('email', 'firstname', 'lastname', 'is_readonly', 'phone', 'public_key','password1', 'password2')}
Siobhan Tully53437282013-04-26 19:30:27 -04001040 ),
1041 )
Scott Baker40c00762014-08-21 16:55:59 -07001042 readonly_fields = ('backend_status_text', )
Siobhan Tully53437282013-04-26 19:30:27 -04001043 search_fields = ('email',)
1044 ordering = ('email',)
1045 filter_horizontal = ()
1046
Scott Baker3ca51f62014-05-23 12:05:11 -07001047 user_readonly_fields = fieldListLoginDetails + fieldListContactInfo
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001048
Scott Baker2c3cb642014-05-19 17:55:56 -07001049 suit_form_tabs =(('general','Login Details'),
1050 ('contact','Contact Information'),
1051 ('sliceprivileges','Slice Privileges'),
1052 ('siteprivileges','Site Privileges'),
1053 ('deploymentprivileges','Deployment Privileges'),
1054 ('dashboards','Dashboard Views'))
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001055
Tony Mackc2835a92013-05-28 09:18:49 -04001056 def formfield_for_foreignkey(self, db_field, request, **kwargs):
1057 if db_field.name == 'site':
Tony Mack5b061472014-02-04 07:57:10 -05001058 kwargs['queryset'] = Site.select_by_user(request.user)
Tony Mackc2835a92013-05-28 09:18:49 -04001059
1060 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
1061
Tony Mack5b061472014-02-04 07:57:10 -05001062 def queryset(self, request):
1063 return User.select_by_user(request.user)
1064
Scott Baker2c3cb642014-05-19 17:55:56 -07001065class DashboardViewAdmin(PlanetStackBaseAdmin):
1066 fieldsets = [('Dashboard View Details',
Scott Baker40c00762014-08-21 16:55:59 -07001067 {'fields': ['backend_status_text', 'name', 'url'],
Scott Baker2c3cb642014-05-19 17:55:56 -07001068 'classes': ['suit-tab suit-tab-general']})
1069 ]
Scott Baker40c00762014-08-21 16:55:59 -07001070 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001071
Scott Baker2c3cb642014-05-19 17:55:56 -07001072 suit_form_tabs =(('general','Dashboard View Details'),)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001073
Scott Baker0165fac2014-01-13 11:49:26 -08001074class ServiceResourceInline(PlStackTabularInline):
Scott Baker3de3e372013-05-10 16:50:44 -07001075 model = ServiceResource
1076 extra = 0
1077
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001078class ServiceClassAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001079 list_display = ('backend_status_icon', 'name', 'commitment', 'membershipFee')
1080 list_display_links = ('backend_status_icon', 'name', )
Scott Baker3de3e372013-05-10 16:50:44 -07001081 inlines = [ServiceResourceInline]
1082
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001083 user_readonly_fields = ['name', 'commitment', 'membershipFee']
1084 user_readonly_inlines = []
1085
Scott Baker0165fac2014-01-13 11:49:26 -08001086class ReservedResourceInline(PlStackTabularInline):
Scott Baker133c9212013-05-17 09:09:11 -07001087 model = ReservedResource
1088 extra = 0
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001089 suit_classes = 'suit-tab suit-tab-reservedresources'
Scott Baker133c9212013-05-17 09:09:11 -07001090
1091 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
1092 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
1093
1094 if db_field.name == 'resource':
1095 # restrict resources to those that the slice's service class allows
1096 if request._slice is not None:
1097 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
1098 if len(field.queryset) > 0:
1099 field.initial = field.queryset.all()[0]
1100 else:
1101 field.queryset = field.queryset.none()
1102 elif db_field.name == 'sliver':
1103 # restrict slivers to those that belong to the slice
1104 if request._slice is not None:
1105 field.queryset = field.queryset.filter(slice = request._slice)
1106 else:
1107 field.queryset = field.queryset.none()
1108
1109 return field
1110
Tony Mack5b061472014-02-04 07:57:10 -05001111 def queryset(self, request):
1112 return ReservedResource.select_by_user(request.user)
1113
Scott Baker133c9212013-05-17 09:09:11 -07001114class ReservationChangeForm(forms.ModelForm):
1115 class Meta:
1116 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001117 widgets = {
1118 'slice' : LinkedSelect
1119 }
Scott Baker133c9212013-05-17 09:09:11 -07001120
1121class ReservationAddForm(forms.ModelForm):
1122 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
1123 refresh = forms.CharField(widget=forms.HiddenInput())
1124
1125 class Media:
1126 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
1127
1128 def clean_slice(self):
1129 slice = self.cleaned_data.get("slice")
1130 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
1131 if len(x) == 0:
1132 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
1133 return slice
1134
1135 class Meta:
1136 model = Reservation
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001137 widgets = {
1138 'slice' : LinkedSelect
1139 }
1140
Scott Baker133c9212013-05-17 09:09:11 -07001141
1142class ReservationAddRefreshForm(ReservationAddForm):
1143 """ This form is displayed when the Reservation Form receives an update
1144 from the Slice dropdown onChange handler. It doesn't validate the
1145 data and doesn't save the data. This will cause the form to be
1146 redrawn.
1147 """
1148
Scott Baker8737e5f2013-05-17 09:35:32 -07001149 """ don't validate anything other than slice """
1150 dont_validate_fields = ("startTime", "duration")
1151
Scott Baker133c9212013-05-17 09:09:11 -07001152 def full_clean(self):
1153 result = super(ReservationAddForm, self).full_clean()
Scott Baker8737e5f2013-05-17 09:35:32 -07001154
1155 for fieldname in self.dont_validate_fields:
1156 if fieldname in self._errors:
1157 del self._errors[fieldname]
1158
Scott Baker133c9212013-05-17 09:09:11 -07001159 return result
1160
1161 """ don't save anything """
1162 def is_valid(self):
1163 return False
1164
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001165class ReservationAdmin(PlanetStackBaseAdmin):
Scott Baker40c00762014-08-21 16:55:59 -07001166 fieldList = ['backend_status_text', 'slice', 'startTime', 'duration']
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001167 fieldsets = [('Reservation Details', {'fields': fieldList, 'classes': ['suit-tab suit-tab-general']})]
Scott Baker40c00762014-08-21 16:55:59 -07001168 readonly_fields = ('backend_status_text', )
Scott Baker133c9212013-05-17 09:09:11 -07001169 list_display = ('startTime', 'duration')
Scott Baker133c9212013-05-17 09:09:11 -07001170 form = ReservationAddForm
1171
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001172 suit_form_tabs = (('general','Reservation Details'), ('reservedresources','Reserved Resources'))
1173
1174 inlines = [ReservedResourceInline]
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001175 user_readonly_fields = fieldList
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001176
Scott Baker133c9212013-05-17 09:09:11 -07001177 def add_view(self, request, form_url='', extra_context=None):
Scott Bakeracd45142013-05-19 16:19:16 -07001178 timezone.activate(request.user.timezone)
Scott Baker133c9212013-05-17 09:09:11 -07001179 request._refresh = False
1180 request._slice = None
1181 if request.method == 'POST':
Scott Baker8737e5f2013-05-17 09:35:32 -07001182 # "refresh" will be set to "1" if the form was submitted due to
1183 # a change in the Slice dropdown.
Scott Baker133c9212013-05-17 09:09:11 -07001184 if request.POST.get("refresh","1") == "1":
1185 request._refresh = True
1186 request.POST["refresh"] = "0"
Scott Baker8737e5f2013-05-17 09:35:32 -07001187
1188 # Keep track of the slice that was selected, so the
1189 # reservedResource inline can filter items for the slice.
Scott Baker133c9212013-05-17 09:09:11 -07001190 request._slice = request.POST.get("slice",None)
1191 if (request._slice is not None):
1192 request._slice = Slice.objects.get(id=request._slice)
1193
1194 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
1195 return result
1196
Scott Bakeracd45142013-05-19 16:19:16 -07001197 def changelist_view(self, request, extra_context = None):
1198 timezone.activate(request.user.timezone)
1199 return super(ReservationAdmin, self).changelist_view(request, extra_context)
1200
Scott Baker133c9212013-05-17 09:09:11 -07001201 def get_form(self, request, obj=None, **kwargs):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001202 request._obj_ = obj
1203 if obj is not None:
1204 # For changes, set request._slice to the slice already set in the
1205 # object.
1206 request._slice = obj.slice
1207 self.form = ReservationChangeForm
1208 else:
1209 if getattr(request, "_refresh", False):
1210 self.form = ReservationAddRefreshForm
1211 else:
1212 self.form = ReservationAddForm
1213 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
1214
Scott Baker133c9212013-05-17 09:09:11 -07001215 def get_readonly_fields(self, request, obj=None):
Siobhan Tullyd3515752013-06-21 16:34:53 -04001216 if (obj is not None):
1217 # Prevent slice from being changed after the reservation has been
1218 # created.
1219 return ['slice']
1220 else:
Scott Baker133c9212013-05-17 09:09:11 -07001221 return []
Scott Baker3de3e372013-05-10 16:50:44 -07001222
Tony Mack5b061472014-02-04 07:57:10 -05001223 def queryset(self, request):
1224 return Reservation.select_by_user(request.user)
1225
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001226class NetworkParameterTypeAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001227 list_display = ("backend_status_icon", "name", )
1228 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001229 user_readonly_fields = ['name']
1230 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001231
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001232class RouterAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001233 list_display = ("backend_status_icon", "name", )
1234 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001235 user_readonly_fields = ['name']
1236 user_readonly_inlines = []
1237
Scott Baker0165fac2014-01-13 11:49:26 -08001238class RouterInline(PlStackTabularInline):
Scott Baker74d8e622013-07-29 16:04:22 -07001239 model = Router.networks.through
1240 extra = 0
1241 verbose_name_plural = "Routers"
1242 verbose_name = "Router"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001243 suit_classes = 'suit-tab suit-tab-routers'
Scott Baker74d8e622013-07-29 16:04:22 -07001244
Scott Bakerb27b62c2014-08-15 16:29:16 -07001245class NetworkParameterInline(PlStackGenericTabularInline):
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001246 model = NetworkParameter
Scott Baker618e3792014-08-15 13:42:29 -07001247 extra = 0
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001248 verbose_name_plural = "Parameters"
1249 verbose_name = "Parameter"
1250 suit_classes = 'suit-tab suit-tab-netparams'
Scott Baker40c00762014-08-21 16:55:59 -07001251 fields = ['backend_status_icon', 'parameter', 'value']
1252 readonly_fields = ('backend_status_icon', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001253
Scott Baker0165fac2014-01-13 11:49:26 -08001254class NetworkSliversInline(PlStackTabularInline):
Scott Baker40c00762014-08-21 16:55:59 -07001255 fields = ['backend_status_icon', 'network','sliver','ip']
1256 readonly_fields = ("backend_status_icon", "ip", )
Scott Baker74d8e622013-07-29 16:04:22 -07001257 model = NetworkSliver
Scott Baker874936e2014-01-13 18:15:34 -08001258 selflink_fieldname = "sliver"
Scott Baker74d8e622013-07-29 16:04:22 -07001259 extra = 0
1260 verbose_name_plural = "Slivers"
1261 verbose_name = "Sliver"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001262 suit_classes = 'suit-tab suit-tab-networkslivers'
Scott Baker74d8e622013-07-29 16:04:22 -07001263
Scott Baker0165fac2014-01-13 11:49:26 -08001264class NetworkSlicesInline(PlStackTabularInline):
Scott Bakerd7d2a392013-08-06 08:57:30 -07001265 model = NetworkSlice
Scott Baker874936e2014-01-13 18:15:34 -08001266 selflink_fieldname = "slice"
Scott Bakerd7d2a392013-08-06 08:57:30 -07001267 extra = 0
1268 verbose_name_plural = "Slices"
1269 verbose_name = "Slice"
Siobhan Tully2d95e482013-09-06 10:56:06 -04001270 suit_classes = 'suit-tab suit-tab-networkslices'
Scott Baker40c00762014-08-21 16:55:59 -07001271 fields = ['backend_status_icon', 'network','slice']
1272 readonly_fields = ('backend_status_icon', )
Scott Bakerd7d2a392013-08-06 08:57:30 -07001273
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001274class NetworkAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001275 list_display = ("backend_status_icon", "name", "subnet", "ports", "labels")
1276 list_display_links = ('backend_status_icon', 'name', )
Scott Baker74d8e622013-07-29 16:04:22 -07001277 readonly_fields = ("subnet", )
Siobhan Tully2d95e482013-09-06 10:56:06 -04001278
Scott Bakerd7d2a392013-08-06 08:57:30 -07001279 inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline]
Scott Baker74d8e622013-07-29 16:04:22 -07001280
Siobhan Tully2d95e482013-09-06 10:56:06 -04001281 fieldsets = [
Scott Baker40c00762014-08-21 16:55:59 -07001282 (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 -05001283
Scott Baker40c00762014-08-21 16:55:59 -07001284 readonly_fields = ('backend_status_text', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001285 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 -04001286
1287 suit_form_tabs =(
1288 ('general','Network Details'),
1289 ('netparams', 'Parameters'),
1290 ('networkslivers','Slivers'),
1291 ('networkslices','Slices'),
1292 ('routers','Routers'),
1293 )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001294class NetworkTemplateAdmin(PlanetStackBaseAdmin):
Scott Baker63d1a552014-08-21 15:19:07 -07001295 list_display = ("backend_status_icon", "name", "guaranteedBandwidth", "visibility")
1296 list_display_links = ('backend_status_icon', 'name', )
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001297 user_readonly_fields = ["name", "guaranteedBandwidth", "visibility"]
1298 user_readonly_inlines = []
Scott Baker74d8e622013-07-29 16:04:22 -07001299
Scott Baker37b47902014-09-02 14:37:41 -07001300class FlavorAdmin(PlanetStackBaseAdmin):
1301 list_display = ("backend_status_icon", "name", "flavor", "order", "default")
1302 list_display_links = ("backend_status_icon", "name")
1303 user_readonly_fields = ("name", "flavor")
1304 fields = ("name", "description", "flavor", "order", "default")
1305
Tony Mack31c2b8f2013-04-26 20:01:42 -04001306# register a signal that caches the user's credentials when they log in
1307def cache_credentials(sender, user, request, **kwds):
1308 auth = {'username': request.POST['username'],
1309 'password': request.POST['password']}
1310 request.session['auth'] = auth
1311user_logged_in.connect(cache_credentials)
1312
Scott Baker15cddfa2013-12-09 13:45:19 -08001313def dollar_field(fieldName, short_description):
1314 def newFunc(self, obj):
1315 try:
1316 x= "$ %0.2f" % float(getattr(obj, fieldName, 0.0))
1317 except:
1318 x=getattr(obj, fieldName, 0.0)
1319 return x
1320 newFunc.short_description = short_description
1321 return newFunc
1322
1323def right_dollar_field(fieldName, short_description):
1324 def newFunc(self, obj):
1325 try:
1326 #x= '<div align=right style="width:6em">$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1327 x= '<div align=right>$ %0.2f</div>' % float(getattr(obj, fieldName, 0.0))
1328 except:
1329 x=getattr(obj, fieldName, 0.0)
1330 return x
1331 newFunc.short_description = short_description
1332 newFunc.allow_tags = True
1333 return newFunc
Scott Baker43105042013-12-06 23:23:36 -08001334
Scott Baker0165fac2014-01-13 11:49:26 -08001335class InvoiceChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001336 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001337 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001338 verbose_name_plural = "Charges"
1339 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001340 exclude = ['account']
Scott Baker9cb88a22013-12-09 18:56:00 -08001341 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1342 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1343 can_delete = False
1344 max_num = 0
1345
1346 dollar_amount = right_dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001347
1348class InvoiceAdmin(admin.ModelAdmin):
1349 list_display = ("date", "account")
1350
1351 inlines = [InvoiceChargeInline]
1352
Scott Baker9cb88a22013-12-09 18:56:00 -08001353 fields = ["date", "account", "dollar_amount"]
1354 readonly_fields = ["date", "account", "dollar_amount"]
1355
1356 dollar_amount = dollar_field("amount", "Amount")
Scott Baker43105042013-12-06 23:23:36 -08001357
Scott Baker0165fac2014-01-13 11:49:26 -08001358class InvoiceInline(PlStackTabularInline):
Scott Baker15cddfa2013-12-09 13:45:19 -08001359 model = Invoice
1360 extra = 0
1361 verbose_name_plural = "Invoices"
1362 verbose_name = "Invoice"
Scott Baker0165fac2014-01-13 11:49:26 -08001363 fields = ["date", "dollar_amount"]
1364 readonly_fields = ["date", "dollar_amount"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001365 suit_classes = 'suit-tab suit-tab-accountinvoice'
1366 can_delete=False
1367 max_num=0
1368
1369 dollar_amount = right_dollar_field("amount", "Amount")
1370
Scott Baker0165fac2014-01-13 11:49:26 -08001371class PendingChargeInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001372 model = Charge
Scott Baker15cddfa2013-12-09 13:45:19 -08001373 extra = 0
Scott Baker43105042013-12-06 23:23:36 -08001374 verbose_name_plural = "Charges"
1375 verbose_name = "Charge"
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001376 exclude = ["invoice"]
Scott Baker15cddfa2013-12-09 13:45:19 -08001377 fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
1378 readonly_fields = ["date", "kind", "state", "object", "coreHours", "dollar_amount", "slice"]
Scott Baker43105042013-12-06 23:23:36 -08001379 suit_classes = 'suit-tab suit-tab-accountpendingcharges'
Scott Baker15cddfa2013-12-09 13:45:19 -08001380 can_delete=False
1381 max_num=0
Scott Baker43105042013-12-06 23:23:36 -08001382
1383 def queryset(self, request):
1384 qs = super(PendingChargeInline, self).queryset(request)
1385 qs = qs.filter(state="pending")
1386 return qs
1387
Scott Baker15cddfa2013-12-09 13:45:19 -08001388 dollar_amount = right_dollar_field("amount", "Amount")
1389
Scott Baker0165fac2014-01-13 11:49:26 -08001390class PaymentInline(PlStackTabularInline):
Scott Baker43105042013-12-06 23:23:36 -08001391 model=Payment
1392 extra = 1
1393 verbose_name_plural = "Payments"
1394 verbose_name = "Payment"
Scott Baker15cddfa2013-12-09 13:45:19 -08001395 fields = ["date", "dollar_amount"]
1396 readonly_fields = ["date", "dollar_amount"]
Scott Baker43105042013-12-06 23:23:36 -08001397 suit_classes = 'suit-tab suit-tab-accountpayments'
Scott Baker15cddfa2013-12-09 13:45:19 -08001398 can_delete=False
1399 max_num=0
1400
1401 dollar_amount = right_dollar_field("amount", "Amount")
1402
Scott Baker43105042013-12-06 23:23:36 -08001403class AccountAdmin(admin.ModelAdmin):
1404 list_display = ("site", "balance_due")
1405
1406 inlines = [InvoiceInline, PaymentInline, PendingChargeInline]
1407
1408 fieldsets = [
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001409 (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 -08001410
Scott Baker15cddfa2013-12-09 13:45:19 -08001411 readonly_fields = ['site', 'dollar_balance_due', 'dollar_total_invoices', 'dollar_total_payments']
Scott Baker43105042013-12-06 23:23:36 -08001412
1413 suit_form_tabs =(
1414 ('general','Account Details'),
1415 ('accountinvoice', 'Invoices'),
1416 ('accountpayments', 'Payments'),
1417 ('accountpendingcharges','Pending Charges'),
1418 )
1419
Scott Baker15cddfa2013-12-09 13:45:19 -08001420 dollar_balance_due = dollar_field("balance_due", "Balance Due")
1421 dollar_total_invoices = dollar_field("total_invoices", "Total Invoices")
1422 dollar_total_payments = dollar_field("total_payments", "Total Payments")
1423
Siobhan Tully53437282013-04-26 19:30:27 -04001424# Now register the new UserAdmin...
Siobhan Tully30fd4292013-05-10 08:59:56 -04001425admin.site.register(User, UserAdmin)
Siobhan Tully53437282013-04-26 19:30:27 -04001426# ... and, since we're not using Django's builtin permissions,
1427# unregister the Group model from admin.
Siobhan Tullyce652d02013-10-08 21:52:35 -04001428#admin.site.unregister(Group)
Siobhan Tully53437282013-04-26 19:30:27 -04001429
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001430#Do not show django evolution in the admin interface
1431from django_evolution.models import Version, Evolution
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001432#admin.site.unregister(Version)
1433#admin.site.unregister(Evolution)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001434
1435
1436# When debugging it is often easier to see all the classes, but for regular use
1437# only the top-levels should be displayed
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001438showAll = False
Scott Baker43105042013-12-06 23:23:36 -08001439
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001440admin.site.register(Deployment, DeploymentAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001441admin.site.register(Site, SiteAdmin)
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001442admin.site.register(Slice, SliceAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001443admin.site.register(Service, ServiceAdmin)
smbakera3cf70c2013-06-27 02:01:41 -07001444admin.site.register(Reservation, ReservationAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001445admin.site.register(Network, NetworkAdmin)
1446admin.site.register(Router, RouterAdmin)
Scott Baker74d8e622013-07-29 16:04:22 -07001447admin.site.register(NetworkTemplate, NetworkTemplateAdmin)
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001448admin.site.register(Account, AccountAdmin)
1449admin.site.register(Invoice, InvoiceAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001450
Siobhan Tullycf04fb62014-01-11 11:25:57 -05001451if True:
1452 admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin)
1453 admin.site.register(ServiceClass, ServiceClassAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001454 #admin.site.register(PlanetStack)
Siobhan Tullyd3515752013-06-21 16:34:53 -04001455 admin.site.register(Tag, TagAdmin)
Siobhan Tullyce652d02013-10-08 21:52:35 -04001456 admin.site.register(DeploymentRole)
1457 admin.site.register(SiteRole)
1458 admin.site.register(SliceRole)
1459 admin.site.register(PlanetStackRole)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001460 admin.site.register(Node, NodeAdmin)
Siobhan Tullybfd11dc2013-09-03 12:59:24 -04001461 #admin.site.register(SlicePrivilege, SlicePrivilegeAdmin)
1462 #admin.site.register(SitePrivilege, SitePrivilegeAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001463 admin.site.register(Sliver, SliverAdmin)
Siobhan Tullybf1153a2013-05-27 20:53:48 -04001464 admin.site.register(Image, ImageAdmin)
Scott Baker2c3cb642014-05-19 17:55:56 -07001465 admin.site.register(DashboardView, DashboardViewAdmin)
Scott Baker37b47902014-09-02 14:37:41 -07001466 admin.site.register(Flavor, FlavorAdmin)
Tony Mack7130ac32013-03-22 21:58:00 -04001467