339 lines
12 KiB
Python
339 lines
12 KiB
Python
"""Django 1.0 admin interface declarations."""
|
|
|
|
from django import forms
|
|
from django.contrib import admin, messages
|
|
from django.forms.util import flatatt
|
|
from django.utils.encoding import smart_str
|
|
from django.utils.safestring import mark_safe
|
|
|
|
from autotest_lib.cli import rpc, host
|
|
from autotest_lib.frontend import settings
|
|
from autotest_lib.frontend.afe import model_logic, models
|
|
|
|
|
|
class SiteAdmin(admin.ModelAdmin):
|
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
|
field = super(SiteAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
|
if (db_field.rel and
|
|
issubclass(db_field.rel.to, model_logic.ModelWithInvalid)):
|
|
model = db_field.rel.to
|
|
field.choices = model.valid_objects.all().values_list(
|
|
'id', model.name_field)
|
|
return field
|
|
|
|
|
|
class ModelWithInvalidForm(forms.ModelForm):
|
|
def validate_unique(self):
|
|
# Don't validate name uniqueness if the duplicate model is invalid
|
|
model = self.Meta.model
|
|
filter_data = {
|
|
model.name_field : self.cleaned_data[model.name_field],
|
|
'invalid' : True
|
|
}
|
|
needs_remove = bool(self.Meta.model.objects.filter(**filter_data))
|
|
if needs_remove:
|
|
name_field = self.fields.pop(model.name_field)
|
|
super(ModelWithInvalidForm, self).validate_unique()
|
|
if needs_remove:
|
|
self.fields[model.name_field] = name_field
|
|
|
|
|
|
class AtomicGroupForm(ModelWithInvalidForm):
|
|
class Meta:
|
|
model = models.AtomicGroup
|
|
|
|
|
|
class AtomicGroupAdmin(SiteAdmin):
|
|
list_display = ('name', 'description', 'max_number_of_machines')
|
|
|
|
form = AtomicGroupForm
|
|
|
|
def queryset(self, request):
|
|
return models.AtomicGroup.valid_objects
|
|
|
|
admin.site.register(models.AtomicGroup, AtomicGroupAdmin)
|
|
|
|
|
|
class LabelForm(ModelWithInvalidForm):
|
|
class Meta:
|
|
model = models.Label
|
|
|
|
|
|
class LabelAdmin(SiteAdmin):
|
|
list_display = ('name', 'kernel_config')
|
|
raw_id_fields = ()
|
|
|
|
form = LabelForm
|
|
|
|
def queryset(self, request):
|
|
return models.Label.valid_objects
|
|
|
|
admin.site.register(models.Label, LabelAdmin)
|
|
|
|
|
|
class UserAdmin(SiteAdmin):
|
|
list_display = ('login', 'access_level')
|
|
search_fields = ('login',)
|
|
|
|
admin.site.register(models.User, UserAdmin)
|
|
|
|
|
|
class LabelsCommaSpacedWidget(forms.Widget):
|
|
"""A widget that renders the labels in a comman separated text field."""
|
|
|
|
def render(self, name, value, attrs=None):
|
|
"""Convert label ids to names and render them in HTML.
|
|
|
|
@param name: Name attribute of the HTML tag.
|
|
@param value: A list of label ids to be rendered.
|
|
@param attrs: A dict of extra attributes rendered in the HTML tag.
|
|
@return: A Unicode string in HTML format.
|
|
"""
|
|
final_attrs = self.build_attrs(attrs, type='text', name=name)
|
|
|
|
if value:
|
|
label_names =(models.Label.objects.filter(id__in=value)
|
|
.values_list('name', flat=True))
|
|
value = ', '.join(label_names)
|
|
else:
|
|
value = ''
|
|
final_attrs['value'] = smart_str(value)
|
|
return mark_safe(u'<input%s />' % flatatt(final_attrs))
|
|
|
|
def value_from_datadict(self, data, files, name):
|
|
"""Convert input string to a list of label ids.
|
|
|
|
@param data: A dict of input data from HTML form. The keys are name
|
|
attrs of HTML tags.
|
|
@param files: A dict of input file names from HTML form. The keys are
|
|
name attrs of HTML tags.
|
|
@param name: The name attr of the HTML tag of labels.
|
|
@return: A list of label ids in string. Return None if no label is
|
|
specified.
|
|
"""
|
|
label_names = data.get(name)
|
|
if label_names:
|
|
label_names = label_names.split(',')
|
|
label_names = filter(None,
|
|
[name.strip(', ') for name in label_names])
|
|
label_ids = (models.Label.objects.filter(name__in=label_names)
|
|
.values_list('id', flat=True))
|
|
return [str(label_id) for label_id in label_ids]
|
|
|
|
|
|
class HostForm(ModelWithInvalidForm):
|
|
# A checkbox triggers label autodetection.
|
|
labels_autodetection = forms.BooleanField(initial=True, required=False)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(HostForm, self).__init__(*args, **kwargs)
|
|
self.fields['labels'].widget = LabelsCommaSpacedWidget()
|
|
self.fields['labels'].help_text = ('Please enter a comma seperated '
|
|
'list of labels.')
|
|
|
|
def clean(self):
|
|
""" ModelForm validation
|
|
|
|
Ensure that a lock_reason is provided when locking a device.
|
|
"""
|
|
cleaned_data = super(HostForm, self).clean()
|
|
locked = cleaned_data.get('locked')
|
|
lock_reason = cleaned_data.get('lock_reason')
|
|
if locked and not lock_reason:
|
|
raise forms.ValidationError(
|
|
'Please provide a lock reason when locking a device.')
|
|
return cleaned_data
|
|
|
|
class Meta:
|
|
model = models.Host
|
|
|
|
|
|
class HostAttributeInline(admin.TabularInline):
|
|
model = models.HostAttribute
|
|
extra = 1
|
|
|
|
|
|
class HostAdmin(SiteAdmin):
|
|
# TODO(showard) - showing platform requires a SQL query for
|
|
# each row (since labels are many-to-many) - should we remove
|
|
# it?
|
|
list_display = ('hostname', 'platform', 'locked', 'status')
|
|
list_filter = ('locked', 'protection', 'status')
|
|
search_fields = ('hostname',)
|
|
|
|
form = HostForm
|
|
|
|
def __init__(self, model, admin_site):
|
|
self.successful_hosts = []
|
|
super(HostAdmin, self).__init__(model, admin_site)
|
|
|
|
def add_view(self, request, form_url='', extra_context=None):
|
|
""" Field layout for admin page.
|
|
|
|
fields specifies the visibility and order of HostAdmin attributes
|
|
displayed on the device addition page.
|
|
|
|
@param request: django request
|
|
@param form_url: url
|
|
@param extra_context: A dict used to alter the page view
|
|
"""
|
|
self.fields = ('hostname', 'locked', 'lock_reason', 'leased',
|
|
'protection', 'labels', 'shard', 'labels_autodetection')
|
|
return super(HostAdmin, self).add_view(request, form_url, extra_context)
|
|
|
|
def change_view(self, request, obj_id, form_url='', extra_context=None):
|
|
# Hide labels_autodetection when editing a host.
|
|
self.fields = ('hostname', 'locked', 'lock_reason',
|
|
'leased', 'protection', 'labels')
|
|
# Only allow editing host attributes when a host has been created.
|
|
self.inlines = [
|
|
HostAttributeInline,
|
|
]
|
|
return super(HostAdmin, self).change_view(request,
|
|
obj_id,
|
|
form_url,
|
|
extra_context)
|
|
|
|
def queryset(self, request):
|
|
return models.Host.valid_objects
|
|
|
|
def response_add(self, request, obj, post_url_continue=None):
|
|
# Disable the 'save and continue editing option' when adding a host.
|
|
if "_continue" in request.POST:
|
|
request.POST = request.POST.copy()
|
|
del request.POST['_continue']
|
|
return super(HostAdmin, self).response_add(request,
|
|
obj,
|
|
post_url_continue)
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
if not form.cleaned_data.get('labels_autodetection'):
|
|
return super(HostAdmin, self).save_model(request, obj,
|
|
form, change)
|
|
|
|
# Get submitted info from form.
|
|
web_server = rpc.get_autotest_server()
|
|
hostname = form.cleaned_data['hostname']
|
|
hosts = [str(hostname)]
|
|
platform = None
|
|
locked = form.cleaned_data['locked']
|
|
lock_reason = form.cleaned_data['lock_reason']
|
|
labels = [label.name for label in form.cleaned_data['labels']]
|
|
protection = form.cleaned_data['protection']
|
|
acls = []
|
|
|
|
# Pipe to cli to perform autodetection and create host.
|
|
host_create_obj = host.host_create.construct_without_parse(
|
|
web_server, hosts, platform,
|
|
locked, lock_reason, labels, acls,
|
|
protection)
|
|
try:
|
|
self.successful_hosts = host_create_obj.execute()
|
|
except SystemExit:
|
|
# Invalid server name.
|
|
messages.error(request, 'Invalid server name %s.' % web_server)
|
|
|
|
# Successful_hosts is an empty list if there's time out,
|
|
# server error, or JSON error.
|
|
if not self.successful_hosts:
|
|
messages.error(request,
|
|
'Label autodetection failed. '
|
|
'Host created with selected labels.')
|
|
super(HostAdmin, self).save_model(request, obj, form, change)
|
|
|
|
|
|
def save_related(self, request, form, formsets, change):
|
|
"""Save many-to-many relations between host and labels."""
|
|
# Skip save_related if autodetection succeeded, since cli has already
|
|
# handled many-to-many relations.
|
|
if not self.successful_hosts:
|
|
super(HostAdmin, self).save_related(request,
|
|
form,
|
|
formsets,
|
|
change)
|
|
|
|
admin.site.register(models.Host, HostAdmin)
|
|
|
|
|
|
class TestAdmin(SiteAdmin):
|
|
fields = ('name', 'author', 'test_category', 'test_class',
|
|
'test_time', 'sync_count', 'test_type', 'path',
|
|
'dependencies', 'experimental', 'run_verify',
|
|
'description')
|
|
list_display = ('name', 'test_type', 'admin_description', 'sync_count')
|
|
search_fields = ('name',)
|
|
filter_horizontal = ('dependency_labels',)
|
|
|
|
admin.site.register(models.Test, TestAdmin)
|
|
|
|
|
|
class ProfilerAdmin(SiteAdmin):
|
|
list_display = ('name', 'description')
|
|
search_fields = ('name',)
|
|
|
|
admin.site.register(models.Profiler, ProfilerAdmin)
|
|
|
|
|
|
class AclGroupAdmin(SiteAdmin):
|
|
list_display = ('name', 'description')
|
|
search_fields = ('name',)
|
|
filter_horizontal = ('users', 'hosts')
|
|
|
|
def queryset(self, request):
|
|
return models.AclGroup.objects.exclude(name='Everyone')
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
super(AclGroupAdmin, self).save_model(request, obj, form, change)
|
|
_orig_save_m2m = form.save_m2m
|
|
|
|
def save_m2m():
|
|
_orig_save_m2m()
|
|
obj.perform_after_save(change)
|
|
|
|
form.save_m2m = save_m2m
|
|
|
|
admin.site.register(models.AclGroup, AclGroupAdmin)
|
|
|
|
|
|
class DroneSetForm(forms.ModelForm):
|
|
def __init__(self, *args, **kwargs):
|
|
super(DroneSetForm, self).__init__(*args, **kwargs)
|
|
drone_ids_used = set()
|
|
for drone_set in models.DroneSet.objects.exclude(id=self.instance.id):
|
|
drone_ids_used.update(drone_set.drones.values_list('id', flat=True))
|
|
available_drones = models.Drone.objects.exclude(id__in=drone_ids_used)
|
|
|
|
self.fields['drones'].widget.choices = [(drone.id, drone.hostname)
|
|
for drone in available_drones]
|
|
|
|
|
|
class DroneSetAdmin(SiteAdmin):
|
|
filter_horizontal = ('drones',)
|
|
form = DroneSetForm
|
|
|
|
admin.site.register(models.DroneSet, DroneSetAdmin)
|
|
|
|
admin.site.register(models.Drone)
|
|
|
|
|
|
if settings.FULL_ADMIN:
|
|
class JobAdmin(SiteAdmin):
|
|
list_display = ('id', 'owner', 'name', 'control_type')
|
|
filter_horizontal = ('dependency_labels',)
|
|
|
|
admin.site.register(models.Job, JobAdmin)
|
|
|
|
|
|
class IneligibleHostQueueAdmin(SiteAdmin):
|
|
list_display = ('id', 'job', 'host')
|
|
|
|
admin.site.register(models.IneligibleHostQueue, IneligibleHostQueueAdmin)
|
|
|
|
|
|
class HostQueueEntryAdmin(SiteAdmin):
|
|
list_display = ('id', 'job', 'host', 'status',
|
|
'meta_host')
|
|
|
|
admin.site.register(models.HostQueueEntry, HostQueueEntryAdmin)
|
|
|
|
admin.site.register(models.AbortedHostQueueEntry)
|