diff options
Diffstat (limited to 'todolists/views.py')
-rw-r--r-- | todolists/views.py | 327 |
1 files changed, 199 insertions, 128 deletions
diff --git a/todolists/views.py b/todolists/views.py index 25186243..a0b56e25 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -1,169 +1,240 @@ -from django import forms +import json +from operator import attrgetter +from django import forms from django.http import HttpResponse +from django.conf import settings from django.core.mail import send_mail -from django.shortcuts import get_object_or_404, redirect -from django.contrib.auth.decorators import login_required, permission_required -from django.db.models import Count +from django.shortcuts import (get_list_or_404, get_object_or_404, + redirect, render) +from django.db import transaction from django.views.decorators.cache import never_cache -from django.views.generic.create_update import delete_object -from django.views.generic.simple import direct_to_template +from django.views.generic import DeleteView, ListView from django.template import Context, loader -from django.utils import simplejson +from django.utils.timezone import now -from main.models import Todolist, TodolistPkg, Package +from main.models import Package, Repo +from main.utils import find_unique_slug +from packages.utils import attach_maintainers +from .models import Todolist, TodolistPackage +from .utils import get_annotated_todolists, attach_staging -class TodoListForm(forms.Form): - name = forms.CharField(max_length=255, - widget=forms.TextInput(attrs={'size': '30'})) - description = forms.CharField(required=False, - widget=forms.Textarea(attrs={'rows': '4', 'cols': '60'})) - packages = forms.CharField(required=False, + +class TodoListForm(forms.ModelForm): + raw = forms.CharField(label='Packages', required=False, help_text='(one per line)', widget=forms.Textarea(attrs={'rows': '20', 'cols': '60'})) - def clean_packages(self): - package_names = [s.strip() for s in - self.cleaned_data['packages'].split("\n")] - package_names = set(package_names) - packages = Package.objects.filter( - pkgname__in=package_names).exclude( - repo__testing=True).order_by('arch') - return packages + def package_names(self): + return {s.strip() for s in self.cleaned_data['raw'].split("\n")} + + def packages(self): + return Package.objects.normal().filter( + pkgname__in=self.package_names(), + repo__testing=False, repo__staging=False).order_by('arch') + + class Meta: + model = Todolist + fields = ('name', 'description', 'raw') -@login_required @never_cache -def flag(request, listid, pkgid): - list = get_object_or_404(Todolist, id=listid) - pkg = get_object_or_404(TodolistPkg, id=pkgid) - pkg.complete = not pkg.complete - pkg.save() +def flag(request, slug, pkg_id): + todolist = get_object_or_404(Todolist, slug=slug) + tlpkg = get_object_or_404(TodolistPackage, id=pkg_id, removed__isnull=True) + # TODO: none of this; require absolute value on submit + if tlpkg.status == TodolistPackage.INCOMPLETE: + tlpkg.status = TodolistPackage.COMPLETE + else: + tlpkg.status = TodolistPackage.INCOMPLETE + tlpkg.user = request.user + tlpkg.save(update_fields=('status', 'user', 'last_modified')) if request.is_ajax(): - return HttpResponse( - simplejson.dumps({'complete': pkg.complete}), - mimetype='application/json') - return redirect(list) + data = { + 'status': tlpkg.get_status_display(), + 'css_class': tlpkg.status_css_class(), + } + return HttpResponse(json.dumps(data), content_type='application/json') + return redirect(todolist) + + +def view_redirect(request, old_id): + todolist = get_object_or_404(Todolist, old_id=old_id) + return redirect(todolist, permanent=True) + + +def view(request, slug): + todolist = get_object_or_404(Todolist, slug=slug) + svn_roots = Repo.objects.values_list( + 'svn_root', flat=True).order_by().distinct() + # we don't hold onto the result, but the objects are the same here, + # so accessing maintainers in the template is now cheap + attach_maintainers(todolist.packages()) + attach_staging(todolist.packages(), todolist.pk) + arches = {tp.arch for tp in todolist.packages()} + repos = {tp.repo for tp in todolist.packages()} + context = { + 'list': todolist, + 'svn_roots': svn_roots, + 'arches': sorted(arches), + 'repos': sorted(repos), + } + return render(request, 'todolists/view.html', context) + + +def list_pkgbases(request, slug, svn_root): + '''Used to make bulk moves of packages a lot easier.''' + todolist = get_object_or_404(Todolist, slug=slug) + repos = get_list_or_404(Repo, svn_root=svn_root) + pkgbases = TodolistPackage.objects.values_list( + 'pkgbase', flat=True).filter( + todolist=todolist, repo__in=repos, removed__isnull=True).order_by( + 'pkgbase').distinct() + return HttpResponse('\n'.join(pkgbases), content_type='text/plain') + + +class TodolistListView(ListView): + context_object_name = "lists" + template_name = "todolists/list.html" + paginate_by = 50 + + def get_queryset(self): + return get_annotated_todolists() -@login_required -@never_cache -def view(request, listid): - list = get_object_or_404(Todolist, id=listid) - return direct_to_template(request, 'todolists/view.html', {'list': list}) -@login_required -@never_cache -def list(request): - lists = Todolist.objects.select_related('creator').annotate( - pkg_count=Count('todolistpkg')).order_by('-date_added') - incomplete = Todolist.objects.filter(todolistpkg__complete=False).annotate( - Count('todolistpkg')).values_list('id', 'todolistpkg__count') - - # tag each list with an incomplete package count - lookup = {} - for k, v in incomplete: - lookup[k] = v - for l in lists: - l.incomplete_count = lookup.get(l.id, 0) - - return direct_to_template(request, 'todolists/list.html', {'lists': lists}) - -@permission_required('main.add_todolist') @never_cache def add(request): if request.POST: form = TodoListForm(request.POST) if form.is_valid(): - todo = Todolist.objects.create( - creator = request.user, - name = form.cleaned_data['name'], - description = form.cleaned_data['description']) - - for pkg in form.cleaned_data['packages']: - tpkg = TodolistPkg.objects.create(list = todo, pkg = pkg) - send_todolist_email(tpkg) - - return redirect('/todo/') + new_packages = create_todolist_packages(form, creator=request.user) + send_todolist_emails(form.instance, new_packages) + return redirect(form.instance) else: form = TodoListForm() page_dict = { 'title': 'Add Todo List', + 'description': '', 'form': form, 'submit_text': 'Create List' - } - return direct_to_template(request, 'general_form.html', page_dict) + } + return render(request, 'general_form.html', page_dict) -@permission_required('main.change_todolist') + +# TODO: this calls for transaction management and async emailing @never_cache -def edit(request, list_id): - todo_list = get_object_or_404(Todolist, id=list_id) +def edit(request, slug): + todo_list = get_object_or_404(Todolist, slug=slug) if request.POST: - form = TodoListForm(request.POST) + form = TodoListForm(request.POST, instance=todo_list) if form.is_valid(): - todo_list.name = form.cleaned_data['name'] - todo_list.description = form.cleaned_data['description'] - todo_list.save() - - packages = [p.pkg for p in todo_list.packages] - - # first delete any packages not in the new list - for p in todo_list.packages: - if p.pkg not in form.cleaned_data['packages']: - p.delete() - - # now add any packages not in the old list - for pkg in form.cleaned_data['packages']: - if pkg not in packages: - tpkg = TodolistPkg.objects.create( - list = todo_list, pkg = pkg) - send_todolist_email(tpkg) - + new_packages = create_todolist_packages(form) + send_todolist_emails(todo_list, new_packages) return redirect(todo_list) else: - form = TodoListForm(initial={ - 'name': todo_list.name, - 'description': todo_list.description, - 'packages': todo_list.package_names, - }) + form = TodoListForm(instance=todo_list, + initial={'packages': todo_list.raw}) + page_dict = { 'title': 'Edit Todo List: %s' % todo_list.name, + 'description': '', 'form': form, 'submit_text': 'Save List' - } - return direct_to_template(request, 'general_form.html', page_dict) - -@permission_required('main.delete_todolist') -@never_cache -def delete_todolist(request, object_id): - return delete_object(request, object_id=object_id, model=Todolist, - template_name="todolists/todolist_confirm_delete.html", - post_delete_redirect='/todo/') - -def send_todolist_email(todo): - '''Sends an e-mail to the maintainer of a package notifying them that the - package has been added to a todo list''' - maints = todo.pkg.maintainers - if not maints: - return - - page_dict = { - 'pkg': todo.pkg, - 'todolist': todo.list, - 'weburl': todo.pkg.get_full_url() } - t = loader.get_template('todolists/email_notification.txt') - c = Context(page_dict) - send_mail('arch: Package [%s] added to Todolist' % todo.pkg.pkgname, - t.render(c), - 'Arch Website Notification <nobody@archlinux.org>', - [m.email for m in maints], - fail_silently=True) - -def public_list(request): - todo_lists = Todolist.objects.incomplete() - return direct_to_template(request, "todolists/public_list.html", - {"todo_lists": todo_lists}) - + return render(request, 'general_form.html', page_dict) + + +class DeleteTodolist(DeleteView): + model = Todolist + # model in main == assumes name 'main/todolist_confirm_delete.html' + template_name = 'todolists/todolist_confirm_delete.html' + success_url = '/todo/' + + +@transaction.atomic +def create_todolist_packages(form, creator=None): + package_names = form.package_names() + packages = form.packages() + timestamp = now() + if creator: + # todo list is new, populate creator and slug fields + todolist = form.save(commit=False) + todolist.creator = creator + todolist.slug = find_unique_slug(Todolist, todolist.name) + todolist.save() + else: + # todo list already existed + form.save() + todolist = form.instance + + # first mark removed any packages not in the new list + to_remove = set() + for todo_pkg in todolist.packages(): + if todo_pkg.pkg and todo_pkg.pkg not in packages: + to_remove.add(todo_pkg.pk) + elif todo_pkg.pkgname not in package_names: + to_remove.add(todo_pkg.pk) + + TodolistPackage.objects.filter( + pk__in=to_remove).update(removed=timestamp) + + # Add (or mark unremoved) any packages in the new packages list + todo_pkgs = [] + for package in packages: + # ensure get_or_create uses the fields in our unique constraint + defaults = { + 'pkg': package, + 'pkgbase': package.pkgbase, + 'repo': package.repo, + } + todo_pkg, created = TodolistPackage.objects.get_or_create( + todolist=todolist, + pkgname=package.pkgname, + arch=package.arch, + defaults=defaults) + if created: + todo_pkgs.append(todo_pkg) + else: + save = False + if todo_pkg.removed is not None: + todo_pkg.removed = None + save = True + if todo_pkg.pkg != package: + todo_pkg.pkg = package + save = True + if save: + todo_pkg.save() + + return todo_pkgs + + +def send_todolist_emails(todo_list, new_packages): + '''Sends emails to package maintainers notifying them that packages have + been added to a todo list.''' + # start by flipping the incoming list on its head: we want a list of + # involved maintainers and the packages they need to be notified about. + orphan_packages = [] + maint_packages = {} + for todo_package in new_packages: + maints = todo_package.pkg.maintainers.values_list('email', flat=True) + if not maints: + orphan_packages.append(todo_package) + else: + for maint in maints: + maint_packages.setdefault(maint, []).append(todo_package) + + for maint, packages in maint_packages.iteritems(): + packages = sorted(packages, key=attrgetter('pkgname', 'arch')) + ctx = Context({ + 'todo_packages': packages, + 'todolist': todo_list, + }) + template = loader.get_template('todolists/email_notification.txt') + send_mail('Packages added to todo list \'%s\'' % todo_list.name, + template.render(ctx), + settings.BRANDING_EMAIL, + [maint], + fail_silently=True) # vim: set ts=4 sw=4 et: |