showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 1 | """Django 1.0 admin interface declarations.""" |
| 2 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 3 | from django import forms |
Jiaxi Luo | c342f9f | 2014-05-19 16:22:03 -0700 | [diff] [blame] | 4 | from django.contrib import admin, messages |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 5 | from django.db import models as dbmodels |
Jiaxi Luo | c342f9f | 2014-05-19 16:22:03 -0700 | [diff] [blame] | 6 | from django.forms.util import flatatt |
| 7 | from django.utils.encoding import smart_str |
| 8 | from django.utils.safestring import mark_safe |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 9 | |
Jiaxi Luo | c342f9f | 2014-05-19 16:22:03 -0700 | [diff] [blame] | 10 | from autotest_lib.cli import rpc, site_host |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 11 | from autotest_lib.frontend import settings |
| 12 | from autotest_lib.frontend.afe import model_logic, models |
| 13 | |
| 14 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 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 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 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 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 47 | class AtomicGroupAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 48 | list_display = ('name', 'description', 'max_number_of_machines') |
| 49 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 50 | form = AtomicGroupForm |
| 51 | |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 52 | def queryset(self, request): |
| 53 | return models.AtomicGroup.valid_objects |
| 54 | |
| 55 | admin.site.register(models.AtomicGroup, AtomicGroupAdmin) |
| 56 | |
| 57 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 58 | class LabelForm(ModelWithInvalidForm): |
| 59 | class Meta: |
| 60 | model = models.Label |
| 61 | |
| 62 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 63 | class LabelAdmin(SiteAdmin): |
Dale Curtis | 74a314b | 2011-06-23 14:55:46 -0700 | [diff] [blame] | 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',) |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 69 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 70 | form = LabelForm |
| 71 | |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 72 | def queryset(self, request): |
| 73 | return models.Label.valid_objects |
| 74 | |
| 75 | admin.site.register(models.Label, LabelAdmin) |
| 76 | |
| 77 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 78 | class UserAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 79 | list_display = ('login', 'access_level') |
| 80 | search_fields = ('login',) |
| 81 | |
| 82 | admin.site.register(models.User, UserAdmin) |
| 83 | |
| 84 | |
Jiaxi Luo | c342f9f | 2014-05-19 16:22:03 -0700 | [diff] [blame] | 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 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 128 | class HostForm(ModelWithInvalidForm): |
Jiaxi Luo | c342f9f | 2014-05-19 16:22:03 -0700 | [diff] [blame] | 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 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 138 | class Meta: |
| 139 | model = models.Host |
| 140 | |
| 141 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 142 | class HostAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 143 | # TODO(showard) - showing platform requires a SQL query for |
| 144 | # each row (since labels are many-to-many) - should we remove |
| 145 | # it? |
| 146 | list_display = ('hostname', 'platform', 'locked', 'status') |
Jiaxi Luo | c342f9f | 2014-05-19 16:22:03 -0700 | [diff] [blame] | 147 | list_filter = ('locked', 'protection', 'status') |
| 148 | search_fields = ('hostname',) |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 149 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 150 | form = HostForm |
| 151 | |
Jiaxi Luo | c342f9f | 2014-05-19 16:22:03 -0700 | [diff] [blame] | 152 | def __init__(self, model, admin_site): |
| 153 | self.successful_hosts = [] |
| 154 | super(HostAdmin, self).__init__(model, admin_site) |
| 155 | |
| 156 | def change_view(self, request, obj_id, form_url='', extra_context=None): |
| 157 | # Hide labels_autodetection when editing a host. |
| 158 | self.fields = ('hostname', 'locked', 'leased', 'protection', 'labels') |
| 159 | return super(HostAdmin, self).change_view(request, |
| 160 | obj_id, |
| 161 | form_url, |
| 162 | extra_context) |
| 163 | |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 164 | def queryset(self, request): |
| 165 | return models.Host.valid_objects |
| 166 | |
Jiaxi Luo | c342f9f | 2014-05-19 16:22:03 -0700 | [diff] [blame] | 167 | def response_add(self, request, obj, post_url_continue=None): |
| 168 | # Disable the 'save and continue editing option' when adding a host. |
| 169 | if "_continue" in request.POST: |
| 170 | request.POST = request.POST.copy() |
| 171 | del request.POST['_continue'] |
| 172 | return super(HostAdmin, self).response_add(request, |
| 173 | obj, |
| 174 | post_url_continue) |
| 175 | |
| 176 | def save_model(self, request, obj, form, change): |
| 177 | if not form.cleaned_data.get('labels_autodetection'): |
| 178 | return super(HostAdmin, self).save_model(request, obj, |
| 179 | form, change) |
| 180 | |
| 181 | # Get submitted info from form. |
| 182 | web_server = rpc.get_autotest_server() |
| 183 | hostname = form.cleaned_data['hostname'] |
| 184 | hosts = [str(hostname)] |
| 185 | platform = None |
| 186 | locked = form.cleaned_data['locked'] |
| 187 | labels = [label.name for label in form.cleaned_data['labels']] |
| 188 | protection = form.cleaned_data['protection'] |
| 189 | acls = [] |
| 190 | |
| 191 | # Pipe to cli to perform autodetection and create host. |
| 192 | host_create_obj = site_host.site_host_create.construct_without_parse( |
| 193 | web_server, hosts, platform, |
| 194 | locked, labels, acls, |
| 195 | protection) |
| 196 | try: |
| 197 | self.successful_hosts = host_create_obj.execute() |
| 198 | except SystemExit: |
| 199 | # Invalid server name. |
| 200 | messages.error(request, 'Invalid server name %s.' % web_server) |
| 201 | |
| 202 | # Successful_hosts is an empty list if there's time out, |
| 203 | # server error, or JSON error. |
| 204 | if not self.successful_hosts: |
| 205 | messages.error(request, |
| 206 | 'Label autodetection failed. ' |
| 207 | 'Host created with selected labels.') |
| 208 | super(HostAdmin, self).save_model(request, obj, form, change) |
| 209 | |
| 210 | def save_related(self, request, form, formsets, change): |
| 211 | """Save many-to-many relations between host and labels.""" |
| 212 | # Skip save_related if autodetection succeeded, subce cli has already |
| 213 | # handled many-to-many relations. |
| 214 | if not self.successful_hosts: |
| 215 | super(HostAdmin, self).save_related(request, |
| 216 | form, |
| 217 | formsets, |
| 218 | change) |
| 219 | |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 220 | admin.site.register(models.Host, HostAdmin) |
| 221 | |
| 222 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 223 | class TestAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 224 | fields = ('name', 'author', 'test_category', 'test_class', |
| 225 | 'test_time', 'sync_count', 'test_type', 'path', |
| 226 | 'dependencies', 'experimental', 'run_verify', |
| 227 | 'description') |
jamesren | 35a7022 | 2010-02-16 19:30:46 +0000 | [diff] [blame] | 228 | list_display = ('name', 'test_type', 'admin_description', 'sync_count') |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 229 | search_fields = ('name',) |
| 230 | filter_horizontal = ('dependency_labels',) |
| 231 | |
| 232 | admin.site.register(models.Test, TestAdmin) |
| 233 | |
| 234 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 235 | class ProfilerAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 236 | list_display = ('name', 'description') |
| 237 | search_fields = ('name',) |
| 238 | |
| 239 | admin.site.register(models.Profiler, ProfilerAdmin) |
| 240 | |
| 241 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 242 | class AclGroupAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 243 | list_display = ('name', 'description') |
| 244 | search_fields = ('name',) |
| 245 | filter_horizontal = ('users', 'hosts') |
| 246 | |
showard | 8cbaf1e | 2009-09-08 16:27:04 +0000 | [diff] [blame] | 247 | def queryset(self, request): |
| 248 | return models.AclGroup.objects.exclude(name='Everyone') |
| 249 | |
| 250 | def save_model(self, request, obj, form, change): |
| 251 | super(AclGroupAdmin, self).save_model(request, obj, form, change) |
| 252 | _orig_save_m2m = form.save_m2m |
| 253 | |
| 254 | def save_m2m(): |
| 255 | _orig_save_m2m() |
| 256 | obj.perform_after_save(change) |
| 257 | |
| 258 | form.save_m2m = save_m2m |
| 259 | |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 260 | admin.site.register(models.AclGroup, AclGroupAdmin) |
| 261 | |
| 262 | |
jamesren | 543d9fb | 2010-06-07 20:45:31 +0000 | [diff] [blame] | 263 | class DroneSetForm(forms.ModelForm): |
| 264 | def __init__(self, *args, **kwargs): |
| 265 | super(DroneSetForm, self).__init__(*args, **kwargs) |
| 266 | drone_ids_used = set() |
| 267 | for drone_set in models.DroneSet.objects.exclude(id=self.instance.id): |
| 268 | drone_ids_used.update(drone_set.drones.values_list('id', flat=True)) |
| 269 | available_drones = models.Drone.objects.exclude(id__in=drone_ids_used) |
| 270 | |
| 271 | self.fields['drones'].widget.choices = [(drone.id, drone.hostname) |
| 272 | for drone in available_drones] |
| 273 | |
| 274 | |
jamesren | 76fcf19 | 2010-04-21 20:39:50 +0000 | [diff] [blame] | 275 | class DroneSetAdmin(SiteAdmin): |
| 276 | filter_horizontal = ('drones',) |
jamesren | 543d9fb | 2010-06-07 20:45:31 +0000 | [diff] [blame] | 277 | form = DroneSetForm |
jamesren | 76fcf19 | 2010-04-21 20:39:50 +0000 | [diff] [blame] | 278 | |
| 279 | admin.site.register(models.DroneSet, DroneSetAdmin) |
| 280 | |
| 281 | admin.site.register(models.Drone) |
| 282 | |
| 283 | |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 284 | if settings.FULL_ADMIN: |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 285 | class JobAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 286 | list_display = ('id', 'owner', 'name', 'control_type') |
| 287 | filter_horizontal = ('dependency_labels',) |
| 288 | |
| 289 | admin.site.register(models.Job, JobAdmin) |
| 290 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 291 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 292 | class IneligibleHostQueueAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 293 | list_display = ('id', 'job', 'host') |
| 294 | |
| 295 | admin.site.register(models.IneligibleHostQueue, IneligibleHostQueueAdmin) |
| 296 | |
showard | 3370e75 | 2009-08-31 18:32:30 +0000 | [diff] [blame] | 297 | |
showard | 65974e1 | 2009-08-20 23:34:38 +0000 | [diff] [blame] | 298 | class HostQueueEntryAdmin(SiteAdmin): |
showard | a5288b4 | 2009-07-28 20:06:08 +0000 | [diff] [blame] | 299 | list_display = ('id', 'job', 'host', 'status', |
| 300 | 'meta_host') |
| 301 | |
| 302 | admin.site.register(models.HostQueueEntry, HostQueueEntryAdmin) |
| 303 | |
| 304 | admin.site.register(models.AbortedHostQueueEntry) |