From b893682f356ca2861d676a51c4ae1c937d4c7c44 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 14 Sep 2011 14:37:13 -0400 Subject: Ensure we have a mirror URL to return If our query returned zero results, then try a slightly less exclusive query followed by returning a 404 result. Signed-off-by: Dan McGee --- packages/views.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 8c9c1b18..79793355 100644 --- a/packages/views.py +++ b/packages/views.py @@ -548,15 +548,21 @@ def flag_confirmed(request, name, repo, arch): def download(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) - mirrorurl = MirrorUrl.objects.filter(mirror__country='Any', + mirror_urls = MirrorUrl.objects.filter( mirror__public=True, mirror__active=True, - protocol__protocol__iexact='HTTP')[0] + protocol__protocol__iexact='HTTP') + # look first for an 'Any' URL, then fall back to any HTTP URL + filtered_urls = mirror_urls.filter(mirror__country='Any')[:1] + if not filtered_urls: + filtered_urls = mirror_urls[:1] + if not filtered_urls: + raise Http404 arch = pkg.arch.name if pkg.arch.agnostic: # grab the first non-any arch to fake the download path arch = Arch.objects.exclude(agnostic=True)[0].name values = { - 'host': mirrorurl.url, + 'host': filtered_urls[0].url, 'arch': arch, 'repo': pkg.repo.name.lower(), 'file': pkg.filename, -- cgit v1.2.3-2-g168b From 797185faed0555efb88a1e6a18e447548a9935fd Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 19 Sep 2011 08:44:22 -0500 Subject: Add some dev dashboard info regarding signed package count This adds a column similar to the flagged package count for the number of signed packages in a given architecture or repository. It is up to the user to do some simple math to figure out the number of unsigned packages. Also, add 'signed' as a hidden search field option similar to what we did for packager. Signed-off-by: Dan McGee --- packages/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 79793355..61e4d290 100644 --- a/packages/views.py +++ b/packages/views.py @@ -199,6 +199,9 @@ class PackageSearchForm(forms.Form): flagged = forms.ChoiceField( choices=[('', 'All')] + make_choice(['Flagged', 'Not Flagged']), required=False) + signed = forms.ChoiceField( + choices=[('', 'All')] + make_choice(['Signed', 'Unsigned']), + required=False) limit = LimitTypedChoiceField( choices=make_choice([50, 100, 250]) + [('all', 'All')], coerce=coerce_limit_value, @@ -254,6 +257,11 @@ def search(request, page=None): elif form.cleaned_data['flagged'] == 'Not Flagged': packages = packages.filter(flag_date__isnull=True) + if form.cleaned_data['signed'] == 'Signed': + packages = packages.filter(pgp_signature__isnull=False) + elif form.cleaned_data['signed'] == 'Unsigned': + packages = packages.filter(pgp_signature__isnull=True) + if form.cleaned_data['q']: query = form.cleaned_data['q'] q = Q(pkgname__icontains=query) | Q(pkgdesc__icontains=query) -- cgit v1.2.3-2-g168b From 71e57570c262fffb11ca6e0dc97342119198f740 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 11 Oct 2011 19:29:15 -0500 Subject: Pylint suggested and other cleanups Signed-off-by: Dan McGee --- packages/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 61e4d290..a8216c7a 100644 --- a/packages/views.py +++ b/packages/views.py @@ -19,7 +19,7 @@ from django.views.generic.simple import direct_to_template from datetime import datetime from operator import attrgetter -import string +from string import Template from urllib import urlencode from main.models import Package, PackageFile, Arch, Repo @@ -575,7 +575,7 @@ def download(request, name, repo, arch): 'repo': pkg.repo.name.lower(), 'file': pkg.filename, } - url = string.Template('${host}${repo}/os/${arch}/${file}').substitute(values) + url = Template('${host}${repo}/os/${arch}/${file}').substitute(values) return redirect(url) def arch_differences(request): -- cgit v1.2.3-2-g168b From 0d693fa1fb788a61359415f56dc487f4aa504a55 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 12 Oct 2011 08:47:26 -0500 Subject: Add hidden name and desc fields to package search Not linked from anywhere just yet, but they are available if you know they exist and can be used in the standard query string. Signed-off-by: Dan McGee --- packages/views.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index a8216c7a..a740e689 100644 --- a/packages/views.py +++ b/packages/views.py @@ -191,6 +191,8 @@ class LimitTypedChoiceField(forms.TypedChoiceField): class PackageSearchForm(forms.Form): repo = forms.MultipleChoiceField(required=False) arch = forms.MultipleChoiceField(required=False) + name = forms.CharField(required=False) + desc = forms.CharField(required=False) q = forms.CharField(required=False) maintainer = forms.ChoiceField(required=False) packager = forms.ChoiceField(required=False) @@ -262,15 +264,24 @@ def search(request, page=None): elif form.cleaned_data['signed'] == 'Unsigned': packages = packages.filter(pgp_signature__isnull=True) - if form.cleaned_data['q']: - query = form.cleaned_data['q'] - q = Q(pkgname__icontains=query) | Q(pkgdesc__icontains=query) - packages = packages.filter(q) if form.cleaned_data['last_update']: lu = form.cleaned_data['last_update'] packages = packages.filter(last_update__gte= datetime(lu.year, lu.month, lu.day, 0, 0)) + if form.cleaned_data['name']: + name = form.cleaned_data['name'] + packages = packages.filter(pkgname__icontains=name) + + if form.cleaned_data['desc']: + desc = form.cleaned_data['desc'] + packages = packages.filter(pkgdesc__icontains=desc) + + if form.cleaned_data['q']: + query = form.cleaned_data['q'] + q = Q(pkgname__icontains=query) | Q(pkgdesc__icontains=query) + packages = packages.filter(q) + asked_limit = form.cleaned_data['limit'] if asked_limit and asked_limit < 0: limit = None -- cgit v1.2.3-2-g168b From 21a8fec980eb0613d3cce8aae7d6bfe6680c038a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 12 Oct 2011 08:54:51 -0500 Subject: Package search and sort code cleanup Move initializations closer to where they are actually needed, and remove the sorting on multiple columns when a sort field is passed in. We don't do this for the default sort, so let's not do it here either. Signed-off-by: Dan McGee --- packages/views.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index a740e689..dab06919 100644 --- a/packages/views.py +++ b/packages/views.py @@ -293,24 +293,22 @@ def search(request, page=None): else: form = PackageSearchForm() - current_query = request.GET.urlencode() - page_dict = { - 'search_form': form, - 'current_query': current_query - } allowed_sort = ["arch", "repo", "pkgname", "pkgbase", "compressed_size", "installed_size", "build_date", "last_update", "flag_date"] allowed_sort += ["-" + s for s in allowed_sort] sort = request.GET.get('sort', None) - # TODO: sorting by multiple fields makes using a DB index much harder if sort in allowed_sort: - packages = packages.order_by( - request.GET['sort'], 'repo', 'arch', 'pkgname') + packages = packages.order_by(sort) page_dict['sort'] = sort else: packages = packages.order_by('pkgname') + current_query = request.GET.urlencode() + page_dict = { + 'search_form': form, + 'current_query': current_query + } return list_detail.object_list(request, packages, template_name="packages/search.html", page=page, -- cgit v1.2.3-2-g168b From 16cac1ad88cb2fb653cf7abcb91e6f51c7510e27 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 12 Oct 2011 09:03:28 -0500 Subject: Revert movement of search initialization code I'm stupid and didn't realize it was referenced before the location I moved it to. Signed-off-by: Dan McGee --- packages/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index dab06919..5114c87f 100644 --- a/packages/views.py +++ b/packages/views.py @@ -293,6 +293,11 @@ def search(request, page=None): else: form = PackageSearchForm() + current_query = request.GET.urlencode() + page_dict = { + 'search_form': form, + 'current_query': current_query + } allowed_sort = ["arch", "repo", "pkgname", "pkgbase", "compressed_size", "installed_size", "build_date", "last_update", "flag_date"] @@ -304,11 +309,6 @@ def search(request, page=None): else: packages = packages.order_by('pkgname') - current_query = request.GET.urlencode() - page_dict = { - 'search_form': form, - 'current_query': current_query - } return list_detail.object_list(request, packages, template_name="packages/search.html", page=page, -- cgit v1.2.3-2-g168b From ac2278423a3d449fdfe8c813f1f2d391ef9aff08 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 3 Nov 2011 14:59:00 -0500 Subject: Many signoff page improvements Add a new 'SignoffSpecification' model which will capture metadata regarding a specific package if it differs from the norm- e.g. more or less than 2 required signoffs, is known to be bad, a comment from the maintainer, etc. The groundwork is laid here; much of this will still need to be wired up in the future. Enhance the view with a lot more JS prettiness and add revoking of signoffs. The signoff page can be filtered and the links and all the fun stuff are totally dynamic now. Signed-off-by: Dan McGee --- packages/views.py | 71 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 21 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 5114c87f..035d51cb 100644 --- a/packages/views.py +++ b/packages/views.py @@ -25,7 +25,7 @@ from urllib import urlencode from main.models import Package, PackageFile, Arch, Repo from main.utils import make_choice, groupby_preserve_order, PackageStandin from mirrors.models import MirrorUrl -from .models import PackageRelation, PackageGroup, Signoff +from .models import PackageRelation, PackageGroup, SignoffSpecification, Signoff from .utils import (get_group_info, get_differences_info, get_wrong_permissions, get_current_signoffs) @@ -369,14 +369,24 @@ def unflag_all(request, name, repo, arch): pkgs.update(flag_date=None) return redirect(pkg) +DEFAULT_SIGNOFF_SPEC = SignoffSpecification(required=2) + +def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_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, target_repo=None, signoffs=None): + def __init__(self, packages, user=None): if len(packages) == 0: raise Exception self.packages = packages - self.target_repo = target_repo - self.signoffs = signoffs + self.user = user + self.target_repo = None + self.signoffs = set() + self.specification = DEFAULT_SIGNOFF_SPEC first = packages[0] self.pkgbase = first.pkgbase @@ -406,21 +416,24 @@ class PackageSignoffGroup(object): 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.''' - if self.signoffs is None: - self.signoffs = [] 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.append(s) + self.signoffs.add(s) def approved(self): - if self.signoffs: - good_signoffs = [s for s in self.signoffs if not s.revoked] - return len(good_signoffs) >= Signoff.REQUIRED - return False + return approved_by_signoffs(self.signoffs, self.specification) + + 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) @permission_required('main.change_package') @never_cache @@ -443,7 +456,7 @@ def signoffs(request): grouped = groupby_preserve_order(packages, same_pkgbase_key) signoff_groups = [] for group in grouped: - signoff_group = PackageSignoffGroup(group) + signoff_group = PackageSignoffGroup(group, user=request.user) signoff_group.target_repo = pkgtorepo.get(signoff_group.pkgbase, "Unknown") signoff_group.find_signoffs(signoffs) @@ -451,27 +464,43 @@ def signoffs(request): signoff_groups.sort(key=attrgetter('pkgbase')) - return direct_to_template(request, 'packages/signoffs.html', - {'signoff_groups': signoff_groups}) + context = { + 'signoff_groups': signoff_groups, + 'arches': Arch.objects.all(), + } + return direct_to_template(request, 'packages/signoffs.html', context) @permission_required('main.change_package') @never_cache -def signoff_package(request, name, repo, arch): +def signoff_package(request, name, repo, arch, revoke=False): packages = get_list_or_404(Package, pkgbase=name, arch__name=arch, repo__name__iexact=repo, repo__testing=True) - pkg = packages[0] - signoff, created = Signoff.objects.get_or_create( - pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, - epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo, user=request.user) + package = packages[0] + + if revoke: + try: + signoff = Signoff.objects.get_from_package( + package, request.user, False) + except Signoff.DoesNotExist: + raise Http404 + signoff.revoked = datetime.utcnow() + signoff.save() + created = False + else: + signoff, created = Signoff.objects.get_or_create_from_package( + package, request.user) + + all_signoffs = Signoff.objects.for_package(package) if request.is_ajax(): data = { 'created': created, - 'approved': pkg.approved_for_signoff(), + 'revoked': bool(signoff.revoked), + 'approved': approved_by_signoffs(all_signoffs), 'user': str(request.user), } - return HttpResponse(simplejson.dumps(data), + return HttpResponse(simplejson.dumps(data, ensure_ascii=False), mimetype='application/json') return redirect('package-signoffs') -- cgit v1.2.3-2-g168b From 74d2a5df5ca7ee4b6497a6e7609491d72cdbb309 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 3 Nov 2011 17:18:13 -0500 Subject: Refactor more package signoff stuff This sets up some shared utility code for use in a later package signoff email report command. Signed-off-by: Dan McGee --- packages/views.py | 100 ++++-------------------------------------------------- 1 file changed, 6 insertions(+), 94 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 035d51cb..e102760b 100644 --- a/packages/views.py +++ b/packages/views.py @@ -23,11 +23,11 @@ from string import Template from urllib import urlencode from main.models import Package, PackageFile, Arch, Repo -from main.utils import make_choice, groupby_preserve_order, PackageStandin +from main.utils import make_choice from mirrors.models import MirrorUrl -from .models import PackageRelation, PackageGroup, SignoffSpecification, Signoff +from .models import PackageRelation, PackageGroup, Signoff from .utils import (get_group_info, get_differences_info, - get_wrong_permissions, get_current_signoffs) + get_wrong_permissions, get_signoff_groups, approved_by_signoffs) class PackageJSONEncoder(DjangoJSONEncoder): pkg_attributes = [ 'pkgname', 'pkgbase', 'repo', 'arch', 'pkgver', @@ -369,100 +369,12 @@ def unflag_all(request, name, repo, arch): pkgs.update(flag_date=None) return redirect(pkg) -DEFAULT_SIGNOFF_SPEC = SignoffSpecification(required=2) - -def approved_by_signoffs(signoffs, spec=DEFAULT_SIGNOFF_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, user=None): - if len(packages) == 0: - raise Exception - self.packages = packages - self.user = user - self.target_repo = None - self.signoffs = set() - self.specification = DEFAULT_SIGNOFF_SPEC - - first = packages[0] - self.pkgbase = first.pkgbase - self.arch = first.arch - self.repo = first.repo - self.version = '' - - 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 approved(self): - return approved_by_signoffs(self.signoffs, self.specification) - - 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) - @permission_required('main.change_package') @never_cache def signoffs(request): - test_pkgs = Package.objects.normal().filter(repo__testing=True) - packages = test_pkgs.order_by('pkgname') - - # Collect all pkgbase values in testing repos - q_pkgbase = test_pkgs.values('pkgbase') - package_repos = Package.objects.order_by().values_list( - 'pkgbase', 'repo__name').filter( - repo__testing=False, repo__staging=False, - pkgbase__in=q_pkgbase).distinct() - pkgtorepo = dict(package_repos) - - # Collect all existing signoffs for these packages - signoffs = get_current_signoffs() - - 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, user=request.user) - signoff_group.target_repo = pkgtorepo.get(signoff_group.pkgbase, - "Unknown") - signoff_group.find_signoffs(signoffs) - signoff_groups.append(signoff_group) - - signoff_groups.sort(key=attrgetter('pkgbase')) + signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase')) + for group in signoff_groups: + group.user = request.user context = { 'signoff_groups': signoff_groups, -- cgit v1.2.3-2-g168b From 8187b87143081a2be75032db91287f9deb9d1f89 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 3 Nov 2011 19:10:07 -0500 Subject: Add signoff options form and data entry page This allows the criteria and other information about certain signoffs to be overridden as necessary. Signed-off-by: Dan McGee --- packages/views.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index e102760b..66bcd3fc 100644 --- a/packages/views.py +++ b/packages/views.py @@ -25,7 +25,7 @@ from urllib import urlencode from main.models import Package, PackageFile, Arch, Repo from main.utils import make_choice from mirrors.models import MirrorUrl -from .models import PackageRelation, PackageGroup, Signoff +from .models import PackageRelation, PackageGroup, SignoffSpecification, Signoff from .utils import (get_group_info, get_differences_info, get_wrong_permissions, get_signoff_groups, approved_by_signoffs) @@ -417,6 +417,44 @@ def signoff_package(request, name, repo, arch, revoke=False): return redirect('package-signoffs') +class SignoffOptionsForm(forms.ModelForm): + class Meta: + model = SignoffSpecification + fields = ('required', 'enabled', 'known_bad', 'comments') + +@permission_required('main.change_package') +@never_cache +def signoff_options(request, name, repo, arch): + packages = get_list_or_404(Package, pkgbase=name, + arch__name=arch, repo__name__iexact=repo, repo__testing=True) + package = packages[0] + + # TODO ensure submitter is maintainer and/or packager + + try: + spec = SignoffSpecification.objects.get_from_package(package) + except SignoffSpecification.DoesNotExist: + # create a fake one, but don't save it just yet + spec = SignoffSpecification(pkgbase=package.pkgbase, + pkgver=package.pkgver, pkgrel=package.pkgrel, + epoch=package.epoch, arch=package.arch, repo=package.repo) + spec.user = request.user + + if request.POST: + form = SignoffOptionsForm(request.POST, instance=spec) + if form.is_valid(): + form.save() + return redirect('package-signoffs') + else: + form = SignoffOptionsForm(instance=spec) + + context = { + 'packages': packages, + 'package': package, + 'form': form, + } + return direct_to_template(request, 'packages/signoff_options.html', context) + def flaghelp(request): return direct_to_template(request, 'packages/flaghelp.html') -- cgit v1.2.3-2-g168b From 5f2c3bf98baabf919681525e600639643aa2c119 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 3 Nov 2011 20:39:59 -0500 Subject: Signoffs changes and improvements * Better signoff report with more detail * Show signoff specification in signoffs view * Honor disabled/bad flags and display in approval column * Various other small bugfixes and tweaks Signed-off-by: Dan McGee --- packages/views.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 66bcd3fc..307691e2 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) -- cgit v1.2.3-2-g168b From 800ea45528e297c38e068775951e666f8191ef45 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 3 Nov 2011 21:20:28 -0500 Subject: Ensure signoffs can only be created if allowed Signed-off-by: Dan McGee --- packages/views.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 307691e2..00dd7f7d 100644 --- a/packages/views.py +++ b/packages/views.py @@ -388,9 +388,10 @@ def signoffs(request): def signoff_package(request, name, repo, arch, revoke=False): packages = get_list_or_404(Package, pkgbase=name, arch__name=arch, repo__name__iexact=repo, repo__testing=True) - package = packages[0] + spec = SignoffSpecification.objects.get_or_default_from_package(package) + if revoke: try: signoff = Signoff.objects.get_from_package( @@ -401,11 +402,13 @@ def signoff_package(request, name, repo, arch, revoke=False): signoff.save() created = False else: + # ensure we should even be accepting signoffs + if spec.known_bad or not spec.enabled: + return render(request, '403.html', status=403) signoff, created = Signoff.objects.get_or_create_from_package( package, request.user) all_signoffs = Signoff.objects.for_package(package) - spec = SignoffSpecification.objects.get_or_default_from_package(package) if request.is_ajax(): data = { -- cgit v1.2.3-2-g168b From 5e295a3dbb0b64f229e9419384721b154e013b9e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 3 Nov 2011 21:20:50 -0500 Subject: Allow signoff options to apply to all packages across architectures If you check the new box, you can set the options for both the i686 and the x86_64 packages at the same time. Signed-off-by: Dan McGee --- packages/views.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 00dd7f7d..aa15d0cf 100644 --- a/packages/views.py +++ b/packages/views.py @@ -6,6 +6,7 @@ from django.contrib.auth.decorators import permission_required from django.conf import settings from django.core.mail import send_mail from django.core.serializers.json import DjangoJSONEncoder +from django.db import transaction from django.db.models import Q from django.http import HttpResponse, Http404, HttpResponseForbidden from django.shortcuts import (get_object_or_404, get_list_or_404, @@ -426,10 +427,36 @@ def signoff_package(request, name, repo, arch, revoke=False): return redirect('package-signoffs') class SignoffOptionsForm(forms.ModelForm): + apply_all = forms.BooleanField(required=False, + help_text="Apply these options to all architectures?") + class Meta: model = SignoffSpecification fields = ('required', 'enabled', 'known_bad', 'comments') +def _signoff_options_all(request, name, repo): + seen_ids = set() + with transaction.commit_on_success(): + # find or create a specification for all architectures, then + # graft the form data onto them + packages = Package.objects.filter(pkgbase=name, + repo__name__iexact=repo, repo__testing=True) + for package in packages: + try: + spec = SignoffSpecification.objects.get_from_package(package) + if spec.pk in seen_ids: + continue + except SignoffSpecification.DoesNotExist: + spec = SignoffSpecification(pkgbase=package.pkgbase, + pkgver=package.pkgver, pkgrel=package.pkgrel, + epoch=package.epoch, arch=package.arch, + repo=package.repo) + spec.user = request.user + form = SignoffOptionsForm(request.POST, instance=spec) + if form.is_valid(): + form.save() + seen_ids.add(form.instance.pk) + @permission_required('main.change_package') @never_cache def signoff_options(request, name, repo, arch): @@ -453,7 +480,10 @@ def signoff_options(request, name, repo, arch): if request.POST: form = SignoffOptionsForm(request.POST, instance=spec) if form.is_valid(): - form.save() + if form.cleaned_data['apply_all']: + _signoff_options_all(request, name, repo) + else: + form.save() return redirect('package-signoffs') else: form = SignoffOptionsForm(instance=spec) -- cgit v1.2.3-2-g168b From 8ba68aed370c2369bebaaca4d4158b6c40223c0f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 4 Nov 2011 10:59:45 -0500 Subject: Add filter by target repo on signoffs page And add a count of displayed rows below the filter options. Signed-off-by: Dan McGee --- packages/views.py | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index aa15d0cf..3c0c2bee 100644 --- a/packages/views.py +++ b/packages/views.py @@ -381,6 +381,7 @@ def signoffs(request): context = { 'signoff_groups': signoff_groups, 'arches': Arch.objects.all(), + 'repo_names': sorted(set(g.target_repo for g in signoff_groups)), } return direct_to_template(request, 'packages/signoffs.html', context) -- cgit v1.2.3-2-g168b From d80f4236d01f70380f71a46dd98f1f789d91d31c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 10 Nov 2011 18:09:19 -0600 Subject: Add package signoffs JSON view This allows access to the same data (and even a bit more) from the signoffs overview page in a machine-friendly way. Signed-off-by: Dan McGee --- packages/views.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py index 3c0c2bee..cac5d076 100644 --- a/packages/views.py +++ b/packages/views.py @@ -29,7 +29,8 @@ from main.utils import make_choice from mirrors.models import MirrorUrl from .models import PackageRelation, PackageGroup, SignoffSpecification, Signoff from .utils import (get_group_info, get_differences_info, - get_wrong_permissions, get_signoff_groups, approved_by_signoffs) + get_wrong_permissions, get_signoff_groups, approved_by_signoffs, + PackageSignoffGroup) class PackageJSONEncoder(DjangoJSONEncoder): pkg_attributes = [ 'pkgname', 'pkgbase', 'repo', 'arch', 'pkgver', @@ -371,6 +372,7 @@ def unflag_all(request, name, repo, arch): pkgs.update(flag_date=None) return redirect(pkg) + @permission_required('main.change_package') @never_cache def signoffs(request): @@ -496,6 +498,49 @@ def signoff_options(request, name, repo, arch): } return direct_to_template(request, 'packages/signoff_options.html', context) +class SignoffJSONEncoder(DjangoJSONEncoder): + '''Base JSONEncoder extended to handle all serialization of all classes + related to signoffs.''' + signoff_group_attrs = ['arch', 'last_update', 'maintainers', 'packager', + 'pkgbase', 'repo', 'signoffs', 'target_repo', 'version'] + signoff_spec_attrs = ['required', 'enabled', 'known_bad', 'comments'] + signoff_attrs = ['user', 'created', 'revoked'] + + def default(self, obj): + if isinstance(obj, PackageSignoffGroup): + data = dict((attr, getattr(obj, attr)) + for attr in self.signoff_group_attrs) + data['package_count'] = len(obj.packages) + data['approved'] = obj.approved() + data.update((attr, getattr(obj.specification, attr)) + for attr in self.signoff_spec_attrs) + return data + elif isinstance(obj, Signoff): + data = dict((attr, getattr(obj, attr)) + for attr in self.signoff_attrs) + return data + elif isinstance(obj, Arch) or isinstance(obj, Repo): + return unicode(obj) + elif isinstance(obj, User): + return obj.username + elif isinstance(obj, set): + return list(obj) + return super(SignoffJSONEncoder, self).default(obj) + +@permission_required('main.change_package') +@never_cache +def signoffs_json(request): + signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase')) + data = { + 'version': 1, + 'signoff_groups': signoff_groups, + } + to_json = simplejson.dumps(data, ensure_ascii=False, + cls=SignoffJSONEncoder) + response = HttpResponse(to_json, mimetype='application/json') + return response + + def flaghelp(request): return direct_to_template(request, 'packages/flaghelp.html') -- cgit v1.2.3-2-g168b From 83feb682c2909cbd8c332a9b16aacbc8d696a13a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 10 Nov 2011 18:12:47 -0600 Subject: Move package views into subdirectory This simply moves views.py to views/__init__.py and adjusts the imports accordingly; future patches will split this into multiple files as this module is getting quite large. Signed-off-by: Dan McGee --- packages/views.py | 696 ------------------------------------------------------ 1 file changed, 696 deletions(-) delete mode 100644 packages/views.py (limited to 'packages/views.py') diff --git a/packages/views.py b/packages/views.py deleted file mode 100644 index cac5d076..00000000 --- a/packages/views.py +++ /dev/null @@ -1,696 +0,0 @@ -from django import forms -from django.contrib import messages -from django.contrib.admin.widgets import AdminDateWidget -from django.contrib.auth.models import User -from django.contrib.auth.decorators import permission_required -from django.conf import settings -from django.core.mail import send_mail -from django.core.serializers.json import DjangoJSONEncoder -from django.db import transaction -from django.db.models import Q -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 -from django.views.decorators.http import require_POST -from django.views.decorators.vary import vary_on_headers -from django.views.generic import list_detail -from django.views.generic.simple import direct_to_template - -from datetime import datetime -from operator import attrgetter -from string import Template -from urllib import urlencode - -from main.models import Package, PackageFile, Arch, Repo -from main.utils import make_choice -from mirrors.models import MirrorUrl -from .models import PackageRelation, PackageGroup, SignoffSpecification, Signoff -from .utils import (get_group_info, get_differences_info, - get_wrong_permissions, get_signoff_groups, approved_by_signoffs, - PackageSignoffGroup) - -class PackageJSONEncoder(DjangoJSONEncoder): - pkg_attributes = [ 'pkgname', 'pkgbase', 'repo', 'arch', 'pkgver', - 'pkgrel', 'epoch', 'pkgdesc', 'url', 'filename', 'compressed_size', - 'installed_size', 'build_date', 'last_update', 'flag_date' ] - - def default(self, obj): - if hasattr(obj, '__iter__'): - # mainly for queryset serialization - return list(obj) - if isinstance(obj, Package): - data = dict((attr, getattr(obj, attr)) - for attr in self.pkg_attributes) - data['groups'] = obj.groups.all() - return data - if isinstance(obj, PackageFile): - filename = obj.filename or '' - return obj.directory + filename - if isinstance(obj, (Repo, Arch, PackageGroup)): - return obj.name.lower() - return super(PackageJSONEncoder, self).default(obj) - -def opensearch(request): - if request.is_secure(): - domain = "https://%s" % request.META['HTTP_HOST'] - else: - domain = "http://%s" % request.META['HTTP_HOST'] - - return direct_to_template(request, 'packages/opensearch.xml', - {'domain': domain}, - mimetype='application/opensearchdescription+xml') - -@permission_required('main.change_package') -@require_POST -def update(request): - ids = request.POST.getlist('pkgid') - count = 0 - - if request.POST.has_key('adopt'): - repos = request.user.userprofile.allowed_repos.all() - pkgs = Package.objects.filter(id__in=ids, repo__in=repos) - disallowed_pkgs = Package.objects.filter(id__in=ids).exclude( - repo__in=repos) - - if disallowed_pkgs: - messages.warning(request, - "You do not have permission to adopt: %s." % ( - ' '.join([p.pkgname for p in disallowed_pkgs]) - )) - - for pkg in pkgs: - if request.user not in pkg.maintainers: - prel = PackageRelation(pkgbase=pkg.pkgbase, - user=request.user, - type=PackageRelation.MAINTAINER) - count += 1 - prel.save() - - messages.info(request, "%d base packages adopted." % count) - - elif request.POST.has_key('disown'): - # allow disowning regardless of allowed repos, helps things like - # [community] -> [extra] moves - for pkg in Package.objects.filter(id__in=ids): - if request.user in pkg.maintainers: - rels = PackageRelation.objects.filter(pkgbase=pkg.pkgbase, - user=request.user, - type=PackageRelation.MAINTAINER) - count += rels.count() - rels.delete() - - messages.info(request, "%d base packages disowned." % count) - - else: - messages.error(request, "Are you trying to adopt or disown?") - return redirect('/packages/') - -def details(request, name='', repo='', arch=''): - if all([name, repo, arch]): - try: - pkg = Package.objects.select_related( - 'arch', 'repo', 'packager').get(pkgname=name, - repo__name__iexact=repo, arch__name=arch) - return direct_to_template(request, 'packages/details.html', - {'pkg': pkg, }) - except Package.DoesNotExist: - arch = get_object_or_404(Arch, name=arch) - arches = [ arch ] - arches.extend(Arch.objects.filter(agnostic=True)) - repo = get_object_or_404(Repo, name__iexact=repo) - pkgs = Package.objects.normal().filter(pkgbase=name, - repo__testing=repo.testing, repo__staging=repo.staging, - arch__in=arches).order_by('pkgname') - if len(pkgs) == 0: - raise Http404 - context = { - 'list_title': 'Split Package Details', - 'name': name, - 'arch': arch, - 'packages': pkgs, - } - return direct_to_template(request, 'packages/packages_list.html', - context) - else: - pkg_data = [ - ('arch', arch.lower()), - ('repo', repo.lower()), - ('q', name), - ] - # only include non-blank values in the query we generate - pkg_data = [(x, y) for x, y in pkg_data if y] - return redirect("/packages/?%s" % urlencode(pkg_data)) - -def groups(request, arch=None): - arches = [] - if arch: - get_object_or_404(Arch, name=arch, agnostic=False) - arches.append(arch) - grps = get_group_info(arches) - context = { - 'groups': grps, - 'arch': arch, - } - return direct_to_template(request, 'packages/groups.html', context) - -def group_details(request, arch, name): - arch = get_object_or_404(Arch, name=arch) - arches = [ arch ] - arches.extend(Arch.objects.filter(agnostic=True)) - pkgs = Package.objects.normal().filter( - groups__name=name, arch__in=arches).order_by('pkgname') - if len(pkgs) == 0: - raise Http404 - context = { - 'list_title': 'Group Details', - 'name': name, - 'arch': arch, - 'packages': pkgs, - } - return direct_to_template(request, 'packages/packages_list.html', context) - -def coerce_limit_value(value): - if not value: - return None - if value == 'all': - # negative value indicates show all results - return -1 - value = int(value) - if value < 0: - raise ValueError - return value - -class LimitTypedChoiceField(forms.TypedChoiceField): - def valid_value(self, value): - try: - coerce_limit_value(value) - return True - except (ValueError, TypeError): - return False - -class PackageSearchForm(forms.Form): - repo = forms.MultipleChoiceField(required=False) - arch = forms.MultipleChoiceField(required=False) - name = forms.CharField(required=False) - desc = forms.CharField(required=False) - q = forms.CharField(required=False) - maintainer = forms.ChoiceField(required=False) - packager = forms.ChoiceField(required=False) - last_update = forms.DateField(required=False, widget=AdminDateWidget(), - label='Last Updated After') - flagged = forms.ChoiceField( - choices=[('', 'All')] + make_choice(['Flagged', 'Not Flagged']), - required=False) - signed = forms.ChoiceField( - choices=[('', 'All')] + make_choice(['Signed', 'Unsigned']), - required=False) - limit = LimitTypedChoiceField( - choices=make_choice([50, 100, 250]) + [('all', 'All')], - coerce=coerce_limit_value, - required=False, - initial=50) - - def __init__(self, *args, **kwargs): - super(PackageSearchForm, self).__init__(*args, **kwargs) - self.fields['repo'].choices = make_choice( - [repo.name for repo in Repo.objects.all()]) - self.fields['arch'].choices = make_choice( - [arch.name for arch in Arch.objects.all()]) - self.fields['q'].widget.attrs.update({"size": "30"}) - maints = User.objects.filter(is_active=True).order_by('username') - self.fields['maintainer'].choices = \ - [('', 'All'), ('orphan', 'Orphan')] + \ - [(m.username, m.get_full_name()) for m in maints] - self.fields['packager'].choices = \ - [('', 'All'), ('unknown', 'Unknown')] + \ - [(m.username, m.get_full_name()) for m in maints] - -def search(request, page=None): - limit = 50 - packages = Package.objects.normal() - - if request.GET: - form = PackageSearchForm(data=request.GET) - if form.is_valid(): - if form.cleaned_data['repo']: - packages = packages.filter( - repo__name__in=form.cleaned_data['repo']) - - if form.cleaned_data['arch']: - packages = packages.filter( - arch__name__in=form.cleaned_data['arch']) - - if form.cleaned_data['maintainer'] == 'orphan': - inner_q = PackageRelation.objects.all().values('pkgbase') - packages = packages.exclude(pkgbase__in=inner_q) - elif form.cleaned_data['maintainer']: - inner_q = PackageRelation.objects.filter( - user__username=form.cleaned_data['maintainer']).values('pkgbase') - packages = packages.filter(pkgbase__in=inner_q) - - if form.cleaned_data['packager'] == 'unknown': - packages = packages.filter(packager__isnull=True) - elif form.cleaned_data['packager']: - packages = packages.filter( - packager__username=form.cleaned_data['packager']) - - if form.cleaned_data['flagged'] == 'Flagged': - packages = packages.filter(flag_date__isnull=False) - elif form.cleaned_data['flagged'] == 'Not Flagged': - packages = packages.filter(flag_date__isnull=True) - - if form.cleaned_data['signed'] == 'Signed': - packages = packages.filter(pgp_signature__isnull=False) - elif form.cleaned_data['signed'] == 'Unsigned': - packages = packages.filter(pgp_signature__isnull=True) - - if form.cleaned_data['last_update']: - lu = form.cleaned_data['last_update'] - packages = packages.filter(last_update__gte= - datetime(lu.year, lu.month, lu.day, 0, 0)) - - if form.cleaned_data['name']: - name = form.cleaned_data['name'] - packages = packages.filter(pkgname__icontains=name) - - if form.cleaned_data['desc']: - desc = form.cleaned_data['desc'] - packages = packages.filter(pkgdesc__icontains=desc) - - if form.cleaned_data['q']: - query = form.cleaned_data['q'] - q = Q(pkgname__icontains=query) | Q(pkgdesc__icontains=query) - packages = packages.filter(q) - - asked_limit = form.cleaned_data['limit'] - if asked_limit and asked_limit < 0: - limit = None - elif asked_limit: - limit = asked_limit - else: - # Form had errors, don't return any results, just the busted form - packages = Package.objects.none() - else: - form = PackageSearchForm() - - current_query = request.GET.urlencode() - page_dict = { - 'search_form': form, - 'current_query': current_query - } - allowed_sort = ["arch", "repo", "pkgname", "pkgbase", - "compressed_size", "installed_size", - "build_date", "last_update", "flag_date"] - allowed_sort += ["-" + s for s in allowed_sort] - sort = request.GET.get('sort', None) - if sort in allowed_sort: - packages = packages.order_by(sort) - page_dict['sort'] = sort - else: - packages = packages.order_by('pkgname') - - return list_detail.object_list(request, packages, - template_name="packages/search.html", - page=page, - paginate_by=limit, - template_object_name="package", - extra_context=page_dict) - -@vary_on_headers('X-Requested-With') -def files(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - fileslist = PackageFile.objects.filter(pkg=pkg).order_by('directory', 'filename') - context = { - 'pkg': pkg, - 'files': fileslist, - } - template = 'packages/files.html' - if request.is_ajax(): - template = 'packages/files-list.html' - return direct_to_template(request, template, context) - -def details_json(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - to_json = simplejson.dumps(pkg, ensure_ascii=False, - cls=PackageJSONEncoder) - return HttpResponse(to_json, mimetype='application/json') - -def files_json(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - fileslist = PackageFile.objects.filter(pkg=pkg).order_by('directory', 'filename') - data = { - 'pkgname': pkg.pkgname, - 'repo': pkg.repo.name.lower(), - 'arch': pkg.arch.name.lower(), - 'files': fileslist, - } - to_json = simplejson.dumps(data, ensure_ascii=False, - cls=PackageJSONEncoder) - return HttpResponse(to_json, mimetype='application/json') - -@permission_required('main.change_package') -def unflag(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - pkg.flag_date = None - pkg.save() - return redirect(pkg) - -@permission_required('main.change_package') -def unflag_all(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - # find all packages from (hopefully) the same PKGBUILD - pkgs = Package.objects.filter(pkgbase=pkg.pkgbase, - repo__testing=pkg.repo.testing, repo__staging=pkg.repo.staging) - pkgs.update(flag_date=None) - return redirect(pkg) - - -@permission_required('main.change_package') -@never_cache -def signoffs(request): - signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase')) - for group in signoff_groups: - group.user = request.user - - context = { - 'signoff_groups': signoff_groups, - 'arches': Arch.objects.all(), - 'repo_names': sorted(set(g.target_repo for g in signoff_groups)), - } - return direct_to_template(request, 'packages/signoffs.html', context) - -@permission_required('main.change_package') -@never_cache -def signoff_package(request, name, repo, arch, revoke=False): - packages = get_list_or_404(Package, pkgbase=name, - arch__name=arch, repo__name__iexact=repo, repo__testing=True) - package = packages[0] - - spec = SignoffSpecification.objects.get_or_default_from_package(package) - - if revoke: - try: - signoff = Signoff.objects.get_from_package( - package, request.user, False) - except Signoff.DoesNotExist: - raise Http404 - signoff.revoked = datetime.utcnow() - signoff.save() - created = False - else: - # ensure we should even be accepting signoffs - if spec.known_bad or not spec.enabled: - return render(request, '403.html', status=403) - signoff, created = Signoff.objects.get_or_create_from_package( - package, request.user) - - all_signoffs = Signoff.objects.for_package(package) - - if request.is_ajax(): - data = { - 'created': created, - 'revoked': bool(signoff.revoked), - '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), - mimetype='application/json') - - return redirect('package-signoffs') - -class SignoffOptionsForm(forms.ModelForm): - apply_all = forms.BooleanField(required=False, - help_text="Apply these options to all architectures?") - - class Meta: - model = SignoffSpecification - fields = ('required', 'enabled', 'known_bad', 'comments') - -def _signoff_options_all(request, name, repo): - seen_ids = set() - with transaction.commit_on_success(): - # find or create a specification for all architectures, then - # graft the form data onto them - packages = Package.objects.filter(pkgbase=name, - repo__name__iexact=repo, repo__testing=True) - for package in packages: - try: - spec = SignoffSpecification.objects.get_from_package(package) - if spec.pk in seen_ids: - continue - except SignoffSpecification.DoesNotExist: - spec = SignoffSpecification(pkgbase=package.pkgbase, - pkgver=package.pkgver, pkgrel=package.pkgrel, - epoch=package.epoch, arch=package.arch, - repo=package.repo) - spec.user = request.user - form = SignoffOptionsForm(request.POST, instance=spec) - if form.is_valid(): - form.save() - seen_ids.add(form.instance.pk) - -@permission_required('main.change_package') -@never_cache -def signoff_options(request, name, repo, arch): - packages = get_list_or_404(Package, pkgbase=name, - arch__name=arch, repo__name__iexact=repo, repo__testing=True) - package = packages[0] - - 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) - except SignoffSpecification.DoesNotExist: - # create a fake one, but don't save it just yet - spec = SignoffSpecification(pkgbase=package.pkgbase, - pkgver=package.pkgver, pkgrel=package.pkgrel, - epoch=package.epoch, arch=package.arch, repo=package.repo) - spec.user = request.user - - if request.POST: - form = SignoffOptionsForm(request.POST, instance=spec) - if form.is_valid(): - if form.cleaned_data['apply_all']: - _signoff_options_all(request, name, repo) - else: - form.save() - return redirect('package-signoffs') - else: - form = SignoffOptionsForm(instance=spec) - - context = { - 'packages': packages, - 'package': package, - 'form': form, - } - return direct_to_template(request, 'packages/signoff_options.html', context) - -class SignoffJSONEncoder(DjangoJSONEncoder): - '''Base JSONEncoder extended to handle all serialization of all classes - related to signoffs.''' - signoff_group_attrs = ['arch', 'last_update', 'maintainers', 'packager', - 'pkgbase', 'repo', 'signoffs', 'target_repo', 'version'] - signoff_spec_attrs = ['required', 'enabled', 'known_bad', 'comments'] - signoff_attrs = ['user', 'created', 'revoked'] - - def default(self, obj): - if isinstance(obj, PackageSignoffGroup): - data = dict((attr, getattr(obj, attr)) - for attr in self.signoff_group_attrs) - data['package_count'] = len(obj.packages) - data['approved'] = obj.approved() - data.update((attr, getattr(obj.specification, attr)) - for attr in self.signoff_spec_attrs) - return data - elif isinstance(obj, Signoff): - data = dict((attr, getattr(obj, attr)) - for attr in self.signoff_attrs) - return data - elif isinstance(obj, Arch) or isinstance(obj, Repo): - return unicode(obj) - elif isinstance(obj, User): - return obj.username - elif isinstance(obj, set): - return list(obj) - return super(SignoffJSONEncoder, self).default(obj) - -@permission_required('main.change_package') -@never_cache -def signoffs_json(request): - signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase')) - data = { - 'version': 1, - 'signoff_groups': signoff_groups, - } - to_json = simplejson.dumps(data, ensure_ascii=False, - cls=SignoffJSONEncoder) - response = HttpResponse(to_json, mimetype='application/json') - return response - - -def flaghelp(request): - return direct_to_template(request, 'packages/flaghelp.html') - -class FlagForm(forms.Form): - email = forms.EmailField(label='* E-mail Address') - usermessage = forms.CharField(label='Message To Dev', - widget=forms.Textarea, required=False) - # The field below is used to filter out bots that blindly fill out all input elements - website = forms.CharField(label='', - widget=forms.TextInput(attrs={'style': 'display:none;'}), - required=False) - -@never_cache -def flag(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - if pkg.flag_date is not None: - # already flagged. do nothing. - return direct_to_template(request, 'packages/flagged.html', {'pkg': pkg}) - # find all packages from (hopefully) the same PKGBUILD - pkgs = Package.objects.normal().filter( - pkgbase=pkg.pkgbase, flag_date__isnull=True, - repo__testing=pkg.repo.testing, - repo__staging=pkg.repo.staging).order_by( - 'pkgname', 'repo__name', 'arch__name') - - if request.POST: - form = FlagForm(request.POST) - if form.is_valid() and form.cleaned_data['website'] == '': - # save the package list for later use - flagged_pkgs = list(pkgs) - pkgs.update(flag_date=datetime.utcnow()) - - maints = pkg.maintainers - if not maints: - toemail = settings.NOTIFICATIONS - subject = 'Orphan %s package [%s] marked out-of-date' % \ - (pkg.repo.name, pkg.pkgname) - else: - toemail = [] - subject = '%s package [%s] marked out-of-date' % \ - (pkg.repo.name, pkg.pkgname) - for maint in maints: - if maint.get_profile().notify == True: - toemail.append(maint.email) - - if toemail: - # send notification email to the maintainers - t = loader.get_template('packages/outofdate.txt') - c = Context({ - 'email': form.cleaned_data['email'], - 'message': form.cleaned_data['usermessage'], - 'pkg': pkg, - 'packages': flagged_pkgs, - }) - send_mail(subject, - t.render(c), - 'Arch Website Notification ', - toemail, - fail_silently=True) - - return redirect('package-flag-confirmed', name=name, repo=repo, - arch=arch) - else: - form = FlagForm() - - context = { - 'package': pkg, - 'packages': pkgs, - 'form': form - } - return direct_to_template(request, 'packages/flag.html', context) - -def flag_confirmed(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - pkgs = Package.objects.normal().filter( - pkgbase=pkg.pkgbase, flag_date=pkg.flag_date, - repo__testing=pkg.repo.testing, - repo__staging=pkg.repo.staging).order_by( - 'pkgname', 'repo__name', 'arch__name') - - context = {'package': pkg, 'packages': pkgs} - - return direct_to_template(request, 'packages/flag_confirmed.html', context) - -def download(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - mirror_urls = MirrorUrl.objects.filter( - mirror__public=True, mirror__active=True, - protocol__protocol__iexact='HTTP') - # look first for an 'Any' URL, then fall back to any HTTP URL - filtered_urls = mirror_urls.filter(mirror__country='Any')[:1] - if not filtered_urls: - filtered_urls = mirror_urls[:1] - if not filtered_urls: - raise Http404 - arch = pkg.arch.name - if pkg.arch.agnostic: - # grab the first non-any arch to fake the download path - arch = Arch.objects.exclude(agnostic=True)[0].name - values = { - 'host': filtered_urls[0].url, - 'arch': arch, - 'repo': pkg.repo.name.lower(), - 'file': pkg.filename, - } - url = Template('${host}${repo}/os/${arch}/${file}').substitute(values) - return redirect(url) - -def arch_differences(request): - # TODO: we have some hardcoded magic here with respect to the arches. - arch_a = Arch.objects.get(name='i686') - arch_b = Arch.objects.get(name='x86_64') - differences = get_differences_info(arch_a, arch_b) - context = { - 'arch_a': arch_a, - 'arch_b': arch_b, - 'differences': differences, - } - return direct_to_template(request, 'packages/differences.html', context) - -@permission_required('main.change_package') -@never_cache -def stale_relations(request): - relations = PackageRelation.objects.select_related('user') - pkgbases = Package.objects.all().values('pkgbase') - - inactive_user = relations.filter(user__is_active=False) - missing_pkgbase = relations.exclude( - pkgbase__in=pkgbases).order_by('pkgbase') - wrong_permissions = get_wrong_permissions() - - context = { - 'inactive_user': inactive_user, - 'missing_pkgbase': missing_pkgbase, - 'wrong_permissions': wrong_permissions, - } - return direct_to_template(request, 'packages/stale_relations.html', context) - -@permission_required('packages.delete_packagerelation') -@require_POST -def stale_relations_update(request): - ids = set(request.POST.getlist('relation_id')) - - if ids: - PackageRelation.objects.filter(id__in=ids).delete() - - messages.info(request, "%d package relations deleted." % len(ids)) - return redirect('/packages/stale_relations/') - -# vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b