[afe] afe admin add host label auto detection
Add a checkbox when add/edit host from afe admin interface,
which will trigger label auto detection by piping to cli host create.
Add a helper function in host_create to fill in data without parsing
from command line.
TEST=Compared adding hosts through afe and cli, ran host_unittest.
BUG=chromium:371898
DEPLOY=apache
Change-Id: Id5307e68d55dc5120e6a8036888987d2819b4ad6
Reviewed-on: https://chromium-review.googlesource.com/199607
Tested-by: Jiaxi Luo <jiaxiluo@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
Commit-Queue: Jiaxi Luo <jiaxiluo@chromium.org>
diff --git a/frontend/afe/admin.py b/frontend/afe/admin.py
index 4c73837..fdd3958 100644
--- a/frontend/afe/admin.py
+++ b/frontend/afe/admin.py
@@ -1,9 +1,13 @@
"""Django 1.0 admin interface declarations."""
from django import forms
-from django.contrib import admin
+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
@@ -78,7 +82,59 @@
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.')
+
class Meta:
model = models.Host
@@ -88,15 +144,79 @@
# each row (since labels are many-to-many) - should we remove
# it?
list_display = ('hostname', 'platform', 'locked', 'status')
- list_filter = ('labels', 'locked', 'protection')
- search_fields = ('hostname', 'status')
- filter_horizontal = ('labels',)
+ 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 change_view(self, request, obj_id, form_url='', extra_context=None):
+ # Hide labels_autodetection when editing a host.
+ self.fields = ('hostname', 'locked', 'leased', 'protection', 'labels')
+ 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']
+ 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, 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, subce 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)