summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/archweb.css8
-rw-r--r--media/archweb.js30
-rw-r--r--packages/management/commands/signoff_report.py13
-rw-r--r--packages/models.py28
-rw-r--r--packages/utils.py16
-rw-r--r--packages/views.py15
-rw-r--r--templates/packages/signoff_cell.html2
-rw-r--r--templates/packages/signoff_report.txt13
-rw-r--r--templates/packages/signoffs.html19
9 files changed, 109 insertions, 35 deletions
diff --git a/media/archweb.css b/media/archweb.css
index f817e18..303173f 100644
--- a/media/archweb.css
+++ b/media/archweb.css
@@ -914,8 +914,12 @@ ul.admin-actions {
color: red;
}
-#dev-signoffs .signed-username {
- color: #888;
+#dev-signoffs .signoff-bad {
+ color: darkorange;
+}
+
+#dev-signoffs .signoff-disabled {
+ color: gray;
}
/* iso testing feedback form */
diff --git a/media/archweb.js b/media/archweb.js
index 43812b3..a9f4e0c 100644
--- a/media/archweb.js
+++ b/media/archweb.js
@@ -215,28 +215,33 @@ function signoff_package() {
$.getJSON(link.href, function(data) {
link = $(link);
var signoff = null;
+ var cell = link.closest('td');
if (data.created) {
signoff = $('<li>').addClass('signed-username').text(data.user);
- link.closest('td').children('ul').append(signoff);
+ var list = cell.children('ul');
+ if (list.size() == 0) {
+ list = $('<ul>').prependTo(cell);
+ }
+ list.append(signoff);
} else if(data.user) {
signoff = link.closest('td').find('li').filter(function(index) {
return $(this).text() == data.user;
});
}
- console.log(signoff, data.revoked, data.user);
if (signoff && data.revoked) {
signoff.text(signoff.text() + ' (revoked)');
}
/* update the approved column to reflect reality */
- var approved;
- if (data.approved) {
- approved = link.closest('tr').children('.signoff-no');
- approved.text('Yes').addClass(
- 'signoff-yes').removeClass('signoff-no');
+ var approved = link.closest('tr').children('.approval');
+ approved.attr('class', '');
+ if (data.known_bad) {
+ approved.text('Bad').addClass('signoff-bad');
+ } else if (!data.enabled) {
+ approved.text('Disabled').addClass('signoff-disabled');
+ } else if (data.approved) {
+ approved.text('Yes').addClass('signoff-yes');
} else {
- approved = link.closest('tr').children('.signoff-yes');
- approved.text('No').addClass(
- 'signoff-no').removeClass('signoff-yes');
+ approved.text('No').addClass('signoff-no');
}
link.removeAttr('title');
/* Form our new link. The current will be something like
@@ -245,6 +250,10 @@ function signoff_package() {
if (data.revoked) {
link.text('Signoff');
link.attr('href', base_href + '/signoff/');
+ /* should we be hiding the link? */
+ if (data.known_bad || !data.enabled) {
+ link.remove();
+ }
} else {
link.text('Revoke Signoff');
link.attr('href', base_href + '/signoff/revoke/');
@@ -260,7 +269,6 @@ function filter_signoffs() {
var all_rows = rows;
$('#signoffs_filter .arch_filter').each(function() {
if (!$(this).is(':checked')) {
- console.log($(this).val());
rows = rows.not('.' + $(this).val());
}
});
diff --git a/packages/management/commands/signoff_report.py b/packages/management/commands/signoff_report.py
index 02f3d98..3431dad 100644
--- a/packages/management/commands/signoff_report.py
+++ b/packages/management/commands/signoff_report.py
@@ -56,6 +56,8 @@ def generate_report(email, repo_name):
# Collect all existing signoffs for these packages
signoff_groups = sorted(get_signoff_groups([repo]),
key=attrgetter('target_repo', 'arch', 'pkgbase'))
+ disabled = []
+ bad = []
complete = []
incomplete = []
new = []
@@ -68,10 +70,16 @@ def generate_report(email, repo_name):
old_cutoff = now - timedelta(days=old_days)
for group in signoff_groups:
- if group.approved():
+ spec = group.specification
+ if spec.known_bad:
+ bad.append(group)
+ elif not spec.enabled:
+ disabled.append(group)
+ elif group.approved():
complete.append(group)
else:
incomplete.append(group)
+
if group.package.last_update > new_cutoff:
new.append(group)
if group.package.last_update < old_cutoff:
@@ -96,6 +104,9 @@ def generate_report(email, repo_name):
c = Context({
'repo': repo,
'signoffs_url': signoffs_url,
+ 'disabled': disabled,
+ 'bad': bad,
+ 'all': signoff_groups,
'incomplete': incomplete,
'complete': complete,
'new': new,
diff --git a/packages/models.py b/packages/models.py
index a2b53a0..b70c21b 100644
--- a/packages/models.py
+++ b/packages/models.py
@@ -1,3 +1,5 @@
+from collections import namedtuple
+
from django.db import models
from django.db.models.signals import pre_save, post_save
from django.contrib.auth.models import User
@@ -42,22 +44,26 @@ class PackageRelation(models.Model):
class SignoffSpecificationManager(models.Manager):
def get_from_package(self, pkg):
'''Utility method to pull all relevant name-version fields from a
- package and get a matching specification.'''
+ package and get a matching signoff specification.'''
return self.get(
pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel,
epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo)
- def get_or_create_from_package(self, pkg):
- '''Utility method to pull all relevant name-version fields from a
- package and get or create a matching specification.'''
- return self.get_or_create(
- pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel,
- epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo)
+ def get_or_default_from_package(self, pkg):
+ '''utility method to pull all relevant name-version fields from a
+ package and get a matching signoff specification, or return the default
+ base case.'''
+ try:
+ return self.get(
+ pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel,
+ epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo)
+ except SignoffSpecification.DoesNotExist:
+ return DEFAULT_SIGNOFF_SPEC
class SignoffSpecification(models.Model):
'''
A specification for the signoff policy for this particular revision of a
- pakcage. The default is requiring two signoffs for a given package. These
+ package. The default is requiring two signoffs for a given package. These
are created only if necessary; e.g., if one wanted to override the
required=2 attribute, otherwise a sane default object is used.
'''
@@ -89,6 +95,12 @@ class SignoffSpecification(models.Model):
return u'%s-%s' % (self.pkgbase, self.full_version)
+# fake default signoff spec when we don't have a persisted one in the database
+FakeSignoffSpecification = namedtuple('FakeSignoffSpecification',
+ ('required', 'enabled', 'known_bad', 'comments'))
+DEFAULT_SIGNOFF_SPEC = FakeSignoffSpecification(2, True, False, u'')
+
+
class SignoffManager(models.Manager):
def get_from_package(self, pkg, user, revoked=False):
'''Utility method to pull all relevant name-version fields from a
diff --git a/packages/utils.py b/packages/utils.py
index 42cfbe0..60b95e2 100644
--- a/packages/utils.py
+++ b/packages/utils.py
@@ -5,7 +5,8 @@ from django.db.models import Count, Max
from main.models import Package, Repo
from main.utils import cache_function, groupby_preserve_order, PackageStandin
-from .models import PackageGroup, PackageRelation, SignoffSpecification, Signoff
+from .models import (PackageGroup, PackageRelation,
+ SignoffSpecification, Signoff, DEFAULT_SIGNOFF_SPEC)
@cache_function(300)
def get_group_info(include_arches=None):
@@ -148,9 +149,7 @@ SELECT DISTINCT id
return relations
-DEFAULT_SIGNOFF_SPEC = SignoffSpecification()
-
-def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_SPEC):
+def approved_by_signoffs(signoffs, spec):
if signoffs:
good_signoffs = sum(1 for s in signoffs if not s.revoked)
return good_signoffs >= spec.required
@@ -158,14 +157,13 @@ def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_SPEC):
class PackageSignoffGroup(object):
'''Encompasses all packages in testing with the same pkgbase.'''
- def __init__(self, packages, user=None):
+ def __init__(self, packages):
if len(packages) == 0:
raise Exception
self.packages = packages
- self.user = user
+ self.user = None
self.target_repo = None
self.signoffs = set()
- self.specification = DEFAULT_SIGNOFF_SPEC
first = packages[0]
self.pkgbase = first.pkgbase
@@ -175,6 +173,10 @@ class PackageSignoffGroup(object):
self.last_update = first.last_update
self.packager = first.packager
+ self.specification = \
+ SignoffSpecification.objects.get_or_default_from_package(first)
+ self.default_spec = self.specification is DEFAULT_SIGNOFF_SPEC
+
version = first.full_version
if all(version == pkg.full_version for pkg in packages):
self.version = version
diff --git a/packages/views.py b/packages/views.py
index 66bcd3f..307691e 100644
--- a/packages/views.py
+++ b/packages/views.py
@@ -7,8 +7,9 @@ from django.conf import settings
from django.core.mail import send_mail
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Q
-from django.http import HttpResponse, Http404
-from django.shortcuts import get_object_or_404, get_list_or_404, redirect
+from django.http import HttpResponse, Http404, HttpResponseForbidden
+from django.shortcuts import (get_object_or_404, get_list_or_404,
+ redirect, render)
from django.template import loader, Context
from django.utils import simplejson
from django.views.decorators.cache import never_cache
@@ -404,12 +405,16 @@ def signoff_package(request, name, repo, arch, revoke=False):
package, request.user)
all_signoffs = Signoff.objects.for_package(package)
+ spec = SignoffSpecification.objects.get_or_default_from_package(package)
if request.is_ajax():
data = {
'created': created,
'revoked': bool(signoff.revoked),
- 'approved': approved_by_signoffs(all_signoffs),
+ 'approved': approved_by_signoffs(all_signoffs, spec),
+ 'required': spec.required,
+ 'enabled': spec.enabled,
+ 'known_bad': spec.known_bad,
'user': str(request.user),
}
return HttpResponse(simplejson.dumps(data, ensure_ascii=False),
@@ -429,7 +434,9 @@ def signoff_options(request, name, repo, arch):
arch__name=arch, repo__name__iexact=repo, repo__testing=True)
package = packages[0]
- # TODO ensure submitter is maintainer and/or packager
+ if request.user != package.packager and \
+ request.user not in package.maintainers:
+ return render(request, '403.html', status=403)
try:
spec = SignoffSpecification.objects.get_from_package(package)
diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html
index 0a63011..6c705b4 100644
--- a/templates/packages/signoff_cell.html
+++ b/templates/packages/signoff_cell.html
@@ -11,10 +11,12 @@
<a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/revoke/"
title="Revoke signoff {{ group.pkgbase }} for {{ group.arch }}">Revoke Signoff</a></div>
{% else %}
+{% if not group.specification.known_bad and group.specification.enabled %}
<div>
<a class="signoff-link" href="{{ group.package.get_absolute_url }}signoff/"
title="Signoff {{ group.pkgbase }} for {{ group.arch }}">Signoff</a></div>
{% endif %}
+{% endif %}
{% if group.packager == user %}
<div>
<a class="signoff-options" href="{{ group.package.get_absolute_url }}signoff/options/">Packager Options</a>
diff --git a/templates/packages/signoff_report.txt b/templates/packages/signoff_report.txt
index 84e3fc6..81020c8 100644
--- a/templates/packages/signoff_report.txt
+++ b/templates/packages/signoff_report.txt
@@ -1,6 +1,19 @@
=== {% autoescape off %}Signoff report for [{{ repo|lower }}] ===
{{ signoffs_url }}
+There are currently:
+* {{ new|length }} new package{{ new|length|pluralize }} in last {{ new_hours }} hours
+* {{ bad|length }} known bad package{{ bad|length|pluralize }}
+* {{ disabled|length }} package{{ disabled|length|pluralize }} not accepting signoffs
+* {{ complete|length }} fully signed off package{{ complete|length|pluralize }}
+* {{ incomplete|length }} package{{ incomplete|length|pluralize }} missing signoffs
+* {{ old|length }} package{{ old|length|pluralize }} older than {{ old_days }} days
+
+(Note: the word 'package' as used here refers to packages as grouped by
+pkgbase, architecture, and repository; e.g., one PKGBUILD produces one
+package per architecture, even if it is a split package.)
+
+
== New packages in [{{ repo|lower}}] in last {{ new_hours }} hours ({{ new|length }} total) ==
{% for group in new %}
* {{ group.pkgbase }}-{{ group.version }} ({{ group.arch }}){% endfor %}
diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html
index 9bc7fd7..d517e5e 100644
--- a/templates/packages/signoffs.html
+++ b/templates/packages/signoffs.html
@@ -39,6 +39,7 @@
<th>Last Updated</th>
<th>Approved</th>
<th>Signoffs</th>
+ <th>Notes</th>
</tr>
</thead>
<tbody id="tbody_signoffs">
@@ -50,8 +51,22 @@
<td>{{ group.packager|default:"Unknown" }}</td>
<td>{{ group.packages|length }}</td>
<td>{{ group.last_update|date }}</td>
- <td class="signoff-{{ group.approved|yesno }}">{{ group.approved|yesno|capfirst }}</td>
+ {% if group.specification.known_bad %}
+ <td class="approval signoff-bad">Bad</td>
+ {% else %}
+ {% if not group.specification.enabled %}
+ <td class="approval signoff-disabled">Disabled</td>
+ {% else %}
+ <td class="approval signoff-{{ group.approved|yesno }}">{{ group.approved|yesno|capfirst }}</td>
+ {% endif %}
+ {% endif %}
<td>{% include "packages/signoff_cell.html" %}</td>
+ <td class="wrap">{% if not group.default_spec %}{% with group.specification as spec %}
+ {% if spec.required != 2 %}Required signoffs: {{ spec.required }}<br/>{% endif %}
+ {% if not spec.enabled %}Signoffs are not currently enabled<br/>{% endif %}
+ {% if spec.known_bad %}Package is known to be bad<br/>{% endif %}
+ {{ spec.comments|default:""|linebreaks }}
+ {% endwith %}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
@@ -64,7 +79,7 @@
$(document).ready(function() {
$('a.signoff-link').click(signoff_package);
$(".results").tablesorter({widgets: ['zebra'], sortList: [[0,0]],
- headers: { 7: { sorter: false } } });
+ headers: { 7: { sorter: false }, 8: {sorter: false } } });
$('#signoffs_filter input').change(filter_signoffs);
$('#criteria_reset').click(filter_signoffs_reset);
// fire function on page load to ensure the current form selections take effect