summaryrefslogtreecommitdiff
path: root/releng
diff options
context:
space:
mode:
Diffstat (limited to 'releng')
-rw-r--r--releng/admin.py4
-rw-r--r--releng/management/commands/syncisos.py16
-rw-r--r--releng/migrations/0002_auto__add_field_iso_removed.py99
-rw-r--r--releng/models.py37
-rw-r--r--releng/urls.py1
-rw-r--r--releng/views.py71
6 files changed, 185 insertions, 43 deletions
diff --git a/releng/admin.py b/releng/admin.py
index be5e211f..e1411b84 100644
--- a/releng/admin.py
+++ b/releng/admin.py
@@ -5,8 +5,8 @@ from .models import (Architecture, BootType, Bootloader, ClockChoice,
Test)
class IsoAdmin(admin.ModelAdmin):
- list_display = ('name', 'created', 'active')
- list_filter = ('active',)
+ list_display = ('name', 'created', 'active', 'removed')
+ list_filter = ('active', 'created')
class TestAdmin(admin.ModelAdmin):
list_display = ('user_name', 'user_email', 'created', 'ip_address',
diff --git a/releng/management/commands/syncisos.py b/releng/management/commands/syncisos.py
index ba174131..270c6c34 100644
--- a/releng/management/commands/syncisos.py
+++ b/releng/management/commands/syncisos.py
@@ -1,3 +1,4 @@
+from datetime import datetime
import re
import urllib
from HTMLParser import HTMLParser, HTMLParseError
@@ -33,19 +34,28 @@ class IsoListParser(HTMLParser):
raise CommandError('Couldn\'t parse "%s"' % url)
class Command(BaseCommand):
- help = 'Gets new isos from %s' % settings.ISO_LIST_URL
+ help = 'Gets new ISOs from %s' % settings.ISO_LIST_URL
def handle(self, *args, **options):
parser = IsoListParser()
isonames = Iso.objects.values_list('name', flat=True)
active_isos = parser.parse(settings.ISO_LIST_URL)
- # create any names that don't already exist
for iso in active_isos:
+ # create any names that don't already exist
if iso not in isonames:
new = Iso(name=iso, active=True)
new.save()
+ # update those that do if they were marked inactive
+ else:
+ existing = Iso.objects.get(name=iso)
+ if not existing.active:
+ existing.active = True
+ existing.removed = None
+ existing.save()
+ now = datetime.utcnow()
# and then mark all other names as no longer active
- Iso.objects.exclude(name__in=active_isos).update(active=False)
+ Iso.objects.filter(active=True).exclude(name__in=active_isos).update(
+ active=False, removed=now)
# vim: set ts=4 sw=4 et:
diff --git a/releng/migrations/0002_auto__add_field_iso_removed.py b/releng/migrations/0002_auto__add_field_iso_removed.py
new file mode 100644
index 00000000..d5cd09c8
--- /dev/null
+++ b/releng/migrations/0002_auto__add_field_iso_removed.py
@@ -0,0 +1,99 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+ db.add_column('releng_iso', 'removed', self.gf('django.db.models.fields.DateTimeField')(default=None, null=True, blank=True), keep_default=False)
+
+ def backwards(self, orm):
+ db.delete_column('releng_iso', 'removed')
+
+ models = {
+ 'releng.architecture': {
+ 'Meta': {'object_name': 'Architecture'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.bootloader': {
+ 'Meta': {'object_name': 'Bootloader'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.boottype': {
+ 'Meta': {'object_name': 'BootType'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.clockchoice': {
+ 'Meta': {'object_name': 'ClockChoice'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.filesystem': {
+ 'Meta': {'object_name': 'Filesystem'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.hardwaretype': {
+ 'Meta': {'object_name': 'HardwareType'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.installtype': {
+ 'Meta': {'object_name': 'InstallType'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.iso': {
+ 'Meta': {'object_name': 'Iso'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'removed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'})
+ },
+ 'releng.isotype': {
+ 'Meta': {'object_name': 'IsoType'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.module': {
+ 'Meta': {'object_name': 'Module'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.source': {
+ 'Meta': {'object_name': 'Source'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+ },
+ 'releng.test': {
+ 'Meta': {'object_name': 'Test'},
+ 'architecture': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.Architecture']"}),
+ 'boot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.BootType']"}),
+ 'bootloader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.Bootloader']"}),
+ 'clock_choice': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.ClockChoice']"}),
+ 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {}),
+ 'filesystem': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.Filesystem']"}),
+ 'hardware_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.HardwareType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'install_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.InstallType']"}),
+ 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}),
+ 'iso': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.Iso']"}),
+ 'iso_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.IsoType']"}),
+ 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['releng.Module']", 'null': 'True', 'blank': 'True'}),
+ 'rollback_filesystem': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'rollback_test_set'", 'null': 'True', 'to': "orm['releng.Filesystem']"}),
+ 'rollback_modules': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rollback_test_set'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['releng.Module']"}),
+ 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['releng.Source']"}),
+ 'success': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+ 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '500'})
+ }
+ }
+
+ complete_apps = ['releng']
diff --git a/releng/models.py b/releng/models.py
index 5510db6a..56187766 100644
--- a/releng/models.py
+++ b/releng/models.py
@@ -1,3 +1,4 @@
+from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.signals import pre_save
@@ -9,52 +10,34 @@ class IsoOption(models.Model):
def __unicode__(self):
return self.name
- def get_test_result(self, success):
- try:
- return self.test_set.filter(success=success).select_related(
- 'iso').latest('iso__id').iso
- except Test.DoesNotExist:
- return None
-
- def get_last_success(self):
- return self.get_test_result(True)
-
- def get_last_failure(self):
- return self.get_test_result(False)
-
class Meta:
abstract = True
class RollbackOption(IsoOption):
- def get_rollback_test_result(self, success):
- try:
- return self.rollback_test_set.filter(success=success).select_related(
- 'iso').latest('iso__id').iso
- except Test.DoesNotExist:
- return None
-
- def get_last_rollback_success(self):
- return self.get_rollback_test_result(True)
-
- def get_last_rollback_failure(self):
- return self.get_rollback_test_result(False)
-
class Meta:
abstract = True
class Iso(models.Model):
name = models.CharField(max_length=255)
created = models.DateTimeField(editable=False)
+ removed = models.DateTimeField(null=True, blank=True, default=None)
active = models.BooleanField(default=True)
+ def get_absolute_url(self):
+ return reverse('releng-results-iso', args=[self.pk])
+
def __unicode__(self):
return self.name
+ class Meta:
+ verbose_name = 'ISO'
+
class Architecture(IsoOption):
pass
class IsoType(IsoOption):
- pass
+ class Meta:
+ verbose_name = 'ISO type'
class BootType(IsoOption):
pass
diff --git a/releng/urls.py b/releng/urls.py
index 4a125dff..239ad02b 100644
--- a/releng/urls.py
+++ b/releng/urls.py
@@ -6,6 +6,7 @@ feedback_patterns = patterns('releng.views',
(r'^thanks/$', 'submit_test_thanks', {}, 'releng-test-thanks'),
(r'^iso/(?P<iso_id>\d+)/$', 'test_results_iso', {}, 'releng-results-iso'),
(r'^(?P<option>.+)/(?P<value>\d+)/$','test_results_for', {}, 'releng-results-for'),
+ (r'^iso/overview/$', 'iso_overview', {}, 'releng-iso-overview'),
)
urlpatterns = patterns('',
diff --git a/releng/views.py b/releng/views.py
index 1d4a0b5e..2b3d0936 100644
--- a/releng/views.py
+++ b/releng/views.py
@@ -1,5 +1,6 @@
from django import forms
from django.conf import settings
+from django.db.models import Count, Max
from django.http import Http404
from django.shortcuts import get_object_or_404, redirect
from django.views.generic.simple import direct_to_template
@@ -80,22 +81,53 @@ def calculate_option_overview(field_name):
is_rollback = field_name.startswith('rollback_')
option = {
'option': model,
- 'name': field_name,
+ 'name': model._meta.verbose_name,
'is_rollback': is_rollback,
'values': []
}
+ if not is_rollback:
+ successes = dict(model.objects.values_list('pk').filter(
+ test__success=True).annotate(latest=Max('test__iso__id')))
+ failures = dict(model.objects.values_list('pk').filter(
+ test__success=False).annotate(latest=Max('test__iso__id')))
+ else:
+ successes = dict(model.objects.values_list('pk').filter(
+ rollback_test_set__success=True).annotate(
+ latest=Max('rollback_test_set__iso__id')))
+ failures = dict(model.objects.values_list('pk').filter(
+ rollback_test_set__success=False).annotate(
+ latest=Max('rollback_test_set__iso__id')))
+
for value in model.objects.all():
- data = { 'value': value }
- if is_rollback:
- data['success'] = value.get_last_rollback_success()
- data['failure'] = value.get_last_rollback_failure()
- else:
- data['success'] = value.get_last_success()
- data['failure'] = value.get_last_failure()
+ data = {
+ 'value': value,
+ 'success': successes.get(value.pk),
+ 'failure': failures.get(value.pk),
+ }
option['values'].append(data)
return option
+def options_fetch_iso(options):
+ '''Replaces the Iso PK with a full Iso model object in a list of options
+ used on the overview page. We do it this way to only have to query the Iso
+ table once rather than once per option.'''
+ # collect all necessary Iso PKs
+ all_pks = set()
+ for option in options:
+ all_pks.update(v['success'] for v in option['values'])
+ all_pks.update(v['failure'] for v in option['values'])
+
+ all_pks.discard(None)
+ all_isos = Iso.objects.in_bulk(all_pks)
+
+ for option in options:
+ for value in option['values']:
+ value['success'] = all_isos.get(value['success'])
+ value['failure'] = all_isos.get(value['failure'])
+
+ return options
+
def test_results_overview(request):
# data structure produced:
# [ { option, name, is_rollback, values: [ { value, success, failure } ... ] } ... ]
@@ -106,6 +138,8 @@ def test_results_overview(request):
for field in fields:
all_options.append(calculate_option_overview(field))
+ all_options = options_fetch_iso(all_options)
+
context = {
'options': all_options,
'iso_url': settings.ISO_LIST_URL,
@@ -114,7 +148,7 @@ def test_results_overview(request):
def test_results_iso(request, iso_id):
iso = get_object_or_404(Iso, pk=iso_id)
- test_list = iso.test_set.all()
+ test_list = iso.test_set.select_related()
context = {
'iso_name': iso.name,
'test_list': test_list
@@ -125,10 +159,12 @@ def test_results_for(request, option, value):
if option not in Test._meta.get_all_field_names():
raise Http404
option_model = getattr(Test, option).field.rel.to
+ option_model.verbose_name = option_model._meta.verbose_name
real_value = get_object_or_404(option_model, pk=value)
- test_list = real_value.test_set.order_by('-iso__name', '-pk')
+ test_list = real_value.test_set.select_related().order_by(
+ '-iso__name', '-pk')
context = {
- 'option': option,
+ 'option': option_model,
'value': real_value,
'value_id': value,
'test_list': test_list
@@ -138,4 +174,17 @@ def test_results_for(request, option, value):
def submit_test_thanks(request):
return direct_to_template(request, "releng/thanks.html", None)
+def iso_overview(request):
+ isos = Iso.objects.all().order_by('-pk')
+ successes = dict(Iso.objects.values_list('pk').filter(test__success=True).annotate(ct=Count('test')))
+ failures = dict(Iso.objects.values_list('pk').filter(test__success=False).annotate(ct=Count('test')))
+ for iso in isos:
+ iso.successes = successes.get(iso.pk, 0)
+ iso.failures = failures.get(iso.pk, 0)
+
+ context = {
+ 'isos': isos
+ }
+ return direct_to_template(request, 'releng/iso_overview.html', context)
+
# vim: set ts=4 sw=4 et: