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']
50 class SiteInline(PlStackTabularInline):
54 class UserInline(PlStackTabularInline):
56 fields = ['email', 'firstname', 'lastname']
59 class SliceInline(PlStackTabularInline):
63 class RoleInline(PlStackTabularInline):
67 class NodeInline(PlStackTabularInline):
71 class SitePrivilegeInline(PlStackTabularInline):
75 def formfield_for_foreignkey(self, db_field, request, **kwargs):
76 if db_field.name == 'site':
77 if not request.user.is_admin:
78 # only show sites where user is an admin or pi
79 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
80 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
81 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
82 sites = Site.objects.filter(login_base__in=login_bases)
83 kwargs['queryset'] = sites
85 if db_field.name == 'user':
86 if not request.user.is_admin:
87 # only show users from sites where caller has admin or pi role
88 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
89 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
90 sites = [site_privilege.site for site_privilege in site_privileges]
91 site_privileges = SitePrivilege.objects.filter(site__in=sites)
92 emails = [site_privilege.user.email for site_privilege in site_privileges]
93 users = User.objects.filter(email__in=emails)
94 kwargs['queryset'] = users
95 return super(SitePrivilegeInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
97 class SliceMembershipInline(PlStackTabularInline):
98 model = SliceMembership
100 fields = ('user', 'role')
102 def formfield_for_foreignkey(self, db_field, request, **kwargs):
103 if db_field.name == 'slice':
104 if not request.user.is_admin:
105 # only show slices at sites where caller has admin or pi role
106 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
107 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
108 sites = [site_privilege.site for site_privilege in site_privileges]
109 slices = Slice.objects.filter(site__in=sites)
110 kwargs['queryset'] = slices
111 if db_field.name == 'user':
112 if not request.user.is_admin:
113 # only show users from sites where caller has admin or pi role
114 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
115 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
116 sites = [site_privilege.site for site_privilege in site_privileges]
117 site_privileges = SitePrivilege.objects.filter(site__in=sites)
118 emails = [site_privilege.user.email for site_privilege in site_privileges]
119 users = User.objects.filter(email__in=emails)
120 kwargs['queryset'] = list(users)
122 return super(SliceMembershipInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
124 class SliceTagInline(PlStackTabularInline):
128 class PlainTextWidget(forms.HiddenInput):
129 input_type = 'hidden'
131 def render(self, name, value, attrs=None):
134 return mark_safe(str(value) + super(PlainTextWidget, self).render(name, value, attrs))
136 class PlanetStackBaseAdmin(admin.ModelAdmin):
138 exclude = ['enacted']
140 class RoleAdmin(PlanetStackBaseAdmin):
142 ('Role', {'fields': ['role_type']})
144 list_display = ('role_type',)
147 class DeploymentAdminForm(forms.ModelForm):
148 sites = forms.ModelMultipleChoiceField(
149 queryset=Site.objects.all(),
151 widget=FilteredSelectMultiple(
152 verbose_name=('Sites'), is_stacked=False
158 def __init__(self, *args, **kwargs):
159 super(DeploymentAdminForm, self).__init__(*args, **kwargs)
161 if self.instance and self.instance.pk:
162 self.fields['sites'].initial = self.instance.sites.all()
164 def save(self, commit=True):
165 deploymentNetwork = super(DeploymentAdminForm, self).save(commit=False)
167 deploymentNetwork.save()
169 if deploymentNetwork.pk:
170 deploymentNetwork.sites = self.cleaned_data['sites']
173 return deploymentNetwork
175 class DeploymentAdmin(PlanetStackBaseAdmin):
176 form = DeploymentAdminForm
177 inlines = [NodeInline,SliverInline]
179 def get_formsets(self, request, obj=None):
180 for inline in self.get_inline_instances(request, obj):
181 # hide MyInline in the add view
184 # give inline object access to driver and caller
185 auth = request.session.get('auth', {})
186 if request.user.site:
187 auth['tenant'] = request.user.site.login_base
188 inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
189 yield inline.get_formset(request, obj)
191 class SiteAdmin(PlanetStackBaseAdmin):
193 (None, {'fields': ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'location']}),
194 ('Deployment Networks', {'fields': ['deployments']})
196 list_display = ('name', 'login_base','site_url', 'enabled')
197 filter_horizontal = ('deployments',)
198 inlines = [TagInline, NodeInline, UserInline, SitePrivilegeInline]
199 search_fields = ['name']
201 def queryset(self, request):
202 # admins can see all keys. Users can only see sites they belong to.
203 qs = super(SiteAdmin, self).queryset(request)
204 if not request.user.is_admin:
205 valid_sites = [request.user.site.login_base]
206 roles = request.user.get_roles()
207 for tenant_list in roles.values():
208 valid_sites.extend(tenant_list)
209 qs = qs.filter(login_base__in=valid_sites)
212 def get_formsets(self, request, obj=None):
213 for inline in self.get_inline_instances(request, obj):
214 # hide MyInline in the add view
217 if isinstance(inline, SliceInline):
218 inline.model.caller = request.user
219 yield inline.get_formset(request, obj)
221 def get_formsets(self, request, obj=None):
222 for inline in self.get_inline_instances(request, obj):
223 # hide MyInline in the add view
226 if isinstance(inline, SliverInline):
227 inline.model.caller = request.user
228 yield inline.get_formset(request, obj)
230 class SitePrivilegeAdmin(PlanetStackBaseAdmin):
232 (None, {'fields': ['user', 'site', 'role']})
234 list_display = ('user', 'site', 'role')
236 def formfield_for_foreignkey(self, db_field, request, **kwargs):
237 if db_field.name == 'site':
238 if not request.user.is_admin:
239 # only show sites where user is an admin or pi
241 for site_privilege in SitePrivilege.objects.filer(user=request.user):
242 if site_privilege.role.role_type in ['admin', 'pi']:
243 sites.add(site_privilege.site)
244 kwargs['queryset'] = Site.objects.filter(site__in=list(sites))
246 if db_field.name == 'user':
247 if not request.user.is_admin:
248 # only show users from sites where caller has admin or pi role
249 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
250 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
251 sites = [site_privilege.site for site_privilege in site_privileges]
252 site_privileges = SitePrivilege.objects.filter(site__in=sites)
253 emails = [site_privilege.user.email for site_privilege in site_privileges]
254 users = User.objects.filter(email__in=emails)
255 kwargs['queryset'] = users
257 return super(SitePrivilegeAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
259 def queryset(self, request):
260 # admins can see all privileges. Users can only see privileges at sites
261 # where they have the admin role or pi role.
262 qs = super(SitePrivilegeAdmin, self).queryset(request)
263 if not request.user.is_admin:
264 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
265 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
266 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
267 sites = Site.objects.filter(login_base__in=login_bases)
268 qs = qs.filter(site__in=sites)
271 class SliceAdmin(PlanetStackBaseAdmin):
272 fields = ['name', 'site', 'serviceClass', 'description', 'slice_url']
273 list_display = ('name', 'site','serviceClass', 'slice_url')
274 inlines = [SliverInline, SliceMembershipInline, TagInline, SliceTagInline]
276 def formfield_for_foreignkey(self, db_field, request, **kwargs):
277 if db_field.name == 'site':
278 if not request.user.is_admin:
279 # only show sites where user is a pi or admin
280 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
281 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
282 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
283 sites = Site.objects.filter(login_base__in=login_bases)
284 kwargs['queryset'] = sites
286 return super(SliceAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
288 def queryset(self, request):
289 # admins can see all keys. Users can only see slices they belong to.
290 qs = super(SliceAdmin, self).queryset(request)
291 if not request.user.is_admin:
293 roles = request.user.get_roles()
294 for tenant_list in roles.values():
295 valid_slices.extend(tenant_list)
296 qs = qs.filter(name__in=valid_slices)
299 def get_formsets(self, request, obj=None):
300 for inline in self.get_inline_instances(request, obj):
301 # hide MyInline in the add view
304 if isinstance(inline, SliverInline):
305 inline.model.caller = request.user
306 yield inline.get_formset(request, obj)
308 def get_queryset(self, request):
309 qs = super(SliceAdmin, self).get_queryset(request)
310 if request.user.is_superuser:
312 # users can only see slices at their site
313 return qs.filter(site=request.user.site)
315 def save_model(self, request, obj, form, change):
316 # update openstack connection to use this site/tenant
317 obj.caller = request.user
320 class SliceMembershipAdmin(PlanetStackBaseAdmin):
322 (None, {'fields': ['user', 'slice', 'role']})
324 list_display = ('user', 'slice', 'role')
326 def formfield_for_foreignkey(self, db_field, request, **kwargs):
327 if db_field.name == 'slice':
328 if not request.user.is_admin:
329 # only show slices at sites where caller has admin or pi role
330 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
331 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
332 sites = [site_privilege.site for site_privilege in site_privileges]
333 slices = Slice.objects.filter(site__in=sites)
334 kwargs['queryset'] = slices
336 if db_field.name == 'user':
337 if not request.user.is_admin:
338 # only show users from sites where caller has admin or pi role
339 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
340 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
341 sites = [site_privilege.site for site_privilege in site_privileges]
342 site_privileges = SitePrivilege.objects.filter(site__in=sites)
343 emails = [site_privilege.user.email for site_privilege in site_privileges]
344 users = User.objects.filter(email__in=emails)
345 kwargs['queryset'] = users
347 return super(SliceMembershipAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
349 def queryset(self, request):
350 # admins can see all memberships. Users can only see memberships of
351 # slices where they have the admin role.
352 qs = super(SliceMembershipAdmin, self).queryset(request)
353 if not request.user.is_admin:
354 roles = Role.objects.filter(role_type__in=['admin', 'pi'])
355 site_privileges = SitePrivilege.objects.filter(user=request.user).filter(role__in=roles)
356 login_bases = [site_privilege.site.login_base for site_privilege in site_privileges]
357 sites = Site.objects.filter(login_base__in=login_bases)
358 slices = Slice.objects.filter(site__in=sites)
359 qs = qs.filter(slice__in=slices)
362 def save_model(self, request, obj, form, change):
363 # update openstack connection to use this site/tenant
364 auth = request.session.get('auth', {})
365 auth['tenant'] = obj.slice.name
366 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
369 def delete_model(self, request, obj):
370 # update openstack connection to use this site/tenant
371 auth = request.session.get('auth', {})
372 auth['tenant'] = obj.slice.name
373 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
377 class ImageAdmin(admin.ModelAdmin):
378 fields = ['image_id', 'name', 'disk_format', 'container_format']
380 class NodeAdmin(admin.ModelAdmin):
381 list_display = ('name', 'site', 'deployment')
382 list_filter = ('deployment',)
383 inlines = [TagInline]
386 class SliverForm(forms.ModelForm):
389 ip = forms.CharField(widget=PlainTextWidget)
390 instance_name = forms.CharField(widget=PlainTextWidget)
392 'ip': PlainTextWidget(),
393 'instance_name': PlainTextWidget(),
396 class ProjectAdmin(admin.ModelAdmin):
397 exclude = ['enacted']
399 class TagAdmin(admin.ModelAdmin):
400 exclude = ['enacted']
402 class SliverAdmin(PlanetStackBaseAdmin):
405 ('Sliver', {'fields': ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']})
407 list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']
408 inlines = [TagInline]
410 def formfield_for_foreignkey(self, db_field, request, **kwargs):
411 if db_field.name == 'slice':
412 if not request.user.is_admin:
413 slices = set([sm.slice.name for sm in SliceMembership.objects.filter(user=request.user)])
414 kwargs['queryset'] = Slice.objects.filter(name__in=list(slices))
416 return super(SliverAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
418 def queryset(self, request):
419 # admins can see all slivers. Users can only see slivers of
420 # the slices they belong to.
421 qs = super(SliverAdmin, self).queryset(request)
422 if not request.user.is_admin:
424 roles = request.user.get_roles()
425 for tenant_list in roles.values():
426 tenants.extend(tenant_list)
427 valid_slices = Slice.objects.filter(name__in=tenants)
428 qs = qs.filter(slice__in=valid_slices)
431 def get_formsets(self, request, obj=None):
432 # make some fields read only if we are updating an existing record
434 #self.readonly_fields = ('ip', 'instance_name')
435 self.readonly_fields = ()
437 self.readonly_fields = ()
438 #self.readonly_fields = ('ip', 'instance_name', 'slice', 'image', 'key')
440 for inline in self.get_inline_instances(request, obj):
441 # hide MyInline in the add view
444 # give inline object access to driver and caller
445 auth = request.session.get('auth', {})
446 auth['tenant'] = obj.name # meed to connect using slice's tenant
447 inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
448 yield inline.get_formset(request, obj)
450 def save_model(self, request, obj, form, change):
451 # update openstack connection to use this site/tenant
452 auth = request.session.get('auth', {})
453 auth['tenant'] = obj.slice.name
454 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
455 obj.creator = request.user
458 def delete_model(self, request, obj):
459 # update openstack connection to use this site/tenant
460 auth = request.session.get('auth', {})
461 auth['tenant'] = obj.slice.name
462 obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
465 class UserCreationForm(forms.ModelForm):
466 """A form for creating new users. Includes all the required
467 fields, plus a repeated password."""
468 password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
469 password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
473 fields = ('email', 'firstname', 'lastname', 'phone', 'public_key', 'site')
475 def clean_password2(self):
476 # Check that the two password entries match
477 password1 = self.cleaned_data.get("password1")
478 password2 = self.cleaned_data.get("password2")
479 if password1 and password2 and password1 != password2:
480 raise forms.ValidationError("Passwords don't match")
483 def save(self, commit=True):
484 # Save the provided password in hashed format
485 user = super(UserCreationForm, self).save(commit=False)
486 user.password = self.cleaned_data["password1"]
487 #user.set_password(self.cleaned_data["password1"])
493 class UserChangeForm(forms.ModelForm):
494 """A form for updating users. Includes all the fields on
495 the user, but replaces the password field with admin's
496 password hash display field.
498 password = ReadOnlyPasswordHashField()
503 def clean_password(self):
504 # Regardless of what the user provides, return the initial value.
505 # This is done here, rather than on the field, because the
506 # field does not have access to the initial value
507 return self.initial["password"]
510 class UserAdmin(UserAdmin):
514 # The forms to add and change user instances
515 form = UserChangeForm
516 add_form = UserCreationForm
518 # The fields to be used in displaying the User model.
519 # These override the definitions on the base UserAdmin
520 # that reference specific fields on auth.User.
521 list_display = ('email', 'site', 'firstname', 'lastname', 'is_admin', 'last_login')
522 list_filter = ('site',)
523 inlines = [SitePrivilegeInline, SliceMembershipInline]
525 (None, {'fields': ('email', 'password', 'site', 'is_admin', 'timezone')}),
526 ('Personal info', {'fields': ('firstname','lastname','phone', 'public_key')}),
527 #('Important dates', {'fields': ('last_login',)}),
531 'classes': ('wide',),
532 'fields': ('email', 'firstname', 'lastname', 'phone', 'site', 'public_key','password1', 'password2', 'is_admin')}
535 search_fields = ('email',)
536 ordering = ('email',)
537 filter_horizontal = ()
539 def formfield_for_foreignkey(self, db_field, request, **kwargs):
540 if db_field.name == 'site':
541 if not request.user.is_admin:
542 # show sites where caller is an admin or pi
544 for site_privilege in SitePrivilege.objects.filer(user=request.user):
545 if site_privilege.role.role_type in ['admin', 'pi']:
546 sites.append(site_privilege.site.login_base)
547 kwargs['queryset'] = Site.objects.filter(login_base__in(list(sites)))
549 return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
551 class ServiceResourceInline(admin.TabularInline):
552 model = ServiceResource
555 class ServiceClassAdmin(admin.ModelAdmin):
556 list_display = ('name', 'commitment', 'membershipFee')
557 inlines = [ServiceResourceInline]
559 class ReservedResourceInline(admin.TabularInline):
560 exclude = ['enacted']
561 model = ReservedResource
564 def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
565 field = super(ReservedResourceInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
567 if db_field.name == 'resource':
568 # restrict resources to those that the slice's service class allows
569 if request._slice is not None:
570 field.queryset = field.queryset.filter(serviceClass = request._slice.serviceClass, calendarReservable=True)
571 if len(field.queryset) > 0:
572 field.initial = field.queryset.all()[0]
574 field.queryset = field.queryset.none()
\r
575 elif db_field.name == 'sliver':
\r
576 # restrict slivers to those that belong to the slice
\r
577 if request._slice is not None:
\r
578 field.queryset = field.queryset.filter(slice = request._slice)
580 field.queryset = field.queryset.none()
\r
584 class ReservationChangeForm(forms.ModelForm):
588 class ReservationAddForm(forms.ModelForm):
589 slice = forms.ModelChoiceField(queryset=Slice.objects.all(), widget=forms.Select(attrs={"onChange":"document.getElementById('id_refresh').value=1; submit()"}))
590 refresh = forms.CharField(widget=forms.HiddenInput())
593 css = {'all': ('planetstack.css',)} # .field-refresh { display: none; }
595 def clean_slice(self):
596 slice = self.cleaned_data.get("slice")
597 x = ServiceResource.objects.filter(serviceClass = slice.serviceClass, calendarReservable=True)
599 raise forms.ValidationError("The slice you selected does not have a service class that allows reservations")
605 class ReservationAddRefreshForm(ReservationAddForm):
606 """ This form is displayed when the Reservation Form receives an update
607 from the Slice dropdown onChange handler. It doesn't validate the
608 data and doesn't save the data. This will cause the form to be
612 """ don't validate anything other than slice """
613 dont_validate_fields = ("startTime", "duration")
615 def full_clean(self):
616 result = super(ReservationAddForm, self).full_clean()
618 for fieldname in self.dont_validate_fields:
619 if fieldname in self._errors:
620 del self._errors[fieldname]
624 """ don't save anything """
628 class ReservationAdmin(admin.ModelAdmin):
629 exclude = ['enacted']
630 list_display = ('startTime', 'duration')
631 inlines = [ReservedResourceInline]
632 form = ReservationAddForm
634 def add_view(self, request, form_url='', extra_context=None):
635 timezone.activate(request.user.timezone)
636 request._refresh = False
637 request._slice = None
638 if request.method == 'POST':
639 # "refresh" will be set to "1" if the form was submitted due to
640 # a change in the Slice dropdown.
641 if request.POST.get("refresh","1") == "1":
642 request._refresh = True
643 request.POST["refresh"] = "0"
645 # Keep track of the slice that was selected, so the
646 # reservedResource inline can filter items for the slice.
647 request._slice = request.POST.get("slice",None)
648 if (request._slice is not None):
649 request._slice = Slice.objects.get(id=request._slice)
651 result = super(ReservationAdmin, self).add_view(request, form_url, extra_context)
654 def changelist_view(self, request, extra_context = None):
655 timezone.activate(request.user.timezone)
656 return super(ReservationAdmin, self).changelist_view(request, extra_context)
658 def get_form(self, request, obj=None, **kwargs):
661 # For changes, set request._slice to the slice already set in the
663 request._slice = obj.slice
664 self.form = ReservationChangeForm
666 if getattr(request, "_refresh", False):
667 self.form = ReservationAddRefreshForm
669 self.form = ReservationAddForm
670 return super(ReservationAdmin, self).get_form(request, obj, **kwargs)
672 def get_readonly_fields(self, request, obj=None):
673 if (obj is not None):
674 # Prevent slice from being changed after the reservation has been
680 # register a signal that caches the user's credentials when they log in
681 def cache_credentials(sender, user, request, **kwds):
682 auth = {'username': request.POST['username'],
683 'password': request.POST['password']}
684 request.session['auth'] = auth
685 user_logged_in.connect(cache_credentials)
687 # Now register the new UserAdmin...
688 admin.site.register(User, UserAdmin)
689 # ... and, since we're not using Django's builtin permissions,
690 # unregister the Group model from admin.
691 admin.site.unregister(Group)
693 #Do not show django evolution in the admin interface
694 from django_evolution.models import Version, Evolution
695 admin.site.unregister(Version)
696 admin.site.unregister(Evolution)
699 # When debugging it is often easier to see all the classes, but for regular use
700 # only the top-levels should be displayed
703 admin.site.register(Deployment, DeploymentAdmin)
704 admin.site.register(Site, SiteAdmin)
705 admin.site.register(Slice, SliceAdmin)
706 admin.site.register(Project, ProjectAdmin)
707 admin.site.register(ServiceClass, ServiceClassAdmin)
708 admin.site.register(Reservation, ReservationAdmin)
711 admin.site.register(Tag, TagAdmin)
712 admin.site.register(Node, NodeAdmin)
713 admin.site.register(SliceMembership, SliceMembershipAdmin)
714 admin.site.register(SitePrivilege, SitePrivilegeAdmin)
715 admin.site.register(Role, RoleAdmin)
716 admin.site.register(Sliver, SliverAdmin)
717 admin.site.register(Image, ImageAdmin)