summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan McGee <dan@archlinux.org>2011-04-28 18:01:01 -0500
committerDan McGee <dan@archlinux.org>2011-04-28 18:01:01 -0500
commitb87a2c4864db79b29df1e2ea3ac3c3c2759f05fa (patch)
tree97defc7569c5e52161681576296ba90bff47f62a
parent174d04ad0334b1c441bc0237e3e2ed8f581575ef (diff)
parent461d27d75ffb36ad4e7f428b8b6722ae1d3afb98 (diff)
Merge branch 'releng'
-rw-r--r--media/archweb.css5
-rw-r--r--releng/__init__.py0
-rw-r--r--releng/admin.py18
-rw-r--r--releng/fixtures/architecture.json30
-rw-r--r--releng/fixtures/bootloaders.json23
-rw-r--r--releng/fixtures/boottype.json23
-rw-r--r--releng/fixtures/clockchoices.json23
-rw-r--r--releng/fixtures/filesystems.json23
-rw-r--r--releng/fixtures/hardware.json44
-rw-r--r--releng/fixtures/installtype.json30
-rw-r--r--releng/fixtures/isotypes.json16
-rw-r--r--releng/fixtures/modules.json86
-rw-r--r--releng/fixtures/source.json23
-rw-r--r--releng/management/__init__.py0
-rw-r--r--releng/management/commands/__init__.py0
-rw-r--r--releng/management/commands/syncisos.py48
-rw-r--r--releng/migrations/0001_initial.py258
-rw-r--r--releng/migrations/__init__.py0
-rw-r--r--releng/models.py121
-rw-r--r--releng/urls.py11
-rw-r--r--releng/views.py132
-rw-r--r--settings.py4
-rw-r--r--templates/public/index.html2
-rw-r--r--templates/releng/add.html20
-rw-r--r--templates/releng/result_list.html41
-rw-r--r--templates/releng/result_section.html26
-rw-r--r--templates/releng/results.html25
-rw-r--r--templates/releng/thanks.html13
-rw-r--r--urls.py3
29 files changed, 1047 insertions, 1 deletions
diff --git a/media/archweb.css b/media/archweb.css
index 4b0b9e89..504c8270 100644
--- a/media/archweb.css
+++ b/media/archweb.css
@@ -260,6 +260,11 @@ ul.admin-actions li { display: inline; padding-left: 1.5em; }
#dev-signoffs .signoff-no { color: red; }
#dev-signoffs .signed-username { color: #888; margin-left: 0.5em; }
+/* iso testing feedback form */
+#releng-feedback label { width: auto; display: inline; font-weight: normal; }
+#releng-feedback ul { padding-left: 1em; }
+#releng-feedback li { list-style: none; }
+
/* highlight current website in the navbar */
#archnavbar.anb-home ul li#anb-home a { color: white !important; }
#archnavbar.anb-packages ul li#anb-packages a { color: white !important; }
diff --git a/releng/__init__.py b/releng/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/releng/__init__.py
diff --git a/releng/admin.py b/releng/admin.py
new file mode 100644
index 00000000..10acaa98
--- /dev/null
+++ b/releng/admin.py
@@ -0,0 +1,18 @@
+from django.contrib import admin
+
+from .models import (Architecture, BootType, Bootloader, ClockChoice,
+ Filesystem, HardwareType, InstallType, Iso, IsoType, Module, Source)
+
+admin.site.register(Iso)
+admin.site.register(Architecture)
+admin.site.register(IsoType)
+admin.site.register(BootType)
+admin.site.register(HardwareType)
+admin.site.register(InstallType)
+admin.site.register(Source)
+admin.site.register(ClockChoice)
+admin.site.register(Filesystem)
+admin.site.register(Module)
+admin.site.register(Bootloader)
+
+# vim: set ts=4 sw=4 et:
diff --git a/releng/fixtures/architecture.json b/releng/fixtures/architecture.json
new file mode 100644
index 00000000..0bf9b8bf
--- /dev/null
+++ b/releng/fixtures/architecture.json
@@ -0,0 +1,30 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.architecture",
+ "fields": {
+ "name": "dual, option i686"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.architecture",
+ "fields": {
+ "name": "dual, option x86_64"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.architecture",
+ "fields": {
+ "name": "i686"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "releng.architecture",
+ "fields": {
+ "name": "x86_64"
+ }
+ }
+]
diff --git a/releng/fixtures/bootloaders.json b/releng/fixtures/bootloaders.json
new file mode 100644
index 00000000..bee02f2b
--- /dev/null
+++ b/releng/fixtures/bootloaders.json
@@ -0,0 +1,23 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.bootloader",
+ "fields": {
+ "name": "grub"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.bootloader",
+ "fields": {
+ "name": "syslinux"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.bootloader",
+ "fields": {
+ "name": "other/manual"
+ }
+ }
+]
diff --git a/releng/fixtures/boottype.json b/releng/fixtures/boottype.json
new file mode 100644
index 00000000..ed4636eb
--- /dev/null
+++ b/releng/fixtures/boottype.json
@@ -0,0 +1,23 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.boottype",
+ "fields": {
+ "name": "optical"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.boottype",
+ "fields": {
+ "name": "usb"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.boottype",
+ "fields": {
+ "name": "pxe"
+ }
+ }
+]
diff --git a/releng/fixtures/clockchoices.json b/releng/fixtures/clockchoices.json
new file mode 100644
index 00000000..f328801a
--- /dev/null
+++ b/releng/fixtures/clockchoices.json
@@ -0,0 +1,23 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.clockchoice",
+ "fields": {
+ "name": "unchanged"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.clockchoice",
+ "fields": {
+ "name": "configured manually"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.clockchoice",
+ "fields": {
+ "name": "NTP"
+ }
+ }
+]
diff --git a/releng/fixtures/filesystems.json b/releng/fixtures/filesystems.json
new file mode 100644
index 00000000..208f5c73
--- /dev/null
+++ b/releng/fixtures/filesystems.json
@@ -0,0 +1,23 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.filesystem",
+ "fields": {
+ "name": "autoprepare"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.filesystem",
+ "fields": {
+ "name": "manual"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.filesystem",
+ "fields": {
+ "name": "from config file"
+ }
+ }
+]
diff --git a/releng/fixtures/hardware.json b/releng/fixtures/hardware.json
new file mode 100644
index 00000000..a2bb9ec0
--- /dev/null
+++ b/releng/fixtures/hardware.json
@@ -0,0 +1,44 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.hardwaretype",
+ "fields": {
+ "name": "virtualbox"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.hardwaretype",
+ "fields": {
+ "name": "qemu"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.hardwaretype",
+ "fields": {
+ "name": "intel i686"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "releng.hardwaretype",
+ "fields": {
+ "name": "intel x86_64"
+ }
+ },
+ {
+ "pk": 5,
+ "model": "releng.hardwaretype",
+ "fields": {
+ "name": "amd i686"
+ }
+ },
+ {
+ "pk": 6,
+ "model": "releng.hardwaretype",
+ "fields": {
+ "name": "amd x86_64"
+ }
+ }
+]
diff --git a/releng/fixtures/installtype.json b/releng/fixtures/installtype.json
new file mode 100644
index 00000000..7fa21fc1
--- /dev/null
+++ b/releng/fixtures/installtype.json
@@ -0,0 +1,30 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.installtype",
+ "fields": {
+ "name": "interactive install"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.installtype",
+ "fields": {
+ "name": "automatic install generic example"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.installtype",
+ "fields": {
+ "name": "automatic install fancy example"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "releng.installtype",
+ "fields": {
+ "name": "automatic install custom config (specify in comments)"
+ }
+ }
+]
diff --git a/releng/fixtures/isotypes.json b/releng/fixtures/isotypes.json
new file mode 100644
index 00000000..a529b181
--- /dev/null
+++ b/releng/fixtures/isotypes.json
@@ -0,0 +1,16 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.isotype",
+ "fields": {
+ "name": "core"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.isotype",
+ "fields": {
+ "name": "net"
+ }
+ }
+]
diff --git a/releng/fixtures/modules.json b/releng/fixtures/modules.json
new file mode 100644
index 00000000..9cdf1a8d
--- /dev/null
+++ b/releng/fixtures/modules.json
@@ -0,0 +1,86 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.module",
+ "fields": {
+ "name": "lvm2"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.module",
+ "fields": {
+ "name": "dm_crypt"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.module",
+ "fields": {
+ "name": "softraid"
+ }
+ },
+ {
+ "pk": 4,
+ "model": "releng.module",
+ "fields": {
+ "name": "nilfs2"
+ }
+ },
+ {
+ "pk": 5,
+ "model": "releng.module",
+ "fields": {
+ "name": "btrfs"
+ }
+ },
+ {
+ "pk": 6,
+ "model": "releng.module",
+ "fields": {
+ "name": "ext2"
+ }
+ },
+ {
+ "pk": 7,
+ "model": "releng.module",
+ "fields": {
+ "name": "ext3"
+ }
+ },
+ {
+ "pk": 8,
+ "model": "releng.module",
+ "fields": {
+ "name": "ext4"
+ }
+ },
+ {
+ "pk": 9,
+ "model": "releng.module",
+ "fields": {
+ "name": "swap"
+ }
+ },
+ {
+ "pk": 10,
+ "model": "releng.module",
+ "fields": {
+ "name": "xfs"
+ }
+ },
+ {
+ "pk": 11,
+ "model": "releng.module",
+ "fields": {
+ "name": "jfs"
+ }
+ },
+ {
+ "pk": 12,
+ "model": "releng.module",
+ "fields": {
+ "name": "reiserFS"
+ }
+ }
+]
diff --git a/releng/fixtures/source.json b/releng/fixtures/source.json
new file mode 100644
index 00000000..2622f4c1
--- /dev/null
+++ b/releng/fixtures/source.json
@@ -0,0 +1,23 @@
+[
+ {
+ "pk": 1,
+ "model": "releng.source",
+ "fields": {
+ "name": "net install manual networking config (Check that it works + rc.conf, resolv.conf, mirrorlist)"
+ }
+ },
+ {
+ "pk": 2,
+ "model": "releng.source",
+ "fields": {
+ "name": "net install dhcp (Check that it works + rc.conf)"
+ }
+ },
+ {
+ "pk": 3,
+ "model": "releng.source",
+ "fields": {
+ "name": "core"
+ }
+ }
+]
diff --git a/releng/management/__init__.py b/releng/management/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/releng/management/__init__.py
diff --git a/releng/management/commands/__init__.py b/releng/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/releng/management/commands/__init__.py
diff --git a/releng/management/commands/syncisos.py b/releng/management/commands/syncisos.py
new file mode 100644
index 00000000..247b01cd
--- /dev/null
+++ b/releng/management/commands/syncisos.py
@@ -0,0 +1,48 @@
+import re
+import urllib
+from HTMLParser import HTMLParser, HTMLParseError
+
+from django.conf import settings
+from django.core.management.base import BaseCommand, CommandError
+
+from releng.models import Iso
+
+class IsoListParser(HTMLParser):
+ def __init__(self):
+ HTMLParser.__init__(self)
+
+ self.hyperlinks = []
+ self.url_re = re.compile('(?!\.{2})/$')
+
+ def handle_starttag(self, tag, attrs):
+ if tag == 'a':
+ for name, value in attrs:
+ if name == "href":
+ if value != '../' and self.url_re.search(value) != None:
+ self.hyperlinks.append(value[:len(value)-1])
+
+ def parse(self, url):
+ try:
+ remote_file = urllib.urlopen(url)
+ data = remote_file.read()
+ remote_file.close()
+ self.feed(data)
+ self.close()
+ return self.hyperlinks
+ except HTMLParseError:
+ raise CommandError('Couldn\'t parse "%s"' % url)
+
+class Command(BaseCommand):
+ 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)
+ new_isos = parser.parse(settings.ISO_LIST_URL)
+
+ for iso in new_isos:
+ if iso not in isonames:
+ new = Iso(name=iso)
+ new.save()
+
+# vim: set ts=4 sw=4 et:
diff --git a/releng/migrations/0001_initial.py b/releng/migrations/0001_initial.py
new file mode 100644
index 00000000..91fab8b7
--- /dev/null
+++ b/releng/migrations/0001_initial.py
@@ -0,0 +1,258 @@
+# 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):
+
+ # Adding model 'Iso'
+ db.create_table('releng_iso', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')()),
+ ('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
+ ))
+ db.send_create_signal('releng', ['Iso'])
+
+ # Adding model 'Architecture'
+ db.create_table('releng_architecture', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['Architecture'])
+
+ # Adding model 'IsoType'
+ db.create_table('releng_isotype', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['IsoType'])
+
+ # Adding model 'BootType'
+ db.create_table('releng_boottype', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['BootType'])
+
+ # Adding model 'HardwareType'
+ db.create_table('releng_hardwaretype', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['HardwareType'])
+
+ # Adding model 'InstallType'
+ db.create_table('releng_installtype', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['InstallType'])
+
+ # Adding model 'Source'
+ db.create_table('releng_source', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['Source'])
+
+ # Adding model 'ClockChoice'
+ db.create_table('releng_clockchoice', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['ClockChoice'])
+
+ # Adding model 'Filesystem'
+ db.create_table('releng_filesystem', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['Filesystem'])
+
+ # Adding model 'Module'
+ db.create_table('releng_module', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['Module'])
+
+ # Adding model 'Bootloader'
+ db.create_table('releng_bootloader', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=200)),
+ ))
+ db.send_create_signal('releng', ['Bootloader'])
+
+ # Adding model 'Test'
+ db.create_table('releng_test', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('user_name', self.gf('django.db.models.fields.CharField')(max_length=500)),
+ ('user_email', self.gf('django.db.models.fields.EmailField')(max_length=75)),
+ ('ip_address', self.gf('django.db.models.fields.IPAddressField')(max_length=15)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')()),
+ ('iso', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.Iso'])),
+ ('architecture', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.Architecture'])),
+ ('iso_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.IsoType'])),
+ ('boot_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.BootType'])),
+ ('hardware_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.HardwareType'])),
+ ('install_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.InstallType'])),
+ ('source', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.Source'])),
+ ('clock_choice', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.ClockChoice'])),
+ ('filesystem', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.Filesystem'])),
+ ('bootloader', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['releng.Bootloader'])),
+ ('rollback_filesystem', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='rollback_test_set', null=True, to=orm['releng.Filesystem'])),
+ ('success', self.gf('django.db.models.fields.BooleanField')(default=False)),
+ ('comments', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
+ ))
+ db.send_create_signal('releng', ['Test'])
+
+ # Adding M2M table for field modules on 'Test'
+ db.create_table('releng_test_modules', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('test', models.ForeignKey(orm['releng.test'], null=False)),
+ ('module', models.ForeignKey(orm['releng.module'], null=False))
+ ))
+ db.create_unique('releng_test_modules', ['test_id', 'module_id'])
+
+ # Adding M2M table for field rollback_modules on 'Test'
+ db.create_table('releng_test_rollback_modules', (
+ ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+ ('test', models.ForeignKey(orm['releng.test'], null=False)),
+ ('module', models.ForeignKey(orm['releng.module'], null=False))
+ ))
+ db.create_unique('releng_test_rollback_modules', ['test_id', 'module_id'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Iso'
+ db.delete_table('releng_iso')
+
+ # Deleting model 'Architecture'
+ db.delete_table('releng_architecture')
+
+ # Deleting model 'IsoType'
+ db.delete_table('releng_isotype')
+
+ # Deleting model 'BootType'
+ db.delete_table('releng_boottype')
+
+ # Deleting model 'HardwareType'
+ db.delete_table('releng_hardwaretype')
+
+ # Deleting model 'InstallType'
+ db.delete_table('releng_installtype')
+
+ # Deleting model 'Source'
+ db.delete_table('releng_source')
+
+ # Deleting model 'ClockChoice'
+ db.delete_table('releng_clockchoice')
+
+ # Deleting model 'Filesystem'
+ db.delete_table('releng_filesystem')
+
+ # Deleting model 'Module'
+ db.delete_table('releng_module')
+
+ # Deleting model 'Bootloader'
+ db.delete_table('releng_bootloader')
+
+ # Deleting model 'Test'
+ db.delete_table('releng_test')
+
+ # Removing M2M table for field modules on 'Test'
+ db.delete_table('releng_test_modules')
+
+ # Removing M2M table for field rollback_modules on 'Test'
+ db.delete_table('releng_test_rollback_modules')
+
+
+ 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'})
+ },
+ '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/migrations/__init__.py b/releng/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/releng/migrations/__init__.py
diff --git a/releng/models.py b/releng/models.py
new file mode 100644
index 00000000..dc297d5b
--- /dev/null
+++ b/releng/models.py
@@ -0,0 +1,121 @@
+from datetime import datetime
+
+from django.db import models
+from django.db.models import Max
+
+class IsoOption(models.Model):
+ name = models.CharField(max_length=200)
+
+ 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)
+ active = models.BooleanField(default=True)
+
+ def __unicode__(self):
+ return self.name
+
+class Architecture(IsoOption):
+ pass
+
+class IsoType(IsoOption):
+ pass
+
+class BootType(IsoOption):
+ pass
+
+class HardwareType(IsoOption):
+ pass
+
+class InstallType(IsoOption):
+ pass
+
+class Source(IsoOption):
+ pass
+
+class ClockChoice(IsoOption):
+ pass
+
+class Filesystem(RollbackOption):
+ pass
+
+class Module(RollbackOption):
+ pass
+
+class Bootloader(IsoOption):
+ pass
+
+class Test(models.Model):
+ user_name = models.CharField(max_length=500)
+ user_email = models.EmailField()
+ ip_address = models.IPAddressField()
+ created = models.DateTimeField(editable=False)
+
+ iso = models.ForeignKey(Iso)
+ architecture = models.ForeignKey(Architecture)
+ iso_type = models.ForeignKey(IsoType)
+ boot_type = models.ForeignKey(BootType)
+ hardware_type = models.ForeignKey(HardwareType)
+ install_type = models.ForeignKey(InstallType)
+ source = models.ForeignKey(Source)
+ clock_choice = models.ForeignKey(ClockChoice)
+ filesystem = models.ForeignKey(Filesystem)
+ modules = models.ManyToManyField(Module, null=True, blank=True)
+ bootloader = models.ForeignKey(Bootloader)
+ rollback_filesystem = models.ForeignKey(Filesystem,
+ related_name="rollback_test_set", null=True, blank=True)
+ rollback_modules = models.ManyToManyField(Module,
+ related_name="rollback_test_set", null=True, blank=True)
+
+ success = models.BooleanField()
+ comments = models.TextField(null=True, blank=True)
+
+def set_created_field(sender, **kwargs):
+ # We use this same callback for both Isos and Tests
+ obj = kwargs['instance']
+ if not obj.created:
+ obj.created = datetime.utcnow()
+
+from django.db.models.signals import pre_save
+
+pre_save.connect(set_created_field, sender=Iso,
+ dispatch_uid="releng.models")
+pre_save.connect(set_created_field, sender=Test,
+ dispatch_uid="releng.models")
+
+# vim: set ts=4 sw=4 et:
diff --git a/releng/urls.py b/releng/urls.py
new file mode 100644
index 00000000..f76a9593
--- /dev/null
+++ b/releng/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import patterns
+
+urlpatterns = patterns('releng.views',
+ (r'^$', 'test_results_overview', {}, 'releng-test-overview'),
+ (r'^submit/$', 'submit_test_result', {}, 'releng-test-submit'),
+ (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'),
+)
+
+# vim: set ts=4 sw=4 et:
diff --git a/releng/views.py b/releng/views.py
new file mode 100644
index 00000000..5cffb2f5
--- /dev/null
+++ b/releng/views.py
@@ -0,0 +1,132 @@
+from django import forms
+from django.conf import settings
+from django.http import Http404
+from django.shortcuts import get_object_or_404, redirect
+from django.views.generic.simple import direct_to_template
+
+from .models import (Architecture, BootType, Bootloader, ClockChoice,
+ Filesystem, HardwareType, InstallType, Iso, IsoType, Module, Source,
+ Test)
+
+def standard_field(model, help_text=None):
+ return forms.ModelChoiceField(queryset=model.objects.all(),
+ widget=forms.RadioSelect(), empty_label=None, help_text=help_text)
+
+class TestForm(forms.ModelForm):
+ iso = forms.ModelChoiceField(queryset=Iso.objects.filter(active=True))
+ architecture = standard_field(Architecture)
+ iso_type = standard_field(IsoType)
+ boot_type = standard_field(BootType)
+ hardware_type = standard_field(HardwareType)
+ install_type = standard_field(InstallType)
+ source = standard_field(Source)
+ clock_choice = standard_field(ClockChoice)
+ filesystem = standard_field(Filesystem,
+ help_text="Check the installed system, including fstab.")
+ modules = forms.ModelMultipleChoiceField(queryset=Module.objects.all(),
+ help_text="", widget=forms.CheckboxSelectMultiple(), required=False)
+ bootloader = standard_field(Bootloader)
+ rollback_filesystem = forms.ModelChoiceField(queryset=Filesystem.objects.all(),
+ help_text="If you did a rollback followed by a new attempt to setup " \
+ "your lockdevices/filesystems, select which option you took here.",
+ widget=forms.RadioSelect(), required=False)
+ rollback_modules = forms.ModelMultipleChoiceField(queryset=Module.objects.all(),
+ help_text="If you did a rollback followed b a new attempt to setup " \
+ "your lockdevices/filesystems, select which option you took here.",
+ widget=forms.CheckboxSelectMultiple(), required=False)
+ success = forms.BooleanField(help_text="Only check this if everything went fine. " \
+ "If you you ran into any errors please specify them in the " \
+ "comments.", required=False)
+ website = forms.CharField(label='',
+ widget=forms.TextInput(attrs={'style': 'display:none;'}), required=False)
+
+ class Meta:
+ model = Test
+ fields = ("user_name", "user_email", "iso", "architecture",
+ "iso_type", "boot_type", "hardware_type",
+ "install_type", "source", "clock_choice", "filesystem",
+ "modules", "bootloader", "rollback_filesystem",
+ "rollback_modules", "success", "comments")
+ widgets = {
+ "modules": forms.CheckboxSelectMultiple(),
+ }
+
+def submit_test_result(request):
+ if request.POST:
+ form = TestForm(request.POST)
+ if form.is_valid() and request.POST['website'] == '':
+ test = form.save(commit=False)
+ test.ip_address = request.META.get("REMOTE_ADDR", None)
+ test.save()
+ return redirect('releng-test-thanks')
+ else:
+ form = TestForm()
+
+ context = {'form': form}
+ return direct_to_template(request, 'releng/add.html', context)
+
+def calculate_option_overview(model, is_rollback=False):
+ option = {
+ 'option': model,
+ 'name': model._meta.verbose_name,
+ 'is_rollback': is_rollback,
+ 'values': []
+ }
+ 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()
+ option['values'].append(data)
+
+ return option
+
+def test_results_overview(request):
+ # data structure produced:
+ # [ { option, name, is_rollback, values: [ { value, success, failure } ... ] } ... ]
+ all_options = []
+ models = [ Architecture, IsoType, BootType, HardwareType, InstallType,
+ Source, ClockChoice, Filesystem, Module, Bootloader ]
+ for model in models:
+ all_options.append(calculate_option_overview(model))
+ # now handle rollback options
+ for model in [ Filesystem, Module ]:
+ all_options.append(calculate_option_overview(model, True))
+
+ print all_options
+ context = {
+ 'options': all_options,
+ 'iso_url': settings.ISO_LIST_URL,
+ }
+ return direct_to_template(request, 'releng/results.html', context)
+
+def test_results_iso(request, iso_id):
+ iso = get_object_or_404(Iso, pk=iso_id)
+ test_list = iso.test_set.all()
+ context = {
+ 'iso_name': iso.name,
+ 'test_list': test_list
+ }
+ return direct_to_template(request, 'releng/result_list.html', context)
+
+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
+ real_value = get_object_or_404(option_model, pk=value)
+ test_list = real_value.test_set.order_by("iso__name", "pk")
+ context = {
+ 'option': option,
+ 'value': real_value,
+ 'value_id': value,
+ 'test_list': test_list
+ }
+ return direct_to_template(request, 'releng/result_list.html', context)
+
+def submit_test_thanks(request):
+ return direct_to_template(request, "releng/thanks.html", None)
+
+# vim: set ts=4 sw=4 et:
diff --git a/settings.py b/settings.py
index 1d26d9eb..db1c93d3 100644
--- a/settings.py
+++ b/settings.py
@@ -104,6 +104,7 @@ INSTALLED_APPS = (
'devel',
'public',
'south', # database migration support
+ 'releng',
)
## Import local settings
@@ -123,4 +124,7 @@ if DEBUG_TOOLBAR:
INSTALLED_APPS = list(INSTALLED_APPS) + [ 'debug_toolbar' ]
+# URL to fetch a current list of available ISOs
+ISO_LIST_URL = 'http://releng.archlinux.org/isos/'
+
# vim: set ts=4 sw=4 et:
diff --git a/templates/public/index.html b/templates/public/index.html
index 132412f6..5b79a1fe 100644
--- a/templates/public/index.html
+++ b/templates/public/index.html
@@ -119,6 +119,8 @@
title="Arch communities in your native language">International Communities</a></li>
<li><a href="https://wiki.archlinux.org/index.php/Related_Projects"
title="Projects that are in some way related to Arch Linux">Related Projects</a></li>
+ <li><a href="{% url releng-test-overview %}"
+ title="Releng Testbuild Feedback">Releng Testbuild Feedback</a></li>
</ul>
<h4>Support</h4>
diff --git a/templates/releng/add.html b/templates/releng/add.html
new file mode 100644
index 00000000..3678532d
--- /dev/null
+++ b/templates/releng/add.html
@@ -0,0 +1,20 @@
+{% extends "base.html" %}
+
+{% block title %}Arch Linux - Test Result Entry{% endblock %}
+
+{% block content %}
+<div class="box">
+ <h2>Arch Releng Testbuild Feedback Entry</h2>
+
+ <p>This page allows you to submit feedback after testing and using a
+ release engineering install ISO. If you do not currently have feedback to
+ submit, you may want to take a look at the current
+ <a href="{% url releng-test-overview %}">results page</a>.</p>
+
+ <div id="releng-feedback"> <form action="" method="post">{% csrf_token %}
+ {{ form.as_p }}
+ <input type="submit" value="Submit" />
+ </form>
+ </div>
+</div>
+{% endblock %}
diff --git a/templates/releng/result_list.html b/templates/releng/result_list.html
new file mode 100644
index 00000000..b3ae025b
--- /dev/null
+++ b/templates/releng/result_list.html
@@ -0,0 +1,41 @@
+{% extends "base.html" %}
+
+{% block content %}
+<div class="box">
+ <h2>Results for:
+ {% if option %}{{ option|title }}: {{ value }}{% endif %}
+ {{ iso_name|default:"" }}
+ </h2>
+
+ <p><a href="{% url releng-test-overview %}">Go back to testing results</a></p>
+
+ <table id="releng-result" class="results">
+ <thead>
+ <tr>
+ <th>Iso</th>
+ <th>Submitted By</th>
+ <th>Date Submitted</th>
+ <th>Success</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for test in test_list %}
+ <tr>
+ <td>{{ test.iso.name }}</td>
+ <td>{{ test.user_name }}</td>
+ <td>{{ test.created|date }}</td>
+ <td>{{ test.success|yesno }}</td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</div>
+{% load cdn %}{% jquery %}
+<script type="text/javascript" src="/media/jquery.tablesorter.min.js"></script>
+<script type="text/javascript" src="/media/archweb.js"></script>
+<script type="text/javascript">
+$(document).ready(function() {
+ $(".results:not(:has(tbody tr.empty))").tablesorter({widgets: ['zebra']});
+});
+</script>
+{% endblock %}
diff --git a/templates/releng/result_section.html b/templates/releng/result_section.html
new file mode 100644
index 00000000..52f03339
--- /dev/null
+++ b/templates/releng/result_section.html
@@ -0,0 +1,26 @@
+<tr>
+ <td><h3>{% if option.is_rollback %}Rollback: {% endif %}{{ option.name|title }}</h3></td>
+</tr>
+{% for item in option.values %}
+<tr>
+ <td>
+ <a href="{% url releng-results-for option.name|lower item.value.pk %}">
+ {{ item.value.name|lower }}
+ </a>
+ </td>
+ <td>
+ {% if item.success %}
+ <a href="{% url releng-results-iso item.success.pk %}">
+ {{ item.success.name }}
+ </a>
+ {% else %}Never succeeded{% endif %}
+ </td>
+ <td>
+ {% if item.failure %}
+ <a href="{% url releng-results-iso item.failure.pk %}">
+ {{ item.failure.name }}
+ </a>
+ {% else %}Never failed{% endif %}
+ </td>
+</tr>
+{% endfor %}
diff --git a/templates/releng/results.html b/templates/releng/results.html
new file mode 100644
index 00000000..4aca1f10
--- /dev/null
+++ b/templates/releng/results.html
@@ -0,0 +1,25 @@
+{% extends "base.html" %}
+
+{% block title %}Arch Linux - Release Engineering Testbuild Results{% endblock %}
+
+{% block content %}
+<div class="box">
+ <h2>Release Engineering Testbuild Results</h2>
+
+ <p>This is a overview screen showing a test results matrix of release
+ engineering produced ISOs. Various options and configurations are shown
+ with last success and last failure results, if known. To help improve ISO
+ quality, you are encouraged to <a href="{% url releng-test-submit %}">give feedback</a>
+ if you have tested and used any ISOs. Both successful and failed results
+ are encouraged and welcome.</p>
+
+ <p>All ISOs referenced on this page are available from
+ <a href="{{ iso_url }}">{{ iso_url }}</a>.</p>
+
+ <table>
+ {% for option in options %}
+ {% include "releng/result_section.html" %}
+ {% endfor %}
+ </table>
+</div>
+{% endblock %}
diff --git a/templates/releng/thanks.html b/templates/releng/thanks.html
new file mode 100644
index 00000000..b261426d
--- /dev/null
+++ b/templates/releng/thanks.html
@@ -0,0 +1,13 @@
+{% extends "base.html" %}
+
+{% block title %}Arch Linux - Feedback - Thanks!{% endblock %}
+
+{% block content %}
+<div class="box">
+ <h2>Thanks!</h2>
+ <p>Thank you for taking the time to give us this information!
+ Your results have been succesfully added to our database.</p>
+ <p>You can now <a href="{% url releng-test-overview %}">go back to the results</a>,
+ or <a href="{% url releng-test-submit %}">give more feedback</a>.</p>
+</div>
+{% endblock %}
diff --git a/urls.py b/urls.py
index cc8b593a..36a2d79f 100644
--- a/urls.py
+++ b/urls.py
@@ -73,6 +73,7 @@ urlpatterns += patterns('',
(r'^mirrors/', include('mirrors.urls')),
(r'^news/', include('news.urls')),
(r'^packages/', include('packages.urls')),
+ (r'^releng/', include('releng.urls')),
(r'^todo/', include('todolists.urls')),
(r'^opensearch/packages/$', 'packages.views.opensearch',
{}, 'opensearch-packages'),
@@ -81,7 +82,7 @@ urlpatterns += patterns('',
if settings.DEBUG == True:
urlpatterns += patterns('',
- (r'^media/(.*)$', 'django.views.static.serve',
+ (r'^media/(.*)$', 'django.views.static.serve',
{'document_root': os.path.join(settings.DEPLOY_PATH, 'media')}))
# vim: set ts=4 sw=4 et: