summaryrefslogtreecommitdiff
path: root/todolists/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'todolists/views.py')
-rw-r--r--todolists/views.py327
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: