summaryrefslogtreecommitdiff
path: root/packages/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/utils.py')
-rw-r--r--packages/utils.py229
1 files changed, 218 insertions, 11 deletions
diff --git a/packages/utils.py b/packages/utils.py
index c8c1f8a6..f8e1f2a1 100644
--- a/packages/utils.py
+++ b/packages/utils.py
@@ -2,11 +2,13 @@ from collections import defaultdict
from operator import itemgetter
from django.db import connection
-from django.db.models import Count, Max
+from django.db.models import Count, Max, F
+from django.contrib.auth.models import User
-from main.models import Package
-from main.utils import cache_function
-from .models import PackageGroup, PackageRelation, Signoff
+from main.models import Package, Arch, Repo
+from main.utils import cache_function, groupby_preserve_order, PackageStandin
+from .models import (PackageGroup, PackageRelation,
+ SignoffSpecification, Signoff, DEFAULT_SIGNOFF_SPEC)
@cache_function(300)
def get_group_info(include_arches=None):
@@ -47,6 +49,20 @@ def get_group_info(include_arches=None):
groups.extend(val.itervalues())
return sorted(groups, key=itemgetter('name', 'arch'))
+def get_split_packages_info():
+ '''Return info on split packages that do not have an actual package name
+ matching the split pkgbase.'''
+ pkgnames = Package.objects.values('pkgname')
+ split_pkgs = Package.objects.exclude(pkgname=F('pkgbase')).exclude(
+ pkgbase__in=pkgnames).values('pkgbase', 'repo', 'arch').annotate(
+ last_update=Max('last_update'))
+ all_arches = Arch.objects.in_bulk(set(s['arch'] for s in split_pkgs))
+ all_repos = Repo.objects.in_bulk(set(s['repo'] for s in split_pkgs))
+ for split in split_pkgs:
+ split['arch'] = all_arches[split['arch']]
+ split['repo'] = all_repos[split['repo']]
+ return split_pkgs
+
class Difference(object):
def __init__(self, pkgname, repo, pkg_a, pkg_b):
self.pkgname = pkgname
@@ -126,6 +142,7 @@ SELECT p.id, q.id
differences.sort(key=lambda a: (a.repo.name, a.pkgname))
return differences
+
def get_wrong_permissions():
sql = """
SELECT DISTINCT id
@@ -148,11 +165,128 @@ SELECT DISTINCT id
id__in=to_fetch)
return relations
-def get_current_signoffs():
- '''Returns a mapping of pkgbase -> signoff objects.'''
- sql = """
+
+def attach_maintainers(packages):
+ '''Given a queryset or something resembling it of package objects, find all
+ the maintainers and attach them to the packages to prevent N+1 query
+ cascading.'''
+ packages = list(packages)
+ pkgbases = set(p.pkgbase for p in packages)
+ rels = PackageRelation.objects.filter(type=PackageRelation.MAINTAINER,
+ pkgbase__in=pkgbases).values_list('pkgbase', 'user_id').distinct()
+
+ # get all the user objects we will need
+ user_ids = set(rel[1] for rel in rels)
+ users = User.objects.in_bulk(user_ids)
+
+ # now build a pkgbase -> [maintainers...] map
+ maintainers = defaultdict(list)
+ for rel in rels:
+ maintainers[rel[0]].append(users[rel[1]])
+
+ annotated = []
+ # and finally, attach the maintainer lists on the original packages
+ for package in packages:
+ package.maintainers = maintainers[package.pkgbase]
+ annotated.append(package)
+
+ return annotated
+
+
+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
+ return False
+
+class PackageSignoffGroup(object):
+ '''Encompasses all packages in testing with the same pkgbase.'''
+ def __init__(self, packages):
+ if len(packages) == 0:
+ raise Exception
+ self.packages = packages
+ self.user = None
+ self.target_repo = None
+ self.signoffs = set()
+ self.specification = DEFAULT_SIGNOFF_SPEC
+ self.default_spec = True
+
+ first = packages[0]
+ self.pkgbase = first.pkgbase
+ self.arch = first.arch
+ self.repo = first.repo
+ self.version = ''
+ self.last_update = first.last_update
+ self.packager = first.packager
+ self.maintainers = first.maintainers
+
+ version = first.full_version
+ if all(version == pkg.full_version for pkg in packages):
+ self.version = version
+
+ @property
+ def package(self):
+ '''Try and return a relevant single package object representing this
+ group. Start by seeing if there is only one package, then look for the
+ matching package by name, finally falling back to a standin package
+ object.'''
+ if len(self.packages) == 1:
+ return self.packages[0]
+
+ same_pkgs = [p for p in self.packages if p.pkgname == p.pkgbase]
+ if same_pkgs:
+ return same_pkgs[0]
+
+ return PackageStandin(self.packages[0])
+
+ def find_signoffs(self, all_signoffs):
+ '''Look through a list of Signoff objects for ones matching this
+ particular group and store them on the object.'''
+ for s in all_signoffs:
+ if s.pkgbase != self.pkgbase:
+ continue
+ if self.version and not s.full_version == self.version:
+ continue
+ if s.arch_id == self.arch.id and s.repo_id == self.repo.id:
+ self.signoffs.add(s)
+
+ def find_specification(self, specifications):
+ for spec in specifications:
+ if spec.pkgbase != self.pkgbase:
+ continue
+ if self.version and not spec.full_version == self.version:
+ continue
+ if spec.arch_id == self.arch.id and spec.repo_id == self.repo.id:
+ self.specification = spec
+ self.default_spec = False
+ return
+
+ def approved(self):
+ return approved_by_signoffs(self.signoffs, self.specification)
+
+ @property
+ def completed(self):
+ return sum(1 for s in self.signoffs if not s.revoked)
+
+ @property
+ def required(self):
+ return self.specification.required
+
+ def user_signed_off(self, user=None):
+ '''Did a given user signoff on this package? user can be passed as an
+ argument, or attached to the group object itself so this can be called
+ from a template.'''
+ if user is None:
+ user = self.user
+ return user in (s.user for s in self.signoffs if not s.revoked)
+
+ def __unicode__(self):
+ return u'%s-%s (%s): %d' % (
+ self.pkgbase, self.version, self.arch, len(self.signoffs))
+
+_SQL_SPEC_OR_SIGNOFF = """
SELECT DISTINCT s.id
- FROM packages_signoff s
+ FROM %s s
JOIN packages p ON (
s.pkgbase = p.pkgbase
AND s.pkgver = p.pkgver
@@ -161,15 +295,88 @@ SELECT DISTINCT s.id
AND s.arch_id = p.arch_id
AND s.repo_id = p.repo_id
)
- JOIN repos r ON p.repo_id = r.id
- WHERE r.testing = %s
+ AND p.repo_id IN (%s)
"""
+
+def get_current_signoffs(repos):
+ '''Returns a mapping of pkgbase -> signoff objects for the given repos.'''
cursor = connection.cursor()
- cursor.execute(sql, [True])
+ # query pre-process- fill in table name and placeholders for IN
+ sql = _SQL_SPEC_OR_SIGNOFF % ('packages_signoff',
+ ','.join(['%s' for r in repos]))
+ cursor.execute(sql, [r.pk for r in repos])
+
results = cursor.fetchall()
# fetch all of the returned signoffs by ID
to_fetch = [row[0] for row in results]
signoffs = Signoff.objects.select_related('user').in_bulk(to_fetch)
return signoffs.values()
+def get_current_specifications(repos):
+ '''Returns a mapping of pkgbase -> signoff specification objects for the
+ given repos.'''
+ cursor = connection.cursor()
+ sql = _SQL_SPEC_OR_SIGNOFF % ('packages_signoffspecification',
+ ','.join(['%s' for r in repos]))
+ cursor.execute(sql, [r.pk for r in repos])
+
+ results = cursor.fetchall()
+ to_fetch = [row[0] for row in results]
+ return SignoffSpecification.objects.in_bulk(to_fetch).values()
+
+def get_target_repo_map(repos):
+ sql = """
+SELECT DISTINCT p1.pkgbase, r.name
+ FROM packages p1
+ JOIN repos r ON p1.repo_id = r.id
+ JOIN packages p2 ON p1.pkgbase = p2.pkgbase
+ WHERE r.staging = %s
+ AND r.testing = %s
+ AND p2.repo_id IN (
+ """
+ sql += ','.join(['%s' for r in repos])
+ sql += ")"
+
+ params = [False, False]
+ params.extend(r.pk for r in repos)
+
+ cursor = connection.cursor()
+ cursor.execute(sql, params)
+ return dict(cursor.fetchall())
+
+def get_signoff_groups(repos=None, user=None):
+ if repos is None:
+ repos = Repo.objects.filter(testing=True)
+ repo_ids = [r.pk for r in repos]
+
+ test_pkgs = Package.objects.select_related(
+ 'arch', 'repo', 'packager').filter(repo__in=repo_ids)
+ packages = test_pkgs.order_by('pkgname')
+ packages = attach_maintainers(packages)
+
+ # Filter by user if asked to do so
+ if user is not None:
+ packages = [p for p in packages if user == p.packager
+ or user in p.maintainers]
+
+ # Collect all pkgbase values in testing repos
+ pkgtorepo = get_target_repo_map(repos)
+
+ # Collect all possible signoffs and specifications for these packages
+ signoffs = get_current_signoffs(repos)
+ specs = get_current_specifications(repos)
+
+ same_pkgbase_key = lambda x: (x.repo.name, x.arch.name, x.pkgbase)
+ grouped = groupby_preserve_order(packages, same_pkgbase_key)
+ signoff_groups = []
+ for group in grouped:
+ signoff_group = PackageSignoffGroup(group)
+ signoff_group.target_repo = pkgtorepo.get(signoff_group.pkgbase,
+ "Unknown")
+ signoff_group.find_signoffs(signoffs)
+ signoff_group.find_specification(specs)
+ signoff_groups.append(signoff_group)
+
+ return signoff_groups
+
# vim: set ts=4 sw=4 et: