Home | History | Annotate | Download | only in afe
      1 """Django 1.0 admin interface declarations."""
      2 
      3 from django import forms
      4 from django.contrib import admin, messages
      5 from django.db import models as dbmodels
      6 from django.forms.util import flatatt
      7 from django.utils.encoding import smart_str
      8 from django.utils.safestring import mark_safe
      9 
     10 from autotest_lib.cli import rpc, site_host
     11 from autotest_lib.frontend import settings
     12 from autotest_lib.frontend.afe import model_logic, models
     13 
     14 
     15 class SiteAdmin(admin.ModelAdmin):
     16     def formfield_for_dbfield(self, db_field, **kwargs):
     17         field = super(SiteAdmin, self).formfield_for_dbfield(db_field, **kwargs)
     18         if (db_field.rel and
     19                 issubclass(db_field.rel.to, model_logic.ModelWithInvalid)):
     20             model = db_field.rel.to
     21             field.choices = model.valid_objects.all().values_list(
     22                     'id', model.name_field)
     23         return field
     24 
     25 
     26 class ModelWithInvalidForm(forms.ModelForm):
     27     def validate_unique(self):
     28         # Don't validate name uniqueness if the duplicate model is invalid
     29         model = self.Meta.model
     30         filter_data = {
     31                 model.name_field : self.cleaned_data[model.name_field],
     32                 'invalid' : True
     33                 }
     34         needs_remove = bool(self.Meta.model.objects.filter(**filter_data))
     35         if needs_remove:
     36             name_field = self.fields.pop(model.name_field)
     37         super(ModelWithInvalidForm, self).validate_unique()
     38         if needs_remove:
     39             self.fields[model.name_field] = name_field
     40 
     41 
     42 class AtomicGroupForm(ModelWithInvalidForm):
     43     class Meta:
     44         model = models.AtomicGroup
     45 
     46 
     47 class AtomicGroupAdmin(SiteAdmin):
     48     list_display = ('name', 'description', 'max_number_of_machines')
     49 
     50     form = AtomicGroupForm
     51 
     52     def queryset(self, request):
     53         return models.AtomicGroup.valid_objects
     54 
     55 admin.site.register(models.AtomicGroup, AtomicGroupAdmin)
     56 
     57 
     58 class LabelForm(ModelWithInvalidForm):
     59     class Meta:
     60         model = models.Label
     61 
     62 
     63 class LabelAdmin(SiteAdmin):
     64     list_display = ('name', 'atomic_group', 'kernel_config')
     65     # Avoid a bug with the admin interface showing a select box pointed at an
     66     # AtomicGroup when this field is intentionally NULL such that editing a
     67     # label via the admin UI unintentionally sets an atomicgroup.
     68     raw_id_fields = ('atomic_group',)
     69 
     70     form = LabelForm
     71 
     72     def queryset(self, request):
     73         return models.Label.valid_objects
     74 
     75 admin.site.register(models.Label, LabelAdmin)
     76 
     77 
     78 class UserAdmin(SiteAdmin):
     79     list_display = ('login', 'access_level')
     80     search_fields = ('login',)
     81 
     82 admin.site.register(models.User, UserAdmin)
     83 
     84 
     85 class LabelsCommaSpacedWidget(forms.Widget):
     86     """A widget that renders the labels in a comman separated text field."""
     87 
     88     def render(self, name, value, attrs=None):
     89         """Convert label ids to names and render them in HTML.
     90 
     91         @param name: Name attribute of the HTML tag.
     92         @param value: A list of label ids to be rendered.
     93         @param attrs: A dict of extra attributes rendered in the HTML tag.
     94         @return: A Unicode string in HTML format.
     95         """
     96         final_attrs = self.build_attrs(attrs, type='text', name=name)
     97 
     98         if value:
     99             label_names =(models.Label.objects.filter(id__in=value)
    100                           .values_list('name', flat=True))
    101             value = ', '.join(label_names)
    102         else:
    103             value = ''
    104         final_attrs['value'] = smart_str(value)
    105         return mark_safe(u'<input%s />' % flatatt(final_attrs))
    106 
    107     def value_from_datadict(self, data, files, name):
    108         """Convert input string to a list of label ids.
    109 
    110         @param data: A dict of input data from HTML form. The keys are name
    111             attrs of HTML tags.
    112         @param files: A dict of input file names from HTML form. The keys are
    113             name attrs of HTML tags.
    114         @param name: The name attr of the HTML tag of labels.
    115         @return: A list of label ids in string. Return None if no label is
    116             specified.
    117         """
    118         label_names = data.get(name)
    119         if label_names:
    120             label_names = label_names.split(',')
    121             label_names = filter(None,
    122                                  [name.strip(', ') for name in label_names])
    123             label_ids = (models.Label.objects.filter(name__in=label_names)
    124                          .values_list('id', flat=True))
    125             return [str(label_id) for label_id in label_ids]
    126 
    127 
    128 class HostForm(ModelWithInvalidForm):
    129     # A checkbox triggers label autodetection.
    130     labels_autodetection = forms.BooleanField(initial=True, required=False)
    131 
    132     def __init__(self, *args, **kwargs):
    133         super(HostForm, self).__init__(*args, **kwargs)
    134         self.fields['labels'].widget = LabelsCommaSpacedWidget()
    135         self.fields['labels'].help_text = ('Please enter a comma seperated '
    136                                            'list of labels.')
    137 
    138     def clean(self):
    139         """ ModelForm validation
    140 
    141         Ensure that a lock_reason is provided when locking a device.
    142         """
    143         cleaned_data = super(HostForm, self).clean()
    144         locked = cleaned_data.get('locked')
    145         lock_reason = cleaned_data.get('lock_reason')
    146         if locked and not lock_reason:
    147             raise forms.ValidationError(
    148                     'Please provide a lock reason when locking a device.')
    149         return cleaned_data
    150 
    151     class Meta:
    152         model = models.Host
    153 
    154 
    155 class HostAttributeInline(admin.TabularInline):
    156     model = models.HostAttribute
    157     extra = 1
    158 
    159 
    160 class HostAdmin(SiteAdmin):
    161     # TODO(showard) - showing platform requires a SQL query for
    162     # each row (since labels are many-to-many) - should we remove
    163     # it?
    164     list_display = ('hostname', 'platform', 'locked', 'status')
    165     list_filter = ('locked', 'protection', 'status')
    166     search_fields = ('hostname',)
    167 
    168     form = HostForm
    169 
    170     def __init__(self, model, admin_site):
    171         self.successful_hosts = []
    172         super(HostAdmin, self).__init__(model, admin_site)
    173 
    174     def add_view(self, request, form_url='', extra_context=None):
    175         """ Field layout for admin page.
    176 
    177         fields specifies the visibility and order of HostAdmin attributes
    178         displayed on the device addition page.
    179 
    180         @param request:  django request
    181         @param form_url: url
    182         @param extra_context: A dict used to alter the page view
    183         """
    184         self.fields = ('hostname', 'locked', 'lock_reason', 'leased',
    185                        'protection', 'labels', 'shard', 'labels_autodetection')
    186         return super(HostAdmin, self).add_view(request, form_url, extra_context)
    187 
    188     def change_view(self, request, obj_id, form_url='', extra_context=None):
    189         # Hide labels_autodetection when editing a host.
    190         self.fields = ('hostname', 'locked', 'lock_reason',
    191                        'leased', 'protection', 'labels')
    192         # Only allow editing host attributes when a host has been created.
    193         self.inlines = [
    194             HostAttributeInline,
    195         ]
    196         return super(HostAdmin, self).change_view(request,
    197                                                   obj_id,
    198                                                   form_url,
    199                                                   extra_context)
    200 
    201     def queryset(self, request):
    202         return models.Host.valid_objects
    203 
    204     def response_add(self, request, obj, post_url_continue=None):
    205         # Disable the 'save and continue editing option' when adding a host.
    206         if "_continue" in request.POST:
    207             request.POST = request.POST.copy()
    208             del request.POST['_continue']
    209         return super(HostAdmin, self).response_add(request,
    210                                                    obj,
    211                                                    post_url_continue)
    212 
    213     def save_model(self, request, obj, form, change):
    214         if not form.cleaned_data.get('labels_autodetection'):
    215             return super(HostAdmin, self).save_model(request, obj,
    216                                                      form, change)
    217 
    218         # Get submitted info from form.
    219         web_server = rpc.get_autotest_server()
    220         hostname = form.cleaned_data['hostname']
    221         hosts = [str(hostname)]
    222         platform = None
    223         locked = form.cleaned_data['locked']
    224         lock_reason = form.cleaned_data['lock_reason']
    225         labels = [label.name for label in form.cleaned_data['labels']]
    226         protection = form.cleaned_data['protection']
    227         acls = []
    228 
    229         # Pipe to cli to perform autodetection and create host.
    230         host_create_obj = site_host.site_host_create.construct_without_parse(
    231                 web_server, hosts, platform,
    232                 locked, lock_reason, labels, acls,
    233                 protection)
    234         try:
    235             self.successful_hosts = host_create_obj.execute()
    236         except SystemExit:
    237             # Invalid server name.
    238             messages.error(request, 'Invalid server name %s.' % web_server)
    239 
    240         # Successful_hosts is an empty list if there's time out,
    241         # server error, or JSON error.
    242         if not self.successful_hosts:
    243             messages.error(request,
    244                            'Label autodetection failed. '
    245                            'Host created with selected labels.')
    246             super(HostAdmin, self).save_model(request, obj, form, change)
    247 
    248     def save_related(self, request, form, formsets, change):
    249         """Save many-to-many relations between host and labels."""
    250         # Skip save_related if autodetection succeeded, since cli has already
    251         # handled many-to-many relations.
    252         if not self.successful_hosts:
    253             super(HostAdmin, self).save_related(request,
    254                                                 form,
    255                                                 formsets,
    256                                                 change)
    257 
    258 admin.site.register(models.Host, HostAdmin)
    259 
    260 
    261 class TestAdmin(SiteAdmin):
    262     fields = ('name', 'author', 'test_category', 'test_class',
    263               'test_time', 'sync_count', 'test_type', 'path',
    264               'dependencies', 'experimental', 'run_verify',
    265               'description')
    266     list_display = ('name', 'test_type', 'admin_description', 'sync_count')
    267     search_fields = ('name',)
    268     filter_horizontal = ('dependency_labels',)
    269 
    270 admin.site.register(models.Test, TestAdmin)
    271 
    272 
    273 class ProfilerAdmin(SiteAdmin):
    274     list_display = ('name', 'description')
    275     search_fields = ('name',)
    276 
    277 admin.site.register(models.Profiler, ProfilerAdmin)
    278 
    279 
    280 class AclGroupAdmin(SiteAdmin):
    281     list_display = ('name', 'description')
    282     search_fields = ('name',)
    283     filter_horizontal = ('users', 'hosts')
    284 
    285     def queryset(self, request):
    286         return models.AclGroup.objects.exclude(name='Everyone')
    287 
    288     def save_model(self, request, obj, form, change):
    289         super(AclGroupAdmin, self).save_model(request, obj, form, change)
    290         _orig_save_m2m = form.save_m2m
    291 
    292         def save_m2m():
    293             _orig_save_m2m()
    294             obj.perform_after_save(change)
    295 
    296         form.save_m2m = save_m2m
    297 
    298 admin.site.register(models.AclGroup, AclGroupAdmin)
    299 
    300 
    301 class DroneSetForm(forms.ModelForm):
    302     def __init__(self, *args, **kwargs):
    303         super(DroneSetForm, self).__init__(*args, **kwargs)
    304         drone_ids_used = set()
    305         for drone_set in models.DroneSet.objects.exclude(id=self.instance.id):
    306             drone_ids_used.update(drone_set.drones.values_list('id', flat=True))
    307         available_drones = models.Drone.objects.exclude(id__in=drone_ids_used)
    308 
    309         self.fields['drones'].widget.choices = [(drone.id, drone.hostname)
    310                                                 for drone in available_drones]
    311 
    312 
    313 class DroneSetAdmin(SiteAdmin):
    314     filter_horizontal = ('drones',)
    315     form = DroneSetForm
    316 
    317 admin.site.register(models.DroneSet, DroneSetAdmin)
    318 
    319 admin.site.register(models.Drone)
    320 
    321 
    322 if settings.FULL_ADMIN:
    323     class JobAdmin(SiteAdmin):
    324         list_display = ('id', 'owner', 'name', 'control_type')
    325         filter_horizontal = ('dependency_labels',)
    326 
    327     admin.site.register(models.Job, JobAdmin)
    328 
    329 
    330     class IneligibleHostQueueAdmin(SiteAdmin):
    331         list_display = ('id', 'job', 'host')
    332 
    333     admin.site.register(models.IneligibleHostQueue, IneligibleHostQueueAdmin)
    334 
    335 
    336     class HostQueueEntryAdmin(SiteAdmin):
    337         list_display = ('id', 'job', 'host', 'status',
    338                         'meta_host')
    339 
    340     admin.site.register(models.HostQueueEntry, HostQueueEntryAdmin)
    341 
    342     admin.site.register(models.AbortedHostQueueEntry)
    343