| """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) |