From dc94eade03022ce3a5286f5e781576321a5f1653 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 27 Apr 2012 08:59:00 -0500 Subject: Incomplete-only todolists optimization We can push this down to the database if we know in advance we only need the incomplete lists. This helps our call on the developer dashboard quite a bit; the time of the single query in question drops from >1300ms to around 40ms. Signed-off-by: Dan McGee --- todolists/utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'todolists') diff --git a/todolists/utils.py b/todolists/utils.py index 24101e86..94f39f71 100644 --- a/todolists/utils.py +++ b/todolists/utils.py @@ -3,7 +3,7 @@ from django.db.models import Count from main.models import Todolist -def get_annotated_todolists(): +def get_annotated_todolists(incomplete_only=False): qs = Todolist.objects.all() lists = qs.select_related('creator').defer( 'creator__email', 'creator__password', 'creator__is_staff', @@ -13,8 +13,12 @@ def get_annotated_todolists(): incomplete = qs.filter(todolistpkg__complete=False).annotate( Count('todolistpkg')).values_list('id', 'todolistpkg__count') - # tag each list with an incomplete package count lookup = dict(incomplete) + + if incomplete_only: + lists = lists.filter(id__in=lookup.keys()) + + # tag each list with an incomplete package count for todolist in lists: todolist.incomplete_count = lookup.get(todolist.id, 0) -- cgit v1.2.3 From badc535aeb1d310a9b8aa59aade07045e6eae653 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 18 Apr 2012 15:05:43 -0500 Subject: Ensure order_by default value is cleared when using distinct() Otherwise the queryset returns nonsensical results. I find the design of this less than obvious but so be it; we can ensure the results work regardless of a default ordering on the model. Signed-off-by: Dan McGee --- todolists/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index e5cc0823..70209b6d 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -49,8 +49,8 @@ def flag(request, list_id, pkg_id): @login_required def view(request, list_id): todolist = get_object_or_404(Todolist, id=list_id) - svn_roots = Repo.objects.order_by().values_list( - 'svn_root', flat=True).distinct() + 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(tp.pkg for tp in todolist.packages) -- cgit v1.2.3 From a5f5557493446bede78adb0584c88208234f874e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 12 May 2012 09:32:30 -0500 Subject: Use python json module directly in place of simplejson As of Python 2.6, this is a builtin module that has all the same functions and capabilities of the Django simplejson module. Additionally simplejson is deprecated in the upcoming Django 1.5 release. Signed-off-by: Dan McGee --- todolists/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index 70209b6d..b9ba0903 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -1,5 +1,6 @@ -from django import forms +import json +from django import forms from django.http import HttpResponse from django.core.mail import send_mail from django.shortcuts import get_list_or_404, get_object_or_404, redirect @@ -9,7 +10,6 @@ from django.views.decorators.cache import never_cache from django.views.generic import DeleteView from django.views.generic.simple import direct_to_template from django.template import Context, loader -from django.utils import simplejson from main.models import Todolist, TodolistPkg, Package, Repo from packages.utils import attach_maintainers @@ -42,7 +42,7 @@ def flag(request, list_id, pkg_id): pkg.save() if request.is_ajax(): return HttpResponse( - simplejson.dumps({'complete': pkg.complete}), + json.dumps({'complete': pkg.complete}), mimetype='application/json') return redirect(todolist) -- cgit v1.2.3 From 5b176fc672a89cc3711a6e581d076ad8be25c516 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 23 Jul 2012 21:48:26 -0500 Subject: Add blank description to todo list creation and editing This is a field shown on the general_form.html, and shows up as @@@INVALID@@@ in development environments. Signed-off-by: Dan McGee --- todolists/views.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index b9ba0903..580ec00f 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -88,6 +88,7 @@ def add(request): page_dict = { 'title': 'Add Todo List', + 'description': '', 'form': form, 'submit_text': 'Create List' } @@ -110,6 +111,7 @@ def edit(request, list_id): page_dict = { 'title': 'Edit Todo List: %s' % todo_list.name, + 'description': '', 'form': form, 'submit_text': 'Save List' } -- cgit v1.2.3 From 76c37ce3acc7a4af0271c7535d4a33042f7749b5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 24 Jul 2012 09:35:55 -0500 Subject: Replace deprecated direct_to_template() with render() shortcut Now that Django actually provides a concise way to use a RequestContext object without instantiating it, we can use that rather than the old function-based generic view that worked well to do the same. Additionally, these function-based generic views will be gone in Django 1.5, so might as well make the move now. Signed-off-by: Dan McGee --- todolists/views.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index 580ec00f..c7ba2560 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -3,12 +3,11 @@ import json from django import forms from django.http import HttpResponse from django.core.mail import send_mail -from django.shortcuts import get_list_or_404, get_object_or_404, redirect +from django.shortcuts import get_list_or_404, get_object_or_404, redirect, render from django.contrib.auth.decorators import login_required, permission_required from django.db import transaction from django.views.decorators.cache import never_cache from django.views.generic import DeleteView -from django.views.generic.simple import direct_to_template from django.template import Context, loader from main.models import Todolist, TodolistPkg, Package, Repo @@ -54,7 +53,7 @@ def view(request, list_id): # 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(tp.pkg for tp in todolist.packages) - return direct_to_template(request, 'todolists/view.html', { + return render(request, 'todolists/view.html', { 'list': todolist, 'svn_roots': svn_roots, }) @@ -72,7 +71,7 @@ def list_pkgbases(request, list_id, svn_root): @login_required def todolist_list(request): lists = get_annotated_todolists() - return direct_to_template(request, 'todolists/list.html', {'lists': lists}) + return render(request, 'todolists/list.html', {'lists': lists}) @permission_required('main.add_todolist') @never_cache @@ -92,7 +91,7 @@ def add(request): 'form': form, 'submit_text': 'Create List' } - return direct_to_template(request, 'general_form.html', page_dict) + return render(request, 'general_form.html', page_dict) # TODO: this calls for transaction management and async emailing @permission_required('main.change_todolist') @@ -115,7 +114,7 @@ def edit(request, list_id): 'form': form, 'submit_text': 'Save List' } - return direct_to_template(request, 'general_form.html', page_dict) + return render(request, 'general_form.html', page_dict) class DeleteTodolist(DeleteView): model = Todolist @@ -185,7 +184,7 @@ def public_list(request): # total hackjob, but it makes this a lot less query-intensive. all_pkgs = [tp for tl in todo_lists for tp in tl.packages] attach_maintainers([tp.pkg for tp in all_pkgs]) - return direct_to_template(request, "todolists/public_list.html", + return render(request, "todolists/public_list.html", {"todo_lists": todo_lists}) # vim: set ts=4 sw=4 et: -- cgit v1.2.3 From c1a6a87e23864ea044cb15f76b9dbb16734f08d8 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 25 Jul 2012 00:45:36 -0500 Subject: Add arch and repo filter to todolist packages This matches what we do on signoffs. Also beef up the styling a bit and add the dynamically updated package count info. Signed-off-by: Dan McGee --- todolists/views.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index c7ba2560..b8d1dae1 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -53,9 +53,13 @@ def view(request, list_id): # 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(tp.pkg for tp in todolist.packages) + arches = set(tp.pkg.arch for tp in todolist.packages) + repos = set(tp.pkg.repo for tp in todolist.packages) return render(request, 'todolists/view.html', { 'list': todolist, 'svn_roots': svn_roots, + 'arches': sorted(arches), + 'repos': sorted(repos), }) # really no need for login_required on this one... -- cgit v1.2.3 From 9e9157d0a8cbf9ea076231e438fb30f58bff8e29 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 16 Nov 2012 16:37:31 -0600 Subject: Use python set comprehension syntax supported in 2.7 Signed-off-by: Dan McGee --- todolists/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index b8d1dae1..9984ef9a 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -53,8 +53,8 @@ def view(request, list_id): # 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(tp.pkg for tp in todolist.packages) - arches = set(tp.pkg.arch for tp in todolist.packages) - repos = set(tp.pkg.repo for tp in todolist.packages) + arches = {tp.pkg.arch for tp in todolist.packages} + repos = {tp.pkg.repo for tp in todolist.packages} return render(request, 'todolists/view.html', { 'list': todolist, 'svn_roots': svn_roots, @@ -67,8 +67,8 @@ def list_pkgbases(request, list_id, svn_root): '''Used to make bulk moves of packages a lot easier.''' todolist = get_object_or_404(Todolist, id=list_id) repos = get_list_or_404(Repo, svn_root=svn_root) - pkgbases = set(tp.pkg.pkgbase for tp in todolist.packages - if tp.pkg.repo in repos) + pkgbases = {tp.pkg.pkgbase for tp in todolist.packages + if tp.pkg.repo in repos} return HttpResponse('\n'.join(sorted(pkgbases)), mimetype='text/plain') -- cgit v1.2.3 From 160a08bba5324b25abd9e866b884c91d75e597b0 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 20 Nov 2012 15:57:05 -0600 Subject: Improve performance of todolists query Use some standard SQL and split the query into two different parts to save a lot of unnecessary sorting and field retrieval at the database level. The `CASE WHEN complete THEN 1 ELSE 0 END` syntax should be accepted by any database that implements proper SQL; it was tested in PostgreSQL and sqlite3 without issues. Signed-off-by: Dan McGee --- todolists/utils.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) (limited to 'todolists') diff --git a/todolists/utils.py b/todolists/utils.py index 94f39f71..03c47931 100644 --- a/todolists/utils.py +++ b/todolists/utils.py @@ -1,26 +1,37 @@ +from django.db import connections, router from django.db.models import Count -from main.models import Todolist +from main.models import Todolist, TodolistPkg -def get_annotated_todolists(incomplete_only=False): - qs = Todolist.objects.all() - lists = qs.select_related('creator').defer( - 'creator__email', 'creator__password', 'creator__is_staff', - 'creator__is_active', 'creator__is_superuser', - 'creator__last_login', 'creator__date_joined').annotate( - pkg_count=Count('todolistpkg')).order_by('-date_added') - incomplete = qs.filter(todolistpkg__complete=False).annotate( - Count('todolistpkg')).values_list('id', 'todolistpkg__count') +def todo_counts(): + sql = """ +SELECT list_id, count(*), sum(CASE WHEN complete THEN 1 ELSE 0 END) + FROM todolist_pkgs + GROUP BY list_id + """ + database = router.db_for_write(TodolistPkg) + connection = connections[database] + cursor = connection.cursor() + cursor.execute(sql) + results = cursor.fetchall() + return {row[0]: (row[1], row[2]) for row in results} - lookup = dict(incomplete) - if incomplete_only: - lists = lists.filter(id__in=lookup.keys()) +def get_annotated_todolists(incomplete_only=False): + lists = Todolist.objects.all().select_related( + 'creator').order_by('-date_added') + lookup = todo_counts() - # tag each list with an incomplete package count + # tag each list with package counts for todolist in lists: - todolist.incomplete_count = lookup.get(todolist.id, 0) + counts = lookup.get(todolist.id, (0, 0)) + todolist.pkg_count = counts[0] + todolist.complete_count = counts[1] + todolist.incomplete_count = counts[0] - counts[1] + + if incomplete_only: + lists = [l for l in lists if l.incomplete_count > 0] return lists -- cgit v1.2.3 From e761db4cc51ead40730702a4b05e27347340886c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 21 Dec 2012 19:19:09 -0600 Subject: Add initial todolists models migration Signed-off-by: Dan McGee --- todolists/migrations/0001_initial.py | 19 +++++++++++++++++++ todolists/migrations/__init__.py | 0 todolists/models.py | 0 3 files changed, 19 insertions(+) create mode 100644 todolists/migrations/0001_initial.py create mode 100644 todolists/migrations/__init__.py create mode 100644 todolists/models.py (limited to 'todolists') diff --git a/todolists/migrations/0001_initial.py b/todolists/migrations/0001_initial.py new file mode 100644 index 00000000..48d38aae --- /dev/null +++ b/todolists/migrations/0001_initial.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + pass + + def backwards(self, orm): + pass + + models = { + + } + + complete_apps = ['todolists'] diff --git a/todolists/migrations/__init__.py b/todolists/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/todolists/models.py b/todolists/models.py new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 8be27a5cafde87c439b19f64a7c215410c88484b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 21 Dec 2012 19:26:35 -0600 Subject: Add new todolists and todolist package model Move the todolist model from main to the todolists application, and make a few minor tweaks to field names along the way. Also add a 'raw' field that will hold the originally input text data from the creator or last modifier of the todolist. Add pkgname, pkgbase, arch, and repo fields to a new todolist package model, which will supplement the former foreign key to an actual package object. This will prevent todolist package objects from ever being deleted as they can be now, which is not intuitive. Also change the current boolean 'complete' flag to a 'status' enum that can hold other values. For now, we add 'In-progress' to the mix. Finally, add a 'user' field, and a 'comments' field that will be utilized later by the UI. Signed-off-by: Dan McGee --- .../0002_add_todolist_and_todolistpackage.py | 151 +++++++++++++++++++++ todolists/models.py | 81 +++++++++++ 2 files changed, 232 insertions(+) create mode 100644 todolists/migrations/0002_add_todolist_and_todolistpackage.py (limited to 'todolists') diff --git a/todolists/migrations/0002_add_todolist_and_todolistpackage.py b/todolists/migrations/0002_add_todolist_and_todolistpackage.py new file mode 100644 index 00000000..8365535a --- /dev/null +++ b/todolists/migrations/0002_add_todolist_and_todolistpackage.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.create_table('todolists_todolist', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('old_id', self.gf('django.db.models.fields.IntegerField')(unique=True, null=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('description', self.gf('django.db.models.fields.TextField')()), + ('creator', self.gf('django.db.models.fields.related.ForeignKey')(related_name='created_todolists', on_delete=models.PROTECT, to=orm['auth.User'])), + ('created', self.gf('django.db.models.fields.DateTimeField')(db_index=True)), + ('last_modified', self.gf('django.db.models.fields.DateTimeField')()), + ('raw', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('todolists', ['Todolist']) + + db.create_table('todolists_todolistpackage', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('todolist', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['todolists.Todolist'])), + ('pkg', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Package'], null=True, on_delete=models.SET_NULL)), + ('pkgname', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('pkgbase', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('arch', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Arch'])), + ('repo', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Repo'])), + ('created', self.gf('django.db.models.fields.DateTimeField')()), + ('status', self.gf('django.db.models.fields.SmallIntegerField')(default=0)), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.SET_NULL)), + ('comments', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + )) + db.send_create_signal('todolists', ['TodolistPackage']) + + db.create_unique('todolists_todolistpackage', ['todolist_id', 'pkgname', 'arch_id']) + + + def backwards(self, orm): + db.delete_unique('todolists_todolistpackage', ['todolist_id', 'pkgname', 'arch_id']) + + db.delete_table('todolists_todolist') + + db.delete_table('todolists_todolistpackage') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'todolists.todolist': { + 'Meta': {'object_name': 'Todolist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_todolists'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'old_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True'}), + 'raw': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'todolists.todolistpackage': { + 'Meta': {'unique_together': "(('todolist', 'pkgname', 'arch'),)", 'object_name': 'TodolistPackage'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'todolist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['todolists.Todolist']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}) + } + } + + complete_apps = ['todolists'] diff --git a/todolists/models.py b/todolists/models.py index e69de29b..7af7faf9 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -0,0 +1,81 @@ +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.db import models +from django.db.models import Q +from django.db.models.signals import pre_save + +from main.models import Arch, Repo, Package +from main.utils import set_created_field + + +class TodolistManager(models.Manager): + def incomplete(self): + not_done = (Q(todolistpackage__status=TodolistPackage.INCOMPLETE) | + Q(todolistpackage__status=TodolistPackage.IN_PROGRESS)) + return self.order_by().filter(not_done).distinct() + + +class Todolist(models.Model): + old_id = models.IntegerField(null=True, unique=True) + name = models.CharField(max_length=255) + description = models.TextField() + creator = models.ForeignKey(User, on_delete=models.PROTECT, + related_name="created_todolists") + created = models.DateTimeField(db_index=True) + last_modified = models.DateTimeField(editable=False) + raw = models.TextField(blank=True) + + objects = TodolistManager() + + class Meta: + get_latest_by = 'created' + + def __unicode__(self): + return self.name + + def get_absolute_url(self): + return '/todo/%i/' % self.id + + def get_full_url(self, proto='https'): + '''get a URL suitable for things like email including the domain''' + domain = Site.objects.get_current().domain + return '%s://%s%s' % (proto, domain, self.get_absolute_url()) + + +class TodolistPackage(models.Model): + INCOMPLETE = 0 + COMPLETE = 1 + IN_PROGRESS = 2 + STATUS_CHOICES = ( + (INCOMPLETE, 'Incomplete'), + (COMPLETE, 'Complete'), + (IN_PROGRESS, 'In-progress'), + ) + + todolist = models.ForeignKey(Todolist) + pkg = models.ForeignKey(Package, null=True, on_delete=models.SET_NULL) + pkgname = models.CharField(max_length=255) + pkgbase = models.CharField(max_length=255) + arch = models.ForeignKey(Arch) + repo = models.ForeignKey(Repo) + created = models.DateTimeField() + status = models.SmallIntegerField(default=0, choices=STATUS_CHOICES) + user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) + comments = models.TextField(null=True, blank=True) + + class Meta: + unique_together = (('todolist','pkgname', 'arch'),) + + def __unicode__(self): + return self.pkgname + + def status_css_class(self): + return self.get_status_display().lower().replace('-', '') + + +pre_save.connect(set_created_field, sender=Todolist, + dispatch_uid="todolists.models") +pre_save.connect(set_created_field, sender=TodolistPackage, + dispatch_uid="todolists.models") + +# vim: set ts=4 sw=4 et: -- cgit v1.2.3 From f167d6e6e8ba2ca5e29f6a1a82b0e273ffec13ab Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 27 Dec 2012 21:03:09 -0600 Subject: Add todolist admin for new model Signed-off-by: Dan McGee --- todolists/admin.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 todolists/admin.py (limited to 'todolists') diff --git a/todolists/admin.py b/todolists/admin.py new file mode 100644 index 00000000..181febb1 --- /dev/null +++ b/todolists/admin.py @@ -0,0 +1,14 @@ +from django.contrib import admin + +from .models import Todolist + + +class TodolistAdmin(admin.ModelAdmin): + list_display = ('name', 'creator', 'created', 'description') + search_fields = ('name', 'description') + date_hierarchy = 'created' + + +admin.site.register(Todolist, TodolistAdmin) + +# vim: set ts=4 sw=4 et: -- cgit v1.2.3 From f07a5862c9ed40646677344eaf920dbd05a1a137 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 27 Dec 2012 23:32:05 -0600 Subject: Add packages method to new Todolist model Signed-off-by: Dan McGee --- todolists/models.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'todolists') diff --git a/todolists/models.py b/todolists/models.py index 7af7faf9..c38c564e 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -41,6 +41,12 @@ class Todolist(models.Model): domain = Site.objects.get_current().domain return '%s://%s%s' % (proto, domain, self.get_absolute_url()) + def packages(self): + if not hasattr(self, '_packages'): + self._packages = self.todolistpackage_set.select_related( + 'pkg', 'repo', 'arch').order_by('pkgname', 'arch') + return self._packages + class TodolistPackage(models.Model): INCOMPLETE = 0 -- cgit v1.2.3 From c7658ca4cd0f89969086fed172519ca2097270ba Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 01:10:13 -0600 Subject: Add data migration for todolists This moves the data from the old models into the new ones. Note that IDs are not preserved across the move, but we do store the old ID in the old_id column so we don't break every link out there. Links will become slugs in a future commit, so there should be no ambiguity when linking via number vs string. Signed-off-by: Dan McGee --- todolists/migrations/0003_migrate_todolist_data.py | 183 +++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 todolists/migrations/0003_migrate_todolist_data.py (limited to 'todolists') diff --git a/todolists/migrations/0003_migrate_todolist_data.py b/todolists/migrations/0003_migrate_todolist_data.py new file mode 100644 index 00000000..d7d8f66f --- /dev/null +++ b/todolists/migrations/0003_migrate_todolist_data.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + depends_on = ( + ('main', '0045_add_todolist_date_added_index'), + ) + + def forwards(self, orm): + list_id_map = {} + list_added = {} + # start by converting the todo lists themselves + for old in orm['main.Todolist'].objects.all().order_by('id'): + new = orm.Todolist.objects.create(name=old.name, old_id=old.id, + description=old.description, created=old.date_added, + last_modified=old.date_added, creator_id=old.creator_id) + # set the 'raw' field to something useful + pkgnames = orm['main.Package'].objects.values_list('pkgname', + flat=True).distinct().order_by('pkgname').filter( + todolistpkg__list_id=old.id) + pkgname_text = '\n'.join(pkgnames) + orm.Todolist.objects.filter(id=new.id).update(raw=pkgname_text) + list_id_map[old.id] = new.id + list_added[old.id] = old.date_added + + # 1 and 0 come from TodolistPackage.COMPLETE, INCOMPLETE + get_status = lambda v: 1 if v is True else 0 + # next, loop each old todolist package, creating a new one + for old in orm['main.TodolistPkg'].objects.all().select_related( + 'pkg').order_by('id'): + pkg = old.pkg + new_list_id = list_id_map[old.list_id] + orm.TodolistPackage.objects.create(todolist_id=new_list_id, + pkg=pkg, pkgname=pkg.pkgname, pkgbase=pkg.pkgbase, + arch_id=pkg.arch_id, repo_id=pkg.repo_id, + status=get_status(old.complete), + created=list_added[old.list_id]) + + def backwards(self, orm): + #orm.Todolist.objects.all().delete() + #orm.TodolistPackage.objects.all().delete() + pass + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.donor': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Donor', 'db_table': "'donors'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.packagefile': { + 'Meta': {'object_name': 'PackageFile', 'db_table': "'package_files'"}, + 'directory': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_directory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'main.todolist': { + 'Meta': {'object_name': 'Todolist', 'db_table': "'todolists'"}, + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'on_delete': 'models.PROTECT'}), + 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'main.todolistpkg': { + 'Meta': {'unique_together': "(('list', 'pkg'),)", 'object_name': 'TodolistPkg', 'db_table': "'todolist_pkgs'"}, + 'complete': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Todolist']"}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'todolists.todolist': { + 'Meta': {'object_name': 'Todolist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_todolists'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'old_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True'}), + 'raw': ('django.db.models.fields.TextField', [], {'blank': 'True'}) + }, + 'todolists.todolistpackage': { + 'Meta': {'unique_together': "(('todolist', 'pkgname', 'arch'),)", 'object_name': 'TodolistPackage'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'todolist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['todolists.Todolist']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}) + } + } + + complete_apps = ['main', 'todolists'] + symmetrical = True -- cgit v1.2.3 From c8ece67cec9c421ac0c711554edd34f022623b45 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 00:27:20 -0600 Subject: Convert to using new todolist models everywhere This is a rather widespread set of changes converting usage to the new todo list and todo list package model recently introduced. The data migration is not included in this commit. After this commit, the old model should no longer be referenced anywhere. Signed-off-by: Dan McGee --- todolists/urls.py | 2 +- todolists/utils.py | 14 +++++----- todolists/views.py | 76 ++++++++++++++++++++++++++++++------------------------ 3 files changed, 51 insertions(+), 41 deletions(-) (limited to 'todolists') diff --git a/todolists/urls.py b/todolists/urls.py index a379468f..81ac11f5 100644 --- a/todolists/urls.py +++ b/todolists/urls.py @@ -11,7 +11,7 @@ urlpatterns = patterns('todolists.views', (r'^edit/(?P\d+)/$', 'edit'), (r'^flag/(\d+)/(\d+)/$', 'flag'), (r'^delete/(?P\d+)/$', - permission_required('main.delete_todolist')(DeleteTodolist.as_view())), + permission_required('todolists.delete_todolist')(DeleteTodolist.as_view())), ) # vim: set ts=4 sw=4 et: diff --git a/todolists/utils.py b/todolists/utils.py index 03c47931..d084c645 100644 --- a/todolists/utils.py +++ b/todolists/utils.py @@ -1,26 +1,26 @@ from django.db import connections, router from django.db.models import Count -from main.models import Todolist, TodolistPkg +from .models import Todolist, TodolistPackage def todo_counts(): sql = """ -SELECT list_id, count(*), sum(CASE WHEN complete THEN 1 ELSE 0 END) - FROM todolist_pkgs - GROUP BY list_id +SELECT todolist_id, count(*), sum(CASE WHEN status = %s THEN 1 ELSE 0 END) + FROM todolists_todolistpackage + GROUP BY todolist_id """ - database = router.db_for_write(TodolistPkg) + database = router.db_for_write(TodolistPackage) connection = connections[database] cursor = connection.cursor() - cursor.execute(sql) + cursor.execute(sql, [TodolistPackage.COMPLETE]) results = cursor.fetchall() return {row[0]: (row[1], row[2]) for row in results} def get_annotated_todolists(incomplete_only=False): lists = Todolist.objects.all().select_related( - 'creator').order_by('-date_added') + 'creator').order_by('-created') lookup = todo_counts() # tag each list with package counts diff --git a/todolists/views.py b/todolists/views.py index 9984ef9a..94164391 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -10,39 +10,45 @@ from django.views.decorators.cache import never_cache from django.views.generic import DeleteView from django.template import Context, loader -from main.models import Todolist, TodolistPkg, Package, Repo +from main.models import Package, Repo from packages.utils import attach_maintainers +from .models import Todolist, TodolistPackage from .utils import get_annotated_todolists + class TodoListForm(forms.ModelForm): - packages = forms.CharField(required=False, + 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).filter( - repo__testing=False, repo__staging=False).select_related( - 'arch', 'repo').order_by('arch') - return packages + def packages(self): + package_names = {s.strip() for s in + self.cleaned_data['raw'].split("\n")} + return Package.objects.normal().filter(pkgname__in=package_names, + repo__testing=False, repo__staging=False).order_by('arch') class Meta: model = Todolist - fields = ('name', 'description') + fields = ('name', 'description', 'raw') + -@permission_required('main.change_todolistpkg') +@permission_required('todolists.change_todolistpackage') @never_cache def flag(request, list_id, pkg_id): todolist = get_object_or_404(Todolist, id=list_id) - pkg = get_object_or_404(TodolistPkg, id=pkg_id) - pkg.complete = not pkg.complete - pkg.save() + tlpkg = get_object_or_404(TodolistPackage, id=pkg_id) + # TODO: none of this; require absolute value on submit + if tlpkg.status == TodolistPackage.INCOMPLETE: + tlpkg.status = TodolistPackage.COMPLETE + else: + tlpkg.status = TodolistPackage.INCOMPLETE + tlpkg.save() if request.is_ajax(): - return HttpResponse( - json.dumps({'complete': pkg.complete}), - mimetype='application/json') + data = { + 'status': tlpkg.get_status_display(), + 'css_class': tlpkg.status_css_class(), + } + return HttpResponse(json.dumps(data), mimetype='application/json') return redirect(todolist) @login_required @@ -52,9 +58,9 @@ def view(request, list_id): '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(tp.pkg for tp in todolist.packages) - arches = {tp.pkg.arch for tp in todolist.packages} - repos = {tp.pkg.repo for tp in todolist.packages} + attach_maintainers(todolist.packages()) + arches = {tp.arch for tp in todolist.packages()} + repos = {tp.repo for tp in todolist.packages()} return render(request, 'todolists/view.html', { 'list': todolist, 'svn_roots': svn_roots, @@ -67,9 +73,9 @@ def list_pkgbases(request, list_id, svn_root): '''Used to make bulk moves of packages a lot easier.''' todolist = get_object_or_404(Todolist, id=list_id) repos = get_list_or_404(Repo, svn_root=svn_root) - pkgbases = {tp.pkg.pkgbase for tp in todolist.packages - if tp.pkg.repo in repos} - return HttpResponse('\n'.join(sorted(pkgbases)), + pkgbases = TodolistPackage.objects.values_list('pkgbase', flat=True).filter( + todolist=todolist, repo__in=repos).distinct().order_by('pkgbase') + return HttpResponse('\n'.join(pkgbases), mimetype='text/plain') @login_required @@ -77,7 +83,7 @@ def todolist_list(request): lists = get_annotated_todolists() return render(request, 'todolists/list.html', {'lists': lists}) -@permission_required('main.add_todolist') +@permission_required('todolists.add_todolist') @never_cache def add(request): if request.POST: @@ -98,7 +104,7 @@ def add(request): return render(request, 'general_form.html', page_dict) # TODO: this calls for transaction management and async emailing -@permission_required('main.change_todolist') +@permission_required('todolists.change_todolist') @never_cache def edit(request, list_id): todo_list = get_object_or_404(Todolist, id=list_id) @@ -110,7 +116,7 @@ def edit(request, list_id): return redirect(todo_list) else: form = TodoListForm(instance=todo_list, - initial={ 'packages': '\n'.join(todo_list.package_names) }) + initial={ 'packages': todo_list.raw }) page_dict = { 'title': 'Edit Todo List: %s' % todo_list.name, @@ -128,7 +134,7 @@ class DeleteTodolist(DeleteView): @transaction.commit_on_success def create_todolist_packages(form, creator=None): - packages = form.cleaned_data['packages'] + packages = form.packages() if creator: # todo list is new todolist = form.save(commit=False) @@ -140,18 +146,22 @@ def create_todolist_packages(form, creator=None): # todo list already existed form.save() todolist = form.instance + # first delete any packages not in the new list - for todo_pkg in todolist.packages: + for todo_pkg in todolist.packages(): if todo_pkg.pkg not in packages: todo_pkg.delete() # save the old package list so we know what to add - old_packages = [p.pkg for p in todolist.packages] + old_packages = [todo_pkg.pkg for todo_pkg in todolist.packages()] todo_pkgs = [] for package in packages: if package not in old_packages: - todo_pkg = TodolistPkg.objects.create(list=todolist, pkg=package) + todo_pkg = TodolistPackage.objects.create(todolist=todolist, + pkg=package, pkgname=package.pkgname, + pkgbase=package.pkgbase, + arch=package.arch, repo=package.repo) todo_pkgs.append(todo_pkg) return todo_pkgs @@ -186,8 +196,8 @@ def send_todolist_emails(todo_list, new_packages): def public_list(request): todo_lists = Todolist.objects.incomplete() # total hackjob, but it makes this a lot less query-intensive. - all_pkgs = [tp for tl in todo_lists for tp in tl.packages] - attach_maintainers([tp.pkg for tp in all_pkgs]) + all_pkgs = [tp for tl in todo_lists for tp in tl.packages()] + attach_maintainers(all_pkgs) return render(request, "todolists/public_list.html", {"todo_lists": todo_lists}) -- cgit v1.2.3 From 2ff967c3d9433361b1f086c326f3a473f10373e3 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 09:22:23 -0600 Subject: Todolist URLs map to old_id now, not id This is a short-term fix before adding a slug field to todo lists as we did to news a while back. Signed-off-by: Dan McGee --- todolists/models.py | 2 +- todolists/views.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'todolists') diff --git a/todolists/models.py b/todolists/models.py index c38c564e..d19ad92d 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -34,7 +34,7 @@ class Todolist(models.Model): return self.name def get_absolute_url(self): - return '/todo/%i/' % self.id + return '/todo/%i/' % self.old_id def get_full_url(self, proto='https'): '''get a URL suitable for things like email including the domain''' diff --git a/todolists/views.py b/todolists/views.py index 94164391..ccf65369 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -35,7 +35,7 @@ class TodoListForm(forms.ModelForm): @permission_required('todolists.change_todolistpackage') @never_cache def flag(request, list_id, pkg_id): - todolist = get_object_or_404(Todolist, id=list_id) + todolist = get_object_or_404(Todolist, old_id=list_id) tlpkg = get_object_or_404(TodolistPackage, id=pkg_id) # TODO: none of this; require absolute value on submit if tlpkg.status == TodolistPackage.INCOMPLETE: @@ -53,7 +53,7 @@ def flag(request, list_id, pkg_id): @login_required def view(request, list_id): - todolist = get_object_or_404(Todolist, id=list_id) + todolist = get_object_or_404(Todolist, old_id=list_id) 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, @@ -71,7 +71,7 @@ def view(request, list_id): # really no need for login_required on this one... def list_pkgbases(request, list_id, svn_root): '''Used to make bulk moves of packages a lot easier.''' - todolist = get_object_or_404(Todolist, id=list_id) + todolist = get_object_or_404(Todolist, old_id=list_id) repos = get_list_or_404(Repo, svn_root=svn_root) pkgbases = TodolistPackage.objects.values_list('pkgbase', flat=True).filter( todolist=todolist, repo__in=repos).distinct().order_by('pkgbase') @@ -107,7 +107,7 @@ def add(request): @permission_required('todolists.change_todolist') @never_cache def edit(request, list_id): - todo_list = get_object_or_404(Todolist, id=list_id) + todo_list = get_object_or_404(Todolist, old_id=list_id) if request.POST: form = TodoListForm(request.POST, instance=todo_list) if form.is_valid(): -- cgit v1.2.3 From 46a51a99cc9d1839b622252a4cb0b4cd1d0c50e4 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 09:35:47 -0600 Subject: Add todolists slug field This will be used to make more descriptive URLs for our todolists. The `null=True` bit will be removed once a data migration is added to add slugs to all previously created todolists. Signed-off-by: Dan McGee --- .../0004_auto__add_field_todolist_slug.py | 123 +++++++++++++++++++++ todolists/models.py | 1 + 2 files changed, 124 insertions(+) create mode 100644 todolists/migrations/0004_auto__add_field_todolist_slug.py (limited to 'todolists') diff --git a/todolists/migrations/0004_auto__add_field_todolist_slug.py b/todolists/migrations/0004_auto__add_field_todolist_slug.py new file mode 100644 index 00000000..18fd2f9a --- /dev/null +++ b/todolists/migrations/0004_auto__add_field_todolist_slug.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.add_column('todolists_todolist', 'slug', + self.gf('django.db.models.fields.SlugField')(max_length=255, unique=True, null=True), + keep_default=False) + + + def backwards(self, orm): + db.delete_column('todolists_todolist', 'slug') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'todolists.todolist': { + 'Meta': {'object_name': 'Todolist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_todolists'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'old_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True'}), + 'raw': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'}) + }, + 'todolists.todolistpackage': { + 'Meta': {'unique_together': "(('todolist', 'pkgname', 'arch'),)", 'object_name': 'TodolistPackage'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'todolist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['todolists.Todolist']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}) + } + } + + complete_apps = ['todolists'] diff --git a/todolists/models.py b/todolists/models.py index d19ad92d..8ee4b5ce 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -16,6 +16,7 @@ class TodolistManager(models.Manager): class Todolist(models.Model): + slug = models.SlugField(max_length=255, null=True, unique=True) old_id = models.IntegerField(null=True, unique=True) name = models.CharField(max_length=255) description = models.TextField() -- cgit v1.2.3 From 1dc6b867f48786ab6973d6832f386c9d771b58e0 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 09:42:17 -0600 Subject: Populate the todolist slug field and mark non-null This is ripped off from commit 7c92ddbd3c86d when we added slugs to News objects. Signed-off-by: Dan McGee --- todolists/migrations/0005_add_slugs.py | 133 +++++++++++++++++++++ .../0006_auto__chg_field_todolist_slug.py | 119 ++++++++++++++++++ todolists/models.py | 2 +- 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 todolists/migrations/0005_add_slugs.py create mode 100644 todolists/migrations/0006_auto__chg_field_todolist_slug.py (limited to 'todolists') diff --git a/todolists/migrations/0005_add_slugs.py b/todolists/migrations/0005_add_slugs.py new file mode 100644 index 00000000..d7d67793 --- /dev/null +++ b/todolists/migrations/0005_add_slugs.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import DataMigration +from django.db import models + +from django.template.defaultfilters import slugify + +class Migration(DataMigration): + + def forwards(self, orm): + existing = list(orm.Todolist.objects.values_list( + 'slug', flat=True).distinct()) + for item in orm.Todolist.objects.defer('raw').filter(slug=None): + suffixed = slug = slugify(item.name) + suffix = 1 + while suffixed in existing: + suffix += 1 + suffixed = "%s-%d" % (slug, suffix) + + item.slug = suffixed + existing.append(suffixed) + + item.save() + + def backwards(self, orm): + orm.Todolist.objects.all.update(slug=None) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'todolists.todolist': { + 'Meta': {'object_name': 'Todolist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_todolists'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'old_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True'}), + 'raw': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'}) + }, + 'todolists.todolistpackage': { + 'Meta': {'unique_together': "(('todolist', 'pkgname', 'arch'),)", 'object_name': 'TodolistPackage'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'todolist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['todolists.Todolist']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}) + } + } + + complete_apps = ['todolists'] + symmetrical = True diff --git a/todolists/migrations/0006_auto__chg_field_todolist_slug.py b/todolists/migrations/0006_auto__chg_field_todolist_slug.py new file mode 100644 index 00000000..3073120b --- /dev/null +++ b/todolists/migrations/0006_auto__chg_field_todolist_slug.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.alter_column('todolists_todolist', 'slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=255)) + + def backwards(self, orm): + db.alter_column('todolists_todolist', 'slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=255, null=True)) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'todolists.todolist': { + 'Meta': {'object_name': 'Todolist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_todolists'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'old_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True'}), + 'raw': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'todolists.todolistpackage': { + 'Meta': {'unique_together': "(('todolist', 'pkgname', 'arch'),)", 'object_name': 'TodolistPackage'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'todolist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['todolists.Todolist']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}) + } + } + + complete_apps = ['todolists'] diff --git a/todolists/models.py b/todolists/models.py index 8ee4b5ce..a6dda2ab 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -16,7 +16,7 @@ class TodolistManager(models.Manager): class Todolist(models.Model): - slug = models.SlugField(max_length=255, null=True, unique=True) + slug = models.SlugField(max_length=255, unique=True) old_id = models.IntegerField(null=True, unique=True) name = models.CharField(max_length=255) description = models.TextField() -- cgit v1.2.3 From 0c94cc4465530866da7b6437975a287aa7f063a8 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 10:06:32 -0600 Subject: Use todolist slugs for all URLs Signed-off-by: Dan McGee --- todolists/models.py | 2 +- todolists/urls.py | 29 +++++++++++++++++++---------- todolists/views.py | 36 ++++++++++++++++++++++-------------- 3 files changed, 42 insertions(+), 25 deletions(-) (limited to 'todolists') diff --git a/todolists/models.py b/todolists/models.py index a6dda2ab..76af0d35 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -35,7 +35,7 @@ class Todolist(models.Model): return self.name def get_absolute_url(self): - return '/todo/%i/' % self.old_id + return '/todo/%s/' % self.slug def get_full_url(self, proto='https'): '''get a URL suitable for things like email including the domain''' diff --git a/todolists/urls.py b/todolists/urls.py index 81ac11f5..cbc9547e 100644 --- a/todolists/urls.py +++ b/todolists/urls.py @@ -1,17 +1,26 @@ from django.conf.urls import patterns -from django.contrib.auth.decorators import permission_required +from django.contrib.auth.decorators import login_required, permission_required -from .views import DeleteTodolist +from .views import (view_redirect, view, todolist_list, add, edit, flag, + list_pkgbases, DeleteTodolist) -urlpatterns = patterns('todolists.views', - (r'^$', 'todolist_list'), - (r'^(?P\d+)/$', 'view'), - (r'^(?P\d+)/pkgbases/(?P[a-z]+)/$', 'list_pkgbases'), - (r'^add/$', 'add'), - (r'^edit/(?P\d+)/$', 'edit'), - (r'^flag/(\d+)/(\d+)/$', 'flag'), - (r'^delete/(?P\d+)/$', +urlpatterns = patterns('', + (r'^$', login_required(todolist_list)), + + # old todolists URLs, permanent redirect view so we don't break all links + (r'^(?P\d+)/$', view_redirect), + + (r'^add/$', + permission_required('todolists.add_todolist')(add)), + (r'^(?P[-\w]+)/$', login_required(view)), + (r'^(?P[-\w]+)/edit/$', + permission_required('todolists.change_todolist')(edit)), + (r'^(?P[-\w]+)/delete/$', permission_required('todolists.delete_todolist')(DeleteTodolist.as_view())), + (r'^(?P[-\w]+)/flag/(?P\d+)/$', + permission_required('todolists.change_todolistpackage')(flag)), + (r'^(?P[-\w]+)/pkgbases/(?P[a-z]+)/$', + 'list_pkgbases'), ) # vim: set ts=4 sw=4 et: diff --git a/todolists/views.py b/todolists/views.py index ccf65369..29f543c2 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -4,7 +4,6 @@ from django import forms from django.http import HttpResponse from django.core.mail import send_mail from django.shortcuts import get_list_or_404, get_object_or_404, redirect, render -from django.contrib.auth.decorators import login_required, permission_required from django.db import transaction from django.views.decorators.cache import never_cache from django.views.generic import DeleteView @@ -32,10 +31,9 @@ class TodoListForm(forms.ModelForm): fields = ('name', 'description', 'raw') -@permission_required('todolists.change_todolistpackage') @never_cache -def flag(request, list_id, pkg_id): - todolist = get_object_or_404(Todolist, old_id=list_id) +def flag(request, slug, pkg_id): + todolist = get_object_or_404(Todolist, slug=slug) tlpkg = get_object_or_404(TodolistPackage, id=pkg_id) # TODO: none of this; require absolute value on submit if tlpkg.status == TodolistPackage.INCOMPLETE: @@ -51,9 +49,14 @@ def flag(request, list_id, pkg_id): return HttpResponse(json.dumps(data), mimetype='application/json') return redirect(todolist) -@login_required -def view(request, list_id): - todolist = get_object_or_404(Todolist, old_id=list_id) + +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, @@ -68,22 +71,23 @@ def view(request, list_id): 'repos': sorted(repos), }) + # really no need for login_required on this one... -def list_pkgbases(request, list_id, svn_root): +def list_pkgbases(request, slug, svn_root): '''Used to make bulk moves of packages a lot easier.''' - todolist = get_object_or_404(Todolist, old_id=list_id) + 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).distinct().order_by('pkgbase') return HttpResponse('\n'.join(pkgbases), mimetype='text/plain') -@login_required + def todolist_list(request): lists = get_annotated_todolists() return render(request, 'todolists/list.html', {'lists': lists}) -@permission_required('todolists.add_todolist') + @never_cache def add(request): if request.POST: @@ -103,11 +107,11 @@ def add(request): } return render(request, 'general_form.html', page_dict) + # TODO: this calls for transaction management and async emailing -@permission_required('todolists.change_todolist') @never_cache -def edit(request, list_id): - todo_list = get_object_or_404(Todolist, old_id=list_id) +def edit(request, slug): + todo_list = get_object_or_404(Todolist, slug=slug) if request.POST: form = TodoListForm(request.POST, instance=todo_list) if form.is_valid(): @@ -126,12 +130,14 @@ def edit(request, list_id): } 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.commit_on_success def create_todolist_packages(form, creator=None): packages = form.packages() @@ -166,6 +172,7 @@ def create_todolist_packages(form, creator=None): 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.''' @@ -193,6 +200,7 @@ def send_todolist_emails(todo_list, new_packages): [maint], fail_silently=True) + def public_list(request): todo_lists = Todolist.objects.incomplete() # total hackjob, but it makes this a lot less query-intensive. -- cgit v1.2.3 From 29be1e06032ad8a0a38921b9e04be888141881b1 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 10:14:08 -0600 Subject: Set slug on todolist creation Signed-off-by: Dan McGee --- todolists/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index 29f543c2..413d8675 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -3,13 +3,16 @@ import json from django import forms from django.http import HttpResponse from django.core.mail import send_mail -from django.shortcuts import get_list_or_404, get_object_or_404, redirect, render +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 import DeleteView from django.template import Context, loader +from django.template.defaultfilters import slugify 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 @@ -142,9 +145,10 @@ class DeleteTodolist(DeleteView): def create_todolist_packages(form, creator=None): packages = form.packages() if creator: - # todo list is new + # 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() old_packages = [] -- cgit v1.2.3 From ee507a5b81d7a21eaa67da4c848522a5a97d2e3c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 14:44:09 -0600 Subject: Add a todolist package details link template tag Given the way we retrieve certain related objects, it makes more sense to use a custom tag here rather than our generic package details link tag. When viewing a large todolist, this saves significantly on the number of queries we need to build the page. Signed-off-by: Dan McGee --- todolists/templatetags/__init__.py | 0 todolists/templatetags/todolists.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 todolists/templatetags/__init__.py create mode 100644 todolists/templatetags/todolists.py (limited to 'todolists') diff --git a/todolists/templatetags/__init__.py b/todolists/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/todolists/templatetags/todolists.py b/todolists/templatetags/todolists.py new file mode 100644 index 00000000..5f31dc1f --- /dev/null +++ b/todolists/templatetags/todolists.py @@ -0,0 +1,19 @@ +from django import template + +register = template.Library() + + +def pkg_absolute_url(repo, arch, pkgname): + return '/packages/%s/%s/%s/' % (repo.name.lower(), arch.name, pkgname) + + +@register.simple_tag +def todopkg_details_link(todopkg): + pkg = todopkg.pkg + if not pkg: + return todopkg.pkgname + link = '%s' + url = pkg_absolute_url(todopkg.repo, todopkg.arch, pkg.pkgname) + return link % (url, pkg.pkgname, pkg.pkgname) + +# vim: set ts=4 sw=4 et: -- cgit v1.2.3 From 39a603bf65c4aec780e4711074e9ed27fb7c301e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 29 Dec 2012 12:26:50 -0600 Subject: Defer the 'raw' field when listing todolists A lot like skipping fetching of the news content; we definitely don't need this just to list the todolists on index pages. Signed-off-by: Dan McGee --- todolists/utils.py | 2 +- todolists/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'todolists') diff --git a/todolists/utils.py b/todolists/utils.py index d084c645..0daca3b6 100644 --- a/todolists/utils.py +++ b/todolists/utils.py @@ -19,7 +19,7 @@ SELECT todolist_id, count(*), sum(CASE WHEN status = %s THEN 1 ELSE 0 END) def get_annotated_todolists(incomplete_only=False): - lists = Todolist.objects.all().select_related( + lists = Todolist.objects.all().defer('raw').select_related( 'creator').order_by('-created') lookup = todo_counts() diff --git a/todolists/views.py b/todolists/views.py index 413d8675..788d74f2 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -206,7 +206,7 @@ def send_todolist_emails(todo_list, new_packages): def public_list(request): - todo_lists = Todolist.objects.incomplete() + todo_lists = Todolist.objects.incomplete().defer('raw') # total hackjob, but it makes this a lot less query-intensive. all_pkgs = [tp for tl in todo_lists for tp in tl.packages()] attach_maintainers(all_pkgs) -- cgit v1.2.3 From 58a4ecb0baca75e0acd67396c3564a1e0b1bb4b5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 31 Dec 2012 09:42:06 -0600 Subject: Small admin tweaks for todolists filtering Signed-off-by: Dan McGee --- todolists/admin.py | 1 + 1 file changed, 1 insertion(+) (limited to 'todolists') diff --git a/todolists/admin.py b/todolists/admin.py index 181febb1..246a8bca 100644 --- a/todolists/admin.py +++ b/todolists/admin.py @@ -5,6 +5,7 @@ from .models import Todolist class TodolistAdmin(admin.ModelAdmin): list_display = ('name', 'creator', 'created', 'description') + list_filter = ('created', 'creator') search_fields = ('name', 'description') date_hierarchy = 'created' -- cgit v1.2.3 From 5a09e335ae3b9d1f2bc814d011bcf90a16220777 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 31 Dec 2012 09:52:28 -0600 Subject: Add 'removed' field to todolist packages This will be utilized to soft-delete items from the list if the packages are modified, rather than deleting them outright. Signed-off-by: Dan McGee --- ...0007_auto__add_field_todolistpackage_removed.py | 124 +++++++++++++++++++++ todolists/models.py | 5 +- 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 todolists/migrations/0007_auto__add_field_todolistpackage_removed.py (limited to 'todolists') diff --git a/todolists/migrations/0007_auto__add_field_todolistpackage_removed.py b/todolists/migrations/0007_auto__add_field_todolistpackage_removed.py new file mode 100644 index 00000000..6d9c4fb2 --- /dev/null +++ b/todolists/migrations/0007_auto__add_field_todolistpackage_removed.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.add_column('todolists_todolistpackage', 'removed', + self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + db.delete_column('todolists_todolistpackage', 'removed') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'todolists.todolist': { + 'Meta': {'object_name': 'Todolist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_todolists'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'old_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True'}), + 'raw': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'todolists.todolistpackage': { + 'Meta': {'unique_together': "(('todolist', 'pkgname', 'arch'),)", 'object_name': 'TodolistPackage'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'removed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'todolist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['todolists.Todolist']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}) + } + } + + complete_apps = ['todolists'] diff --git a/todolists/models.py b/todolists/models.py index 76af0d35..e02cdd1a 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -66,12 +66,15 @@ class TodolistPackage(models.Model): arch = models.ForeignKey(Arch) repo = models.ForeignKey(Repo) created = models.DateTimeField() - status = models.SmallIntegerField(default=0, choices=STATUS_CHOICES) + removed = models.DateTimeField(null=True, blank=True) + status = models.SmallIntegerField(default=INCOMPLETE, + choices=STATUS_CHOICES) user = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) comments = models.TextField(null=True, blank=True) class Meta: unique_together = (('todolist','pkgname', 'arch'),) + get_latest_by = 'created' def __unicode__(self): return self.pkgname -- cgit v1.2.3 From c37fe107282f1aa4925d6c3eef9b7c1598ab4aa1 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 31 Dec 2012 10:24:09 -0600 Subject: Minor coding style tweaks Signed-off-by: Dan McGee --- todolists/models.py | 2 +- todolists/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'todolists') diff --git a/todolists/models.py b/todolists/models.py index e02cdd1a..156b041d 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -73,7 +73,7 @@ class TodolistPackage(models.Model): comments = models.TextField(null=True, blank=True) class Meta: - unique_together = (('todolist','pkgname', 'arch'),) + unique_together = (('todolist', 'pkgname', 'arch'),) get_latest_by = 'created' def __unicode__(self): diff --git a/todolists/views.py b/todolists/views.py index 788d74f2..113b27e7 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -123,7 +123,7 @@ def edit(request, slug): return redirect(todo_list) else: form = TodoListForm(instance=todo_list, - initial={ 'packages': todo_list.raw }) + initial={'packages': todo_list.raw}) page_dict = { 'title': 'Edit Todo List: %s' % todo_list.name, -- cgit v1.2.3 From 95394bc4fafa9b677e8baccb8c5d41aee4bc2390 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 31 Dec 2012 10:28:43 -0600 Subject: Fix dependency issue found by south migrationcheck command Due to pgp_signature being added to the Package model, we need to depend on this later change as well. Signed-off-by: Dan McGee --- todolists/migrations/0003_migrate_todolist_data.py | 1 + 1 file changed, 1 insertion(+) (limited to 'todolists') diff --git a/todolists/migrations/0003_migrate_todolist_data.py b/todolists/migrations/0003_migrate_todolist_data.py index d7d8f66f..8a317a67 100644 --- a/todolists/migrations/0003_migrate_todolist_data.py +++ b/todolists/migrations/0003_migrate_todolist_data.py @@ -8,6 +8,7 @@ class Migration(DataMigration): depends_on = ( ('main', '0045_add_todolist_date_added_index'), + ('main', '0053_auto__add_field_package_pgp_signature'), ) def forwards(self, orm): -- cgit v1.2.3 From 827b426b4dce6641e77dac975dae180ce6e20b0a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 31 Dec 2012 11:04:41 -0600 Subject: Fix list_pkgbases view call Signed-off-by: Dan McGee --- todolists/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'todolists') diff --git a/todolists/urls.py b/todolists/urls.py index cbc9547e..a4f93da7 100644 --- a/todolists/urls.py +++ b/todolists/urls.py @@ -20,7 +20,7 @@ urlpatterns = patterns('', (r'^(?P[-\w]+)/flag/(?P\d+)/$', permission_required('todolists.change_todolistpackage')(flag)), (r'^(?P[-\w]+)/pkgbases/(?P[a-z]+)/$', - 'list_pkgbases'), + list_pkgbases), ) # vim: set ts=4 sw=4 et: -- cgit v1.2.3 From 7952fe0ede3a5a68a64f05eccb180194394652f3 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 31 Dec 2012 11:31:35 -0600 Subject: Mark todolist packages as removed rather than deleting them This makes it easier to see the progression of a todolist and its contents easier since we are no longer losing the data. Signed-off-by: Dan McGee --- todolists/models.py | 8 +++++--- todolists/utils.py | 1 + todolists/views.py | 50 +++++++++++++++++++++++++++++++------------------- 3 files changed, 37 insertions(+), 22 deletions(-) (limited to 'todolists') diff --git a/todolists/models.py b/todolists/models.py index 156b041d..040f8a29 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -10,8 +10,9 @@ from main.utils import set_created_field class TodolistManager(models.Manager): def incomplete(self): - not_done = (Q(todolistpackage__status=TodolistPackage.INCOMPLETE) | - Q(todolistpackage__status=TodolistPackage.IN_PROGRESS)) + not_done = ((Q(todolistpackage__status=TodolistPackage.INCOMPLETE) | + Q(todolistpackage__status=TodolistPackage.IN_PROGRESS)) & + Q(todolistpackage__removed__isnull=True)) return self.order_by().filter(not_done).distinct() @@ -44,7 +45,8 @@ class Todolist(models.Model): def packages(self): if not hasattr(self, '_packages'): - self._packages = self.todolistpackage_set.select_related( + self._packages = self.todolistpackage_set.filter( + removed__isnull=True).select_related( 'pkg', 'repo', 'arch').order_by('pkgname', 'arch') return self._packages diff --git a/todolists/utils.py b/todolists/utils.py index 0daca3b6..e86d9054 100644 --- a/todolists/utils.py +++ b/todolists/utils.py @@ -8,6 +8,7 @@ def todo_counts(): sql = """ SELECT todolist_id, count(*), sum(CASE WHEN status = %s THEN 1 ELSE 0 END) FROM todolists_todolistpackage + WHERE removed IS NULL GROUP BY todolist_id """ database = router.db_for_write(TodolistPackage) diff --git a/todolists/views.py b/todolists/views.py index 113b27e7..32ee7cc5 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -10,6 +10,7 @@ from django.views.decorators.cache import never_cache from django.views.generic import DeleteView from django.template import Context, loader from django.template.defaultfilters import slugify +from django.utils.timezone import now from main.models import Package, Repo from main.utils import find_unique_slug @@ -23,10 +24,12 @@ class TodoListForm(forms.ModelForm): help_text='(one per line)', widget=forms.Textarea(attrs={'rows': '20', 'cols': '60'})) + def package_names(self): + return {s.strip() for s in self.cleaned_data['raw'].split("\n")} + def packages(self): - package_names = {s.strip() for s in - self.cleaned_data['raw'].split("\n")} - return Package.objects.normal().filter(pkgname__in=package_names, + return Package.objects.normal().filter( + pkgname__in=self.package_names(), repo__testing=False, repo__staging=False).order_by('arch') class Meta: @@ -37,7 +40,7 @@ class TodoListForm(forms.ModelForm): @never_cache def flag(request, slug, pkg_id): todolist = get_object_or_404(Todolist, slug=slug) - tlpkg = get_object_or_404(TodolistPackage, id=pkg_id) + 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 @@ -75,13 +78,14 @@ def view(request, slug): }) -# really no need for login_required on this one... 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).distinct().order_by('pkgbase') + 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), mimetype='text/plain') @@ -143,36 +147,44 @@ class DeleteTodolist(DeleteView): @transaction.commit_on_success 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() - - old_packages = [] else: # todo list already existed form.save() todolist = form.instance - # first delete any packages not in the new list + # first mark removed any packages not in the new list + to_remove = set() for todo_pkg in todolist.packages(): - if todo_pkg.pkg not in packages: - todo_pkg.delete() + 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) - # save the old package list so we know what to add - old_packages = [todo_pkg.pkg for todo_pkg in todolist.packages()] + 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: - if package not in old_packages: - todo_pkg = TodolistPackage.objects.create(todolist=todolist, - pkg=package, pkgname=package.pkgname, - pkgbase=package.pkgbase, - arch=package.arch, repo=package.repo) + todo_pkg, created = TodolistPackage.objects.get_or_create( + todolist=todolist, + pkg=package, pkgname=package.pkgname, + pkgbase=package.pkgbase, + arch=package.arch, repo=package.repo) + if created: todo_pkgs.append(todo_pkg) + elif todo_pkg.removed is not None: + todo_pkg.removed = None + todo_pkg.save() return todo_pkgs -- cgit v1.2.3 From eea59cd12b6f17c5f0f96805205bc4967143f658 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 31 Dec 2012 11:36:29 -0600 Subject: Set user on todolist packages when flag status changes This will allow us to see who last changed the status of a todolist item. Signed-off-by: Dan McGee --- todolists/views.py | 1 + 1 file changed, 1 insertion(+) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index 32ee7cc5..f4f9ab21 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -46,6 +46,7 @@ def flag(request, slug, pkg_id): tlpkg.status = TodolistPackage.COMPLETE else: tlpkg.status = TodolistPackage.INCOMPLETE + tlpkg.user = request.user tlpkg.save() if request.is_ajax(): data = { -- cgit v1.2.3 From 6fe28b4206979a0db9c7d1f2f5f3a81c49d77951 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 12 Jan 2013 16:33:31 -0600 Subject: Add last_modified field to todolist packages Signed-off-by: Dan McGee --- ...uto__add_field_todolistpackage_last_modified.py | 129 +++++++++++++++++++++ .../0009_update_last_modified_todolist_package.py | 122 +++++++++++++++++++ todolists/models.py | 3 +- 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 todolists/migrations/0008_auto__add_field_todolistpackage_last_modified.py create mode 100644 todolists/migrations/0009_update_last_modified_todolist_package.py (limited to 'todolists') diff --git a/todolists/migrations/0008_auto__add_field_todolistpackage_last_modified.py b/todolists/migrations/0008_auto__add_field_todolistpackage_last_modified.py new file mode 100644 index 00000000..ac3025b4 --- /dev/null +++ b/todolists/migrations/0008_auto__add_field_todolistpackage_last_modified.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models +from pytz import utc + + +class Migration(SchemaMigration): + + def forwards(self, orm): + default = datetime.datetime(2000, 1, 1, 0, 0).replace(tzinfo=utc) + db.add_column('todolists_todolistpackage', 'last_modified', + self.gf('django.db.models.fields.DateTimeField')(default=default), + keep_default=False) + + + def backwards(self, orm): + db.delete_column('todolists_todolistpackage', 'last_modified') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'todolists.todolist': { + 'Meta': {'object_name': 'Todolist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_todolists'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'old_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True'}), + 'raw': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'todolists.todolistpackage': { + 'Meta': {'unique_together': "(('todolist', 'pkgname', 'arch'),)", 'object_name': 'TodolistPackage'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'removed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'todolist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['todolists.Todolist']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}) + } + } + + complete_apps = ['todolists'] diff --git a/todolists/migrations/0009_update_last_modified_todolist_package.py b/todolists/migrations/0009_update_last_modified_todolist_package.py new file mode 100644 index 00000000..f7bf30ae --- /dev/null +++ b/todolists/migrations/0009_update_last_modified_todolist_package.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + orm.TodolistPackage.objects.all().update(last_modified=models.F('created')) + + def backwards(self, orm): + pass + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'todolists.todolist': { + 'Meta': {'object_name': 'Todolist'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_todolists'", 'on_delete': 'models.PROTECT', 'to': "orm['auth.User']"}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'old_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'null': 'True'}), + 'raw': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'todolists.todolistpackage': { + 'Meta': {'unique_together': "(('todolist', 'pkgname', 'arch'),)", 'object_name': 'TodolistPackage'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'removed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'todolist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['todolists.Todolist']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}) + } + } + + complete_apps = ['todolists'] + symmetrical = True diff --git a/todolists/models.py b/todolists/models.py index 040f8a29..3ea80f37 100644 --- a/todolists/models.py +++ b/todolists/models.py @@ -67,7 +67,8 @@ class TodolistPackage(models.Model): pkgbase = models.CharField(max_length=255) arch = models.ForeignKey(Arch) repo = models.ForeignKey(Repo) - created = models.DateTimeField() + created = models.DateTimeField(editable=False) + last_modified = models.DateTimeField(editable=False) removed = models.DateTimeField(null=True, blank=True) status = models.SmallIntegerField(default=INCOMPLETE, choices=STATUS_CHOICES) -- cgit v1.2.3 From 66850026ca934e5a09238e9033c541cdc5085a42 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 13 Jan 2013 22:34:33 -0600 Subject: Use content_type and not mimetype on HttpResponse() Bug #16519 in Django deprecates mimetype, so update our code accordingly. Signed-off-by: Dan McGee --- todolists/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index f4f9ab21..fcf62e23 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -53,7 +53,7 @@ def flag(request, slug, pkg_id): 'status': tlpkg.get_status_display(), 'css_class': tlpkg.status_css_class(), } - return HttpResponse(json.dumps(data), mimetype='application/json') + return HttpResponse(json.dumps(data), content_type='application/json') return redirect(todolist) @@ -87,8 +87,7 @@ def list_pkgbases(request, slug, svn_root): 'pkgbase', flat=True).filter( todolist=todolist, repo__in=repos, removed__isnull=True).order_by( 'pkgbase').distinct() - return HttpResponse('\n'.join(pkgbases), - mimetype='text/plain') + return HttpResponse('\n'.join(pkgbases), content_type='text/plain') def todolist_list(request): -- cgit v1.2.3 From f106379b5382b2b82aa56466c8d3acaae58327a9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 15 Jan 2013 21:29:30 -0600 Subject: Clean up and make several migrations modern This moves most migrations to the v2 format that have been presenting some issues. One missing depends_on relationship has been added, and we allow an index to not be dropped if it does not exist due to the shittyness in sqlite3 actually keeping indexes across DDL on that table. Signed-off-by: Dan McGee --- todolists/migrations/0002_add_todolist_and_todolistpackage.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'todolists') diff --git a/todolists/migrations/0002_add_todolist_and_todolistpackage.py b/todolists/migrations/0002_add_todolist_and_todolistpackage.py index 8365535a..ba8f7ebe 100644 --- a/todolists/migrations/0002_add_todolist_and_todolistpackage.py +++ b/todolists/migrations/0002_add_todolist_and_todolistpackage.py @@ -6,6 +6,10 @@ from django.db import models class Migration(SchemaMigration): + depends_on = ( + ('main', '0024_set_initial_flag_date'), + ) + def forwards(self, orm): db.create_table('todolists_todolist', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), -- cgit v1.2.3 From e9e1c071654edd7b95e20c8105abbc23f426cecc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 22 Jan 2013 16:47:43 -0600 Subject: Show staging version on todolist view page If one exists, it is easy enough to show it here so in-progress todolists can easily be cross-checked with the current state of the repository. Signed-off-by: Dan McGee --- todolists/utils.py | 19 +++++++++++++++++++ todolists/views.py | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'todolists') diff --git a/todolists/utils.py b/todolists/utils.py index e86d9054..51a75a3c 100644 --- a/todolists/utils.py +++ b/todolists/utils.py @@ -2,6 +2,7 @@ from django.db import connections, router from django.db.models import Count from .models import Todolist, TodolistPackage +from packages.models import Package def todo_counts(): @@ -36,4 +37,22 @@ def get_annotated_todolists(incomplete_only=False): return lists + +def attach_staging(packages, list_id): + '''Look for any staging version of the packages provided and attach them + to the 'staging' attribute on each package if found.''' + pkgnames = TodolistPackage.objects.filter( + todolist_id=list_id).values('pkgname') + staging_pkgs = Package.objects.normal().filter(repo__staging=True, + pkgname__in=pkgnames) + # now build a lookup dict to attach to the correct package + lookup = {(p.pkgname, p.arch): p for p in staging_pkgs} + + annotated = [] + for package in packages: + in_staging = lookup.get((package.pkgname, package.arch), None) + package.staging = in_staging + + return annotated + # vim: set ts=4 sw=4 et: diff --git a/todolists/views.py b/todolists/views.py index fcf62e23..f333728a 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -16,7 +16,7 @@ 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 +from .utils import get_annotated_todolists, attach_staging class TodoListForm(forms.ModelForm): @@ -69,6 +69,7 @@ def view(request, slug): # 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()} return render(request, 'todolists/view.html', { -- cgit v1.2.3 From ca4106a7c0f797ba06e15f777c94cddd5d82a3dc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 8 Feb 2013 22:17:02 -0600 Subject: Ensure todolists are consistently sorted This is for the public view page; we had no order_by() call so lists could be displayed in seemingly random order. Signed-off-by: Dan McGee --- todolists/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index f333728a..c7a4369a 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -219,7 +219,8 @@ def send_todolist_emails(todo_list, new_packages): def public_list(request): - todo_lists = Todolist.objects.incomplete().defer('raw') + todo_lists = Todolist.objects.incomplete().defer( + 'raw').order_by('-created') # total hackjob, but it makes this a lot less query-intensive. all_pkgs = [tp for tl in todo_lists for tp in tl.packages()] attach_maintainers(all_pkgs) -- cgit v1.2.3 From 5566d43a7734f6bb2f48d5d511351da12ddc5cc1 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 9 Feb 2013 16:43:40 -0600 Subject: Use 'update_fields' model.save() kwarg This was added in Django 1.5 and allows saving only a subset of a model's fields. It makes sense in a few cases to utilize it. Signed-off-by: Dan McGee --- todolists/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index f333728a..9935987b 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -47,7 +47,7 @@ def flag(request, slug, pkg_id): else: tlpkg.status = TodolistPackage.INCOMPLETE tlpkg.user = request.user - tlpkg.save() + tlpkg.save(update_fields=('status', 'user', 'last_modified')) if request.is_ajax(): data = { 'status': tlpkg.get_status_display(), -- cgit v1.2.3 From 7a5b06c3e77b6369bf4c813dfc5a3c547cca2280 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 7 Mar 2013 16:16:58 -0600 Subject: Fix IntegrityError in corner case todolist update We were seeing this in production: IntegrityError: duplicate key value violates unique constraint "todolists_todolistpackage_todolist_id_700d1b623414814c_uniq" DETAIL: Key (todolist_id, pkgname, arch_id)=(206, ruby-cairo, 2) already exists. This is due to a corner case where a package was originally on a todolist and the underlying package object disappeared, so the todolist entry was unlinked and pkg_id set to NULL. Later, this package came back, but our get_or_create tried to create an object that violated our unique constraint because of the missing pkg_id. Call get_or_create with the minimum necessary bits to find the todolist package object, and pass the rest of the values via defaults to avoid this problem. Additionally, relink any todolist entries up to a package in the repositories if one is available. Signed-off-by: Dan McGee --- todolists/views.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index 36a0d97d..4b4a4dd2 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -176,16 +176,29 @@ def create_todolist_packages(form, creator=None): # 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, - pkg=package, pkgname=package.pkgname, - pkgbase=package.pkgbase, - arch=package.arch, repo=package.repo) + pkgname=package.pkgname, + arch=package.arch, + defaults=defaults) if created: todo_pkgs.append(todo_pkg) - elif todo_pkg.removed is not None: - todo_pkg.removed = None - todo_pkg.save() + 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 -- cgit v1.2.3 From 00b9084303ccb27f7937a9392d2a582d25a04288 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 11 Mar 2013 18:23:59 -0500 Subject: Remove public todolists view Replace this with a redirect to the developer todolist index page. Signed-off-by: Dan McGee --- todolists/views.py | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index 4b4a4dd2..abec9253 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -230,14 +230,4 @@ def send_todolist_emails(todo_list, new_packages): [maint], fail_silently=True) - -def public_list(request): - todo_lists = Todolist.objects.incomplete().defer( - 'raw').order_by('-created') - # total hackjob, but it makes this a lot less query-intensive. - all_pkgs = [tp for tl in todo_lists for tp in tl.packages()] - attach_maintainers(all_pkgs) - return render(request, "todolists/public_list.html", - {"todo_lists": todo_lists}) - # vim: set ts=4 sw=4 et: -- cgit v1.2.3 From 6172b6dd09f932de8db7ab69641370d78d25a4c2 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 11 Mar 2013 18:48:29 -0500 Subject: Make todolists fully public Remove the login_required decorator from the index and detail views to allow everyone to see the same thing. Of course, when I say "same" here, unauthenticated users don't see the same links developers do to mark packages complete and incomplete. Signed-off-by: Dan McGee --- todolists/urls.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'todolists') diff --git a/todolists/urls.py b/todolists/urls.py index a4f93da7..6617d7dd 100644 --- a/todolists/urls.py +++ b/todolists/urls.py @@ -1,18 +1,18 @@ from django.conf.urls import patterns -from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.decorators import permission_required from .views import (view_redirect, view, todolist_list, add, edit, flag, list_pkgbases, DeleteTodolist) urlpatterns = patterns('', - (r'^$', login_required(todolist_list)), + (r'^$', todolist_list), # old todolists URLs, permanent redirect view so we don't break all links (r'^(?P\d+)/$', view_redirect), (r'^add/$', permission_required('todolists.add_todolist')(add)), - (r'^(?P[-\w]+)/$', login_required(view)), + (r'^(?P[-\w]+)/$', view), (r'^(?P[-\w]+)/edit/$', permission_required('todolists.change_todolist')(edit)), (r'^(?P[-\w]+)/delete/$', -- cgit v1.2.3 From 64da94fbc9375fe41aaa190034220eafb09473a9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 11 Mar 2013 18:55:39 -0500 Subject: Only show incomplete todolists to unauthenticated users Signed-off-by: Dan McGee --- todolists/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'todolists') diff --git a/todolists/views.py b/todolists/views.py index abec9253..7636d38e 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -92,7 +92,8 @@ def list_pkgbases(request, slug, svn_root): def todolist_list(request): - lists = get_annotated_todolists() + incomplete_only = request.user.is_anonymous() + lists = get_annotated_todolists(incomplete_only) return render(request, 'todolists/list.html', {'lists': lists}) -- cgit v1.2.3