blob: 8bf4c60ca3a44aa67c7fee0c262511285e7e7e26 [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
Jiaxi Luo020cd192014-06-30 12:45:45 -0700142class HostAttributeInline(admin.TabularInline):
143 model = models.HostAttribute
144 extra = 1
145
146
showard65974e12009-08-20 23:34:38 +0000147class HostAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000148 # TODO(showard) - showing platform requires a SQL query for
149 # each row (since labels are many-to-many) - should we remove
150 # it?
151 list_display = ('hostname', 'platform', 'locked', 'status')
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700152 list_filter = ('locked', 'protection', 'status')
153 search_fields = ('hostname',)
showarda5288b42009-07-28 20:06:08 +0000154
showard3370e752009-08-31 18:32:30 +0000155 form = HostForm
156
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700157 def __init__(self, model, admin_site):
158 self.successful_hosts = []
159 super(HostAdmin, self).__init__(model, admin_site)
160
161 def change_view(self, request, obj_id, form_url='', extra_context=None):
162 # Hide labels_autodetection when editing a host.
163 self.fields = ('hostname', 'locked', 'leased', 'protection', 'labels')
Jiaxi Luo020cd192014-06-30 12:45:45 -0700164 # Only allow editing host attributes when a host has been created.
165 self.inlines = [
166 HostAttributeInline,
167 ]
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700168 return super(HostAdmin, self).change_view(request,
169 obj_id,
170 form_url,
171 extra_context)
172
showarda5288b42009-07-28 20:06:08 +0000173 def queryset(self, request):
174 return models.Host.valid_objects
175
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700176 def response_add(self, request, obj, post_url_continue=None):
177 # Disable the 'save and continue editing option' when adding a host.
178 if "_continue" in request.POST:
179 request.POST = request.POST.copy()
180 del request.POST['_continue']
181 return super(HostAdmin, self).response_add(request,
182 obj,
183 post_url_continue)
184
185 def save_model(self, request, obj, form, change):
186 if not form.cleaned_data.get('labels_autodetection'):
187 return super(HostAdmin, self).save_model(request, obj,
188 form, change)
189
190 # Get submitted info from form.
191 web_server = rpc.get_autotest_server()
192 hostname = form.cleaned_data['hostname']
193 hosts = [str(hostname)]
194 platform = None
195 locked = form.cleaned_data['locked']
196 labels = [label.name for label in form.cleaned_data['labels']]
197 protection = form.cleaned_data['protection']
198 acls = []
199
200 # Pipe to cli to perform autodetection and create host.
201 host_create_obj = site_host.site_host_create.construct_without_parse(
202 web_server, hosts, platform,
203 locked, labels, acls,
204 protection)
205 try:
206 self.successful_hosts = host_create_obj.execute()
207 except SystemExit:
208 # Invalid server name.
209 messages.error(request, 'Invalid server name %s.' % web_server)
210
211 # Successful_hosts is an empty list if there's time out,
212 # server error, or JSON error.
213 if not self.successful_hosts:
214 messages.error(request,
215 'Label autodetection failed. '
216 'Host created with selected labels.')
217 super(HostAdmin, self).save_model(request, obj, form, change)
218
219 def save_related(self, request, form, formsets, change):
220 """Save many-to-many relations between host and labels."""
Jiaxi Luo020cd192014-06-30 12:45:45 -0700221 # Skip save_related if autodetection succeeded, since cli has already
Jiaxi Luoc342f9f2014-05-19 16:22:03 -0700222 # handled many-to-many relations.
223 if not self.successful_hosts:
224 super(HostAdmin, self).save_related(request,
225 form,
226 formsets,
227 change)
228
showarda5288b42009-07-28 20:06:08 +0000229admin.site.register(models.Host, HostAdmin)
230
231
showard65974e12009-08-20 23:34:38 +0000232class TestAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000233 fields = ('name', 'author', 'test_category', 'test_class',
234 'test_time', 'sync_count', 'test_type', 'path',
235 'dependencies', 'experimental', 'run_verify',
236 'description')
jamesren35a70222010-02-16 19:30:46 +0000237 list_display = ('name', 'test_type', 'admin_description', 'sync_count')
showarda5288b42009-07-28 20:06:08 +0000238 search_fields = ('name',)
239 filter_horizontal = ('dependency_labels',)
240
241admin.site.register(models.Test, TestAdmin)
242
243
showard65974e12009-08-20 23:34:38 +0000244class ProfilerAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000245 list_display = ('name', 'description')
246 search_fields = ('name',)
247
248admin.site.register(models.Profiler, ProfilerAdmin)
249
250
showard65974e12009-08-20 23:34:38 +0000251class AclGroupAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000252 list_display = ('name', 'description')
253 search_fields = ('name',)
254 filter_horizontal = ('users', 'hosts')
255
showard8cbaf1e2009-09-08 16:27:04 +0000256 def queryset(self, request):
257 return models.AclGroup.objects.exclude(name='Everyone')
258
259 def save_model(self, request, obj, form, change):
260 super(AclGroupAdmin, self).save_model(request, obj, form, change)
261 _orig_save_m2m = form.save_m2m
262
263 def save_m2m():
264 _orig_save_m2m()
265 obj.perform_after_save(change)
266
267 form.save_m2m = save_m2m
268
showarda5288b42009-07-28 20:06:08 +0000269admin.site.register(models.AclGroup, AclGroupAdmin)
270
271
jamesren543d9fb2010-06-07 20:45:31 +0000272class DroneSetForm(forms.ModelForm):
273 def __init__(self, *args, **kwargs):
274 super(DroneSetForm, self).__init__(*args, **kwargs)
275 drone_ids_used = set()
276 for drone_set in models.DroneSet.objects.exclude(id=self.instance.id):
277 drone_ids_used.update(drone_set.drones.values_list('id', flat=True))
278 available_drones = models.Drone.objects.exclude(id__in=drone_ids_used)
279
280 self.fields['drones'].widget.choices = [(drone.id, drone.hostname)
281 for drone in available_drones]
282
283
jamesren76fcf192010-04-21 20:39:50 +0000284class DroneSetAdmin(SiteAdmin):
285 filter_horizontal = ('drones',)
jamesren543d9fb2010-06-07 20:45:31 +0000286 form = DroneSetForm
jamesren76fcf192010-04-21 20:39:50 +0000287
288admin.site.register(models.DroneSet, DroneSetAdmin)
289
290admin.site.register(models.Drone)
291
292
showarda5288b42009-07-28 20:06:08 +0000293if settings.FULL_ADMIN:
showard65974e12009-08-20 23:34:38 +0000294 class JobAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000295 list_display = ('id', 'owner', 'name', 'control_type')
296 filter_horizontal = ('dependency_labels',)
297
298 admin.site.register(models.Job, JobAdmin)
299
showard3370e752009-08-31 18:32:30 +0000300
showard65974e12009-08-20 23:34:38 +0000301 class IneligibleHostQueueAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000302 list_display = ('id', 'job', 'host')
303
304 admin.site.register(models.IneligibleHostQueue, IneligibleHostQueueAdmin)
305
showard3370e752009-08-31 18:32:30 +0000306
showard65974e12009-08-20 23:34:38 +0000307 class HostQueueEntryAdmin(SiteAdmin):
showarda5288b42009-07-28 20:06:08 +0000308 list_display = ('id', 'job', 'host', 'status',
309 'meta_host')
310
311 admin.site.register(models.HostQueueEntry, HostQueueEntryAdmin)
312
313 admin.site.register(models.AbortedHostQueueEntry)