"""Django 1.0 admin interface declarations.""" from django import forms from django.contrib import admin, messages from django.db import models as dbmodels 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, site_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', 'atomic_group', 'kernel_config') # Avoid a bug with the admin interface showing a select box pointed at an # AtomicGroup when this field is intentionally NULL such that editing a # label via the admin UI unintentionally sets an atomicgroup. raw_id_fields = ('atomic_group',) 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 = site_host.site_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)