blob: fdd39588f0b0d2ad91c0c06db039ce9ddcd0cab7 [file] [log] [blame]
showarda5288b42009-07-28 20:06:08 +00001"""Django 1.0 admin interface declarations."""
2
showard3370e752009-08-31 18:32:30 +00003from django import forms
Jiaxi Luoc342f9f2014-05-19 16:22:03 -07004from django.contrib import admin, messages
showard65974e12009-08-20 23:34:38 +00005from django.db import models as dbmodels
Jiaxi Luoc342f9f2014-05-19 16:22:03 -07006from django.forms.util import flatatt
7from django.utils.encoding import smart_str
8from django.utils.safestring import mark_safe
showarda5288b42009-07-28 20:06:08 +00009
Jiaxi Luoc342f9f2014-05-19 16:22:03 -070010from autotest_lib.cli import rpc, site_host
showarda5288b42009-07-28 20:06:08 +000011from autotest_lib.frontend import settings
12from autotest_lib.frontend.afe import model_logic, models
13
14
showard65974e12009-08-20 23:34:38 +000015class 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
showard3370e752009-08-31 18:32:30 +000026class 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
42class AtomicGroupForm(ModelWithInvalidForm):
43 class Meta:
44 model = models.AtomicGroup
45
46
showard65974e12009-08-20 23:34:38 +000047class AtomicGroupAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +000048 list_display = ('name', 'description', 'max_number_of_machines')
49
showard3370e752009-08-31 18:32:30 +000050 form = AtomicGroupForm
51
showarda5288b42009-07-28 20:06:08 +000052 def queryset(self, request):
53 return models.AtomicGroup.valid_objects
54
55admin.site.register(models.AtomicGroup, AtomicGroupAdmin)
56
57
showard3370e752009-08-31 18:32:30 +000058class LabelForm(ModelWithInvalidForm):
59 class Meta:
60 model = models.Label
61
62
showard65974e12009-08-20 23:34:38 +000063class LabelAdmin(SiteAdmin):
Dale Curtis74a314b2011-06-23 14:55:46 -070064 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',)
showarda5288b42009-07-28 20:06:08 +000069
showard3370e752009-08-31 18:32:30 +000070 form = LabelForm
71
showarda5288b42009-07-28 20:06:08 +000072 def queryset(self, request):
73 return models.Label.valid_objects
74
75admin.site.register(models.Label, LabelAdmin)
76
77
showard65974e12009-08-20 23:34:38 +000078class UserAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +000079 list_display = ('login', 'access_level')
80 search_fields = ('login',)
81
82admin.site.register(models.User, UserAdmin)
83
84
Jiaxi Luoc342f9f2014-05-19 16:22:03 -070085class 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
showard3370e752009-08-31 18:32:30 +0000128class HostForm(ModelWithInvalidForm):
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700129 # 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
showard3370e752009-08-31 18:32:30 +0000138 class Meta:
139 model = models.Host
140
141
showard65974e12009-08-20 23:34:38 +0000142class HostAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000143 # 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 Luoc342f9f2014-05-19 16:22:03 -0700147 list_filter = ('locked', 'protection', 'status')
148 search_fields = ('hostname',)
showarda5288b42009-07-28 20:06:08 +0000149
showard3370e752009-08-31 18:32:30 +0000150 form = HostForm
151
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700152 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
showarda5288b42009-07-28 20:06:08 +0000164 def queryset(self, request):
165 return models.Host.valid_objects
166
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700167 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
showarda5288b42009-07-28 20:06:08 +0000220admin.site.register(models.Host, HostAdmin)
221
222
showard65974e12009-08-20 23:34:38 +0000223class TestAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000224 fields = ('name', 'author', 'test_category', 'test_class',
225 'test_time', 'sync_count', 'test_type', 'path',
226 'dependencies', 'experimental', 'run_verify',
227 'description')
jamesren35a70222010-02-16 19:30:46 +0000228 list_display = ('name', 'test_type', 'admin_description', 'sync_count')
showarda5288b42009-07-28 20:06:08 +0000229 search_fields = ('name',)
230 filter_horizontal = ('dependency_labels',)
231
232admin.site.register(models.Test, TestAdmin)
233
234
showard65974e12009-08-20 23:34:38 +0000235class ProfilerAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000236 list_display = ('name', 'description')
237 search_fields = ('name',)
238
239admin.site.register(models.Profiler, ProfilerAdmin)
240
241
showard65974e12009-08-20 23:34:38 +0000242class AclGroupAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000243 list_display = ('name', 'description')
244 search_fields = ('name',)
245 filter_horizontal = ('users', 'hosts')
246
showard8cbaf1e2009-09-08 16:27:04 +0000247 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
showarda5288b42009-07-28 20:06:08 +0000260admin.site.register(models.AclGroup, AclGroupAdmin)
261
262
jamesren543d9fb2010-06-07 20:45:31 +0000263class 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
jamesren76fcf192010-04-21 20:39:50 +0000275class DroneSetAdmin(SiteAdmin):
276 filter_horizontal = ('drones',)
jamesren543d9fb2010-06-07 20:45:31 +0000277 form = DroneSetForm
jamesren76fcf192010-04-21 20:39:50 +0000278
279admin.site.register(models.DroneSet, DroneSetAdmin)
280
281admin.site.register(models.Drone)
282
283
showarda5288b42009-07-28 20:06:08 +0000284if settings.FULL_ADMIN:
showard65974e12009-08-20 23:34:38 +0000285 class JobAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000286 list_display = ('id', 'owner', 'name', 'control_type')
287 filter_horizontal = ('dependency_labels',)
288
289 admin.site.register(models.Job, JobAdmin)
290
showard3370e752009-08-31 18:32:30 +0000291
showard65974e12009-08-20 23:34:38 +0000292 class IneligibleHostQueueAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000293 list_display = ('id', 'job', 'host')
294
295 admin.site.register(models.IneligibleHostQueue, IneligibleHostQueueAdmin)
296
showard3370e752009-08-31 18:32:30 +0000297
showard65974e12009-08-20 23:34:38 +0000298 class HostQueueEntryAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000299 list_display = ('id', 'job', 'host', 'status',
300 'meta_host')
301
302 admin.site.register(models.HostQueueEntry, HostQueueEntryAdmin)
303
304 admin.site.register(models.AbortedHostQueueEntry)