diff options
39 files changed, 1534 insertions, 331 deletions
@@ -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> |