1"""Django 1.0 admin interface declarations.""" 2 3from django import forms 4from django.contrib import admin, messages 5from django.forms.util import flatatt 6from django.utils.encoding import smart_str 7from django.utils.safestring import mark_safe 8 9from autotest_lib.cli import rpc, host 10from autotest_lib.frontend import settings 11from autotest_lib.frontend.afe import model_logic, models 12 13 14class 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 25class 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 41class AtomicGroupForm(ModelWithInvalidForm): 42 class Meta: 43 model = models.AtomicGroup 44 45 46class 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 54admin.site.register(models.AtomicGroup, AtomicGroupAdmin) 55 56 57class LabelForm(ModelWithInvalidForm): 58 class Meta: 59 model = models.Label 60 61 62class 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 71admin.site.register(models.Label, LabelAdmin) 72 73 74class UserAdmin(SiteAdmin): 75 list_display = ('login', 'access_level') 76 search_fields = ('login',) 77 78admin.site.register(models.User, UserAdmin) 79 80 81class 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 124class 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 151class HostAttributeInline(admin.TabularInline): 152 model = models.HostAttribute 153 extra = 1 154 155 156class 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 255admin.site.register(models.Host, HostAdmin) 256 257 258class 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 267admin.site.register(models.Test, TestAdmin) 268 269 270class ProfilerAdmin(SiteAdmin): 271 list_display = ('name', 'description') 272 search_fields = ('name',) 273 274admin.site.register(models.Profiler, ProfilerAdmin) 275 276 277class 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 295admin.site.register(models.AclGroup, AclGroupAdmin) 296 297 298class 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 310class DroneSetAdmin(SiteAdmin): 311 filter_horizontal = ('drones',) 312 form = DroneSetForm 313 314admin.site.register(models.DroneSet, DroneSetAdmin) 315 316admin.site.register(models.Drone) 317 318 319if 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