=== modified file 'dashboard_app/extension.py'
@@ -45,8 +45,8 @@
Menu("Tests", reverse("dashboard_app.views.test_list")),
Menu("Data Views", reverse("dashboard_app.views.data_view_list")),
Menu("Reports", reverse("dashboard_app.views.report_list")),
- Menu("Image Reports", reverse("dashboard_app.views.image_report_list")),
- Menu("[BETA] Filters", reverse("dashboard_app.views.filters_list")),
+ Menu("Image Reports", reverse("dashboard_app.views.images.image_report_list")),
+ Menu("[BETA] Filters", reverse("dashboard_app.views.filters.views.filters_list")),
]
return menu
=== modified file 'dashboard_app/models.py'
@@ -1444,7 +1444,7 @@
@models.permalink
def get_absolute_url(self):
- return ("dashboard_app.views.image_report_detail", (), dict(name=self.name))
+ return ("dashboard_app.views.images.image_report_detail", (), dict(name=self.name))
class ImageSet(models.Model):
@@ -1878,7 +1878,7 @@
@models.permalink
def get_absolute_url(self):
return (
- "dashboard_app.views.filter_detail",
+ "dashboard_app.views.filters.views.filter_detail",
[self.owner.username, self.name])
=== modified file 'dashboard_app/templates/dashboard_app/filter_detail.html'
@@ -17,11 +17,11 @@
{% if subscription %}
<p>
- <a href="{% url dashboard_app.views.filter_subscribe username=filter.owner.username name=filter.name %}">Manage</a> your subscription to this filter.
+ <a href="{% url dashboard_app.views.filters.views.filter_subscribe username=filter.owner.username name=filter.name %}">Manage</a> your subscription to this filter.
</p>
{% else %}
<p>
- <a href="{% url dashboard_app.views.filter_subscribe username=filter.owner.username name=filter.name %}">Subscribe</a> to this filter.
+ <a href="{% url dashboard_app.views.filters.views.filter_subscribe username=filter.owner.username name=filter.name %}">Subscribe</a> to this filter.
</p>
{% endif %}
=== modified file 'dashboard_app/templates/dashboard_app/filters_list.html'
@@ -16,7 +16,7 @@
{% render_table user_filters_table %}
<p>
- <a href="{% url dashboard_app.views.filter_add %}">Add new filter…</a>
+ <a href="{% url dashboard_app.views.filters.views.filter_add %}">Add new filter…</a>
</p>
{% else %}
=== modified file 'dashboard_app/templates/dashboard_app/image-report.html'
@@ -83,7 +83,7 @@
</table>
<form method="POST"
- action="{% url dashboard_app.views.link_bug_to_testrun %}"
+ action="{% url dashboard_app.views.images.link_bug_to_testrun %}"
id="add-bug-dialog" style="display: none">
{% csrf_token %}
<input type="hidden" name="back" value="{{ request.path }}"/>
@@ -95,7 +95,7 @@
</form>
<form method="POST"
- action="{% url dashboard_app.views.unlink_bug_and_testrun %}"
+ action="{% url dashboard_app.views.images.unlink_bug_and_testrun %}"
id="go-to-bug-dialog" style="display: none">
{% csrf_token %}
<input type="hidden" name="back" value="{{ request.path }}"/>
=== modified file 'dashboard_app/urls.py'
@@ -35,17 +35,17 @@
url(r'^reports/(?P<name>[a-zA-Z0-9-_]+)/$', 'report_detail'),
url(r'^tests/$', 'test_list'),
url(r'^tests/(?P<test_id>[^/]+)/$', 'test_detail'),
- url(r'^filters/$', 'filters_list'),
- url(r'^filters/\+add$', 'filter_add'),
- url(r'^filters/\+add-preview-json$', 'filter_preview_json'),
- url(r'^filters/\+add-cases-for-test-json$', 'filter_add_cases_for_test_json'),
- url(r'^filters/\+attribute-name-completion-json$', 'filter_attr_name_completion_json'),
- url(r'^filters/\+attribute-value-completion-json$', 'filter_attr_value_completion_json'),
- url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)$', 'filter_detail'),
- url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)/json$', 'filter_json'),
- url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)/\+edit$', 'filter_edit'),
- url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)/\+subscribe$', 'filter_subscribe'),
- url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)/\+delete$', 'filter_delete'),
+ url(r'^filters/$', 'filters.views.filters_list'),
+ url(r'^filters/\+add$', 'filters.views.filter_add'),
+ url(r'^filters/\+add-preview-json$', 'filters.views.filter_preview_json'),
+ url(r'^filters/\+add-cases-for-test-json$', 'filters.views.filter_add_cases_for_test_json'),
+ url(r'^filters/\+attribute-name-completion-json$', 'filters.views.filter_attr_name_completion_json'),
+ url(r'^filters/\+attribute-value-completion-json$', 'filters.views.filter_attr_value_completion_json'),
+ url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)$', 'filters.views.filter_detail'),
+ url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)/json$', 'filters.views.filter_json'),
+ url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)/\+edit$', 'filters.views.filter_edit'),
+ url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)/\+subscribe$', 'filters.views.filter_subscribe'),
+ url(r'^filters/~(?P<username>[a-zA-Z0-9-_]+)/(?P<name>[a-zA-Z0-9-_]+)/\+delete$', 'filters.views.filter_delete'),
url(r'^xml-rpc/$', linaro_django_xmlrpc.views.handler,
name='dashboard_app.views.dashboard_xml_rpc_handler',
kwargs={
@@ -81,8 +81,8 @@
url(r'^efforts/(?P<pk>[0-9]+)/$', 'testing_effort_detail'),
url(r'^efforts/(?P<pk>[0-9]+)/update/$', 'testing_effort_update'),
url(r'^efforts/(?P<project_identifier>[a-z0-9-]+)/\+new/$', 'testing_effort_create'),
- url(r'^image-reports/$', 'image_report_list'),
- url(r'^image-reports/(?P<name>[A-Za-z0-9_-]+)$', 'image_report_detail'),
- url(r'^api/link-bug-to-testrun', 'link_bug_to_testrun'),
- url(r'^api/unlink-bug-and-testrun', 'unlink_bug_and_testrun'),
+ url(r'^image-reports/$', 'images.image_report_list'),
+ url(r'^image-reports/(?P<name>[A-Za-z0-9_-]+)$', 'images.image_report_detail'),
+ url(r'^api/link-bug-to-testrun', 'images.link_bug_to_testrun'),
+ url(r'^api/unlink-bug-and-testrun', 'images.unlink_bug_and_testrun'),
)
=== added directory 'dashboard_app/views'
=== renamed file 'dashboard_app/views.py' => 'dashboard_app/views/__init__.py'
@@ -20,29 +20,18 @@
Views for the Dashboard application
"""
-import operator
import re
import json
-from django.conf import settings
-from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.auth.decorators import login_required
-from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
-from django.core.exceptions import PermissionDenied, ValidationError
from django.core.urlresolvers import reverse
from django.db.models.manager import Manager
from django.db.models.query import QuerySet
-from django import forms
-from django.forms.formsets import BaseFormSet, formset_factory
-from django.forms.widgets import Select
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response, redirect, get_object_or_404
from django.template import RequestContext, loader
-from django.template import Template, Context
-from django.utils.html import escape
from django.utils.safestring import mark_safe
-from django.views.decorators.http import require_POST
from django.views.generic.list_detail import object_list, object_detail
from django_tables2 import Attrs, Column, TemplateColumn
@@ -60,17 +49,10 @@
BundleStream,
DataReport,
DataView,
- Image,
- ImageSet,
- LaunchpadBug,
- NamedAttribute,
Tag,
Test,
- TestCase,
TestResult,
TestRun,
- TestRunFilter,
- TestRunFilterSubscription,
TestingEffort,
)
@@ -448,619 +430,6 @@
searchable_columns = ['test_case__test_case_id']
-class UserFiltersTable(DataTablesTable):
-
- name = TemplateColumn('''
- <a href="{{ record.get_absolute_url }}">{{ record.name }}</a>
- ''')
-
- bundle_streams = TemplateColumn('''
- {% for r in record.bundle_streams.all %}
- {{r.pathname}} <br />
- {% endfor %}
- ''')
-
- build_number_attribute = Column()
- def render_build_number_attribute(self, value):
- if not value:
- return ''
- return value
-
- attributes = TemplateColumn('''
- {% for a in record.attributes.all %}
- {{ a }} <br />
- {% endfor %}
- ''')
-
- test = TemplateColumn('''
- <table style="border-collapse: collapse">
- <tbody>
- {% for test in record.tests.all %}
- <tr>
- <td>
- {{ test.test }}
- </td>
- <td>
- {% for test_case in test.all_case_names %}
- {{ test_case }}
- {% empty %}
- <i>any</i>
- {% endfor %}
- </td>
- </tr>
- {% endfor %}
- </tbody>
- </table>
- ''')
-
- subscription = Column()
- def render_subscription(self, record):
- try:
- sub = TestRunFilterSubscription.objects.get(
- user=self.user, filter=record)
- except TestRunFilterSubscription.DoesNotExist:
- return "None"
- else:
- return sub.get_level_display()
-
- public = Column()
-
- def get_queryset(self, user):
- return TestRunFilter.objects.filter(owner=user)
-
-
-class PublicFiltersTable(UserFiltersTable):
-
- name = TemplateColumn('''
- <a href="{{ record.get_absolute_url }}">~{{ record.owner.username }}/{{ record.name }}</a>
- ''')
-
- def __init__(self, *args, **kw):
- super(PublicFiltersTable, self).__init__(*args, **kw)
- del self.base_columns['public']
-
- def get_queryset(self):
- return TestRunFilter.objects.filter(public=True)
-
-
-@BreadCrumb("Filters and Subscriptions", parent=index)
-def filters_list(request):
- public_filters_table = PublicFiltersTable("public-filters", None)
- if request.user.is_authenticated():
- public_filters_table.user = request.user
- user_filters_table = UserFiltersTable("user-filters", None, params=(request.user,))
- user_filters_table.user = request.user
- else:
- user_filters_table = None
- del public_filters_table.base_columns['subscription']
-
- return render_to_response(
- 'dashboard_app/filters_list.html', {
- 'user_filters_table': user_filters_table,
- 'public_filters_table': public_filters_table,
- 'bread_crumb_trail': BreadCrumbTrail.leading_to(
- filters_list),
- }, RequestContext(request)
- )
-
-
-class TestRunColumn(Column):
- def render(self, record):
- # This column is only rendered if we don't really expect
- # record.test_runs to be very long...
- links = []
- trs = [tr for tr in record.test_runs if tr.test.test_id == self.verbose_name]
- for tr in trs:
- text = '%s / %s' % (tr.denormalization.count_pass, tr.denormalization.count_all())
- links.append('<a href="%s">%s</a>' % (tr.get_absolute_url(), text))
- return mark_safe(' '.join(links))
-
-
-class SpecificCaseColumn(Column):
- def __init__(self, verbose_name, test_case_id):
- super(SpecificCaseColumn, self).__init__(verbose_name)
- self.test_case_id = test_case_id
- def render(self, record):
- r = []
- for result in record.specific_results:
- if result.test_case_id != self.test_case_id:
- continue
- if result.result == result.RESULT_PASS and result.units:
- s = '%s %s' % (result.measurement, result.units)
- else:
- s = result.RESULT_MAP[result.result]
- r.append('<a href="' + result.get_absolute_url() + '">'+s+'</a>')
- return mark_safe(', '.join(r))
-
-
-class BundleColumn(Column):
- def render(self, record):
- return mark_safe('<a href="' + record.bundle.get_absolute_url() + '">' + escape(record.bundle.content_filename) + '</a>')
-
-
-class FilterTable(DataTablesTable):
- def __init__(self, *args, **kwargs):
- kwargs['template'] = 'dashboard_app/filter_results_table.html'
- super(FilterTable, self).__init__(*args, **kwargs)
- match_maker = self.data.queryset
- self.base_columns['tag'].verbose_name = match_maker.key_name
- bundle_stream_col = self.base_columns.pop('bundle_stream')
- bundle_col = self.base_columns.pop('bundle')
- tag_col = self.base_columns.pop('tag')
- self.complex_header = False
- if match_maker.filter_data['tests']:
- del self.base_columns['passes']
- del self.base_columns['total']
- for i, t in enumerate(reversed(match_maker.filter_data['tests'])):
- if len(t.all_case_names()) == 0:
- col = TestRunColumn(mark_safe(t.test.test_id))
- self.base_columns.insert(0, 'test_run_%s' % i, col)
- elif len(t.all_case_names()) == 1:
- n = t.test.test_id + ':' + t.all_case_names()[0]
- col = SpecificCaseColumn(mark_safe(n), t.all_case_ids()[0])
- self.base_columns.insert(0, 'test_run_%s_case' % i, col)
- else:
- col0 = SpecificCaseColumn(mark_safe(t.all_case_names()[0]), t.all_case_ids()[0])
- col0.in_group = True
- col0.first_in_group = True
- col0.group_length = len(t.all_case_names())
- col0.group_name = mark_safe(t.test.test_id)
- self.complex_header = True
- self.base_columns.insert(0, 'test_run_%s_case_%s' % (i, 0), col0)
- for j, n in enumerate(t.all_case_names()[1:], 1):
- col = SpecificCaseColumn(mark_safe(n), t.all_case_ids()[j])
- col.in_group = True
- col.first_in_group = False
- self.base_columns.insert(j, 'test_run_%s_case_%s' % (i, j), col)
- else:
- self.base_columns.insert(0, 'bundle', bundle_col)
- if len(match_maker.filter_data['bundle_streams']) > 1:
- self.base_columns.insert(0, 'bundle_stream', bundle_stream_col)
- self.base_columns.insert(0, 'tag', tag_col)
-
- tag = Column()
-
- def render_bundle_stream(self, record):
- bundle_streams = set(tr.bundle.bundle_stream for tr in record.test_runs)
- links = []
- for bs in sorted(bundle_streams, key=operator.attrgetter('pathname')):
- links.append('<a href="%s">%s</a>' % (
- bs.get_absolute_url(), escape(bs.pathname)))
- return mark_safe('<br />'.join(links))
- bundle_stream = Column(mark_safe("Bundle Stream(s)"))
-
- def render_bundle(self, record):
- bundles = set(tr.bundle for tr in record.test_runs)
- links = []
- for b in sorted(bundles, key=operator.attrgetter('uploaded_on')):
- links.append('<a href="%s">%s</a>' % (
- b.get_absolute_url(), escape(b.content_filename)))
- return mark_safe('<br />'.join(links))
- bundle = Column(mark_safe("Bundle(s)"))
-
- passes = Column(accessor='pass_count')
- total = Column(accessor='result_count')
-
- def get_queryset(self, user, filter):
- return filter.get_test_runs(user)
-
- datatable_opts = {
- "sPaginationType": "full_numbers",
- "iDisplayLength": 25,
- "bSort": False,
- }
-
-
-def filter_json(request, username, name):
- filter = TestRunFilter.objects.get(owner__username=username, name=name)
- return FilterTable.json(request, params=(request.user, filter))
-
-
-class FilterPreviewTable(FilterTable):
- def get_queryset(self, user, form):
- return form.get_test_runs(user)
-
- datatable_opts = FilterTable.datatable_opts.copy()
- datatable_opts.update({
- "iDisplayLength": 10,
- })
-
-
-def filter_preview_json(request):
- try:
- filter = TestRunFilter.objects.get(owner=request.user, name=request.GET['name'])
- except TestRunFilter.DoesNotExist:
- filter = None
- form = TestRunFilterForm(request.user, request.GET, instance=filter)
- if not form.is_valid():
- raise ValidationError(str(form.errors))
- return FilterPreviewTable.json(request, params=(request.user, form))
-
-
-@BreadCrumb("Filter ~{username}/{name}", parent=filters_list, needs=['username', 'name'])
-def filter_detail(request, username, name):
- filter = TestRunFilter.objects.get(owner__username=username, name=name)
- if not filter.public and filter.owner != request.user:
- raise PermissionDenied()
- if not request.user.is_authenticated():
- subscription = None
- else:
- try:
- subscription = TestRunFilterSubscription.objects.get(
- user=request.user, filter=filter)
- except TestRunFilterSubscription.DoesNotExist:
- subscription = None
- return render_to_response(
- 'dashboard_app/filter_detail.html', {
- 'filter': filter,
- 'subscription': subscription,
- 'filter_table': FilterTable(
- "filter-table",
- reverse(filter_json, kwargs=dict(username=username, name=name)),
- params=(request.user, filter)),
- 'bread_crumb_trail': BreadCrumbTrail.leading_to(
- filter_detail, name=name, username=username),
- }, RequestContext(request)
- )
-
-
-class TestRunFilterSubscriptionForm(forms.ModelForm):
- class Meta:
- model = TestRunFilterSubscription
- fields = ('level',)
- def __init__(self, filter, user, *args, **kwargs):
- super(TestRunFilterSubscriptionForm, self).__init__(*args, **kwargs)
- self.instance.filter = filter
- self.instance.user = user
-
-
-@BreadCrumb("Manage Subscription", parent=filter_detail, needs=['name', 'username'])
-@login_required
-def filter_subscribe(request, username, name):
- filter = TestRunFilter.objects.get(owner__username=username, name=name)
- if not filter.public and filter.owner != request.user:
- raise PermissionDenied()
- try:
- subscription = TestRunFilterSubscription.objects.get(
- user=request.user, filter=filter)
- except TestRunFilterSubscription.DoesNotExist:
- subscription = None
- if request.method == "POST":
- form = TestRunFilterSubscriptionForm(
- filter, request.user, request.POST, instance=subscription)
- if form.is_valid():
- if 'unsubscribe' in request.POST:
- subscription.delete()
- else:
- form.save()
- return HttpResponseRedirect(filter.get_absolute_url())
- else:
- form = TestRunFilterSubscriptionForm(
- filter, request.user, instance=subscription)
- return render_to_response(
- 'dashboard_app/filter_subscribe.html', {
- 'filter': filter,
- 'form': form,
- 'subscription': subscription,
- 'bread_crumb_trail': BreadCrumbTrail.leading_to(
- filter_subscribe, name=name, username=username),
- }, RequestContext(request)
- )
-
-
-test_run_filter_head = '''
-<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/filter-edit.css" />
-<script type="text/javascript" src="{% url admin:jsi18n %}"></script>
-<script type="text/javascript">
-var django = {};
-django.jQuery = $;
-var test_case_url = "{% url dashboard_app.views.filter_add_cases_for_test_json %}?test=";
-var attr_name_completion_url = "{% url dashboard_app.views.filter_attr_name_completion_json %}";
-var attr_value_completion_url = "{% url dashboard_app.views.filter_attr_value_completion_json %}";
-</script>
-<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.formset.js"></script>
-<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/filter-edit.js"></script>
-'''
-
-
-class AttributesForm(forms.Form):
- name = forms.CharField(max_length=1024)
- value = forms.CharField(max_length=1024)
-
-AttributesFormSet = formset_factory(AttributesForm, extra=0)
-
-
-
-class TruncatingSelect(Select):
-
- def render_option(self, selected_choices, option_value, option_label):
- if len(option_label) > 50:
- option_label = option_label[:50] + '...'
- return super(TruncatingSelect, self).render_option(
- selected_choices, option_value, option_label)
-
-
-class TRFTestCaseForm(forms.Form):
-
- test_case = forms.ModelChoiceField(
- queryset=TestCase.objects.none(), widget=TruncatingSelect, empty_label=None)
-
-
-class BaseTRFTestCaseFormSet(BaseFormSet):
-
- def __init__(self, *args, **kw):
- self._queryset = kw.pop('queryset')
- super(BaseTRFTestCaseFormSet, self).__init__(*args, **kw)
-
- def add_fields(self, form, index):
- super(BaseTRFTestCaseFormSet, self).add_fields(form, index)
- if self._queryset is not None:
- form.fields['test_case'].queryset = self._queryset
-
-
-TRFTestCaseFormSet = formset_factory(
- TRFTestCaseForm, extra=0, formset=BaseTRFTestCaseFormSet)
-
-
-class TRFTestForm(forms.Form):
-
- def __init__(self, *args, **kw):
- super(TRFTestForm, self).__init__(*args, **kw)
- kw['initial'] = kw.get('initial', {}).get('test_cases', None)
- kw.pop('empty_permitted', None)
- kw['queryset'] = None
- v = self['test'].value()
- if v:
- test = self.fields['test'].to_python(v)
- queryset = TestCase.objects.filter(test=test).order_by('test_case_id')
- kw['queryset'] = queryset
- self.test_case_formset = TRFTestCaseFormSet(*args, **kw)
-
- def is_valid(self):
- return super(TRFTestForm, self).is_valid() and \
- self.test_case_formset.is_valid()
-
- def full_clean(self):
- super(TRFTestForm, self).full_clean()
- self.test_case_formset.full_clean()
-
- test = forms.ModelChoiceField(
- queryset=Test.objects.order_by('test_id'), required=True)
-
-
-class BaseTRFTestsFormSet(BaseFormSet):
-
- def is_valid(self):
- if not super(BaseTRFTestsFormSet, self).is_valid():
- return False
- for form in self.forms:
- if not form.is_valid():
- return False
- return True
-
-
-TRFTestsFormSet = formset_factory(
- TRFTestForm, extra=0, formset=BaseTRFTestsFormSet)
-
-
-class FakeTRFTest(object):
- def __init__(self, form):
- self.test = form.cleaned_data['test']
- self.test_id = self.test.id
- self._case_ids = []
- self._case_names = []
- for tc_form in form.test_case_formset:
- self._case_ids.append(tc_form.cleaned_data['test_case'].id)
- self._case_names.append(tc_form.cleaned_data['test_case'].test_case_id)
-
- def all_case_ids(self):
- return self._case_ids
-
- def all_case_names(self):
- return self._case_names
-
-
-class TestRunFilterForm(forms.ModelForm):
- class Meta:
- model = TestRunFilter
- exclude = ('owner',)
- widgets = {
- 'bundle_streams': FilteredSelectMultiple("Bundle Streams", False),
- }
-
- @property
- def media(self):
- super_media = str(super(TestRunFilterForm, self).media)
- return mark_safe(Template(test_run_filter_head).render(
- Context({'STATIC_URL': settings.STATIC_URL})
- )) + super_media
-
- def validate_name(self, value):
- self.instance.name = value
- try:
- self.instance.validate_unique()
- except ValidationError, e:
- if e.message_dict.values() == [[
- u'Test run filter with this Owner and Name already exists.']]:
- raise ValidationError("You already have a filter with this name")
- else:
- raise
-
- def save(self, commit=True, **kwargs):
- instance = super(TestRunFilterForm, self).save(commit=commit, **kwargs)
- if commit:
- instance.attributes.all().delete()
- for a in self.attributes_formset.cleaned_data:
- instance.attributes.create(name=a['name'], value=a['value'])
- instance.tests.all().delete()
- for i, test_form in enumerate(self.tests_formset.forms):
- trf_test = instance.tests.create(
- test=test_form.cleaned_data['test'], index=i)
- for j, test_case_form in enumerate(test_form.test_case_formset.forms):
- trf_test.cases.create(
- test_case=test_case_form.cleaned_data['test_case'], index=j)
- return instance
-
- def is_valid(self):
- return super(TestRunFilterForm, self).is_valid() and \
- self.attributes_formset.is_valid() and \
- self.tests_formset.is_valid()
-
- def full_clean(self):
- super(TestRunFilterForm, self).full_clean()
- self.attributes_formset.full_clean()
- self.tests_formset.full_clean()
-
- @property
- def summary_data(self):
- data = self.cleaned_data.copy()
- tests = []
- for form in self.tests_formset.forms:
- tests.append(FakeTRFTest(form))
- data['attributes'] = [
- (d['name'], d['value']) for d in self.attributes_formset.cleaned_data]
- data['tests'] = tests
- return data
-
- def __init__(self, user, *args, **kwargs):
- super(TestRunFilterForm, self).__init__(*args, **kwargs)
- self.instance.owner = user
- kwargs.pop('instance', None)
-
- attr_set_args = kwargs.copy()
- if self.instance.pk:
- initial = []
- for attr in self.instance.attributes.all():
- initial.append({
- 'name': attr.name,
- 'value': attr.value,
- })
- attr_set_args['initial'] = initial
- attr_set_args['prefix'] = 'attributes'
- self.attributes_formset = AttributesFormSet(*args, **attr_set_args)
-
- tests_set_args = kwargs.copy()
- if self.instance.pk:
- initial = []
- for test in self.instance.tests.all().order_by('index').prefetch_related('cases'):
- initial.append({
- 'test': test.test,
- 'test_cases': [{'test_case': unicode(tc.test_case.id)} for tc in test.cases.all().order_by('index')],
- })
- tests_set_args['initial'] = initial
- tests_set_args['prefix'] = 'tests'
- self.tests_formset = TRFTestsFormSet(*args, **tests_set_args)
-
- self.fields['bundle_streams'].queryset = \
- BundleStream.objects.accessible_by_principal(user).order_by('pathname')
- self.fields['name'].validators.append(self.validate_name)
-
- def get_test_runs(self, user):
- assert self.is_valid(), self.errors
- filter = self.save(commit=False)
- tests = []
- for form in self.tests_formset.forms:
- tests.append(FakeTRFTest(form))
- return filter.get_test_runs_impl(
- user, self.cleaned_data['bundle_streams'], self.summary_data['attributes'], tests)
-
-
-def filter_form(request, bread_crumb_trail, instance=None):
- if request.method == 'POST':
- form = TestRunFilterForm(request.user, request.POST, instance=instance)
-
- if form.is_valid():
- if 'save' in request.POST:
- filter = form.save()
- return HttpResponseRedirect(filter.get_absolute_url())
- else:
- c = request.POST.copy()
- c.pop('csrfmiddlewaretoken', None)
- return render_to_response(
- 'dashboard_app/filter_preview.html', {
- 'bread_crumb_trail': bread_crumb_trail,
- 'form': form,
- 'table': FilterPreviewTable(
- 'filter-preview',
- reverse(filter_preview_json) + '?' + c.urlencode(),
- params=(request.user, form)),
- }, RequestContext(request))
- else:
- form = TestRunFilterForm(request.user, instance=instance)
-
- return render_to_response(
- 'dashboard_app/filter_add.html', {
- 'bread_crumb_trail': bread_crumb_trail,
- 'form': form,
- }, RequestContext(request))
-
-
-@BreadCrumb("Add new filter", parent=filters_list)
-def filter_add(request):
- return filter_form(
- request,
- BreadCrumbTrail.leading_to(filter_add))
-
-
-@BreadCrumb("Edit", parent=filter_detail, needs=['name', 'username'])
-def filter_edit(request, username, name):
- if request.user.username != username:
- raise PermissionDenied()
- filter = TestRunFilter.objects.get(owner=request.user, name=name)
- return filter_form(
- request,
- BreadCrumbTrail.leading_to(filter_edit, name=name, username=username),
- instance=filter)
-
-
-@BreadCrumb("Delete", parent=filter_detail, needs=['name', 'username'])
-def filter_delete(request, username, name):
- if request.user.username != username:
- raise PermissionDenied()
- filter = TestRunFilter.objects.get(owner=request.user, name=name)
- if request.method == "POST":
- if 'yes' in request.POST:
- filter.delete()
- return HttpResponseRedirect(reverse(filters_list))
- else:
- return HttpResponseRedirect(filter.get_absolute_url())
- return render_to_response(
- 'dashboard_app/filter_delete.html', {
- 'bread_crumb_trail': BreadCrumbTrail.leading_to(filter_delete, name=name, username=username),
- 'filter': filter,
- }, RequestContext(request))
-
-
-def filter_add_cases_for_test_json(request):
- test = Test.objects.get(test_id=request.GET['test'])
- result = TestCase.objects.filter(test=test).order_by('test_case_id').values('test_case_id', 'id')
- return HttpResponse(
- json.dumps(list(result)),
- mimetype='application/json')
-
-
-def filter_attr_name_completion_json(request):
- term = request.GET['term']
- content_type_id = ContentType.objects.get_for_model(TestRun).id
- result = NamedAttribute.objects.filter(
- name__startswith=term, content_type_id=content_type_id
- ).distinct().order_by('name').values_list('name', flat=True)
- return HttpResponse(
- json.dumps(list(result)),
- mimetype='application/json')
-
-
-def filter_attr_value_completion_json(request):
- name = request.GET['name']
- term = request.GET['term']
- content_type_id = ContentType.objects.get_for_model(TestRun).id
- result = NamedAttribute.objects.filter(
- name=name, content_type_id=content_type_id, value__startswith=term
- ).distinct().order_by('value').values_list('value', flat=True)
- return HttpResponse(
- json.dumps(list(result)),
- mimetype='application/json')
-
def test_run_detail_test_json(request, pathname, content_sha1, analyzer_assigned_uuid):
test_run = get_restricted_object_or_404(
@@ -1481,124 +850,3 @@
pk=effort.pk)
})
return HttpResponse(t.render(c))
-
-
-@BreadCrumb("Image Reports", parent=index)
-def image_report_list(request):
- imagesets = ImageSet.objects.filter()
- imagesets_data = []
- for imageset in imagesets:
- images_data = []
- for image in imageset.images.all():
- # Migration hack: Image.filter cannot be auto populated, so ignore
- # images that have not been migrated to filters for now.
- if image.filter:
- image_data = {
- 'name': image.name,
- 'bundle_count': image.filter.get_test_runs(request.user).count(),
- 'link': image.name,
- }
- images_data.append(image_data)
- images_data.sort(key=lambda d:d['name'])
- imageset_data = {
- 'name': imageset.name,
- 'images': images_data,
- }
- imagesets_data.append(imageset_data)
- imagesets_data.sort(key=lambda d:d['name'])
- return render_to_response(
- "dashboard_app/image-reports.html", {
- 'bread_crumb_trail': BreadCrumbTrail.leading_to(image_report_list),
- 'imagesets': imagesets_data,
- }, RequestContext(request))
-
-
-@BreadCrumb("{name}", parent=image_report_list, needs=['name'])
-def image_report_detail(request, name):
-
- image = Image.objects.get(name=name)
- matches = image.filter.get_test_runs(request.user, prefetch_related=['launchpad_bugs'])[:50]
-
- build_number_to_cols = {}
-
- test_run_names = set()
-
- for match in matches:
- for test_run in match.test_runs:
- name = test_run.test.test_id
- denorm = test_run.denormalization
- if denorm.count_pass == denorm.count_all():
- cls = 'present pass'
- else:
- cls = 'present fail'
- bug_ids = sorted([b.bug_id for b in test_run.launchpad_bugs.all()])
- test_run_data = dict(
- present=True,
- cls=cls,
- uuid=test_run.analyzer_assigned_uuid,
- passes=denorm.count_pass,
- total=denorm.count_all(),
- link=test_run.get_permalink(),
- bug_ids=bug_ids,
- )
- if match.tag not in build_number_to_cols:
- # This assumes 1 bundle per match...
- build_number_to_cols[match.tag] = {
- 'test_runs': {},
- 'number': match.tag,
- 'date': test_run.bundle.uploaded_on,
- 'link': test_run.bundle.get_absolute_url(),
- }
- build_number_to_cols[match.tag]['test_runs'][name] = test_run_data
- if name != 'lava':
- test_run_names.add(name)
-
-
- test_run_names = sorted(test_run_names)
- test_run_names.insert(0, 'lava')
-
- cols = [c for n, c in sorted(build_number_to_cols.items())]
-
- table_data = []
-
- for test_run_name in test_run_names:
- row_data = []
- for col in cols:
- test_run_data = col['test_runs'].get(test_run_name)
- if not test_run_data:
- test_run_data = dict(
- present=False,
- cls='missing',
- )
- row_data.append(test_run_data)
- table_data.append(row_data)
-
- return render_to_response(
- "dashboard_app/image-report.html", {
- 'bread_crumb_trail': BreadCrumbTrail.leading_to(
- image_report_detail, name=image.name),
- 'image': image,
- 'cols': cols,
- 'table_data': table_data,
- 'test_run_names': test_run_names,
- }, RequestContext(request))
-
-
-@require_POST
-def link_bug_to_testrun(request):
- testrun = get_object_or_404(TestRun, analyzer_assigned_uuid=request.POST['uuid'])
- bug_id = request.POST['bug']
- lpbug = LaunchpadBug.objects.get_or_create(bug_id=int(bug_id))[0]
- testrun.launchpad_bugs.add(lpbug)
- testrun.save()
- return HttpResponseRedirect(request.POST['back'])
-
-
-@require_POST
-def unlink_bug_and_testrun(request):
- testrun = get_object_or_404(TestRun, analyzer_assigned_uuid=request.POST['uuid'])
- bug_id = request.POST['bug']
- lpbug = LaunchpadBug.objects.get_or_create(bug_id=int(bug_id))[0]
- testrun.launchpad_bugs.remove(lpbug)
- testrun.save()
- return HttpResponseRedirect(request.POST['back'])
=== added directory 'dashboard_app/views/filters'
=== added file 'dashboard_app/views/filters/__init__.py'
@@ -0,0 +1,17 @@
+# Copyright (C) 2010-2011 Linaro Limited
+#
+# Author: Michael Hudson-Doyle <michael.hudson@linaro.org>
+#
+# This file is part of Launch Control.
+#
+# Launch Control is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# Launch Control is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
=== added file 'dashboard_app/views/filters/forms.py'
@@ -0,0 +1,261 @@
+# Copyright (C) 2010-2011 Linaro Limited
+#
+# Author: Michael Hudson-Doyle <michael.hudson@linaro.org>
+#
+# This file is part of Launch Control.
+#
+# Launch Control is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# Launch Control is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
+
+from django.conf import settings
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.core.exceptions import ValidationError
+from django import forms
+from django.forms.formsets import BaseFormSet, formset_factory
+from django.forms.widgets import Select
+from django.template import Template, Context
+from django.utils.safestring import mark_safe
+
+from dashboard_app.models import (
+ BundleStream,
+ Test,
+ TestCase,
+ TestRunFilter,
+ TestRunFilterSubscription,
+)
+
+class TestRunFilterSubscriptionForm(forms.ModelForm):
+ class Meta:
+ model = TestRunFilterSubscription
+ fields = ('level',)
+ def __init__(self, filter, user, *args, **kwargs):
+ super(TestRunFilterSubscriptionForm, self).__init__(*args, **kwargs)
+ self.instance.filter = filter
+ self.instance.user = user
+
+
+
+test_run_filter_head = '''
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}dashboard_app/css/filter-edit.css" />
+<script type="text/javascript" src="{% url admin:jsi18n %}"></script>
+<script type="text/javascript">
+var django = {};
+django.jQuery = $;
+var test_case_url = "{% url dashboard_app.views.filter_add_cases_for_test_json %}?test=";
+var attr_name_completion_url = "{% url dashboard_app.views.filter_attr_name_completion_json %}";
+var attr_value_completion_url = "{% url dashboard_app.views.filter_attr_value_completion_json %}";
+</script>
+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/jquery.formset.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}dashboard_app/js/filter-edit.js"></script>
+'''
+
+
+class AttributesForm(forms.Form):
+ name = forms.CharField(max_length=1024)
+ value = forms.CharField(max_length=1024)
+
+AttributesFormSet = formset_factory(AttributesForm, extra=0)
+
+
+class TruncatingSelect(Select):
+
+ def render_option(self, selected_choices, option_value, option_label):
+ if len(option_label) > 50:
+ option_label = option_label[:50] + '...'
+ return super(TruncatingSelect, self).render_option(
+ selected_choices, option_value, option_label)
+
+
+class TRFTestCaseForm(forms.Form):
+
+ test_case = forms.ModelChoiceField(
+ queryset=TestCase.objects.none(), widget=TruncatingSelect, empty_label=None)
+
+
+class BaseTRFTestCaseFormSet(BaseFormSet):
+
+ def __init__(self, *args, **kw):
+ self._queryset = kw.pop('queryset')
+ super(BaseTRFTestCaseFormSet, self).__init__(*args, **kw)
+
+ def add_fields(self, form, index):
+ super(BaseTRFTestCaseFormSet, self).add_fields(form, index)
+ if self._queryset is not None:
+ form.fields['test_case'].queryset = self._queryset
+
+
+TRFTestCaseFormSet = formset_factory(
+ TRFTestCaseForm, extra=0, formset=BaseTRFTestCaseFormSet)
+
+
+class TRFTestForm(forms.Form):
+
+ def __init__(self, *args, **kw):
+ super(TRFTestForm, self).__init__(*args, **kw)
+ kw['initial'] = kw.get('initial', {}).get('test_cases', None)
+ kw.pop('empty_permitted', None)
+ kw['queryset'] = None
+ v = self['test'].value()
+ if v:
+ test = self.fields['test'].to_python(v)
+ queryset = TestCase.objects.filter(test=test).order_by('test_case_id')
+ kw['queryset'] = queryset
+ self.test_case_formset = TRFTestCaseFormSet(*args, **kw)
+
+ def is_valid(self):
+ return super(TRFTestForm, self).is_valid() and \
+ self.test_case_formset.is_valid()
+
+ def full_clean(self):
+ super(TRFTestForm, self).full_clean()
+ self.test_case_formset.full_clean()
+
+ test = forms.ModelChoiceField(
+ queryset=Test.objects.order_by('test_id'), required=True)
+
+
+class BaseTRFTestsFormSet(BaseFormSet):
+
+ def is_valid(self):
+ if not super(BaseTRFTestsFormSet, self).is_valid():
+ return False
+ for form in self.forms:
+ if not form.is_valid():
+ return False
+ return True
+
+
+TRFTestsFormSet = formset_factory(
+ TRFTestForm, extra=0, formset=BaseTRFTestsFormSet)
+
+
+class FakeTRFTest(object):
+ def __init__(self, form):
+ self.test = form.cleaned_data['test']
+ self.test_id = self.test.id
+ self._case_ids = []
+ self._case_names = []
+ for tc_form in form.test_case_formset:
+ self._case_ids.append(tc_form.cleaned_data['test_case'].id)
+ self._case_names.append(tc_form.cleaned_data['test_case'].test_case_id)
+
+ def all_case_ids(self):
+ return self._case_ids
+
+ def all_case_names(self):
+ return self._case_names
+
+
+class TestRunFilterForm(forms.ModelForm):
+ class Meta:
+ model = TestRunFilter
+ exclude = ('owner',)
+ widgets = {
+ 'bundle_streams': FilteredSelectMultiple("Bundle Streams", False),
+ }
+
+ @property
+ def media(self):
+ super_media = str(super(TestRunFilterForm, self).media)
+ return mark_safe(Template(test_run_filter_head).render(
+ Context({'STATIC_URL': settings.STATIC_URL})
+ )) + super_media
+
+ def validate_name(self, value):
+ self.instance.name = value
+ try:
+ self.instance.validate_unique()
+ except ValidationError, e:
+ if e.message_dict.values() == [[
+ u'Test run filter with this Owner and Name already exists.']]:
+ raise ValidationError("You already have a filter with this name")
+ else:
+ raise
+
+ def save(self, commit=True, **kwargs):
+ instance = super(TestRunFilterForm, self).save(commit=commit, **kwargs)
+ if commit:
+ instance.attributes.all().delete()
+ for a in self.attributes_formset.cleaned_data:
+ instance.attributes.create(name=a['name'], value=a['value'])
+ instance.tests.all().delete()
+ for i, test_form in enumerate(self.tests_formset.forms):
+ trf_test = instance.tests.create(
+ test=test_form.cleaned_data['test'], index=i)
+ for j, test_case_form in enumerate(test_form.test_case_formset.forms):
+ trf_test.cases.create(
+ test_case=test_case_form.cleaned_data['test_case'], index=j)
+ return instance
+
+ def is_valid(self):
+ return super(TestRunFilterForm, self).is_valid() and \
+ self.attributes_formset.is_valid() and \
+ self.tests_formset.is_valid()
+
+ def full_clean(self):
+ super(TestRunFilterForm, self).full_clean()
+ self.attributes_formset.full_clean()
+ self.tests_formset.full_clean()
+
+ @property
+ def summary_data(self):
+ data = self.cleaned_data.copy()
+ tests = []
+ for form in self.tests_formset.forms:
+ tests.append(FakeTRFTest(form))
+ data['attributes'] = [
+ (d['name'], d['value']) for d in self.attributes_formset.cleaned_data]
+ data['tests'] = tests
+ return data
+
+ def __init__(self, user, *args, **kwargs):
+ super(TestRunFilterForm, self).__init__(*args, **kwargs)
+ self.instance.owner = user
+ kwargs.pop('instance', None)
+
+ attr_set_args = kwargs.copy()
+ if self.instance.pk:
+ initial = []
+ for attr in self.instance.attributes.all():
+ initial.append({
+ 'name': attr.name,
+ 'value': attr.value,
+ })
+ attr_set_args['initial'] = initial
+ attr_set_args['prefix'] = 'attributes'
+ self.attributes_formset = AttributesFormSet(*args, **attr_set_args)
+
+ tests_set_args = kwargs.copy()
+ if self.instance.pk:
+ initial = []
+ for test in self.instance.tests.all().order_by('index').prefetch_related('cases'):
+ initial.append({
+ 'test': test.test,
+ 'test_cases': [{'test_case': unicode(tc.test_case.id)} for tc in test.cases.all().order_by('index')],
+ })
+ tests_set_args['initial'] = initial
+ tests_set_args['prefix'] = 'tests'
+ self.tests_formset = TRFTestsFormSet(*args, **tests_set_args)
+
+ self.fields['bundle_streams'].queryset = \
+ BundleStream.objects.accessible_by_principal(user).order_by('pathname')
+ self.fields['name'].validators.append(self.validate_name)
+
+ def get_test_runs(self, user):
+ assert self.is_valid(), self.errors
+ filter = self.save(commit=False)
+ tests = []
+ for form in self.tests_formset.forms:
+ tests.append(FakeTRFTest(form))
+ return filter.get_test_runs_impl(
+ user, self.cleaned_data['bundle_streams'], self.summary_data['attributes'], tests)
+
=== added file 'dashboard_app/views/filters/tables.py'
@@ -0,0 +1,223 @@
+# Copyright (C) 2010-2011 Linaro Limited
+#
+# Author: Michael Hudson-Doyle <michael.hudson@linaro.org>
+#
+# This file is part of Launch Control.
+#
+# Launch Control is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# Launch Control is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
+
+import operator
+
+from django.utils.html import escape
+from django.utils.safestring import mark_safe
+
+from django_tables2 import Column, TemplateColumn
+
+from lava.utils.data_tables.tables import DataTablesTable
+
+from dashboard_app.models import (
+ TestRunFilter,
+ TestRunFilterSubscription,
+ )
+
+class UserFiltersTable(DataTablesTable):
+
+ name = TemplateColumn('''
+ <a href="{{ record.get_absolute_url }}">{{ record.name }}</a>
+ ''')
+
+ bundle_streams = TemplateColumn('''
+ {% for r in record.bundle_streams.all %}
+ {{r.pathname}} <br />
+ {% endfor %}
+ ''')
+
+ build_number_attribute = Column()
+ def render_build_number_attribute(self, value):
+ if not value:
+ return ''
+ return value
+
+ attributes = TemplateColumn('''
+ {% for a in record.attributes.all %}
+ {{ a }} <br />
+ {% endfor %}
+ ''')
+
+ test = TemplateColumn('''
+ <table style="border-collapse: collapse">
+ <tbody>
+ {% for test in record.tests.all %}
+ <tr>
+ <td>
+ {{ test.test }}
+ </td>
+ <td>
+ {% for test_case in test.all_case_names %}
+ {{ test_case }}
+ {% empty %}
+ <i>any</i>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ ''')
+
+ subscription = Column()
+ def render_subscription(self, record):
+ try:
+ sub = TestRunFilterSubscription.objects.get(
+ user=self.user, filter=record)
+ except TestRunFilterSubscription.DoesNotExist:
+ return "None"
+ else:
+ return sub.get_level_display()
+
+ public = Column()
+
+ def get_queryset(self, user):
+ return TestRunFilter.objects.filter(owner=user)
+
+
+class PublicFiltersTable(UserFiltersTable):
+
+ name = TemplateColumn('''
+ <a href="{{ record.get_absolute_url }}">~{{ record.owner.username }}/{{ record.name }}</a>
+ ''')
+
+ def __init__(self, *args, **kw):
+ super(PublicFiltersTable, self).__init__(*args, **kw)
+ del self.base_columns['public']
+
+ def get_queryset(self):
+ return TestRunFilter.objects.filter(public=True)
+
+
+
+class TestRunColumn(Column):
+ def render(self, record):
+ # This column is only rendered if we don't really expect
+ # record.test_runs to be very long...
+ links = []
+ trs = [tr for tr in record.test_runs if tr.test.test_id == self.verbose_name]
+ for tr in trs:
+ text = '%s / %s' % (tr.denormalization.count_pass, tr.denormalization.count_all())
+ links.append('<a href="%s">%s</a>' % (tr.get_absolute_url(), text))
+ return mark_safe(' '.join(links))
+
+
+class SpecificCaseColumn(Column):
+ def __init__(self, verbose_name, test_case_id):
+ super(SpecificCaseColumn, self).__init__(verbose_name)
+ self.test_case_id = test_case_id
+ def render(self, record):
+ r = []
+ for result in record.specific_results:
+ if result.test_case_id != self.test_case_id:
+ continue
+ if result.result == result.RESULT_PASS and result.units:
+ s = '%s %s' % (result.measurement, result.units)
+ else:
+ s = result.RESULT_MAP[result.result]
+ r.append('<a href="' + result.get_absolute_url() + '">'+s+'</a>')
+ return mark_safe(', '.join(r))
+
+
+class BundleColumn(Column):
+ def render(self, record):
+ return mark_safe('<a href="' + record.bundle.get_absolute_url() + '">' + escape(record.bundle.content_filename) + '</a>')
+
+
+class FilterTable(DataTablesTable):
+ def __init__(self, *args, **kwargs):
+ kwargs['template'] = 'dashboard_app/filter_results_table.html'
+ super(FilterTable, self).__init__(*args, **kwargs)
+ match_maker = self.data.queryset
+ self.base_columns['tag'].verbose_name = match_maker.key_name
+ bundle_stream_col = self.base_columns.pop('bundle_stream')
+ bundle_col = self.base_columns.pop('bundle')
+ tag_col = self.base_columns.pop('tag')
+ self.complex_header = False
+ if match_maker.filter_data['tests']:
+ del self.base_columns['passes']
+ del self.base_columns['total']
+ for i, t in enumerate(reversed(match_maker.filter_data['tests'])):
+ if len(t.all_case_names()) == 0:
+ col = TestRunColumn(mark_safe(t.test.test_id))
+ self.base_columns.insert(0, 'test_run_%s' % i, col)
+ elif len(t.all_case_names()) == 1:
+ n = t.test.test_id + ':' + t.all_case_names()[0]
+ col = SpecificCaseColumn(mark_safe(n), t.all_case_ids()[0])
+ self.base_columns.insert(0, 'test_run_%s_case' % i, col)
+ else:
+ col0 = SpecificCaseColumn(mark_safe(t.all_case_names()[0]), t.all_case_ids()[0])
+ col0.in_group = True
+ col0.first_in_group = True
+ col0.group_length = len(t.all_case_names())
+ col0.group_name = mark_safe(t.test.test_id)
+ self.complex_header = True
+ self.base_columns.insert(0, 'test_run_%s_case_%s' % (i, 0), col0)
+ for j, n in enumerate(t.all_case_names()[1:], 1):
+ col = SpecificCaseColumn(mark_safe(n), t.all_case_ids()[j])
+ col.in_group = True
+ col.first_in_group = False
+ self.base_columns.insert(j, 'test_run_%s_case_%s' % (i, j), col)
+ else:
+ self.base_columns.insert(0, 'bundle', bundle_col)
+ if len(match_maker.filter_data['bundle_streams']) > 1:
+ self.base_columns.insert(0, 'bundle_stream', bundle_stream_col)
+ self.base_columns.insert(0, 'tag', tag_col)
+
+ tag = Column()
+
+ def render_bundle_stream(self, record):
+ bundle_streams = set(tr.bundle.bundle_stream for tr in record.test_runs)
+ links = []
+ for bs in sorted(bundle_streams, key=operator.attrgetter('pathname')):
+ links.append('<a href="%s">%s</a>' % (
+ bs.get_absolute_url(), escape(bs.pathname)))
+ return mark_safe('<br />'.join(links))
+ bundle_stream = Column(mark_safe("Bundle Stream(s)"))
+
+ def render_bundle(self, record):
+ bundles = set(tr.bundle for tr in record.test_runs)
+ links = []
+ for b in sorted(bundles, key=operator.attrgetter('uploaded_on')):
+ links.append('<a href="%s">%s</a>' % (
+ b.get_absolute_url(), escape(b.content_filename)))
+ return mark_safe('<br />'.join(links))
+ bundle = Column(mark_safe("Bundle(s)"))
+
+ passes = Column(accessor='pass_count')
+ total = Column(accessor='result_count')
+
+ def get_queryset(self, user, filter):
+ return filter.get_test_runs(user)
+
+ datatable_opts = {
+ "sPaginationType": "full_numbers",
+ "iDisplayLength": 25,
+ "bSort": False,
+ }
+
+
+class FilterPreviewTable(FilterTable):
+ def get_queryset(self, user, form):
+ return form.get_test_runs(user)
+
+ datatable_opts = FilterTable.datatable_opts.copy()
+ datatable_opts.update({
+ "iDisplayLength": 10,
+ })
=== added file 'dashboard_app/views/filters/views.py'
@@ -0,0 +1,249 @@
+# Copyright (C) 2010-2011 Linaro Limited
+#
+# Author: Michael Hudson-Doyle <michael.hudson@linaro.org>
+#
+# This file is part of Launch Control.
+#
+# Launch Control is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# Launch Control is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+
+from django.contrib.auth.decorators import login_required
+from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import PermissionDenied, ValidationError
+from django.core.urlresolvers import reverse
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+from lava_server.bread_crumbs import (
+ BreadCrumb,
+ BreadCrumbTrail,
+)
+
+from dashboard_app.models import (
+ NamedAttribute,
+ Test,
+ TestCase,
+ TestRun,
+ TestRunFilter,
+ TestRunFilterSubscription,
+ )
+from dashboard_app.views import index
+from dashboard_app.views.filters.forms import (
+ TestRunFilterForm,
+ TestRunFilterSubscriptionForm,
+ )
+from dashboard_app.views.filters.tables import (
+ FilterTable,
+ FilterPreviewTable,
+ PublicFiltersTable,
+ UserFiltersTable,
+ )
+
+
+@BreadCrumb("Filters and Subscriptions", parent=index)
+def filters_list(request):
+ public_filters_table = PublicFiltersTable("public-filters", None)
+ if request.user.is_authenticated():
+ public_filters_table.user = request.user
+ user_filters_table = UserFiltersTable("user-filters", None, params=(request.user,))
+ user_filters_table.user = request.user
+ else:
+ user_filters_table = None
+ del public_filters_table.base_columns['subscription']
+
+ return render_to_response(
+ 'dashboard_app/filters_list.html', {
+ 'user_filters_table': user_filters_table,
+ 'public_filters_table': public_filters_table,
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ filters_list),
+ }, RequestContext(request)
+ )
+
+
+def filter_json(request, username, name):
+ filter = TestRunFilter.objects.get(owner__username=username, name=name)
+ return FilterTable.json(request, params=(request.user, filter))
+
+
+
+def filter_preview_json(request):
+ try:
+ filter = TestRunFilter.objects.get(owner=request.user, name=request.GET['name'])
+ except TestRunFilter.DoesNotExist:
+ filter = None
+ form = TestRunFilterForm(request.user, request.GET, instance=filter)
+ if not form.is_valid():
+ raise ValidationError(str(form.errors))
+ return FilterPreviewTable.json(request, params=(request.user, form))
+
+
+@BreadCrumb("Filter ~{username}/{name}", parent=filters_list, needs=['username', 'name'])
+def filter_detail(request, username, name):
+ filter = TestRunFilter.objects.get(owner__username=username, name=name)
+ if not filter.public and filter.owner != request.user:
+ raise PermissionDenied()
+ if not request.user.is_authenticated():
+ subscription = None
+ else:
+ try:
+ subscription = TestRunFilterSubscription.objects.get(
+ user=request.user, filter=filter)
+ except TestRunFilterSubscription.DoesNotExist:
+ subscription = None
+ return render_to_response(
+ 'dashboard_app/filter_detail.html', {
+ 'filter': filter,
+ 'subscription': subscription,
+ 'filter_table': FilterTable(
+ "filter-table",
+ reverse(filter_json, kwargs=dict(username=username, name=name)),
+ params=(request.user, filter)),
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ filter_detail, name=name, username=username),
+ }, RequestContext(request)
+ )
+
+
+@BreadCrumb("Manage Subscription", parent=filter_detail, needs=['name', 'username'])
+@login_required
+def filter_subscribe(request, username, name):
+ filter = TestRunFilter.objects.get(owner__username=username, name=name)
+ if not filter.public and filter.owner != request.user:
+ raise PermissionDenied()
+ try:
+ subscription = TestRunFilterSubscription.objects.get(
+ user=request.user, filter=filter)
+ except TestRunFilterSubscription.DoesNotExist:
+ subscription = None
+ if request.method == "POST":
+ form = TestRunFilterSubscriptionForm(
+ filter, request.user, request.POST, instance=subscription)
+ if form.is_valid():
+ if 'unsubscribe' in request.POST:
+ subscription.delete()
+ else:
+ form.save()
+ return HttpResponseRedirect(filter.get_absolute_url())
+ else:
+ form = TestRunFilterSubscriptionForm(
+ filter, request.user, instance=subscription)
+ return render_to_response(
+ 'dashboard_app/filter_subscribe.html', {
+ 'filter': filter,
+ 'form': form,
+ 'subscription': subscription,
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ filter_subscribe, name=name, username=username),
+ }, RequestContext(request)
+ )
+
+
+def filter_form(request, bread_crumb_trail, instance=None):
+ if request.method == 'POST':
+ form = TestRunFilterForm(request.user, request.POST, instance=instance)
+
+ if form.is_valid():
+ if 'save' in request.POST:
+ filter = form.save()
+ return HttpResponseRedirect(filter.get_absolute_url())
+ else:
+ c = request.POST.copy()
+ c.pop('csrfmiddlewaretoken', None)
+ return render_to_response(
+ 'dashboard_app/filter_preview.html', {
+ 'bread_crumb_trail': bread_crumb_trail,
+ 'form': form,
+ 'table': FilterPreviewTable(
+ 'filter-preview',
+ reverse(filter_preview_json) + '?' + c.urlencode(),
+ params=(request.user, form)),
+ }, RequestContext(request))
+ else:
+ form = TestRunFilterForm(request.user, instance=instance)
+
+ return render_to_response(
+ 'dashboard_app/filter_add.html', {
+ 'bread_crumb_trail': bread_crumb_trail,
+ 'form': form,
+ }, RequestContext(request))
+
+
+@BreadCrumb("Add new filter", parent=filters_list)
+def filter_add(request):
+ return filter_form(
+ request,
+ BreadCrumbTrail.leading_to(filter_add))
+
+
+@BreadCrumb("Edit", parent=filter_detail, needs=['name', 'username'])
+def filter_edit(request, username, name):
+ if request.user.username != username:
+ raise PermissionDenied()
+ filter = TestRunFilter.objects.get(owner=request.user, name=name)
+ return filter_form(
+ request,
+ BreadCrumbTrail.leading_to(filter_edit, name=name, username=username),
+ instance=filter)
+
+
+@BreadCrumb("Delete", parent=filter_detail, needs=['name', 'username'])
+def filter_delete(request, username, name):
+ if request.user.username != username:
+ raise PermissionDenied()
+ filter = TestRunFilter.objects.get(owner=request.user, name=name)
+ if request.method == "POST":
+ if 'yes' in request.POST:
+ filter.delete()
+ return HttpResponseRedirect(reverse(filters_list))
+ else:
+ return HttpResponseRedirect(filter.get_absolute_url())
+ return render_to_response(
+ 'dashboard_app/filter_delete.html', {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(filter_delete, name=name, username=username),
+ 'filter': filter,
+ }, RequestContext(request))
+
+
+def filter_add_cases_for_test_json(request):
+ test = Test.objects.get(test_id=request.GET['test'])
+ result = TestCase.objects.filter(test=test).order_by('test_case_id').values('test_case_id', 'id')
+ return HttpResponse(
+ json.dumps(list(result)),
+ mimetype='application/json')
+
+
+def filter_attr_name_completion_json(request):
+ term = request.GET['term']
+ content_type_id = ContentType.objects.get_for_model(TestRun).id
+ result = NamedAttribute.objects.filter(
+ name__startswith=term, content_type_id=content_type_id
+ ).distinct().order_by('name').values_list('name', flat=True)
+ return HttpResponse(
+ json.dumps(list(result)),
+ mimetype='application/json')
+
+
+def filter_attr_value_completion_json(request):
+ name = request.GET['name']
+ term = request.GET['term']
+ content_type_id = ContentType.objects.get_for_model(TestRun).id
+ result = NamedAttribute.objects.filter(
+ name=name, content_type_id=content_type_id, value__startswith=term
+ ).distinct().order_by('value').values_list('value', flat=True)
+ return HttpResponse(
+ json.dumps(list(result)),
+ mimetype='application/json')
+
=== added file 'dashboard_app/views/images.py'
@@ -0,0 +1,157 @@
+# Copyright (C) 2010-2012 Linaro Limited
+#
+# Author: Michael Hudson-Doyle <michael.hudson@linaro.org>
+#
+# This file is part of Launch Control.
+#
+# Launch Control is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# Launch Control is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with Launch Control. If not, see <http://www.gnu.org/licenses/>.
+
+
+from django.http import HttpResponseRedirect
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template import RequestContext
+from django.views.decorators.http import require_POST
+
+from lava_server.bread_crumbs import (
+ BreadCrumb,
+ BreadCrumbTrail,
+)
+
+from dashboard_app.models import (
+ LaunchpadBug,
+ Image,
+ ImageSet,
+ TestRun,
+)
+from dashboard_app.views import index
+
+
+@BreadCrumb("Image Reports", parent=index)
+def image_report_list(request):
+ imagesets = ImageSet.objects.filter()
+ imagesets_data = []
+ for imageset in imagesets:
+ images_data = []
+ for image in imageset.images.all():
+ # Migration hack: Image.filter cannot be auto populated, so ignore
+ # images that have not been migrated to filters for now.
+ if image.filter:
+ image_data = {
+ 'name': image.name,
+ 'bundle_count': image.filter.get_test_runs(request.user).count(),
+ 'link': image.name,
+ }
+ images_data.append(image_data)
+ images_data.sort(key=lambda d:d['name'])
+ imageset_data = {
+ 'name': imageset.name,
+ 'images': images_data,
+ }
+ imagesets_data.append(imageset_data)
+ imagesets_data.sort(key=lambda d:d['name'])
+ return render_to_response(
+ "dashboard_app/image-reports.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(image_report_list),
+ 'imagesets': imagesets_data,
+ }, RequestContext(request))
+
+
+@BreadCrumb("{name}", parent=image_report_list, needs=['name'])
+def image_report_detail(request, name):
+
+ image = Image.objects.get(name=name)
+ matches = image.filter.get_test_runs(request.user, prefetch_related=['launchpad_bugs'])[:50]
+
+ build_number_to_cols = {}
+
+ test_run_names = set()
+
+ for match in matches:
+ for test_run in match.test_runs:
+ name = test_run.test.test_id
+ denorm = test_run.denormalization
+ if denorm.count_pass == denorm.count_all():
+ cls = 'present pass'
+ else:
+ cls = 'present fail'
+ bug_ids = sorted([b.bug_id for b in test_run.launchpad_bugs.all()])
+ test_run_data = dict(
+ present=True,
+ cls=cls,
+ uuid=test_run.analyzer_assigned_uuid,
+ passes=denorm.count_pass,
+ total=denorm.count_all(),
+ link=test_run.get_permalink(),
+ bug_ids=bug_ids,
+ )
+ if match.tag not in build_number_to_cols:
+ # This assumes 1 bundle per match...
+ build_number_to_cols[match.tag] = {
+ 'test_runs': {},
+ 'number': match.tag,
+ 'date': test_run.bundle.uploaded_on,
+ 'link': test_run.bundle.get_absolute_url(),
+ }
+ build_number_to_cols[match.tag]['test_runs'][name] = test_run_data
+ if name != 'lava':
+ test_run_names.add(name)
+
+
+ test_run_names = sorted(test_run_names)
+ test_run_names.insert(0, 'lava')
+
+ cols = [c for n, c in sorted(build_number_to_cols.items())]
+
+ table_data = []
+
+ for test_run_name in test_run_names:
+ row_data = []
+ for col in cols:
+ test_run_data = col['test_runs'].get(test_run_name)
+ if not test_run_data:
+ test_run_data = dict(
+ present=False,
+ cls='missing',
+ )
+ row_data.append(test_run_data)
+ table_data.append(row_data)
+
+ return render_to_response(
+ "dashboard_app/image-report.html", {
+ 'bread_crumb_trail': BreadCrumbTrail.leading_to(
+ image_report_detail, name=image.name),
+ 'image': image,
+ 'cols': cols,
+ 'table_data': table_data,
+ 'test_run_names': test_run_names,
+ }, RequestContext(request))
+
+
+@require_POST
+def link_bug_to_testrun(request):
+ testrun = get_object_or_404(TestRun, analyzer_assigned_uuid=request.POST['uuid'])
+ bug_id = request.POST['bug']
+ lpbug = LaunchpadBug.objects.get_or_create(bug_id=int(bug_id))[0]
+ testrun.launchpad_bugs.add(lpbug)
+ testrun.save()
+ return HttpResponseRedirect(request.POST['back'])
+
+
+@require_POST
+def unlink_bug_and_testrun(request):
+ testrun = get_object_or_404(TestRun, analyzer_assigned_uuid=request.POST['uuid'])
+ bug_id = request.POST['bug']
+ lpbug = LaunchpadBug.objects.get_or_create(bug_id=int(bug_id))[0]
+ testrun.launchpad_bugs.remove(lpbug)
+ testrun.save()
+ return HttpResponseRedirect(request.POST['back'])