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