1 from core.models import Site
2 from core.models import *
3 from openstack.manager import OpenStackManager
5 from django.contrib import admin
6 from django.contrib.auth.models import Group
7 from django import forms
8 from django.utils.safestring import mark_safe
9 from django.contrib.auth.admin import UserAdmin
10 from django.contrib.admin.widgets import FilteredSelectMultiple
11 from django.contrib.auth.forms import ReadOnlyPasswordHashField
12 from django.contrib.auth.signals import user_logged_in
13 from django.utils import timezone
14 from django.contrib.contenttypes import generic
16 import django_evolution
18 class PlStackTabularInline(admin.TabularInline):
21 class ReadonlyTabularInline(PlStackTabularInline):
26 def get_readonly_fields(self, request, obj=None):
28 for field in self.model._meta.get_all_field_names():
29 if (not field == 'id'):
30 if (field not in self.editable_fields):
34 def has_add_permission(self, request):
37 class TagInline(generic.GenericTabularInline):
42 class SliverInline(PlStackTabularInline):
44 fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']
46 #readonly_fields = ['ip', 'instance_name', 'image']
47 readonly_fields = ['ip', 'instance_name']
49 class SiteInline(PlStackTabularInline):
53 class UserInline(PlStackTabularInline):
55 fields = ['email', 'firstname', 'lastname']
58 class SliceInline(PlStackTabularInline):
62 class RoleInline(PlStackTabularInline):
66 class NodeInline(PlStackTabularInline):
70 class SitePrivilegeInline(PlStackTabularInline):
74 def formfield_for_foreignkey(self, db_field, request, **kwargs):
75 if db_field.name == 'site':
76 if not request.user.is_admin:
77 # only show sites where user is an admin or pi
78 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
79 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
80 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
81 sites = Site.objects.filter(login_base__in=login_bases)
82 kwargs['queryset'] = sites
84 if db_field.name == 'user':
85 if not request.user.is_admin:
86 # only show users from sites where caller has admin or pi role
87 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
88 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
89 sites = [site_privilege.site for site_privilege in site_privileges]
90 site_privileges = SitePrivilege.objects.filter(site__in=sites)
91 emails = [site_privilege.user.email for site_privilege in site_privileges]
92 users = User.objects.filter(email__in=emails)
93 kwargs['queryset'] = users
94 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
96 class SliceMembershipInline(PlStackTabularInline):
97 model = SliceMembership
99 fields = ('user', 'role')
101 def formfield_for_foreignkey(self, db_field, request, **kwargs):
102 if db_field.name == 'slice':
103 if not request.user.is_admin:
104 # only show slices at sites where caller has admin or pi role
105 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
106 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
107 sites = [site_privilege.site for site_privilege in site_privileges]
108 slices = Slice.objects.filter(site__in=sites)
109 kwargs['queryset'] = slices
110 if db_field.name == 'user':
111 if not request.user.is_admin:
112 # only show users from sites where caller has admin or pi role
113 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
114 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
115 sites = [site_privilege.site for site_privilege in site_privileges]
116 site_privileges = SitePrivilege.objects.filter(site__in=sites)
117 emails = [site_privilege.user.email for site_privilege in site_privileges]
118 users = User.objects.filter(email__in=emails)
119 kwargs['queryset'] = list(users)
121 return super(SliceMembershipInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
123 class SliceTagInline(PlStackTabularInline):
127 class PlainTextWidget(forms.HiddenInput):
128 input_type = 'hidden'
130 def render(self, name, value, attrs=None):
133 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
135 class PlanetStackBaseAdmin(admin.ModelAdmin):
137 exclude = ['enacted']
139 class RoleAdmin(PlanetStackBaseAdmin):
141 ('Role', {'fields': ['role_type']})
143 list_display = ('role_type',)
145 class DeploymentAdminForm(forms.ModelForm):
146 sites = forms.ModelMultipleChoiceField(
147 queryset=Site.objects.all(),
149 widget=FilteredSelectMultiple(
150 verbose_name=('Sites'), is_stacked=False
156 def __init__(self, *args, **kwargs):
157 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
159 if self.instance and self.instance.pk:
160 self.fields['sites'].initial = self.instance.sites.all()
162 def save(self, commit=True):
163 deploymentNetwork = super(DeploymentAdminForm, self).save(commit=False)
165 deploymentNetwork.save()
167 if deploymentNetwork.pk:
168 deploymentNetwork.sites = self.cleaned_data['sites']
171 return deploymentNetwork
173 class DeploymentAdmin(PlanetStackBaseAdmin):
174 form = DeploymentAdminForm
175 inlines = [NodeInline,SliverInline]
177 def get_formsets(self, request, obj=None):
178 for inline in self.get_inline_instances(request, obj):
179 # hide MyInline in the add view
182 # give inline object access to driver and caller
183 auth = request.session.get('auth', {})
184 if request.user.site:
185 auth['tenant'] = request.user.site.login_base
186 inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
187 yield inline.get_formset(request, obj)
189 class SiteAdmin(PlanetStackBaseAdmin):
191 (None, {'fields': ['name', 'site_url', 'enabled', 'is_public', 'login_base']}),
192 ('Location', {'fields': ['latitude', 'longitude']}),
193 ('Deployment Networks', {'fields': ['deployments']})
195 list_display = ('name', 'login_base','site_url', 'enabled')
196 filter_horizontal = ('deployments',)
197 inlines = [TagInline, NodeInline, UserInline, SitePrivilegeInline]
198 search_fields = ['name']
200 def queryset(self, request):
201 # admins can see all keys. Users can only see sites they belong to.
202 qs = super(SiteAdmin, self).queryset(request)
203 if not request.user.is_admin:
204 valid_sites = [request.user.site.login_base]
205 roles = request.user.get_roles()
206 for tenant_list in roles.values():
207 valid_sites.extend(tenant_list)
208 qs = qs.filter(login_base__in=valid_sites)
211 def get_formsets(self, request, obj=None):
212 for inline in self.get_inline_instances(request, obj):
213 # hide MyInline in the add view
216 if isinstance(inline, SliceInline):
217 inline.model.caller = request.user
218 yield inline.get_formset(request, obj)
220 def get_formsets(self, request, obj=None):
221 for inline in self.get_inline_instances(request, obj):
222 # hide MyInline in the add view
225 if isinstance(inline, SliverInline):
226 inline.model.caller = request.user
227 yield inline.get_formset(request, obj)
229 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
231 (None, {'fields': ['user', 'site', 'role']})
233 list_display = ('user', 'site', 'role')
235 def formfield_for_foreignkey(self, db_field, request, **kwargs):
236 if db_field.name == 'site':
237 if not request.user.is_admin:
238 # only show sites where user is an admin or pi
240 for site_privilege in SitePrivilege.objects.filer(user=request.user):
241 if site_privilege.role.role_type in ['admin', 'pi']:
242 sites.add(site_privilege.site)
243 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
245 if db_field.name == 'user':
246 if not request.user.is_admin:
247 # only show users from sites where caller has admin or pi role
248 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
249 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
250 sites = [site_privilege.site for site_privilege in site_privileges]
251 site_privileges = SitePrivilege.objects.filter(site__in=sites)
252 emails = [site_privilege.user.email for site_privilege in site_privileges]
253 users = User.objects.filter(email__in=emails)
254 kwargs['queryset'] = users
256 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
258 def queryset(self, request):
259 # admins can see all privileges. Users can only see privileges at sites
260 # where they have the admin role or pi role.
261 qs = super(SitePrivilegeAdmin, self).queryset(request)
262 if not request.user.is_admin:
263 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
264 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
265 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
266 sites = Site.objects.filter(login_base__in=login_bases)
267 qs = qs.filter(site__in=sites)
270 class SliceAdmin(PlanetStackBaseAdmin):
271 fields = ['name', 'site', 'serviceClass', 'description', 'slice_url']
272 list_display = ('name', 'site','serviceClass', 'slice_url')
273 inlines = [SliverInline, SliceMembershipInline, TagInline, SliceTagInline]
275 def formfield_for_foreignkey(self, db_field, request, **kwargs):
276 if db_field.name == 'site':
277 if not request.user.is_admin:
278 # only show sites where user is a pi or admin
279 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
280 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
281 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
282 sites = Site.objects.filter(login_base__in=login_bases)
283 kwargs['queryset'] = sites
285 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
287 def queryset(self, request):
288 # admins can see all keys. Users can only see slices they belong to.
289 qs = super(SliceAdmin, self).queryset(request)
290 if not request.user.is_admin:
292 roles = request.user.get_roles()
293 for tenant_list in roles.values():
294 valid_slices.extend(tenant_list)
295 qs = qs.filter(name__in=valid_slices)
298 def get_formsets(self, request, obj=None):
299 for inline in self.get_inline_instances(request, obj):
300 # hide MyInline in the add view
303 if isinstance(inline, SliverInline):
304 inline.model.caller = request.user
305 yield inline.get_formset(request, obj)
307 def get_queryset(self, request):
308 qs = super(SliceAdmin, self).get_queryset(request)
309 if request.user.is_superuser:
311 # users can only see slices at their site
312 return qs.filter(site=request.user.site)
314 def save_model(self, request, obj, form, change):
315 # update openstack connection to use this site/tenant
316 obj.caller = request.user
319 class SliceMembershipAdmin(PlanetStackBaseAdmin):
321 (None, {'fields': ['user', 'slice', 'role']})
323 list_display = ('user', 'slice', 'role')
325 def formfield_for_foreignkey(self, db_field, request, **kwargs):
326 if db_field.name == 'slice':
327 if not request.user.is_admin:
328 # only show slices at sites where caller has admin or pi role
329 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
330 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
331 sites = [site_privilege.site for site_privilege in site_privileges]
332 slices = Slice.objects.filter(site__in=sites)
333 kwargs['queryset'] = slices
335 if db_field.name == 'user':
336 if not request.user.is_admin:
337 # only show users from sites where caller has admin or pi role
338 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
339 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
340 sites = [site_privilege.site for site_privilege in site_privileges]
341 site_privileges = SitePrivilege.objects.filter(site__in=sites)
342 emails = [site_privilege.user.email for site_privilege in site_privileges]
343 users = User.objects.filter(email__in=emails)
344 kwargs['queryset'] = users
346 return super(SliceMembershipAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
348 def queryset(self, request):
349 # admins can see all memberships. Users can only see memberships of
350 # slices where they have the admin role.
351 qs = super(SliceMembershipAdmin, self).queryset(request)
352 if not request.user.is_admin:
353 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
354 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
355 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
356 sites = Site.objects.filter(login_base__in=login_bases)
357 slices = Slice.objects.filter(site__in=sites)
358 qs = qs.filter(slice__in=slices)
361 def save_model(self, request, obj, form, change):
362 # update openstack connection to use this site/tenant
363 auth = request.session.get('auth', {})
364 auth['tenant'] = obj.slice.name
365 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
368 def delete_model(self, request, obj):
369 # update openstack connection to use this site/tenant
370 auth = request.session.get('auth', {})
371 auth['tenant'] = obj.slice.name
372 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
375 class ImageAdmin(admin.ModelAdmin):
376 fields = ['image_id', 'name', 'disk_format', 'container_format']
378 class NodeAdmin(admin.ModelAdmin):
379 list_display = ('name', 'site', 'deployment')
380 list_filter = ('deployment',)
381 inlines = [TagInline]
383 class SliverForm(forms.ModelForm):
386 ip = forms.CharField(widget=PlainTextWidget)
387 instance_name = forms.CharField(widget=PlainTextWidget)
389 'ip': PlainTextWidget(),
390 'instance_name': PlainTextWidget(),
393 class ProjectAdmin(admin.ModelAdmin):
394 exclude = ['enacted']
396 class TagAdmin(admin.ModelAdmin):
397 exclude = ['enacted']
399 class SliverAdmin(PlanetStackBaseAdmin):
402 ('Sliver', {'fields': ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']})
404 list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']
405 inlines = [TagInline]
407 def formfield_for_foreignkey(self, db_field, request, **kwargs):
408 if db_field.name == 'slice':
409 if not request.user.is_admin:
410 slices = set([sm.slice.name for sm in SliceMembership.objects.filter(user=request.user)])
411 kwargs['queryset'] = Slice.objects.filter(name__in=list(slices))
413 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
415 def queryset(self, request):
416 # admins can see all slivers. Users can only see slivers of
417 # the slices they belong to.
418 qs = super(SliverAdmin, self).queryset(request)
419 if not request.user.is_admin:
421 roles = request.user.get_roles()
422 for tenant_list in roles.values():
423 tenants.extend(tenant_list)
424 valid_slices = Slice.objects.filter(name__in=tenants)
425 qs = qs.filter(slice__in=valid_slices)
428 def get_formsets(self, request, obj=None):
429 # make some fields read only if we are updating an existing record
431 #self.readonly_fields = ('ip', 'instance_name')
432 self.readonly_fields = ()
434 self.readonly_fields = ()
435 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
437 for inline in self.get_inline_instances(request, obj):
438 # hide MyInline in the add view
441 # give inline object access to driver and caller
442 auth = request.session.get('auth', {})
443 auth['tenant'] = obj.name # meed to connect using slice's tenant
444 inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
445 yield inline.get_formset(request, obj)
447 def save_model(self, request, obj, form, change):
448 # update openstack connection to use this site/tenant
449 auth = request.session.get('auth', {})
450 auth['tenant'] = obj.slice.name
451 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
452 obj.creator = request.user
455 def delete_model(self, request, obj):
456 # update openstack connection to use this site/tenant
457 auth = request.session.get('auth', {})
458 auth['tenant'] = obj.slice.name
459 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
462 class UserCreationForm(forms.ModelForm):
463 """A form for creating new users. Includes all the required
464 fields, plus a repeated password."""
465 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
466 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
470 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key', 'site')
472 def clean_password2(self):
473 # Check that the two password entries match
474 password1 = self.cleaned_data.get("password1")
475 password2 = self.cleaned_data.get("password2")
476 if password1 and password2 and password1 != password2:
477 raise forms.ValidationError("Passwords don't match")
480 def save(self, commit=True):
481 # Save the provided password in hashed format
482 user = super(UserCreationForm, self).save(commit=False)
483 user.password = self.cleaned_data["password1"]
484 #user.set_password(self.cleaned_data["password1"])
489 class UserChangeForm(forms.ModelForm):
490 """A form for updating users. Includes all the fields on
491 the user, but replaces the password field with admin's
492 password hash display field.
494 password = ReadOnlyPasswordHashField()
499 def clean_password(self):
500 # Regardless of what the user provides, return the initial value.
501 # This is done here, rather than on the field, because the
502 # field does not have access to the initial value
503 return self.initial["password"]
505 class UserAdmin(UserAdmin):
509 # The forms to add and change user instances
510 form = UserChangeForm
511 add_form = UserCreationForm
513 # The fields to be used in displaying the User model.
514 # These override the definitions on the base UserAdmin
515 # that reference specific fields on auth.User.
516 list_display = ('email', 'site', 'firstname', 'lastname', 'is_admin', 'last_login')
517 list_filter = ('site',)
518 inlines = [SitePrivilegeInline, SliceMembershipInline]
520 (None, {'fields': ('email', 'password', 'site', 'is_admin', 'timezone')}),
521 ('Personal info', {'fields': ('firstname','lastname','phone', 'public_key')}),
522 #('Important dates', {'fields': ('last_login',)}),
526 'classes': ('wide',),
527 'fields': ('email', 'firstname', 'lastname', 'phone', 'site', 'public_key','password1', 'password2', 'is_admin')}
530 search_fields = ('email',)
531 ordering = ('email',)
532 filter_horizontal = ()
534 def formfield_for_foreignkey(self, db_field, request, **kwargs):
535 if db_field.name == 'site':
536 if not request.user.is_admin:
537 # show sites where caller is an admin or pi
539 for site_privilege in SitePrivilege.objects.filer(user=request.user):
540 if site_privilege.role.role_type in ['admin', 'pi']:
541 sites.append(site_privilege.site.login_base)
542 kwargs['queryset'] = Site.objects.filter(login_base__in(list(sites)))
544 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
546 class ServiceResourceInline(admin.TabularInline):
547 model = ServiceResource
550 class ServiceClassAdmin(admin.ModelAdmin):
551 list_display = ('name', 'commitment', 'membershipFee')
552 inlines = [ServiceResourceInline]
554 class ReservedResourceInline(admin.TabularInline):
555 model = ReservedResource
558 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
559 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
561 if db_field.name == 'resource':
562 # restrict resources to those that the slice's service class allows
563 if request._slice is not None:
564 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
565 if len(field.queryset) > 0:
566 field.initial = field.queryset.all()[0]
568 field.queryset = field.queryset.none()
\r
569 elif db_field.name == 'sliver':
\r
570 # restrict slivers to those that belong to the slice
\r
571 if request._slice is not None:
\r
572 field.queryset = field.queryset.filter(slice = request._slice)
574 field.queryset = field.queryset.none()
\r
578 class ReservationChangeForm(forms.ModelForm):
582 class ReservationAddForm(forms.ModelForm):
583 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
584 refresh = forms.CharField(widget=forms.HiddenInput())
587 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
589 def clean_slice(self):
590 slice = self.cleaned_data.get("slice")
591 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
593 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
599 class ReservationAddRefreshForm(ReservationAddForm):
600 """ This form is displayed when the Reservation Form receives an update
601 from the Slice dropdown onChange handler. It doesn't validate the
602 data and doesn't save the data. This will cause the form to be
606 """ don't validate anything other than slice """
607 dont_validate_fields = ("startTime", "duration")
609 def full_clean(self):
610 result = super(ReservationAddForm, self).full_clean()
612 for fieldname in self.dont_validate_fields:
613 if fieldname in self._errors:
614 del self._errors[fieldname]
618 """ don't save anything """
622 class ReservationAdmin(admin.ModelAdmin):
623 list_display = ('startTime', 'duration')
624 inlines = [ReservedResourceInline]
625 form = ReservationAddForm
627 def add_view(self, request, form_url='', extra_context=None):
628 timezone.activate(request.user.timezone)
629 request._refresh = False
630 request._slice = None
631 if request.method == 'POST':
632 # "refresh" will be set to "1" if the form was submitted due to
633 # a change in the Slice dropdown.
634 if request.POST.get("refresh","1") == "1":
635 request._refresh = True
636 request.POST["refresh"] = "0"
638 # Keep track of the slice that was selected, so the
639 # reservedResource inline can filter items for the slice.
640 request._slice = request.POST.get("slice",None)
641 if (request._slice is not None):
642 request._slice = Slice.objects.get(id=request._slice)
644 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
647 def changelist_view(self, request, extra_context = None):
648 timezone.activate(request.user.timezone)
649 return super(ReservationAdmin, self).changelist_view(request, extra_context)
651 def get_form(self, request, obj=None, **kwargs):
654 # For changes, set request._slice to the slice already set in the
656 request._slice = obj.slice
657 self.form = ReservationChangeForm
659 if getattr(request, "_refresh", False):
660 self.form = ReservationAddRefreshForm
662 self.form = ReservationAddForm
663 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
665 def get_readonly_fields(self, request, obj=None):
666 if (obj is not None):
667 # Prevent slice from being changed after the reservation has been
673 # register a signal that caches the user's credentials when they log in
674 def cache_credentials(sender, user, request, **kwds):
675 auth = {'username': request.POST['username'],
676 'password': request.POST['password']}
677 request.session['auth'] = auth
678 user_logged_in.connect(cache_credentials)
680 # Now register the new UserAdmin...
681 admin.site.register(User, UserAdmin)
682 # ... and, since we're not using Django's builtin permissions,
683 # unregister the Group model from admin.
684 admin.site.unregister(Group)
686 #Do not show django evolution in the admin interface
687 from django_evolution.models import Version, Evolution
688 admin.site.unregister(Version)
689 admin.site.unregister(Evolution)
692 # When debugging it is often easier to see all the classes, but for regular use
693 # only the top-levels should be displayed
696 admin.site.register(Deployment, DeploymentAdmin)
697 admin.site.register(Site, SiteAdmin)
698 admin.site.register(Slice, SliceAdmin)
699 admin.site.register(Project, ProjectAdmin)
702 admin.site.register(Tag, TagAdmin)
703 admin.site.register(Node, NodeAdmin)
704 admin.site.register(SliceMembership, SliceMembershipAdmin)
705 admin.site.register(SitePrivilege, SitePrivilegeAdmin)
706 admin.site.register(Role, RoleAdmin)
707 admin.site.register(Sliver, SliverAdmin)
708 admin.site.register(ServiceClass, ServiceClassAdmin)
709 admin.site.register(Reservation, ReservationAdmin)
710 admin.site.register(Image, ImageAdmin)