summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README17
-rw-r--r--devel/management/commands/reporead.py3
-rw-r--r--local_settings.py.example60
-rw-r--r--main/migrations/0053_auto__add_field_package_pgp_signature.py152
-rw-r--r--main/models.py110
-rw-r--r--media/archweb.css1026
-rw-r--r--mirrors/admin.py3
-rw-r--r--mirrors/management/commands/mirrorcheck.py2
-rw-r--r--packages/admin.py3
-rw-r--r--packages/templatetags/package_extras.py32
-rw-r--r--public/views.py3
-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
-rw-r--r--requirements.txt2
-rw-r--r--requirements_prod.txt2
-rw-r--r--settings.py5
-rw-r--r--templates/devel/index.html7
-rw-r--r--templates/devel/packages.html4
-rw-r--r--templates/mirrors/mirror_details.html10
-rw-r--r--templates/mirrors/mirrors.html6
-rw-r--r--templates/packages/details.html44
-rw-r--r--templates/packages/flag.html13
-rw-r--r--templates/packages/flag_confirmed.html8
-rw-r--r--templates/packages/flagged.html7
-rw-r--r--templates/packages/packages_list.html5
-rw-r--r--templates/packages/search.html3
-rw-r--r--templates/packages/signoffs.html9
-rw-r--r--templates/public/download.html5
-rw-r--r--templates/public/index.html33
-rw-r--r--templates/registration/logout.html2
-rw-r--r--templates/releng/iso_overview.html40
-rw-r--r--templates/releng/result_list.html8
-rw-r--r--templates/releng/thanks.html4
-rw-r--r--templates/todolists/public_list.html4
-rw-r--r--templates/todolists/view.html5
39 files changed, 1534 insertions, 331 deletions
diff --git a/README b/README
index 184d1c8a..0d3ee082 100644
--- a/README
+++ b/README
@@ -14,8 +14,8 @@ See AUTHORS file.
# Dependencies
-- python
-- python-virtualenv
+- python2
+- python2-virtualenv
# Python dependencies
@@ -31,13 +31,13 @@ packages, you will probably want the following:
# Testing Installation
-1. Run `virtualenv`.
+1. Run `virtualenv2`.
- $ cd /path/to/archweb && virtualenv ../archweb-env
+ $ cd /path/to/archweb && virtualenv2 ../archweb-env
-2. Source the virtualenv.
+2. Activate the virtualenv.
- $ . ../archweb-env/bin/activate
+ $ source ../archweb-env/bin/activate
2. Install dependencies through `pip`.
@@ -58,7 +58,7 @@ packages, you will probably want the following:
provided data, adjust the file glob accordingly.
(archweb-env) $ ./manage.py loaddata */fixtures/*.json
-
+
7. Use the following commands to start a service instance
(archweb-env) $ ./manage.py runserver
@@ -69,7 +69,8 @@ packages, you will probably want the following:
(archweb-env) $ ./manage.py reporead i686 core.db.tar.gz
(archweb-env) $ ./manage.py syncisos
-Alter architecture and repo to get x86\_64 and packages from other repos if needed.
+Alter architecture and repo to get x86\_64 and packages from other repos if
+needed.
# Production Installation
diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py
index 470b785d..cf597577 100644
--- a/devel/management/commands/reporead.py
+++ b/devel/management/commands/reporead.py
@@ -73,7 +73,7 @@ class Command(BaseCommand):
class Pkg(object):
"""An interim 'container' object for holding Arch package data."""
bare = ( 'name', 'base', 'arch', 'desc', 'filename',
- 'md5sum', 'url', 'packager' )
+ 'md5sum', 'sha256sum', 'pgpsig', 'url', 'packager' )
number = ( 'csize', 'isize' )
collections = ( 'depends', 'optdepends', 'conflicts',
'provides', 'replaces', 'groups', 'license', 'files' )
@@ -203,6 +203,7 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None):
dbpkg.packager_str = repopkg.packager
# attempt to find the corresponding django user for this string
dbpkg.packager = finder.find(repopkg.packager)
+ dbpkg.pgp_signature = repopkg.pgpsig
if timestamp:
dbpkg.flag_date = None
diff --git a/local_settings.py.example b/local_settings.py.example
new file mode 100644
index 00000000..beb48f84
--- /dev/null
+++ b/local_settings.py.example
@@ -0,0 +1,60 @@
+### Django settings for archlinux project.
+
+## Debug settings
+DEBUG = False
+TEMPLATE_DEBUG = True
+DEBUG_TOOLBAR = True
+
+## For django debug toolbar
+INTERNAL_IPS = ('127.0.0.1',)
+
+## Notification admins
+ADMINS = (
+ # ('Joe Admin', 'joeadmin@example.com'),
+)
+
+## MySQL Database settings
+DATABASES = {
+ 'default': {
+ 'ENGINE' : 'django.db.backends.mysql',
+ 'NAME' : 'archlinux',
+ 'USER' : 'archlinux',
+ 'PASSWORD': 'archlinux',
+ 'HOST' : '',
+ 'PORT' : '',
+ 'OPTIONS' : {'init_command': 'SET storage_engine=InnoDB'},
+ },
+}
+
+## Define cache settings
+CACHES = {
+ 'default': {
+ 'BACKEND' : 'django.core.cache.backends.dummy.DummyCache',
+ #'BACKEND' : 'django.core.cache.backends.memcached.MemcachedCache',
+ #'LOCATION': '127.0.0.1:11211',
+ }
+}
+CACHE_MIDDLEWARE_KEY_PREFIX = 'arch'
+CACHE_MIDDLEWARE_SECONDS = 300
+
+## Use secure session cookies? Make this true if you want all
+## logged-in actions to take place over HTTPS only. If developing
+## locally, you will want to use False.
+SESSION_COOKIE_SECURE = False
+
+## location for saving dev pictures
+MEDIA_ROOT = '/srv/example.com/img/'
+
+## web url for serving image files
+MEDIA_URL = 'http://example.com/img/'
+
+## Make this unique, and don't share it with anybody.
+SECRET_KEY = '00000000000000000000000000000000000000000000000'
+
+## CDN settings
+CDN_ENABLED = False
+CDN_PATH = 'http://example.com/path/'
+CDN_PATH_SECURE = 'https://example.com/path/'
+
+
+# vim: set ts=4 sw=4 et:
diff --git a/main/migrations/0053_auto__add_field_package_pgp_signature.py b/main/migrations/0053_auto__add_field_package_pgp_signature.py
new file mode 100644
index 00000000..a828d1ef
--- /dev/null
+++ b/main/migrations/0053_auto__add_field_package_pgp_signature.py
@@ -0,0 +1,152 @@
+# 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('packages', 'pgp_signature', self.gf('django.db.models.fields.TextField')(null=True, blank=True), keep_default=False)
+
+ def backwards(self, orm):
+ db.delete_column('packages', 'pgp_signature')
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'main.arch': {
+ 'Meta': {'ordering': "['name']", 'object_name': 'Arch', 'db_table': "'arches'"},
+ 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'main.donor': {
+ 'Meta': {'ordering': "['name']", 'object_name': 'Donor', 'db_table': "'donors'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'})
+ },
+ 'main.package': {
+ 'Meta': {'ordering': "('pkgname',)", 'object_name': 'Package', 'db_table': "'packages'"},
+ 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}),
+ 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'compressed_size': ('main.models.PositiveBigIntegerField', [], {}),
+ 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'installed_size': ('main.models.PositiveBigIntegerField', [], {}),
+ 'last_update': ('django.db.models.fields.DateTimeField', [], {}),
+ 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
+ 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+ 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}),
+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'})
+ },
+ 'main.packagedepend': {
+ 'Meta': {'object_name': 'PackageDepend', 'db_table': "'package_depends'"},
+ 'depname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'depvcmp': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"})
+ },
+ 'main.packagefile': {
+ 'Meta': {'object_name': 'PackageFile', 'db_table': "'package_files'"},
+ 'directory': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_directory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"})
+ },
+ 'main.repo': {
+ 'Meta': {'ordering': "['name']", 'object_name': 'Repo', 'db_table': "'repos'"},
+ 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+ 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
+ },
+ 'main.todolist': {
+ 'Meta': {'object_name': 'Todolist', 'db_table': "'todolists'"},
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'main.todolistpkg': {
+ 'Meta': {'unique_together': "(('list', 'pkg'),)", 'object_name': 'TodolistPkg', 'db_table': "'todolist_pkgs'"},
+ 'complete': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Todolist']"}),
+ 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"})
+ },
+ 'main.userprofile': {
+ 'Meta': {'object_name': 'UserProfile', 'db_table': "'user_profiles'"},
+ 'alias': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'allowed_repos': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Repo']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'favorite_distros': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interests': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'languages': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+ 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'other_contact': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'pgp_key': ('main.models.PGPKeyField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}),
+ 'picture': ('django.db.models.fields.files.FileField', [], {'default': "'devs/silhouette.png'", 'max_length': '100'}),
+ 'public_email': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'roles': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'time_zone': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '100'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'userprofile'", 'unique': 'True', 'to': "orm['auth.User']"}),
+ 'website': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'yob': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['main']
diff --git a/main/models.py b/main/models.py
index 70372823..b5cd8638 100644
--- a/main/models.py
+++ b/main/models.py
@@ -82,6 +82,7 @@ class UserProfile(models.Model):
help_text="Ideally 125px by 125px")
user = models.OneToOneField(User, related_name='userprofile')
allowed_repos = models.ManyToManyField('Repo', blank=True)
+
class Meta:
db_table = 'user_profiles'
verbose_name = 'Additional Profile Data'
@@ -173,6 +174,7 @@ class Package(models.Model):
packager_str = models.CharField(max_length=255)
packager = models.ForeignKey(User, null=True,
on_delete=models.SET_NULL)
+ pgp_signature = models.TextField(null=True, blank=True)
flag_date = models.DateTimeField(null=True)
objects = PackageManager()
@@ -194,11 +196,14 @@ class Package(models.Model):
return '/packages/%s/%s/%s/' % (self.repo.name.lower(),
self.arch.name, self.pkgname)
- def get_full_url(self, proto='http'):
+ def get_full_url(self, proto='https'):
'''get a URL suitable for things like email including the domain'''
domain = Site.objects.get_current().domain
return '%s://%s%s' % (proto, domain, self.get_absolute_url())
+ def is_signed(self):
+ return bool(self.pgp_signature)
+
@property
def maintainers(self):
return User.objects.filter(
@@ -229,9 +234,11 @@ class Package(models.Model):
list slim by including the corresponding package in the same testing
category as this package if that check makes sense.
"""
+ provides = set(self.provides.values_list('name', flat=True))
+ provides.add(self.pkgname)
requiredby = PackageDepend.objects.select_related('pkg',
'pkg__arch', 'pkg__repo').filter(
- depname=self.pkgname).order_by(
+ depname__in=provides).order_by(
'pkg__pkgname', 'pkg__arch__name', 'pkg__repo__name')
if not self.arch.agnostic:
# make sure we match architectures if possible
@@ -269,36 +276,29 @@ class Package(models.Model):
@cache_function(300)
def get_depends(self):
"""
- Returns a list of dicts. Each dict contains ('pkg' and 'dep'). If it
- represents a found package both vars will be available; else pkg will
- be None if it is a 'virtual' dependency. Packages will match the
- testing status of this package if possible.
+ Returns a list of dicts. Each dict contains ('dep', 'pkg', and
+ 'providers'). If it represents a found package both vars will be
+ available; else pkg will be None if it is a 'virtual' dependency.
+ If pkg is None and providers are known, they will be available in
+ providers.
+ Packages will match the testing status of this package if possible.
"""
deps = []
+ arches = None
+ if not self.arch.agnostic:
+ arches = self.applicable_arches()
# TODO: we can use list comprehension and an 'in' query to make this more effective
for dep in self.packagedepend_set.order_by('optional', 'depname'):
- pkgs = Package.objects.normal().filter(pkgname=dep.depname)
- if not self.arch.agnostic:
- # make sure we match architectures if possible
- pkgs = pkgs.filter(arch__in=self.applicable_arches())
- if len(pkgs) == 0:
- # couldn't find a package in the DB
- # it should be a virtual depend (or a removed package)
- pkg = None
- elif len(pkgs) == 1:
- pkg = pkgs[0]
- else:
- # more than one package, see if we can't shrink it down
- # grab the first though in case we fail
- pkg = pkgs[0]
- # prevents yet more DB queries, these lists should be short
- pkgs = [p for p in pkgs if p.repo.testing == self.repo.testing
- and p.repo.staging == self.repo.staging]
- if len(pkgs) > 0:
- pkg = pkgs[0]
- deps.append({'dep': dep, 'pkg': pkg})
+ pkg = dep.get_best_satisfier(arches, testing=self.repo.testing,
+ staging=self.repo.staging)
+ providers = None
+ if not pkg:
+ providers = dep.get_providers(arches,
+ testing=self.repo.testing, staging=self.repo.staging)
+ deps.append({'dep': dep, 'pkg': pkg, 'providers': providers})
return deps
+ @cache_function(300)
def base_package(self):
"""
Locate the base package for this package. It may be this very package,
@@ -386,6 +386,64 @@ class PackageDepend(models.Model):
optional = models.BooleanField(default=False)
description = models.TextField(null=True, blank=True)
+ def get_best_satisfier(self, arches=None, testing=None, staging=None):
+ '''Find a satisfier for this dependency that best matches the given
+ criteria. It will not search provisions, but will find packages named
+ and matching repo characteristics if possible.'''
+ pkgs = Package.objects.normal().filter(pkgname=self.depname)
+ if arches is not None:
+ # make sure we match architectures if possible
+ pkgs = pkgs.filter(arch__in=arches)
+ if len(pkgs) == 0:
+ # couldn't find a package in the DB
+ # it should be a virtual depend (or a removed package)
+ return None
+ if len(pkgs) == 1:
+ return pkgs[0]
+ # more than one package, see if we can't shrink it down
+ # grab the first though in case we fail
+ pkg = pkgs[0]
+ # prevents yet more DB queries, these lists should be short;
+ # after each grab the best available in case we remove all entries
+ if staging is not None:
+ pkgs = [p for p in pkgs if p.repo.staging == staging]
+ if len(pkgs) > 0:
+ pkg = pkgs[0]
+
+ if testing is not None:
+ pkgs = [p for p in pkgs if p.repo.testing == testing]
+ if len(pkgs) > 0:
+ pkg = pkgs[0]
+
+ return pkg
+
+ def get_providers(self, arches=None, testing=None, staging=None):
+ '''Return providers of this dep. Does *not* include exact matches as it
+ checks the Provision names only, use get_best_satisfier() instead.'''
+ pkgs = Package.objects.normal().filter(
+ provides__name=self.depname).distinct()
+ if arches is not None:
+ pkgs = pkgs.filter(arch__in=arches)
+
+ # Logic here is to filter out packages that are in multiple repos if
+ # they are not requested. For example, if testing is False, only show a
+ # testing package if it doesn't exist in a non-testing repo.
+ if staging is not None:
+ filtered = {}
+ for p in pkgs:
+ if p.pkgname not in filtered or p.repo.staging == staging:
+ filtered[p.pkgname] = p
+ pkgs = filtered.values()
+
+ if testing is not None:
+ filtered = {}
+ for p in pkgs:
+ if p.pkgname not in filtered or p.repo.testing == testing:
+ filtered[p.pkgname] = p
+ pkgs = filtered.values()
+
+ return pkgs
+
def __unicode__(self):
return "%s%s" % (self.depname, self.depvcmp)
diff --git a/media/archweb.css b/media/archweb.css
index f01f277c..6a2f96cc 100644
--- a/media/archweb.css
+++ b/media/archweb.css
@@ -13,262 +13,938 @@
@import url('archnavbar/archnavbar.css');
/* simple reset */
-* { margin: 0; padding: 0; line-height: 1.4; }
+* {
+ margin: 0;
+ padding: 0;
+ line-height: 1.4;
+}
/* general styling */
-body { min-width: 650px; background: #f6f9fc; color: #222; font: normal 100% sans-serif; text-align: center; }
-p { margin: .33em 0 1em; }
-ol, ul { margin-bottom: 1em; padding-left: 2em; }
-ul { list-style: square; }
-code { font: 1.2em monospace; background: #ffd; padding: 0.15em 0.25em; }
-pre { font: 1.2em monospace; border: 1px solid #bdb; background: #dfd; padding: 0.5em; margin: 0.25em 2em; }
-pre code { display: block; background: none; }
-blockquote { margin: 1.5em 2em; }
-input { vertical-align: middle; }
-select[multiple] { padding-top: 1px; padding-bottom: 1px; }
-select[multiple] option { padding-left: 0.3em; padding-right: 0.5em; }
-input[type=submit] { padding-left: 0.6em; padding-right: 0.6em; }
-.clear { clear: both; }
-hr { border: none; border-top: 1px solid #888; }
-img { border: 0; }
+body {
+ min-width: 650px;
+ background: #f6f9fc;
+ color: #222;
+ font: normal 100% sans-serif;
+ text-align: center;
+}
+
+p {
+ margin: .33em 0 1em;
+}
+
+ol,
+ul {
+ margin-bottom: 1em;
+ padding-left: 2em;
+}
+
+ ul {
+ list-style: square;
+ }
+
+code {
+ font: 1.2em monospace;
+ background: #ffd;
+ padding: 0.15em 0.25em;
+}
+
+pre {
+ font: 1.2em monospace;
+ border: 1px solid #bdb;
+ background: #dfd;
+ padding: 0.5em;
+ margin: 1em;
+}
+
+ pre code {
+ display: block;
+ background: none;
+ }
+
+blockquote {
+ margin: 1.5em 2em;
+}
+
+input {
+ vertical-align: middle;
+}
+
+select[multiple] {
+ padding: 1px 0;
+}
+
+ select[multiple] option {
+ padding: 0 0.5em 0 0.3em;
+ }
+
+input[type=submit] {
+ padding: 0 0.6em;
+}
+
+.clear {
+ clear: both;
+}
+
+hr {
+ border: none;
+ border-top: 1px solid #888;
+}
+
+img {
+ border: 0;
+}
/* scale fonts down to a sane default (16 * .812 = 13px) */
-#content { font-size: 0.812em; }
+#content {
+ font-size: 0.812em;
+}
/* link style */
-a { text-decoration: none; }
-a:link, th a:visited { color: #07b; }
-a:visited { color: #666; }
-a:hover { text-decoration: underline; color: #666; }
-a:active { color: #e90; }
+a {
+ text-decoration: none;
+}
+
+ a:link,
+ th a:visited {
+ color: #07b;
+ }
+
+ a:visited {
+ color: #666;
+ }
+
+ a:hover {
+ text-decoration: underline;
+ color: #666;
+ }
+
+ a:active {
+ color: #e90;
+ }
/* headings */
-h2 { font-size: 1.5em; margin-bottom: 0.5em; border-bottom: 1px solid #888; }
-h3 { font-size: 1.25em; margin-top: 1em; }
-h4 { font-size: 1.15em; margin-top: 1em; }
-h5 { font-size: 1em; margin-top: 1em; }
+h2 {
+ font-size: 1.5em;
+ margin-bottom: 0.5em;
+ border-bottom: 1px solid #888;
+}
+
+h3 {
+ font-size: 1.25em;
+ margin-top: .5em;
+}
+
+h4 {
+ font-size: 1.15em;
+ margin-top: 1em;
+}
+
+h5 {
+ font-size: 1em;
+ margin-top: 1em;
+}
/* general layout */
-div#content { width: 95%; margin: 0 auto; text-align: left; }
-div#content-left-wrapper { float: left; width: 100%; } /* req to keep content above sidebar in source code */
-div#content-left { margin: 0 340px 0 0; }
-div#content-right { float: left; width: 300px; margin-left: -300px; }
-div.box { margin-bottom: 1.5em; padding: 0.65em; background: #ecf2f5; border: 1px solid #bcd; }
-div#footer { clear: both; margin: 2em 0 1em; }
-div#footer p { margin: 0; text-align: center; font-size: 0.85em; }
+div#content {
+ width: 95%;
+ margin: 0 auto;
+ text-align: left;
+}
+
+div#content-left-wrapper {
+ float: left;
+ width: 100%; /* req to keep content above sidebar in source code */
+}
+
+div#content-left {
+ margin: 0 340px 0 0;
+}
+
+div#content-right {
+ float: left;
+ width: 300px;
+ margin-left: -300px;
+}
+
+div.box {
+ margin-bottom: 1.5em;
+ padding: 0.65em;
+ background: #ecf2f5;
+ border: 1px solid #bcd;
+}
+
+div#footer {
+ clear: both;
+ margin: 2em 0 1em;
+}
+
+ div#footer p {
+ margin: 0;
+ text-align: center;
+ font-size: 0.85em;
+ }
/* alignment */
-div.center, table.center, img.center { width: auto; margin-left: auto; margin-right: auto; }
-p.center, td.center, th.center { text-align: center; }
+div.center,
+table.center,
+img.center {
+ width: auto;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+p.center,
+td.center,
+th.center {
+ text-align: center;
+}
/* table generics */
-table { width: 100%; border-collapse: collapse; }
-table .wrap { white-space: normal; }
-th, td { white-space: nowrap; text-align: left; }
-th { vertical-align: middle; font-weight: bold; }
-td { vertical-align: top; }
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+ table .wrap {
+ white-space: normal;
+ }
+
+th,
+td {
+ white-space: nowrap;
+ text-align: left;
+}
+
+ th {
+ vertical-align: middle;
+ font-weight: bold;
+ }
+
+ td {
+ vertical-align: top;
+ }
/* table pretty styles */
-table.pretty1 { width: auto; margin-top: 0.25em; margin-bottom: 0.5em; border-collapse: collapse; border: 1px solid #bcd; }
-table.pretty1 th { padding: 0.35em; background: #e4eeff; border: 1px solid #bcd; }
-table.pretty1 td { padding: 0.35em; border: 1px dotted #bcd; }
-table.pretty2 { width: auto; margin-top: 0.25em; margin-bottom: 0.5em; border-collapse: collapse; border: 1px solid #bbb; }
-table.pretty2 th { padding: 0.35em; background: #eee; border: 1px solid #bbb; }
-table.pretty2 td { padding: 0.35em; border: 1px dotted #bbb; }
+table.pretty1 {
+ width: auto;
+ margin-top: 0.25em;
+ margin-bottom: 0.5em;
+ border-collapse: collapse;
+ border: 1px solid #bcd;
+}
+
+ table.pretty1 th {
+ padding: 0.35em;
+ background: #e4eeff;
+ border: 1px solid #bcd;
+ }
+
+ table.pretty1 td {
+ padding: 0.35em;
+ border: 1px dotted #bcd;
+ }
+
+table.pretty2 {
+ width: auto;
+ margin-top: 0.25em;
+ margin-bottom: 0.5em;
+ border-collapse: collapse;
+ border: 1px solid #bbb;
+}
+
+ table.pretty2 th {
+ padding: 0.35em;
+ background: #eee;
+ border: 1px solid #bbb;
+ }
+
+ table.pretty2 td {
+ padding: 0.35em;
+ border: 1px dotted #bbb;
+ }
+
+/* definition lists */
+dl {
+ clear: both;
+}
+
+ dl dt,
+ dl dd {
+ margin-bottom: 4px;
+ padding: 8px 0px 4px;
+ font-weight: bold;
+ border-top: 1px solid #888;
+ }
+
+ dl dt {
+ color: #333;
+ float:left;
+ padding-right:15px;
+ }
/* forms and input styling */
-form p { margin: 0.5em 0; }
-fieldset { border: 0; }
-label { width: 12em; vertical-align: top; display: inline-block; font-weight: bold; }
-input[type=text], input[type=password], textarea { padding: 0.10em; }
-form.general-form label, form.general-form .form-help { width: 10em; vertical-align: top; display: inline-block; }
-form.general-form input[type=text], form.general-form textarea { width: 45%; }
+form p {
+ margin: 0.5em 0;
+}
+
+fieldset {
+ border: 0;
+}
+
+label {
+ width: 12em;
+ vertical-align: top;
+ display: inline-block;
+ font-weight: bold;
+}
+
+input[type=text],
+input[type=password],
+textarea {
+ padding: 0.10em;
+}
+
+form.general-form label,
+form.general-form .form-help {
+ width: 10em;
+ vertical-align: top;
+ display: inline-block;
+}
+
+form.general-form input[type=text],
+form.general-form textarea {
+ width: 45%;
+}
/* archdev navbar */
-div#archdev-navbar { margin: 1.5em 0; }
-div#archdev-navbar ul { list-style: none; margin: -0.5em 0; padding: 0; }
-div#archdev-navbar li { display: inline; margin: 0; padding: 0; font-size: 0.9em; }
-div#archdev-navbar li a { padding: 0 0.5em; color: #07b; }
+div#archdev-navbar {
+ margin: 1.5em 0;
+}
+
+ div#archdev-navbar ul {
+ list-style: none;
+ margin: -0.5em 0;
+ padding: 0;
+ }
+
+ div#archdev-navbar li {
+ display: inline;
+ margin: 0;
+ padding: 0;
+ font-size: 0.9em;
+ }
+
+ div#archdev-navbar li a {
+ padding: 0 0.5em;
+ color: #07b;
+ }
/* error/info messages (x pkg is already flagged out-of-date, etc) */
-#sys-message { width: 35em; text-align: center; margin: 1em auto; padding: 0.5em; background: #fff; border: 1px solid #f00; }
-#sys-message p { margin: 0; }
-
-ul.errorlist { color: red; }
-
-/*
+#sys-message {
+ width: 35em;
+ text-align: center;
+ margin: 1em auto;
+ padding: 0.5em;
+ background: #fff;
+ border: 1px solid #f00;
+}
+
+ #sys-message p {
+ margin: 0;
+ }
+
+ul.errorlist {
+ color: red;
+}
+
+/**
* PAGE SPECIFIC STYLES
*/
/* home: introduction */
-#intro p.readmore { margin: -0.5em 0 0 0; font-size: .9em; text-align: right; }
+#intro p.readmore {
+ margin: -0.5em 0 0 0;
+ font-size: .9em;
+ text-align: right;
+}
/* home: news */
-#news { margin-top: 1.5em; }
-#news h3 { border-bottom: 1px solid #888; }
-#news div { margin-bottom: 1em; }
-#news div p { margin-bottom: 0.5em; }
-#news .more { font-weight: normal; }
-#news .rss-icon { float: right; margin: -1.6em 0.4em 0 0; }
-#news h4 { font-size: 1em; margin-top: 1.5em; border-bottom: 1px dotted #bbb; }
-#news .timestamp { float: right; font-size: 0.85em; margin: -1.8em 0.5em 0 0; }
+#news {
+ margin-top: 1.5em;
+}
+
+ #news h3 {
+ float: left;
+ padding-bottom: .5em
+ }
+
+ #news div {
+ margin-bottom: 1em;
+ }
+
+ #news div p {
+ margin-bottom: 0.5em;
+ }
+
+ #news .more {
+ font-weight: normal;
+ }
+
+ #news .rss-icon {
+ float: right;
+ margin-top: 1em;
+ }
+
+ #news h4 {
+ clear: both;
+ font-size: 1em;
+ margin-top: 1.5em;
+ border-bottom: 1px dotted #bbb;
+ }
+
+ #news .timestamp {
+ float: right;
+ font-size: 0.85em;
+ margin: -1.8em 0.5em 0 0;
+ }
+
+/* home: arrowed headings */
+#news h3 a {
+ display: block;
+ background: #787DAB;
+ font-size: 15px;
+ padding: 2px 10px;
+ color: white;
+}
+
+ #news a:active {
+ color: white;
+ }
+
+h3 span.arrow {
+ display: block;
+ width: 0px;
+ height: 0px;
+ border-left: 6px solid transparent;
+ border-right: 6px solid transparent;
+ border-top: 6px solid #787DAB;
+ margin: 0 auto;
+ font-size: 0px;
+ line-height: 0px;
+}
/* home: pkgsearch box */
-#pkgsearch { padding: 1em 0.75em; background: #787DAB; color: #fff; border: 1px solid #3C47AB; }
-#pkgsearch label { width: auto; padding: 0.1em 0; }
-#pkgsearch input { width: 10em; float: right; font-size: 1em; color: #000; background: #fff; border: 1px solid #09c; }
+#pkgsearch {
+ padding: 1em 0.75em;
+ background: #787DAB;
+ color: #fff;
+ border: 1px solid #08b;
+}
+
+ #pkgsearch label {
+ width: auto;
+ padding: 0.1em 0;
+ }
+
+ #pkgsearch input {
+ width: 10em;
+ float: right;
+ font-size: 1em;
+ color: #000;
+ background: #fff;
+ border: 1px solid #09c;
+ }
/* home: recent pkg updates */
-#pkg-updates h3 { margin: 0 0 0.3em; }
-#pkg-updates .more { font-weight: normal; }
-#pkg-updates .rss-icon { float: right; margin: -2em 0 0 0; }
-#pkg-updates table { margin: 0; }
-#pkg-updates td.pkg-name { white-space: normal; }
-#pkg-updates td.pkg-arch { text-align: right; }
-#pkg-updates span.testing, #pkg-updates span.community-testing, span.multilib-testing { font-style: italic; }
-#pkg-updates span.staging, #pkg-updates span.community-staging, span.multilib-staging { font-style: italic; color: #ff8040; }
+#pkg-updates h3 {
+ margin: 0 0 0.3em;
+}
+
+ #pkg-updates .more {
+ font-weight: normal;
+ }
+
+ #pkg-updates .rss-icon {
+ float: right;
+ margin: -2em 0 0 0;
+ }
+
+ #pkg-updates table {
+ margin: 0;
+ }
+
+ #pkg-updates td.pkg-name {
+ white-space: normal;
+ }
+
+ #pkg-updates td.pkg-arch {
+ text-align: right;
+ }
+
+ #pkg-updates span.testing,
+ #pkg-updates span.community-testing,
+ span.multilib-testing {
+ font-style: italic;
+ }
+
+ #pkg-updates span.staging,
+ #pkg-updates span.community-staging,
+ span.multilib-staging {
+ font-style: italic;
+ color: #ff8040;
+ }
/* home: sidebar navigation */
-div#nav-sidebar ul { list-style: none; margin: 0.5em 0 0.5em 1em; padding: 0; }
+div#nav-sidebar ul {
+ list-style: none;
+ margin: 0.5em 0 0.5em 1em;
+ padding: 0;
+}
/* home: sponsor banners */
-div#arch-sponsors img { padding: 0.3em 0; }
+div#arch-sponsors img {
+ padding: 0.3em 0;
+}
/* home: sidebar components (navlist, sponsors, pkgsearch, etc) */
-div.widget { margin-bottom: 1.5em; }
+div.widget {
+ margin-bottom: 1.5em;
+}
/* feeds page */
-#rss-feeds .rss { padding-right: 20px; background: url(rss.png) top right no-repeat; }
+#rss-feeds .rss {
+ padding-right: 20px;
+ background: url(rss.png) top right no-repeat;
+}
/* artwork: logo images */
-#artwork img.inverted { background: #333; padding: 0; }
-#artwork div.imagelist img { display: inline; margin: 0.75em; }
+#artwork img.inverted {
+ background: #333;
+ padding: 0;
+}
+
+#artwork div.imagelist img {
+ display: inline;
+ margin: 0.75em;
+}
/* news: article list */
-.news-nav { float: right; margin-top: -2.2em; }
-.news-nav .prev, .news-nav .next { margin-left: 1em; margin-right: 1em; }
+.news-nav {
+ float: right;
+ margin-top: -2.2em;
+}
+
+ .news-nav .prev,
+ .news-nav .next {
+ margin: 0 1em;
+ }
/* news: article pages */
-div.news-article .article-info { margin: 0; color: #999; }
+div.news-article .article-info {
+ margin: 0;
+ color: #999;
+}
/* news: add/edit article */
-form#newsform { width: 60em; }
-form#newsform input[type=text], form#newsform textarea { width: 75%; }
+form#newsform {
+ width: 60em;
+}
+
+ form#newsform input[type=text],
+ form#newsform textarea {
+ width: 75%;
+ }
/* donate: donor list */
-div#donor-list ul { width: 100%; }
-/* max 4 columns, but possibly fewer if screen size doesn't allow for more */
-div#donor-list li { float: left; width: 25%; min-width: 20em; }
+div#donor-list ul {
+ width: 100%;
+}
+ /* max 4 columns, but possibly fewer if screen size doesn't allow for more */
+ div#donor-list li {
+ float: left;
+ width: 25%;
+ min-width: 20em;
+ }
/* download page */
-#arch-downloads h3 { border-bottom: 1px dotted #aaa; }
-table#download-torrents .cpu-arch { text-align: center; }
-table#download-mirrors { width: auto; margin-bottom: 1em; }
-table#download-mirrors td.mirror-country { padding-top: 1em; }
-table#download-mirrors td.mirror-server { padding-right: 1em; }
-table#download-mirrors a { display: block; float: right; width: 4em; }
+#arch-downloads h3 {
+ border-bottom: 1px dotted #aaa;
+}
+
+table#download-torrents .cpu-arch {
+ text-align: center;
+}
+
+table#download-mirrors {
+ width: auto;
+ margin-bottom: 1em;
+}
+
+ table#download-mirrors td.mirror-country {
+ padding-top: 1em;
+ }
+
+ table#download-mirrors td.mirror-server {
+ padding-right: 1em;
+ }
+
+ table#download-mirrors a {
+ display: block;
+ float: right;
+ width: 4em;
+ }
/* pkglists/devlists */
-table.results { font-size: 0.846em; border-top: 1px dotted #999; border-bottom: 1px dotted #999; }
-table.results th { padding: 0.5em 1em 0.25em 0.25em; border-bottom: 1px solid #999; white-space: nowrap; background-color:#fff; }
-table.results td { padding: .3em 1em .3em 3px; }
-table.results tr.odd { background: #fff; }
-table.results tr.even { background: #e4eeff; }
-/* additional styles for JS sorting */
-table.results th.header { padding-right: 20px; background-image: url(nosort.gif); background-repeat: no-repeat; background-position: center right; cursor: pointer; }
-table.results th.headerSortDown { background-color: #e4eeff; background-image: url(desc.gif); }
-table.results th.headerSortUp { background-color: #e4eeff; background-image: url(asc.gif); }
-table.results .flagged { color: red; }
+table.results {
+ font-size: 0.846em;
+ border-top: 1px dotted #999;
+ border-bottom: 1px dotted #999;
+}
+
+ table.results th {
+ padding: 0.5em 1em 0.25em 0.25em;
+ border-bottom: 1px solid #999;
+ white-space: nowrap;
+ background-color:#fff;
+ }
+
+ /* additional styles for JS sorting */
+ table.results th.header {
+ padding-right: 20px;
+ background-image: url(nosort.gif);
+ background-repeat: no-repeat;
+ background-position: center right;
+ cursor: pointer;
+ }
+
+ table.results th.headerSortDown {
+ background-color: #e4eeff;
+ background-image: url(desc.gif);
+ }
+
+ table.results th.headerSortUp {
+ background-color: #e4eeff;
+ background-image: url(asc.gif);
+ }
+
+ table.results td {
+ padding: .3em 1em .3em 3px;
+ }
+
+ table.results tr.odd {
+ background: #fff;
+ }
+
+ table.results tr.even {
+ background: #e4eeff;
+ }
+
+ table.results .flagged {
+ color: red;
+ }
/* pkglist: layout */
-div#pkglist-about { margin-top: 1.5em; }
+div#pkglist-about {
+ margin-top: 1.5em;
+}
/* pkglist: results navigation */
-#pkglist-stats-top, #pkglist-stats-bottom { font-size: 0.85em; }
-#pkglist-results .pkglist-nav { float: right; margin-top: -2.2em; }
-.pkglist-nav .prev { margin-right: 1em; }
-.pkglist-nav .next { margin-right: 1em; }
+#pkglist-stats-top,
+#pkglist-stats-bottom {
+ font-size: 0.85em;
+}
+
+#pkglist-results .pkglist-nav {
+ float: right;
+ margin-top: -2.2em;
+}
+
+.pkglist-nav .prev {
+ margin-right: 1em;
+}
+
+.pkglist-nav .next {
+ margin-right: 1em;
+}
/* search fields and other filter selections */
-.filter-criteria h3 { font-size: 1em; margin-top:0; }
-.filter-criteria div { float: left; margin-right: 1.65em; font-size: 0.85em; }
-.filter-criteria legend { display: none; }
-.filter-criteria label { width: auto; display: block; font-weight: normal; }
+.filter-criteria h3 {
+ font-size: 1em;
+ margin-top:0;
+}
+
+.filter-criteria div {
+ float: left;
+ margin-right: 1.65em;
+ font-size: 0.85em;
+}
+
+.filter-criteria legend {
+ display: none;
+}
+
+.filter-criteria label {
+ width: auto;
+ display: block;
+ font-weight: normal;
+}
/* pkgdetails: details links that float on the right */
-#pkgdetails #detailslinks { float: right; }
-#pkgdetails #detailslinks h4 { margin-top: 0; margin-bottom: 0.25em; }
-#pkgdetails #detailslinks ul { list-style: none; padding: 0; margin-bottom: 0; font-size: 0.846em; }
-#pkgdetails #detailslinks > div { padding: 0.5em; margin-bottom: 1em; background: #eee; border: 1px solid #bbb; }
-#pkgdetails #actionlist .flagged { color: red; font-size: 0.9em; font-style: italic; }
+#pkgdetails #detailslinks {
+ float: right;
+}
+
+ #pkgdetails #detailslinks h4 {
+ margin-top: 0;
+ margin-bottom: 0.25em;
+ }
+
+ #pkgdetails #detailslinks ul {
+ list-style: none;
+ padding: 0;
+ margin-bottom: 0;
+ font-size: 0.846em;
+ }
+
+ #pkgdetails #detailslinks > div {
+ padding: 0.5em;
+ margin-bottom: 1em;
+ background: #eee;
+ border: 1px solid #bbb;
+ }
+
+#pkgdetails #actionlist .flagged {
+ color: red;
+ font-size: 0.9em;
+ font-style: italic;
+}
/* pkgdetails: pkg info */
-#pkgdetails #pkginfo { width: auto; }
-#pkgdetails #pkginfo td { padding: 0.25em 0 0.25em 1.5em; }
+#pkgdetails #pkginfo {
+ width: auto;
+}
+
+ #pkgdetails #pkginfo td {
+ padding: 0.25em 0 0.25em 1.5em;
+ }
/* pkgdetails: flag package */
-form#flag-pkg-form label { width: 10em; }
-form#flag-pkg-form textarea, form#flag-pkg-form input[type=text] { width: 45%; }
+form#flag-pkg-form label {
+ width: 10em;
+}
+
+form#flag-pkg-form textarea,
+form#flag-pkg-form input[type=text] {
+ width: 45%;
+}
/* pkgdetails: deps, required by and file lists */
-#pkgdetails #metadata h3 { background: #555; color: #fff; font-size: 1em; margin-bottom: 0.5em; padding: 0.2em 0.35em; }
-#pkgdetails #metadata ul { list-style: none; margin: 0; padding: 0; }
-#pkgdetails #metadata li { padding-left: 0.5em; }
-#pkgdetails #metadata p { padding-left: 0.5em; }
-#pkgdetails #metadata .message { font-style: italic; }
-#pkgdetails #metadata br { clear: both; }
-#pkgdetails #pkgdeps { float: left; width: 48%; margin-right: 2%; }
-#pkgdetails #metadata .virtual-dep { font-style: italic; }
-#pkgdetails #metadata .testing-dep { font-style: italic; }
-#pkgdetails #metadata .opt-dep { font-style: italic; }
-#pkgdetails #metadata .dep-desc { font-style: italic; }
-#pkgdetails #pkgreqs { float: left; width: 50%; }
-#pkgdetails #pkgfiles { clear: left; padding-top: 1em; }
+#pkgdetails #metadata {
+ clear: both;
+}
+
+#pkgdetails #metadata h3 {
+ background: #555;
+ color: #fff;
+ font-size: 1em;
+ margin-bottom: 0.5em;
+ padding: 0.2em 0.35em;
+}
+
+#pkgdetails #metadata ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+#pkgdetails #metadata li {
+ padding-left: 0.5em;
+}
+
+#pkgdetails #metadata p {
+ padding-left: 0.5em;
+}
+
+#pkgdetails #metadata .message {
+ font-style: italic;
+}
+
+#pkgdetails #metadata br {
+ clear: both;
+}
+
+#pkgdetails #pkgdeps {
+ float: left;
+ width: 48%;
+ margin-right: 2%;
+}
+
+#pkgdetails #metadata .virtual-dep,
+#pkgdetails #metadata .testing-dep,
+#pkgdetails #metadata .opt-dep,
+#pkgdetails #metadata .dep-desc {
+ font-style: italic;
+}
+#pkgdetails #pkgreqs {
+ float: left;
+ width: 50%;
+}
+
+#pkgdetails #pkgfiles {
+ clear: left;
+ padding-top: 1em;
+}
/* dev/TU biographies */
-div#arch-bio-toc { width: 75%; margin: 0 auto; text-align: center; }
-table.arch-bio-entry td.pic { vertical-align: top; padding-right: 15px; padding-top: 10px; }
-table.arch-bio-entry td.pic img { padding: 4px; border: 1px solid #ccc; }
-table.arch-bio-entry table.bio { margin-bottom: 2em; }
-table.arch-bio-entry table.bio th { text-align: left; padding-right: 0.5em; vertical-align: top; white-space: nowrap; }
-table.arch-bio-entry table.bio td { width: 100%; padding-bottom: 0.25em; }
+div#arch-bio-toc {
+ width: 75%;
+ margin: 0 auto;
+ text-align: center;
+}
+
+table.arch-bio-entry td.pic {
+ vertical-align: top;
+ padding-right: 15px;
+ padding-top: 10px;
+}
+
+ table.arch-bio-entry td.pic img {
+ padding: 4px;
+ border: 1px solid #ccc;
+ }
+
+table.arch-bio-entry table.bio {
+ margin-bottom: 2em;
+}
+
+ table.arch-bio-entry table.bio th {
+ text-align: left;
+ padding-right: 0.5em;
+ vertical-align: top;
+ white-space: nowrap;
+ }
+
+ table.arch-bio-entry table.bio td {
+ width: 100%;
+ padding-bottom: 0.25em;
+ }
/* dev: login/out */
-p.login-error {}
-table#dev-login { width: auto; }
+table#dev-login {
+ width: auto;
+}
/* dev dashboard: flagged packages */
-form#dash-pkg-notify { text-align: right; padding: 1em 0 0; margin-top: 1em; font-size: 0.85em; border-top: 1px dotted #aaa; }
-form#dash-pkg-notify label { width: auto; font-weight: normal; }
-form#dash-pkg-notify input { vertical-align: middle; margin: 0 0.25em; }
-form#dash-pkg-notify input[type=submit] { margin-top: -0.25em; }
-form#dash-pkg-notify p { margin: 0; }
-
-table.dash-stats .key { width: 50%; }
+form#dash-pkg-notify {
+ text-align: right;
+ padding: 1em 0 0;
+ margin-top: 1em;
+ font-size: 0.85em;
+ border-top: 1px dotted #aaa;
+}
+
+ form#dash-pkg-notify label {
+ width: auto;
+ font-weight: normal;
+ }
+
+ form#dash-pkg-notify input {
+ vertical-align: middle;
+ margin: 0 0.25em;
+ }
+
+ form#dash-pkg-notify input[type=submit] {
+ margin-top: -0.25em;
+ }
+
+ form#dash-pkg-notify p {
+ margin: 0;
+ }
+
+table.dash-stats .key {
+ width: 50%;
+}
/* dev dashboard: admin actions (add news items, todo list, etc) */
-ul.admin-actions { float: right; list-style: none; margin-top: -2.5em; }
-ul.admin-actions li { display: inline; padding-left: 1.5em; }
+ul.admin-actions {
+ float: right;
+ list-style: none;
+ margin-top: -2.5em;
+}
+
+ ul.admin-actions li {
+ display: inline;
+ padding-left: 1.5em;
+ }
/* todo lists (public and private) */
-.todo-table .complete { color: green; }
-.todo-table .incomplete { color: red; }
-.todo-info { margin: 0; color: #999; }
-.todo-list h4 { margin-top: 0; margin-bottom: 0.4em; }
+.todo-table .complete {
+ color: green;
+}
+
+.todo-table .incomplete {
+ color: red;
+}
+.todo-info {
+ margin: 0; color: #999;
+}
+
+.todo-list h4 {
+ margin-top: 0;
+ margin-bottom: 0.4em;
+}
/* dev: signoff page */
-#dev-signoffs ul { list-style: none; margin: 0; padding: 0; }
-#dev-signoffs .signoff-yes { color: green; font-weight: bold; }
-#dev-signoffs .signoff-no { color: red; }
-#dev-signoffs .signed-username { color: #888; margin-left: 0.5em; }
+#dev-signoffs ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+#dev-signoffs .signoff-yes {
+ color: green;
+ font-weight: bold;
+}
+
+#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; }
-#releng-feedback ul+.helptext { position: relative; top: -0.9em; }
+#releng-feedback label {
+ width: auto;
+ display: inline;
+ font-weight: normal;
+}
+
+#releng-feedback ul {
+ padding-left: 1em;
+}
+
+#releng-feedback li {
+ list-style: none;
+}
+
+#releng-feedback ul+.helptext {
+ position: relative; top: -0.9em;
+}
+
+#releng-result .success-yes {
+ color: green;
+}
+
+#releng-result .success-no {
+ color: red;
+}
/* 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; }
-#archnavbar.anb-download ul li#anb-download a { color: white !important; }
+#archnavbar.anb-home ul li#anb-home a,
+#archnavbar.anb-packages ul li#anb-packages a,
+#archnavbar.anb-download ul li#anb-download a {
+ color: white !important;
+}
diff --git a/mirrors/admin.py b/mirrors/admin.py
index b7b478de..0632872d 100644
--- a/mirrors/admin.py
+++ b/mirrors/admin.py
@@ -59,7 +59,8 @@ class MirrorAdminForm(forms.ModelForm):
class MirrorAdmin(admin.ModelAdmin):
form = MirrorAdminForm
- list_display = ('name', 'tier', 'country', 'active', 'public', 'isos', 'admin_email', 'supported_protocols')
+ list_display = ('name', 'tier', 'country', 'active', 'public',
+ 'isos', 'admin_email')
list_filter = ('tier', 'active', 'public', 'country')
search_fields = ('name',)
inlines = [
diff --git a/mirrors/management/commands/mirrorcheck.py b/mirrors/management/commands/mirrorcheck.py
index 7bd79c83..8eb8b010 100644
--- a/mirrors/management/commands/mirrorcheck.py
+++ b/mirrors/management/commands/mirrorcheck.py
@@ -68,7 +68,7 @@ def check_mirror_url(mirror_url):
log.last_sync = parsed_time
# if we couldn't parse a time, this is a failure
- if parsed_time == None:
+ if parsed_time is None:
log.error = "Could not parse time from lastsync"
log.is_success = False
log.duration = end - start
diff --git a/packages/admin.py b/packages/admin.py
index 3ecfdbb1..01b6ed6c 100644
--- a/packages/admin.py
+++ b/packages/admin.py
@@ -3,8 +3,9 @@ from django.contrib import admin
from .models import PackageRelation
class PackageRelationAdmin(admin.ModelAdmin):
- list_display = ('user', 'pkgbase', 'type')
+ list_display = ('user', 'pkgbase', 'type', 'created')
list_filter = ('type', 'user')
+ search_fields = ('user__username', 'pkgbase')
admin.site.register(PackageRelation, PackageRelationAdmin)
diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py
index d4d0ca1a..45a534c8 100644
--- a/packages/templatetags/package_extras.py
+++ b/packages/templatetags/package_extras.py
@@ -9,6 +9,10 @@ from django.utils.html import escape
register = template.Library()
+def link_encode(url, query, doseq=False):
+ data = urlencode(query, doseq).replace('&', '&')
+ return "%s?%s" % (url, data)
+
class BuildQueryStringNode(template.Node):
def __init__(self, sortfield):
self.sortfield = sortfield
@@ -22,7 +26,7 @@ class BuildQueryStringNode(template.Node):
qs['sort'] = ['-' + self.sortfield]
else:
qs['sort'] = [self.sortfield]
- return urlencode(qs, True)
+ return urlencode(qs, True).replace('&', '&')
@register.tag(name='buildsortqs')
def do_buildsortqs(parser, token):
@@ -37,6 +41,15 @@ def do_buildsortqs(parser, token):
return BuildQueryStringNode(sortfield[1:-1])
@register.simple_tag
+def pkg_details_link(pkg):
+ template = '<a href="%s" title="View package details for %s">%s</a>'
+ return template % (pkg.get_absolute_url(), pkg.pkgname, pkg.pkgname)
+
+@register.simple_tag
+def multi_pkg_details(pkgs):
+ return ', '.join([pkg_details_link(pkg) for pkg in pkgs])
+
+@register.simple_tag
def userpkgs(user):
if user:
# TODO don't hardcode
@@ -49,24 +62,35 @@ def userpkgs(user):
return ''
@register.simple_tag
+def get_wiki_link(package):
+ url = "http://wiki.parabolagnulinux.org/Special:Search"
+ data = {
+ 'search': package.pkgname,
+ }
+ return link_encode(url, data)
+
+@register.simple_tag
def bugs_list(package):
+ url = "https://bugs.parabolagnulinux.org/bugs/issue"
data = {
'@action': 'search',
'title': package.pkgname,
}
- return "https://bugs.parabolagnulinux.org/bugs/issue?%s" % urlencode(data)
+ return link_encode(url, data)
@register.simple_tag
def bug_report(package):
+ url = "https://bugs.parabolagnulinux.org/bugs/issue"
data = {
'@template': 'item',
'keyword': 'packages',
'title': '[%s]' % package.pkgname,
}
- return "https://bugs.parabolagnulinux.org/bugs/issue?%s" % urlencode(data)
+ return link_encode(url, data)
@register.simple_tag
def flag_unfree(package):
+ url = "https://bugs.parabolagnulinux.org/bugs/issue"
data = {
'@template': 'item',
'keyword': 'packages,unfree',
@@ -74,5 +98,5 @@ def flag_unfree(package):
'priority': 'critical',
'title': '[%s] Please put your reasons here (register first if you haven\'t)' % package.pkgname,
}
- return "https://bugs.parabolagnulinux.org/bugs/issue?%s" % urlencode(data)
+ return link_encode(url, data)
# vim: set ts=4 sw=4 et:
diff --git a/public/views.py b/public/views.py
index a8e2a001..c10be35f 100644
--- a/public/views.py
+++ b/public/views.py
@@ -3,6 +3,7 @@ from mirrors.models import MirrorUrl
from news.models import News
from . import utils
+from django.conf import settings
from django.contrib.auth.models import User
from django.db.models import Q
from django.http import Http404
@@ -14,7 +15,7 @@ from django.shortcuts import redirect
def index(request):
pkgs = utils.get_recent_updates()
context = {
- 'news_updates': News.objects.order_by('-postdate', '-id')[:10],
+ 'news_updates': News.objects.order_by('-postdate', '-id')[:15],
'pkg_updates': pkgs,
}
return direct_to_template(request, 'public/index.html', context)
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:
diff --git a/requirements.txt b/requirements.txt
index 9be5d88e..27fda229 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-Django==1.3
+Django==1.3.1
Markdown==2.0.3
South==0.7.3
pytz>=2011c
diff --git a/requirements_prod.txt b/requirements_prod.txt
index babf65f9..9749a072 100644
--- a/requirements_prod.txt
+++ b/requirements_prod.txt
@@ -1,4 +1,4 @@
-Django==1.3
+Django==1.3.1
Markdown==2.0.3
MySQL-python==1.2.3
South==0.7.3
diff --git a/settings.py b/settings.py
index 0bc1d084..63c30412 100644
--- a/settings.py
+++ b/settings.py
@@ -84,9 +84,12 @@ MIDDLEWARE_CLASSES = (
ROOT_URLCONF = 'urls'
-# Configure where sessions and messages should reside
+# Configure where messages should reside
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
+
+# Session configuration
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
+SESSION_COOKIE_HTTPONLY = True
INSTALLED_APPS = (
'django.contrib.auth',
diff --git a/templates/devel/index.html b/templates/devel/index.html
index 6b50d3a0..08f5ec1b 100644
--- a/templates/devel/index.html
+++ b/templates/devel/index.html
@@ -1,5 +1,6 @@
{% extends "base.html" %}
{% load cache %}
+{% load package_extras %}
{% block title %}Parabola - Hacker Dashboard{% endblock %}
@@ -24,8 +25,7 @@
<tbody>
{% for pkg in flagged %}
<tr class="{% cycle 'odd' 'even' %}">
- <td><a href="{{ pkg.get_absolute_url }}"
- title="View package details for {{ pkg.pkgname }}">{{ pkg.pkgname }}</a></td>
+ <td>{% pkg_details_link pkg %}</td>
<td>{{ pkg.repo.name }}</td>
<td>{{ pkg.full_version }}</td>
<td>{{ pkg.arch.name }}</td>
@@ -55,8 +55,7 @@
<tr class="{% cycle 'odd' 'even' %}">
<td><a href="{{ todopkg.list.get_absolute_url }}"
title="View todo list: {{ todopkg.list.name }}">{{ todopkg.list.name }}</a></td>
- <td><a href="{{ todopkg.pkg.get_absolute_url }}"
- title="View package details for {{ todopkg.pkg.pkgname }}">{{ todopkg.pkg.pkgname }}</a></td>
+ <td>{% pkg_details_link todopkg.pkg %}</td>
<td>{{ todopkg.pkg.repo.name }}</td>
<td>{{ todopkg.pkg.arch.name }}</td>
<td>{{ todopkg.pkg.maintainers|join:', ' }}</td>
diff --git a/templates/devel/packages.html b/templates/devel/packages.html
index 8f149a5c..bd126593 100644
--- a/templates/devel/packages.html
+++ b/templates/devel/packages.html
@@ -1,5 +1,6 @@
{% extends "base.html" %}
{% load attributes %}
+{% load package_extras %}
{% block title %}Parabola - {{ title }}{% endblock %}
@@ -32,8 +33,7 @@
<tr class="{% cycle pkgr2,pkgr1 %}">
<td>{{ pkg.arch.name }}</td>
<td>{{ pkg.repo.name|capfirst }}</td>
- <td><a href="{{ pkg.get_absolute_url }}"
- title="Package details for {{ pkg.pkgname }}">{{ pkg.pkgname }}</a></td>
+ <td>{% pkg_details_link pkg %}</td>
{% if pkg.flag_date %}
<td><span class="flagged">{{ pkg.full_version }}</span></td>
{% else %}
diff --git a/templates/mirrors/mirror_details.html b/templates/mirrors/mirror_details.html
index 1b44f65b..0f071b8c 100644
--- a/templates/mirrors/mirror_details.html
+++ b/templates/mirrors/mirror_details.html
@@ -24,16 +24,16 @@
</tr>
<tr>
<th>Has ISOs:</th>
- <td>{{ mirror.isos|yesno }}</td>
+ <td>{{ mirror.isos|yesno|capfirst }}</td>
</tr>
{% if user.is_authenticated %}
<tr>
<th>Public:</th>
- <td>{{ mirror.public|yesno }}</td>
+ <td>{{ mirror.public|yesno|capfirst }}</td>
</tr>
<tr>
<th>Active:</th>
- <td>{{ mirror.active|yesno }}</td>
+ <td>{{ mirror.active|yesno|capfirst }}</td>
</tr>
<tr>
<th>Rsync IPs:</th>
@@ -91,8 +91,8 @@
{% for m_url in urls %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{% if m_url.protocol.is_download %}<a href="{{ m_url.url }}">{{ m_url.url }}</a>{% else %}{{ m_url.url }}{% endif %}</td>
- <td>{{ m_url.has_ipv4|yesno }}</td>
- <td>{{ m_url.has_ipv6|yesno }}</td>
+ <td>{{ m_url.has_ipv4|yesno|capfirst }}</td>
+ <td>{{ m_url.has_ipv6|yesno|capfirst }}</td>
<td>{{ m_url.last_sync|date:'Y-m-d H:i'|default:'unknown' }}</td>
<td>{{ m_url.completion_pct|percentage:1 }}</td>
<td>{{ m_url.delay|duration|default:'unknown' }}</td>
diff --git a/templates/mirrors/mirrors.html b/templates/mirrors/mirrors.html
index 53a68005..c9ab46db 100644
--- a/templates/mirrors/mirrors.html
+++ b/templates/mirrors/mirrors.html
@@ -27,11 +27,11 @@
title="Mirror details for {{ mirror.name }}">{{ mirror.name }}</a></td>
<td>{{mirror.get_tier_display}}</td>
<td>{{mirror.country}}</td>
- <td>{{mirror.isos|yesno}}</td>
+ <td>{{mirror.isos|yesno|capfirst}}</td>
<td class="wrap">{{mirror.supported_protocols|join:", "}}</td>
{% if user.is_authenticated %}
- <td>{{mirror.public|yesno}}</td>
- <td>{{mirror.active|yesno}}</td>
+ <td>{{mirror.public|yesno|capfirst}}</td>
+ <td>{{mirror.active|yesno|capfirst}}</td>
<td>{{mirror.admin_email}}</td>
<td class="wrap">{{mirror.notes|linebreaks}}</td>
{% endif %}
diff --git a/templates/packages/details.html b/templates/packages/details.html
index 068d1f1a..04fd5758 100644
--- a/templates/packages/details.html
+++ b/templates/packages/details.html
@@ -1,5 +1,6 @@
{% extends "base.html" %}
{% load cache %}
+{% load package_extras %}
{% block title %}Parabola - {{ pkg.pkgname }} {{ pkg.full_version }} - Package Details{% endblock %}
{% block navbarclass %}anb-packages{% endblock %}
@@ -15,9 +16,12 @@
<div id="actionlist">
<h4>Package Actions</h4>
<ul class="small">
- <li><a href="{% bugs_list pkg %}" title="View existing bug tickets for {{ pkg.pkgname }}">Bug Reports</a></li>
- <li><a href="{% bug_report pkg %}" title="Report bug for {{ pkg.pkgname }}">Report a Bug</a></li>
+ <li>
+ <a href="{% bugs_list pkg %}" title="View existing bug tickets for {{ pkg.pkgname }}">Bug Reports</a> /
+ <a href="{% bug_report pkg %}" title="Report new bug for {{ pkg.pkgname }}">Add New Bug</a>
+ </li>
<li><a href="{% flag_unfree pkg %}" title="Report {{ pkg.pkgname }} as unfree">Report as unfree</a></li>
+ <li><a href="{% get_wiki_link pkg %}" title="Search wiki for {{ pkg.pkgname }}">Search Wiki</a></li>
{% if pkg.flag_date %}
<li><span class="flagged">Flagged out-of-date on {{ pkg.flag_date|date }}</span></li>
{% with pkg.in_testing as tp %}{% if tp %}
@@ -80,20 +84,14 @@
{% with pkg.split_packages as splits %}{% if splits %}
<tr>
<th>Split Packages:</th>
- <td>
- {% for s in splits %}
- <a href="{{ s.get_absolute_url }}"
- title="Package details for {{ s.pkgname }}">{{ s.pkgname }}</a><br/>
- {% endfor %}
- </td>
+ <td>{% for s in splits %}{% pkg_details_link s %}<br/>{% endfor %}</td>
</tr>
{% endif %}{% endwith %}
{% else %}
<tr>
<th>Base Package:</th>
{% if pkg.base_package %}
- <td><a href="{{ pkg.base_package.get_absolute_url }}"
- title="Package details for {{ pkg.base_package.pkgname }}">{{ pkg.pkgbase }}</a></td>
+ <td>{% pkg_details_link pkg.base_package %}</td>
{% else %}
<td><a href="../{{ pkg.pkgbase }}/"
title="Split package details for {{ pkg.pkgbase }}">{{ pkg.pkgbase }}</a></td>
@@ -121,7 +119,16 @@
{% else %}None{% endif %}
</td>
{% endwith %}
- </tr><tr>
+ </tr>
+ {% with pkg.provides.all as provides %}
+ {% if provides %}
+ <tr>
+ <th>Provides:</th>
+ <td>{% for p in provides %}{{ p.name }}{% if p.version %}={{ p.version }}{% endif %}<br/>{% endfor %}</td>
+ </tr>
+ {% endif %}
+ {% endwith %}
+ <tr>
<th>Maintainers:</th>
{% with pkg.maintainers as maints %}
<td>{% if maints %}
@@ -141,6 +148,9 @@
<th>Last Packager:</th>
<td>{% with pkg.packager as pkgr %}{% if pkgr %}{% userpkgs pkgr %}{% else %}{{ pkg.packager_str }}{% endif %}{% endwith %}</td>
</tr><tr>
+ <th>Signed:</th>
+ <td>{{ pkg.is_signed|yesno|capfirst }}</td>
+ </tr><tr>
<th>Build Date:</th>
<td>{{ pkg.build_date|date:"DATETIME_FORMAT" }} UTC</td>
</tr><tr>
@@ -161,10 +171,12 @@
<ul>
{% for depend in deps %}
{% ifequal depend.pkg None %}
- <li>{{ depend.dep.depname }} <span class="virtual-dep">(virtual)</span></li>
+ {% if depend.providers %}
+ <li>{{ depend.dep.depname }} <span class="virtual-dep">({% multi_pkg_details depend.providers %})</span></li>
+ {% else %}<li>{{ depend.dep.depname }} <span class="virtual-dep">(virtual)</span></li>
+ {% endif %}
{% else %}
- <li><a href="{{ depend.pkg.get_absolute_url }}"
- title="View package details for {{ depend.dep.depname }}">{{ depend.dep.depname }}</a>{{ depend.dep.depvcmp|default:"" }}
+ <li>{% pkg_details_link depend.pkg %}{{ depend.dep.depvcmp|default:"" }}
{% if depend.pkg.repo.testing %}<span class="testing-dep">(testing)</span>{% endif %}
{% if depend.dep.optional %}<span class="opt-dep">(optional)</span>{% endif %}
{% if depend.dep.description %}- <span class="dep-desc">{{ depend.dep.description }}</span>{% endif %}
@@ -186,8 +198,8 @@
{% if rqdby %}
<ul>
{% for req in rqdby %}
- <li><a href="{{ req.pkg.get_absolute_url }}"
- title="View package details for {{ req.pkg.pkgname }}">{{ req.pkg.pkgname }}</a>
+ <li>{% pkg_details_link req.pkg %}
+ {% if req.depname != pkg.pkgname %}<span class="virtual-dep">(requires {{ req.depname }})</span>{% endif %}
{% if req.pkg.repo.testing %}<span class="testing-dep">(testing)</span>{% endif %}
{% if req.optional %}<span class="opt-dep">(optional)</span>{% endif %}
</li>
diff --git a/templates/packages/flag.html b/templates/packages/flag.html
index 74f6982c..f439ca36 100644
--- a/templates/packages/flag.html
+++ b/templates/packages/flag.html
@@ -1,4 +1,6 @@
{% extends "base.html" %}
+{% load package_extras %}
+
{% block title %}Parabola - Flag Package - {{ package.pkgname }}{% endblock %}
{% block navbarclass %}anb-packages{% endblock %}
@@ -8,12 +10,12 @@
<p>If you notice a package is out-of-date (i.e., there is a newer
<strong>stable</strong> release available), then please notify us using
- the form below.</p>
+ the form below. Do <em>not</em> report bugs via this form!</p>
<p>Note that all of the following packages will be marked out of date:</p>
<ul>
{% for pkg in packages %}
- <li>{{ pkg.pkgname }} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})</li>
+ <li>{% pkg_details_link pkg %} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})</li>
{% endfor %}
</ul>
@@ -24,9 +26,10 @@
title="Visit the dev mailing list">Parabola Development mailing list</a>
with your additional text.</p>
- <p><strong>Note:</strong> Please do <em>not</em> use this facility if the
- package is broken! Please <a href="https://bugs.parabolagnulinux.org"
- title="Parabola Bugtracker">file a bug</a> instead.</p>
+ <p><strong>Note:</strong> Do <em>not</em> use this facility if the
+ package is broken! The package will be unflagged and the report will be ignored!
+ <a href="https://bugs.parabolagnulinux.org/" title="Parabola Bugtracker">Use the
+ bugtracker to file a bug</a> instead.</p>
<p>Please confirm your flag request for {{package.pkgname}}:</p>
diff --git a/templates/packages/flag_confirmed.html b/templates/packages/flag_confirmed.html
index ebb14608..cc743dd6 100644
--- a/templates/packages/flag_confirmed.html
+++ b/templates/packages/flag_confirmed.html
@@ -1,4 +1,6 @@
{% extends "base.html" %}
+{% load package_extras %}
+
{% block title %}Parabola - Package Flagged - {{ package.pkgname }}{% endblock %}
{% block navbarclass %}anb-packages{% endblock %}
@@ -9,12 +11,10 @@
<p>Thank you, the maintainers have been notified the following packages are out-of-date:</p>
<ul>
{% for pkg in packages %}
- <li><a href="{{ pkg.get_absolute_url }}"
- title="Package details for {{package.pkgname}}">{{ pkg.pkgname }} {{ pkg.full_version }}</a> [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})</li>
+ <li>{% pkg_details_link pkg %} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})</li>
{% endfor %}
</ul>
- <p>You can return to the package details page for
- <a href="{{ package.get_absolute_url }}" title="Package details for {{package.pkgname}}">{{package.pkgname}}</a>.</p>
+ <p>You can return to the package details page for {% pkg_details_link package %}.</p>
</div>
{% endblock %}
diff --git a/templates/packages/flagged.html b/templates/packages/flagged.html
index 97a14ff2..d2328381 100644
--- a/templates/packages/flagged.html
+++ b/templates/packages/flagged.html
@@ -1,16 +1,15 @@
{% extends "base.html" %}
+{% load package_extras %}
+
{% block title %}Parabola - Flag Package - {{ pkg.pkgname }}{% endblock %}
{% block navbarclass %}anb-packages{% endblock %}
{% block content %}
<div id="pkg-flagged-error" class="box">
-
<h2>Error: Package already flagged</h2>
<p><strong>{{pkg.pkgname}}</strong> has already been flagged out-of-date.</p>
- <p>You can return to the package details page for
- <a href="{{ pkg.get_absolute_url }}" title="Package details for {{pkg.pkgname}}">{{pkg.pkgname}}</a>.</p>
-
+ <p>You can return to the package details page for {% pkg_details_link pkg %}.</p>
</div>
{% endblock %}
diff --git a/templates/packages/packages_list.html b/templates/packages/packages_list.html
index 13b8caba..ed376705 100644
--- a/templates/packages/packages_list.html
+++ b/templates/packages/packages_list.html
@@ -1,4 +1,6 @@
{% extends "base.html" %}
+{% load package_extras %}
+
{% block title %}Parabola - {{ name }} ({{ arch.name }}) - {{ list_title }}{% endblock %}
{% block navbarclass %}anb-packages{% endblock %}
@@ -23,8 +25,7 @@
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ pkg.arch.name }}</td>
<td>{{ pkg.repo.name|capfirst }}</td>
- <td><a href="{{ pkg.get_absolute_url }}"
- title="Package details for {{ pkg.pkgname }}">{{ pkg.pkgname }}</a></td>
+ <td>{% pkg_details_link pkg %}</td>
{% if pkg.flag_date %}
<td><span class="flagged">{{ pkg.full_version }}</span></td>
{% else %}
diff --git a/templates/packages/search.html b/templates/packages/search.html
index ae9e55f2..8bf63a15 100644
--- a/templates/packages/search.html
+++ b/templates/packages/search.html
@@ -104,8 +104,7 @@
{% endif %}
<td>{{ pkg.arch.name }}</td>
<td>{{ pkg.repo.name|capfirst }}</td>
- <td><a href="{{ pkg.get_absolute_url }}"
- title="Package details for {{ pkg.pkgname }}">{{ pkg.pkgname }}</a></td>
+ <td>{% pkg_details_link pkg %}</td>
{% if pkg.flag_date %}
<td><span class="flagged">{{ pkg.full_version }}</span></td>
{% else %}
diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html
index 53e9e46d..4745ff53 100644
--- a/templates/packages/signoffs.html
+++ b/templates/packages/signoffs.html
@@ -1,4 +1,6 @@
{% extends "base.html" %}
+{% load package_extras %}
+
{% block title %}Parabola - Package Signoffs{% endblock %}
{% block navbarclass %}anb-packages{% endblock %}
@@ -28,14 +30,13 @@
{% with group.package as pkg %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ pkg.arch.name }}</td>
- <td><a href="{{ pkg.get_absolute_url }}"
- title="View package details for {{ pkg.pkgname }}">{{ pkg.pkgname }}</a></td>
+ <td>{% pkg_details_link pkg %}</td>
<td>{{ group.packages|length }}</td>
<td>{{ pkg.full_version }}</td>
<td>{{ pkg.last_update|date }}</td>
<td>{{ group.target_repo }}</td>
- <td class="signoff-{{group.approved|yesno}}">
- {{ group.approved|yesno:"Yes,No" }}</td>
+ <td class="signoff-{{ group.approved|yesno }}">
+ {{ group.approved|yesno|capfirst }}</td>
<td>
<ul>
<li><a class="signoff-link" href="{{ pkg.get_absolute_url }}signoff/"
diff --git a/templates/public/download.html b/templates/public/download.html
index 7c123b3f..5c5cf5bd 100644
--- a/templates/public/download.html
+++ b/templates/public/download.html
@@ -9,7 +9,8 @@
<h2>Parabola Downloads</h2>
- {% with "2010.12.29" as version %} <h3>Release Info</h3>
+ {% with "2011.09.1" as version %}
+ <h3>Release Info</h3>
<p>All available images can be burned to a CD, mounted as an ISO file,
or be directly written to a USB stick using a utility like `dd`. These
@@ -18,7 +19,7 @@
<ul>
<li><strong>Current Release:</strong> {{ version }}</li>
- <li><strong>Included Kernel:</strong> 2.6.36.2</li>
+ <li><strong>Included Kernel:</strong> 3.0.3</li>
<li><strong>Resources:</strong>
<ul>
<li><a
diff --git a/templates/public/index.html b/templates/public/index.html
index 6254d7b0..c68baedb 100644
--- a/templates/public/index.html
+++ b/templates/public/index.html
@@ -33,17 +33,40 @@
<div id="news">
- <h3>Latest News <span class="more">(<a href="{% url news-list %}"
- title="Browse the news archives">more</a>)</span></h3>
+ <h3>
+ <a href="{% url news-list %}" title="Browse the news archives">Latest News</a>
+ <span class="arrow"></span>
+ </h3>
<a href="/feeds/news/" title="Parabola News RSS Feed"
class="rss-icon"><img src="{% cdnprefix %}/media/rss.png" alt="RSS Feed" /></a>
{% for news in news_updates %}
- <h4><a href="{{ news.get_absolute_url }}"
- title="View full article: {{ news.title }}">{{ news.title }}</a></h4>
+ {% if forloop.counter0 < 5 %}
+ <h4>
+ <a href="{{ news.get_absolute_url }}"
+ title="View full article: {{ news.title }}">{{ news.title }}</a>
+ </h4>
<p class="timestamp">{{ news.postdate|date }}</p>
<div class="article-content">{{ news.content|markdown|truncatewords_html:75 }}</div>
+ {% else %}
+ {% if forloop.counter0 == 5 %}
+ <h3>
+ <a href="{% url news-list %}"
+ title="Browse the news archives">Older News</a>
+ <span class="arrow"></span>
+ </h3>
+ <dl class="newslist">
+ {% endif %}
+ <dt>{{ news.postdate|date }}</dt>
+ <dd>
+ <a href="{{ news.get_absolute_url }}"
+ title="View full article: {{ news.title }}">{{ news.title }}</a>
+ </dd>
+ {% if forloop.last %}
+ </dl>
+ {% endif %}
+ {% endif %}
{% endfor %}
</div><!-- #news -->
@@ -75,7 +98,7 @@
{% for update in pkg_updates %}
<tr>
<td class="pkg-name"><span class="{{ update.repo|lower }}">{{ update.pkgbase }} {{ update.version }}</span></td>
- <td class="pkg-arch">
+ <td class="pkg-arch">
{% for pkg in update.package_links %}<a href="{{ pkg.get_absolute_url }}"
title="Details for {{ pkg.pkgname }} [{{ pkg.repo|lower }}]">{{ pkg.arch }}</a>{% if not forloop.last %}/{% endif %}{% endfor %}
</td>
diff --git a/templates/registration/logout.html b/templates/registration/logout.html
index e2e5449c..9b507ec0 100644
--- a/templates/registration/logout.html
+++ b/templates/registration/logout.html
@@ -5,7 +5,7 @@
<div id="dev-logout" class="box">
<h2>Developer Logout</h2>
- <p>Logout was successful.<p>
+ <p>Logout was successful.<p>
</div>
{% endblock %}
diff --git a/templates/releng/iso_overview.html b/templates/releng/iso_overview.html
new file mode 100644
index 00000000..8280f100
--- /dev/null
+++ b/templates/releng/iso_overview.html
@@ -0,0 +1,40 @@
+{% extends "base.html" %}
+
+{% block content %}
+<div class="box">
+ <h2>Failures and Successes for Testing ISOs</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>Currently Available</th>
+ <th># Successes</th>
+ <th># Failures</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for iso in isos %}
+ <tr>
+ <td>
+ <a href="{{ iso.get_absolute_url }}">{{ iso.name }}</a>
+ </td>
+ <td>{{ iso.active|yesno|capfirst }}</td>
+ <td>{{ iso.successes }}</td>
+ <td>{{ iso.failures }}</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_list.html b/templates/releng/result_list.html
index b3ae025b..845d330d 100644
--- a/templates/releng/result_list.html
+++ b/templates/releng/result_list.html
@@ -3,7 +3,7 @@
{% block content %}
<div class="box">
<h2>Results for:
- {% if option %}{{ option|title }}: {{ value }}{% endif %}
+ {% if option %}{{ option.verbose_name|title }}: {{ value }}{% endif %}
{{ iso_name|default:"" }}
</h2>
@@ -12,9 +12,10 @@
<table id="releng-result" class="results">
<thead>
<tr>
- <th>Iso</th>
+ <th>ISO</th>
<th>Submitted By</th>
<th>Date Submitted</th>
+ <th>Architecture</th>
<th>Success</th>
</tr>
</thead>
@@ -24,7 +25,8 @@
<td>{{ test.iso.name }}</td>
<td>{{ test.user_name }}</td>
<td>{{ test.created|date }}</td>
- <td>{{ test.success|yesno }}</td>
+ <td>{{ test.architecture }}</td>
+ <td><span class="success-{{ test.success|yesno }}">{{ test.success|yesno|capfirst }}</span></td>
</tr>
{% endfor %}
</tbody>
diff --git a/templates/releng/thanks.html b/templates/releng/thanks.html
index 984a056d..fdfc4c4a 100644
--- a/templates/releng/thanks.html
+++ b/templates/releng/thanks.html
@@ -8,6 +8,8 @@
<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>
+ <a href="{% url releng-test-submit %}">give more feedback</a>, or
+ have a look at the <a href="{% url releng-iso-overview %}">look at
+ the ISO test overview</a>.</p>
</div>
{% endblock %}
diff --git a/templates/todolists/public_list.html b/templates/todolists/public_list.html
index ceb001de..fcb77c65 100644
--- a/templates/todolists/public_list.html
+++ b/templates/todolists/public_list.html
@@ -1,4 +1,5 @@
{% extends "base.html" %}
+{% load package_extras %}
{% block title %}Parabola - Todo Lists{% endblock %}
@@ -43,8 +44,7 @@
<tbody>
{% for pkg in list.packages %}
<tr class="{% cycle 'odd' 'even' %}">
- <td><a href="{{ pkg.pkg.get_absolute_url }}"
- title="View package details for {{ pkg.pkg.pkgname }}">{{ pkg.pkg.pkgname }}</a></td>
+ <td>{% pkg_details_link pkg.pkg %}</td>
<td>{{ pkg.pkg.arch.name }}</td>
<td>{{ pkg.pkg.repo.name|capfirst }}</td>
<td>{{ pkg.pkg.maintainers|join:', ' }}</td>
diff --git a/templates/todolists/view.html b/templates/todolists/view.html
index d4f5a08d..5a80684f 100644
--- a/templates/todolists/view.html
+++ b/templates/todolists/view.html
@@ -1,4 +1,6 @@
{% extends "base.html" %}
+{% load package_extras %}
+
{% block title %}Parabola - Todo: {{ list.name }}{% endblock %}
{% block content %}
@@ -34,8 +36,7 @@
<tbody>
{% for pkg in list.packages %}
<tr class="{% cycle 'odd' 'even' %}">
- <td><a href="{{ pkg.pkg.get_absolute_url }}"
- title="View package details for {{ pkg.pkg.pkgname }}">{{ pkg.pkg.pkgname }}</a></td>
+ <td>{% pkg_details_link pkg.pkg %}</td>
<td>{{ pkg.pkg.arch.name }}</td>
<td>{{ pkg.pkg.repo.name|capfirst }}</td>
<td>{{ pkg.pkg.maintainers|join:', ' }}</td>