diff options
Diffstat (limited to 'packages/views')
-rw-r--r-- | packages/views/__init__.py | 195 | ||||
-rw-r--r-- | packages/views/display.py | 232 | ||||
-rw-r--r-- | packages/views/flag.py | 88 | ||||
-rw-r--r-- | packages/views/search.py | 143 | ||||
-rw-r--r-- | packages/views/signoff.py | 32 |
5 files changed, 408 insertions, 282 deletions
diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 6a9c5275..4c195385 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -1,38 +1,66 @@ -from string import Template -from urllib import urlencode +import hashlib +import json from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import User -from django.http import HttpResponse, Http404 -from django.shortcuts import get_object_or_404, redirect -from django.utils import simplejson -from django.views.decorators.http import require_POST -from django.views.decorators.vary import vary_on_headers -from django.views.generic.simple import direct_to_template - -from main.models import Package, PackageFile, Arch, Repo -from mirrors.models import MirrorUrl -from mirrors.utils import get_mirror_url_for_download +from django.core.cache import cache +from django.db.models import Q +from django.http import HttpResponse +from django.shortcuts import redirect, render +from django.views.decorators.cache import cache_control +from django.views.decorators.http import require_GET, require_POST + +from main.models import Package, Arch from ..models import PackageRelation -from ..utils import (get_group_info, get_differences_info, - multilib_differences, get_wrong_permissions, PackageJSONEncoder) +from ..utils import (get_differences_info, + multilib_differences, get_wrong_permissions) # make other views available from this same package +from .display import (details, groups, group_details, files, details_json, + files_json, download) from .flag import flaghelp, flag, flag_confirmed, unflag, unflag_all -from .search import search, search_json +from .search import search_json from .signoff import signoffs, signoff_package, signoff_options, signoffs_json +@require_GET +@cache_control(public=True, max_age=86400) 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', + return render(request, 'packages/opensearch.xml', {'domain': domain}, - mimetype='application/opensearchdescription+xml') + content_type='application/opensearchdescription+xml') + + +@require_GET +@cache_control(public=True, max_age=300) +def opensearch_suggest(request): + search_term = request.GET.get('q', '') + if search_term == '': + return HttpResponse('', content_type='application/x-suggestions+json') + + cache_key = 'opensearch:packages:' + \ + hashlib.md5(search_term.encode('utf-8')).hexdigest() + to_json = cache.get(cache_key, None) + if to_json is None: + q = Q(pkgname__startswith=search_term) + lookup = search_term.lower() + if search_term != lookup: + # package names are lowercase by convention, so include that in + # search if original wasn't lowercase already + q |= Q(pkgname__startswith=lookup) + names = Package.objects.filter(q).values_list( + 'pkgname', flat=True).order_by('pkgname').distinct()[:10] + results = [search_term, list(names)] + to_json = json.dumps(results, ensure_ascii=False) + cache.set(cache_key, to_json, 300) + return HttpResponse(to_json, content_type='application/x-suggestions+json') + @permission_required('main.change_package') @require_POST @@ -79,135 +107,6 @@ def update(request): messages.error(request, "Are you trying to adopt or disown?") return redirect('/packages/') -def split_package_details(request, name='', repo='', arch=''): - 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 - # we have packages, but ensure at least one is in the given repo - if not any(True for pkg in pkgs if pkg.repo == repo): - raise Http404 - context = { - 'list_title': 'Split Package Details', - 'name': name, - 'arch': arch, - 'packages': pkgs, - } - return direct_to_template(request, 'packages/packages_list.html', - context) - -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: - return split_package_details(request, name, repo, arch) - 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.encode('utf-8')) 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) - -@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) - # files are inserted in sorted order, so preserve that - fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') - dir_count = sum(1 for f in fileslist if f.is_directory) - files_count = len(fileslist) - dir_count - context = { - 'pkg': pkg, - 'files': fileslist, - 'files_count': files_count, - 'dir_count': dir_count, - } - 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) - # files are inserted in sorted order, so preserve that - fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') - 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') - -def download(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - url = get_mirror_url_for_download() - if not url: - 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': url.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. @@ -221,7 +120,7 @@ def arch_differences(request): 'differences': differences, 'multilib_differences': multilib_diffs } - return direct_to_template(request, 'packages/differences.html', context) + return render(request, 'packages/differences.html', context) @permission_required('main.change_package') def stale_relations(request): @@ -238,7 +137,7 @@ def stale_relations(request): 'missing_pkgbase': missing_pkgbase, 'wrong_permissions': wrong_permissions, } - return direct_to_template(request, 'packages/stale_relations.html', context) + return render(request, 'packages/stale_relations.html', context) @permission_required('packages.delete_packagerelation') @require_POST diff --git a/packages/views/display.py b/packages/views/display.py new file mode 100644 index 00000000..fcf8fdea --- /dev/null +++ b/packages/views/display.py @@ -0,0 +1,232 @@ +import datetime +import json +from urllib import urlencode + +from django.http import HttpResponse, Http404 +from django.shortcuts import get_object_or_404, redirect, render +from django.utils.timezone import now + +from main.models import Package, PackageFile, Arch, Repo +from mirrors.utils import get_mirror_url_for_download +from ..models import Update +from ..utils import get_group_info, PackageJSONEncoder + + +def arch_plus_agnostic(arch): + arches = [ arch ] + arches.extend(Arch.objects.filter(agnostic=True).order_by()) + return arches + + +def split_package_details(request, name, repo, arch): + '''Check if we have a split package (e.g. pkgbase) value matching this + name. If so, we can show a listing page for the entire set of packages.''' + arches = arch_plus_agnostic(arch) + 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: + return None + # we have packages, but ensure at least one is in the given repo + if not any(True for pkg in pkgs if pkg.repo == repo): + return None + context = { + 'list_title': 'Split Package Details', + 'name': name, + 'arch': arch, + 'packages': pkgs, + } + return render(request, 'packages/packages_list.html', context) + + +CUTOFF = datetime.timedelta(days=60) + + +def recently_removed_package(request, name, repo, arch, cutoff=CUTOFF): + '''Check our packages update table to see if this package has existed in + this repo before. If so, we can show a 410 Gone page and point the + requester in the right direction.''' + arches = arch_plus_agnostic(arch) + match = Update.objects.select_related('arch', 'repo').filter( + pkgname=name, repo=repo, arch__in=arches) + if cutoff is not None: + when = now() - cutoff + match = match.filter(created__gte=when) + try: + update = match.latest() + elsewhere = update.elsewhere() + if len(elsewhere) == 1: + return redirect(elsewhere[0]) + context = { + 'update': update, + 'elsewhere': elsewhere, + 'name': name, + 'version': update.old_version, + 'arch': arch, + 'repo': repo, + } + return render(request, 'packages/removed.html', context, status=410) + except Update.DoesNotExist: + return None + + +def replaced_package(request, name, repo, arch): + '''Check our package replacements to see if this is a package we used to + have but no longer do.''' + match = Package.objects.filter(replaces__name=name, repo=repo, arch=arch) + if len(match) == 1: + return redirect(match[0], permanent=True) + elif len(match) > 1: + context = { + 'elsewhere': match, + 'name': name, + 'version': '', + 'arch': arch, + 'repo': repo, + } + return render(request, 'packages/removed.html', context, status=410) + return None + + +def redirect_agnostic(request, name, repo, arch): + '''For arch='any' packages, we can issue a redirect to them if we have a + single non-ambiguous option by changing the arch to match any arch-agnostic + package.''' + if not arch.agnostic: + # limit to 2 results, we only need to know whether there is anything + # except only one matching result + pkgs = Package.objects.select_related( + 'arch', 'repo', 'packager').filter(pkgname=name, + repo=repo, arch__agnostic=True)[:2] + if len(pkgs) == 1: + return redirect(pkgs[0], permanent=True) + return None + + +def redirect_to_search(request, name, repo, arch): + if request.GET.get('q'): + name = request.GET.get('q') + pkg_data = [ + ('arch', arch.lower()), + ('repo', repo.lower()), + ('q', name), + ] + # only include non-blank values in the query we generate + pkg_data = [(x, y.encode('utf-8')) for x, y in pkg_data if y] + return redirect("/packages/?%s" % urlencode(pkg_data)) + + +def details(request, name='', repo='', arch=''): + if all([name, repo, arch]): + arch_obj = get_object_or_404(Arch, name=arch) + repo_obj = get_object_or_404(Repo, name__iexact=repo) + try: + pkg = Package.objects.select_related( + 'arch', 'repo', 'packager').get(pkgname=name, + repo=repo_obj, arch=arch_obj) + return render(request, 'packages/details.html', {'pkg': pkg}) + except Package.DoesNotExist: + # attempt a variety of fallback options before 404ing + options = (redirect_agnostic, split_package_details, + recently_removed_package, replaced_package) + for method in options: + ret = method(request, name, repo_obj, arch_obj) + if ret: + return ret + # we've tried everything at this point, nothing to see + raise Http404 + else: + return redirect_to_search(request, name, repo, arch) + + +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 render(request, 'packages/groups.html', context) + + +def group_details(request, arch, name): + arch = get_object_or_404(Arch, name=arch) + arches = arch_plus_agnostic(arch) + 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 render(request, 'packages/packages_list.html', context) + + +def files(request, name, repo, arch): + pkg = get_object_or_404(Package.objects.normal(), + pkgname=name, repo__name__iexact=repo, arch__name=arch) + # files are inserted in sorted order, so preserve that + fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') + dir_count = sum(1 for f in fileslist if f.is_directory) + files_count = len(fileslist) - dir_count + context = { + 'pkg': pkg, + 'files': fileslist, + 'files_count': files_count, + 'dir_count': dir_count, + } + template = 'packages/files.html' + return render(request, template, context) + + +def details_json(request, name, repo, arch): + pkg = get_object_or_404(Package.objects.normal(), + pkgname=name, repo__name__iexact=repo, arch__name=arch) + to_json = json.dumps(pkg, ensure_ascii=False, cls=PackageJSONEncoder) + return HttpResponse(to_json, content_type='application/json') + + +def files_json(request, name, repo, arch): + pkg = get_object_or_404(Package.objects.normal(), + pkgname=name, repo__name__iexact=repo, arch__name=arch) + # files are inserted in sorted order, so preserve that + fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') + dir_count = sum(1 for f in fileslist if f.is_directory) + files_count = len(fileslist) - dir_count + data = { + 'pkgname': pkg.pkgname, + 'repo': pkg.repo.name.lower(), + 'arch': pkg.arch.name.lower(), + 'pkg_last_update': pkg.last_update, + 'files_last_update': pkg.files_last_update, + 'files_count': files_count, + 'dir_count': dir_count, + 'files': fileslist, + } + to_json = json.dumps(data, ensure_ascii=False, cls=PackageJSONEncoder) + return HttpResponse(to_json, content_type='application/json') + + +def download(request, name, repo, arch): + pkg = get_object_or_404(Package.objects.normal(), + pkgname=name, repo__name__iexact=repo, arch__name=arch) + url = get_mirror_url_for_download() + if not url: + 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 = { + } + url = '{host}{repo}/os/{arch}/{filename}'.format(host=url.url, + repo=pkg.repo.name.lower(), arch=arch, filename=pkg.filename) + return redirect(url) + +# vim: set ts=4 sw=4 et: diff --git a/packages/views/flag.py b/packages/views/flag.py index 760bdd94..92b35b70 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -1,20 +1,18 @@ +import re + from django import forms from django.conf import settings from django.contrib.auth.decorators import permission_required -from django.core.mail import send_mail +from django.core.mail import EmailMessage from django.db import transaction -from django.shortcuts import get_object_or_404, redirect +from django.shortcuts import get_object_or_404, redirect, render from django.template import loader, Context -from django.views.generic.simple import direct_to_template -from django.views.decorators.cache import never_cache +from django.utils.timezone import now +from django.views.decorators.cache import cache_page, never_cache from ..models import FlagRequest from main.models import Package -from main.utils import utc_now - -def flaghelp(request): - return direct_to_template(request, 'packages/flaghelp.html') class FlagForm(forms.Form): email = forms.EmailField(label='E-mail Address') @@ -26,14 +24,36 @@ class FlagForm(forms.Form): widget=forms.TextInput(attrs={'style': 'display:none;'}), required=False) + def __init__(self, *args, **kwargs): + # we remove the 'email' field if this form is being shown to a + # logged-in user, e.g., a developer. + auth = kwargs.pop('authenticated', False) + super(FlagForm, self).__init__(*args, **kwargs) + if auth: + del self.fields['email'] + + def clean_message(self): + data = self.cleaned_data['message'] + # make sure the message isn't garbage (only punctuation or whitespace) + # and ensure a certain minimum length + if re.match(r'^[^0-9A-Za-z]+$', data) or len(data) < 3: + raise forms.ValidationError( + "Enter a valid and useful out-of-date message.") + return data + + +@cache_page(3600) +def flaghelp(request): + return render(request, 'packages/flaghelp.html') + + @never_cache def flag(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), 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}) + return render(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, @@ -41,34 +61,40 @@ def flag(request, name, repo, arch): repo__staging=pkg.repo.staging).order_by( 'pkgname', 'repo__name', 'arch__name') + authenticated = request.user.is_authenticated() + if request.POST: - form = FlagForm(request.POST) + form = FlagForm(request.POST, authenticated=authenticated) if form.is_valid() and form.cleaned_data['website'] == '': # save the package list for later use flagged_pkgs = list(pkgs) # find a common version if there is one available to store - versions = set(pkg.full_version for pkg in flagged_pkgs) + versions = set((pkg.pkgver, pkg.pkgrel, pkg.epoch) + for pkg in flagged_pkgs) if len(versions) == 1: version = versions.pop() else: - version = '' + version = ('', '', 0) - email = form.cleaned_data['email'] message = form.cleaned_data['message'] ip_addr = request.META.get('REMOTE_ADDR') + if authenticated: + email = request.user.email + else: + email = form.cleaned_data['email'] @transaction.commit_on_success def perform_updates(): - now = utc_now() - pkgs.update(flag_date=now) + current_time = now() + pkgs.update(flag_date=current_time) # store our flag request - flag_request = FlagRequest(created=now, + flag_request = FlagRequest(created=current_time, user_email=email, message=message, ip_address=ip_addr, pkgbase=pkg.pkgbase, - version=version, repo=pkg.repo, - num_packages=len(flagged_pkgs)) - if request.user.is_authenticated(): + repo=pkg.repo, pkgver=version[0], pkgrel=version[1], + epoch=version[2], num_packages=len(flagged_pkgs)) + if authenticated: flag_request.user = request.user flag_request.save() @@ -84,7 +110,7 @@ def flag(request, name, repo, arch): subject = '%s package [%s] marked out-of-date' % \ (pkg.repo.name, pkg.pkgname) for maint in maints: - if maint.get_profile().notify == True: + if maint.userprofile.notify == True: toemail.append(maint.email) if toemail: @@ -96,26 +122,26 @@ def flag(request, name, repo, arch): 'pkg': pkg, 'packages': flagged_pkgs, }) - send_mail(subject, + msg = EmailMessage(subject, tmpl.render(ctx), settings.BRANDING_EMAIL, toemail, - fail_silently=True) + headers={"Reply-To": email } + ) + msg.send(fail_silently=True) return redirect('package-flag-confirmed', name=name, repo=repo, arch=arch) else: initial = {} - if request.user.is_authenticated(): - initial['email'] = request.user.email - form = FlagForm(initial=initial) + form = FlagForm(authenticated=authenticated) context = { 'package': pkg, 'packages': pkgs, 'form': form } - return direct_to_template(request, 'packages/flag.html', context) + return render(request, 'packages/flag.html', context) def flag_confirmed(request, name, repo, arch): pkg = get_object_or_404(Package, @@ -128,11 +154,11 @@ def flag_confirmed(request, name, repo, arch): context = {'package': pkg, 'packages': pkgs} - return direct_to_template(request, 'packages/flag_confirmed.html', context) + return render(request, 'packages/flag_confirmed.html', context) @permission_required('main.change_package') def unflag(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) pkg.flag_date = None pkg.save() @@ -140,7 +166,7 @@ def unflag(request, name, repo, arch): @permission_required('main.change_package') def unflag_all(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) # find all packages from (hopefully) the same PKGBUILD pkgs = Package.objects.filter(pkgbase=pkg.pkgbase, diff --git a/packages/views/search.py b/packages/views/search.py index a09de0a7..0362602e 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -1,37 +1,16 @@ -from datetime import datetime +import json from django import forms -from django.contrib.admin.widgets import AdminDateWidget from django.contrib.auth.models import User from django.db.models import Q from django.http import HttpResponse -from django.views.generic import list_detail -from django.utils import simplejson +from django.views.generic import ListView from main.models import Package, Arch, Repo from main.utils import make_choice from ..models import PackageRelation -from ..utils import PackageJSONEncoder - - -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 +from ..utils import attach_maintainers, PackageJSONEncoder + class PackageSearchForm(forms.Form): repo = forms.MultipleChoiceField(required=False) @@ -39,24 +18,21 @@ class PackageSearchForm(forms.Form): name = forms.CharField(required=False) desc = forms.CharField(required=False) q = forms.CharField(required=False) - sort = forms.CharField(required=False) + sort = forms.CharField(required=False, widget=forms.HiddenInput()) 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) - limit = LimitTypedChoiceField( - choices=make_choice([50, 100, 250]) + [('all', 'All')], - coerce=coerce_limit_value, - required=False, - initial=50) def __init__(self, *args, **kwargs): + show_staging = kwargs.pop('show_staging', False) super(PackageSearchForm, self).__init__(*args, **kwargs) + repos = Repo.objects.all() + if not show_staging: + repos = repos.filter(staging=False) self.fields['repo'].choices = make_choice( - [repo.name for repo in Repo.objects.all()]) + [repo.name for repo in repos]) self.fields['arch'].choices = make_choice( [arch.name for arch in Arch.objects.all()]) self.fields['q'].widget.attrs.update({"size": "30"}) @@ -69,6 +45,7 @@ class PackageSearchForm(forms.Form): [('', 'All'), ('unknown', 'Unknown')] + \ [(m.username, m.get_full_name()) for m in maints] + def parse_form(form, packages): if form.cleaned_data['repo']: packages = packages.filter( @@ -97,11 +74,6 @@ def parse_form(form, packages): elif form.cleaned_data['flagged'] == 'Not Flagged': packages = packages.filter(flag_date__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=name) @@ -117,48 +89,43 @@ def parse_form(form, packages): return packages -def search(request, page=None): - limit = 50 - sort = None - packages = Package.objects.normal() - if request.GET: - form = PackageSearchForm(data=request.GET) - if form.is_valid(): - packages = parse_form(form, packages) - asked_limit = form.cleaned_data['limit'] - if asked_limit and asked_limit < 0: - limit = None - elif asked_limit: - limit = asked_limit - sort = form.cleaned_data['sort'] - 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] - 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) +class SearchListView(ListView): + template_name = "packages/search.html" + paginate_by = 100 + + sort_fields = ("arch", "repo", "pkgname", "pkgbase", "compressed_size", + "installed_size", "build_date", "last_update", "flag_date") + allowed_sort = list(sort_fields) + ["-" + s for s in sort_fields] + + def get(self, request, *args, **kwargs): + self.form = PackageSearchForm(data=request.GET, + show_staging=self.request.user.is_authenticated()) + return super(SearchListView, self).get(request, *args, **kwargs) + + def get_queryset(self): + packages = Package.objects.normal() + if not self.request.user.is_authenticated(): + packages = packages.filter(repo__staging=False) + if self.form.is_valid(): + packages = parse_form(self.form, packages) + sort = self.form.cleaned_data['sort'] + if sort in self.allowed_sort: + packages = packages.order_by(sort) + else: + packages = packages.order_by('pkgname') + return packages + + # Form had errors so don't return any results + return Package.objects.none() + + def get_context_data(self, **kwargs): + context = super(SearchListView, self).get_context_data(**kwargs) + query_params = self.request.GET.copy() + query_params.pop('page', None) + context['current_query'] = query_params.urlencode() + context['search_form'] = self.form + return context def search_json(request): @@ -172,15 +139,21 @@ def search_json(request): } if request.GET: - form = PackageSearchForm(data=request.GET) + form = PackageSearchForm(data=request.GET, + show_staging=request.user.is_authenticated()) if form.is_valid(): - packages = Package.objects.normal() + packages = Package.objects.select_related('arch', 'repo', + 'packager') + if not request.user.is_authenticated(): + packages = packages.filter(repo__staging=False) packages = parse_form(form, packages)[:limit] + packages = packages.prefetch_related('groups', 'licenses', + 'conflicts', 'provides', 'replaces', 'depends') + attach_maintainers(packages) container['results'] = packages container['valid'] = True - to_json = simplejson.dumps(container, ensure_ascii=False, - cls=PackageJSONEncoder) - return HttpResponse(to_json, mimetype='application/json') + to_json = json.dumps(container, ensure_ascii=False, cls=PackageJSONEncoder) + return HttpResponse(to_json, content_type='application/json') # vim: set ts=4 sw=4 et: diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 63341a1d..c37aa0fc 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -1,3 +1,4 @@ +import json from operator import attrgetter from django import forms @@ -7,12 +8,10 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db import transaction from django.http import HttpResponse, Http404 from django.shortcuts import get_list_or_404, redirect, render -from django.utils import simplejson +from django.utils.timezone import now from django.views.decorators.cache import never_cache -from django.views.generic.simple import direct_to_template from main.models import Package, Arch, Repo -from main.utils import utc_now from ..models import SignoffSpecification, Signoff from ..utils import (get_signoff_groups, approved_by_signoffs, PackageSignoffGroup) @@ -26,9 +25,9 @@ def signoffs(request): context = { 'signoff_groups': signoff_groups, 'arches': Arch.objects.all(), - 'repo_names': sorted(set(g.target_repo for g in signoff_groups)), + 'repo_names': sorted({g.target_repo for g in signoff_groups}), } - return direct_to_template(request, 'packages/signoffs.html', context) + return render(request, 'packages/signoffs.html', context) @permission_required('main.change_package') @never_cache @@ -45,8 +44,8 @@ def signoff_package(request, name, repo, arch, revoke=False): package, request.user, False) except Signoff.DoesNotExist: raise Http404 - signoff.revoked = utc_now() - signoff.save() + signoff.revoked = now() + signoff.save(update_fields=('revoked',)) created = False else: # ensure we should even be accepting signoffs @@ -67,8 +66,8 @@ def signoff_package(request, name, repo, arch, revoke=False): 'known_bad': spec.known_bad, 'user': str(request.user), } - return HttpResponse(simplejson.dumps(data, ensure_ascii=False), - mimetype='application/json') + return HttpResponse(json.dumps(data, ensure_ascii=False), + content_type='application/json') return redirect('package-signoffs') @@ -144,7 +143,7 @@ def signoff_options(request, name, repo, arch): 'package': package, 'form': form, } - return direct_to_template(request, 'packages/signoff_options.html', context) + return render(request, 'packages/signoff_options.html', context) class SignoffJSONEncoder(DjangoJSONEncoder): '''Base JSONEncoder extended to handle all serialization of all classes @@ -156,8 +155,8 @@ class SignoffJSONEncoder(DjangoJSONEncoder): def default(self, obj): if isinstance(obj, PackageSignoffGroup): - data = dict((attr, getattr(obj, attr)) - for attr in self.signoff_group_attrs) + data = {attr: getattr(obj, attr) + for attr in self.signoff_group_attrs} data['pkgnames'] = [p.pkgname for p in obj.packages] data['package_count'] = len(obj.packages) data['approved'] = obj.approved() @@ -165,9 +164,7 @@ class SignoffJSONEncoder(DjangoJSONEncoder): 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 + return {attr: getattr(obj, attr) for attr in self.signoff_attrs} elif isinstance(obj, Arch) or isinstance(obj, Repo): return unicode(obj) elif isinstance(obj, User): @@ -183,9 +180,8 @@ def signoffs_json(request): 'version': 2, 'signoff_groups': signoff_groups, } - to_json = simplejson.dumps(data, ensure_ascii=False, - cls=SignoffJSONEncoder) - response = HttpResponse(to_json, mimetype='application/json') + to_json = json.dumps(data, ensure_ascii=False, cls=SignoffJSONEncoder) + response = HttpResponse(to_json, content_type='application/json') return response # vim: set ts=4 sw=4 et: |