diff options
author | Luke Shumaker <LukeShu@sbcglobal.net> | 2012-11-14 22:44:27 -0500 |
---|---|---|
committer | Luke Shumaker <LukeShu@sbcglobal.net> | 2012-11-14 22:44:27 -0500 |
commit | 64f6dd9cb41ddbc84376549f558ed7d52d9e600a (patch) | |
tree | 81f31f1ceb5fc6e4d94508d273003996e036191c | |
parent | bc432a1ff0e69bf45c5f3b97077a13952611196d (diff) | |
parent | b2b5c1a064d5d3c33f4c4fc119bd67cf9ca1b7ba (diff) |
Merge tag 'release_2012-01-11'
Pkgbase view in todos, other changes related to caching
Conflicts:
public/views.py
-rw-r--r-- | devel/management/commands/reporead.py | 12 | ||||
-rw-r--r-- | devel/tests.py | 2 | ||||
-rw-r--r-- | devel/urls.py | 4 | ||||
-rw-r--r-- | devel/views.py | 32 | ||||
-rw-r--r-- | main/middleware.py | 52 | ||||
-rw-r--r-- | main/models.py | 14 | ||||
-rw-r--r-- | mirrors/templatetags/mirror_status.py | 4 | ||||
-rw-r--r-- | mirrors/utils.py | 7 | ||||
-rw-r--r-- | packages/admin.py | 32 | ||||
-rw-r--r-- | packages/management/commands/populate_signoffs.py | 1 | ||||
-rw-r--r-- | packages/migrations/0013_auto__add_field_flagrequest_version.py | 180 | ||||
-rw-r--r-- | packages/models.py | 6 | ||||
-rw-r--r-- | packages/templatetags/package_extras.py | 5 | ||||
-rw-r--r-- | packages/utils.py | 6 | ||||
-rw-r--r-- | packages/views/__init__.py | 8 | ||||
-rw-r--r-- | packages/views/flag.py | 41 | ||||
-rw-r--r-- | packages/views/signoff.py | 2 | ||||
-rw-r--r-- | public/utils.py | 2 | ||||
-rw-r--r-- | public/views.py | 9 | ||||
-rw-r--r-- | settings.py | 2 | ||||
-rw-r--r-- | sitestatic/archweb.js | 20 | ||||
-rw-r--r-- | templates/packages/stale_relations.html | 10 | ||||
-rw-r--r-- | templates/public/index.html | 6 | ||||
-rw-r--r-- | templates/todolists/view.html | 5 | ||||
-rw-r--r-- | todolists/urls.py | 3 | ||||
-rw-r--r-- | todolists/views.py | 33 | ||||
-rw-r--r-- | urls.py | 8 |
27 files changed, 367 insertions, 139 deletions
diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 4dd26091..bf1b2562 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -24,7 +24,6 @@ from datetime import datetime from optparse import make_option from django.core.management.base import BaseCommand, CommandError -from django.contrib.auth.models import User from django.db import connections, router, transaction from django.db.utils import IntegrityError @@ -91,6 +90,7 @@ class Pkg(object): setattr(self, k, None) for k in self.collections: setattr(self, k, ()) + self.builddate = None self.files = None self.has_files = False @@ -244,6 +244,12 @@ pkg_same_version = lambda pkg, dbpkg: pkg.ver == dbpkg.pkgver \ and pkg.rel == dbpkg.pkgrel and pkg.epoch == dbpkg.epoch +def delete_pkg_files(dbpkg): + database = router.db_for_write(Package, instance=dbpkg) + cursor = connections[database].cursor() + cursor.execute('DELETE FROM package_files WHERE pkg_id = %s', [dbpkg.id]) + + def populate_files(dbpkg, repopkg, force=False): if not force: if not pkg_same_version(repopkg, dbpkg): @@ -258,7 +264,7 @@ def populate_files(dbpkg, repopkg, force=False): # only delete files if we are reading a DB that contains them if repopkg.has_files: - dbpkg.packagefile_set.all().delete() + delete_pkg_files(dbpkg) logger.info("adding %d files for package %s", len(repopkg.files), dbpkg.pkgname) for f in repopkg.files: @@ -267,6 +273,7 @@ def populate_files(dbpkg, repopkg, force=False): filename = None # this is basically like calling dbpkg.packagefile_set.create(), # but much faster as we can skip a lot of the repeated code paths + # TODO use Django 1.4 bulk_create pkgfile = PackageFile(pkg=dbpkg, is_directory=(filename is None), directory=dirname + '/', @@ -366,6 +373,7 @@ def db_update(archname, reponame, pkgs, force=False): with transaction.commit_on_success(): # no race condition here as long as simultaneous threads both # issue deletes; second delete will be a no-op + delete_pkg_files(dbpkg) dbpkg.delete() # packages in both database and in syncdb (update in database) diff --git a/devel/tests.py b/devel/tests.py index 36691179..01eed0fc 100644 --- a/devel/tests.py +++ b/devel/tests.py @@ -99,7 +99,7 @@ class FindUserTest(TestCase): def test_cache(self): # simply look two of them up, but then do it repeatedly - for i in range(50): + for _ in range(5): self.assertEqual(self.user1, self.finder.find("XXX YYY <user1@example.com>")) self.assertEqual(self.user3, diff --git a/devel/urls.py b/devel/urls.py index bc3bcace..07cb321b 100644 --- a/devel/urls.py +++ b/devel/urls.py @@ -7,8 +7,8 @@ urlpatterns = patterns('devel.views', (r'^$', 'index', {}, 'devel-index'), (r'^newuser/$', 'new_user_form'), (r'^profile/$', 'change_profile'), - (r'^reports/(?P<report>.*)/(?P<username>.*)/$', 'report'), - (r'^reports/(?P<report>.*)/$', 'report'), + (r'^reports/(?P<report_name>.*)/(?P<username>.*)/$', 'report'), + (r'^reports/(?P<report_name>.*)/$', 'report'), ) # vim: set ts=4 sw=4 et: diff --git a/devel/views.py b/devel/views.py index f7ed3539..dc2283ca 100644 --- a/devel/views.py +++ b/devel/views.py @@ -13,6 +13,7 @@ from django.template import loader, Context from django.template.defaultfilters import filesizeformat from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template +from django.utils.http import http_date from main.models import Package, PackageDepend, PackageFile, TodolistPkg from main.models import Arch, Repo @@ -27,9 +28,9 @@ import operator import pytz import random from string import ascii_letters, digits +import time @login_required -@never_cache def index(request): '''the developer dashboard''' if(request.user.is_authenticated()): @@ -80,7 +81,6 @@ def index(request): return direct_to_template(request, 'devel/index.html', page_dict) @login_required -@never_cache def clock(request): devs = User.objects.filter(is_active=True).order_by( 'first_name', 'last_name').select_related('userprofile') @@ -98,7 +98,14 @@ def clock(request): 'utc_now': utc_now, } - return direct_to_template(request, 'devel/clock.html', page_dict) + response = direct_to_template(request, 'devel/clock.html', page_dict) + if not response.has_header('Expires'): + # why this works only with the non-UTC date I have no idea... + expire_time = now.replace(minute=utc_now.minute + 1, + second=0, microsecond=0) + expire_time = time.mktime(expire_time.timetuple()) + response['Expires'] = http_date(expire_time) + return response class ProfileForm(forms.Form): email = forms.EmailField(label='Private email (not shown publicly):', @@ -145,7 +152,7 @@ def change_profile(request): {'form': form, 'profile_form': profile_form}) @login_required -def report(request, report, username=None): +def report(request, report_name, username=None): title = 'Developer Report' packages = Package.objects.normal() names = attrs = user = None @@ -159,17 +166,17 @@ def report(request, report, username=None): maints = User.objects.filter(id__in=PackageRelation.objects.filter( type=PackageRelation.MAINTAINER).values('user')) - if report == 'old': + if report_name == 'old': title = 'Packages last built more than two years ago' cutoff = datetime.utcnow() - timedelta(days=365 * 2) packages = packages.filter( build_date__lt=cutoff).order_by('build_date') - elif report == 'long-out-of-date': + elif report_name == 'long-out-of-date': title = 'Packages marked out-of-date more than 90 days ago' cutoff = datetime.utcnow() - timedelta(days=90) packages = packages.filter( flag_date__lt=cutoff).order_by('flag_date') - elif report == 'big': + elif report_name == 'big': title = 'Packages with compressed size > 50 MiB' cutoff = 50 * 1024 * 1024 packages = packages.filter( @@ -182,7 +189,7 @@ def report(request, report, username=None): package.compressed_size) package.installed_size_pretty = filesizeformat( package.installed_size) - elif report == 'badcompression': + elif report_name == 'badcompression': title = 'Packages that have little need for compression' cutoff = 0.90 * F('installed_size') packages = packages.filter(compressed_size__gt=0, installed_size__gt=0, @@ -199,7 +206,7 @@ def report(request, report, username=None): ratio = package.compressed_size / float(package.installed_size) package.ratio = '%.2f' % ratio package.compress_type = package.filename.split('.')[-1] - elif report == 'uncompressed-man': + elif report_name == 'uncompressed-man': title = 'Packages with uncompressed manpages' # checking for all '.0'...'.9' + '.n' extensions bad_files = PackageFile.objects.filter(directory__contains='/man/', @@ -209,7 +216,7 @@ def report(request, report, username=None): bad_files = bad_files.filter(pkg__in=pkg_ids) bad_files = bad_files.values_list('pkg_id', flat=True).distinct() packages = packages.filter(id__in=set(bad_files)) - elif report == 'uncompressed-info': + elif report_name == 'uncompressed-info': title = 'Packages with uncompressed infopages' # we don't worry about looking for '*.info-1', etc., given that an # uncompressed root page probably exists in the package anyway @@ -220,7 +227,7 @@ def report(request, report, username=None): bad_files = bad_files.filter(pkg__in=pkg_ids) bad_files = bad_files.values_list('pkg_id', flat=True).distinct() packages = packages.filter(id__in=set(bad_files)) - elif report == 'unneeded-orphans': + elif report_name == 'unneeded-orphans': title = 'Orphan packages required by no other packages' owned = PackageRelation.objects.all().values('pkgbase') required = PackageDepend.objects.all().values('depname') @@ -273,7 +280,7 @@ class NewUserForm(forms.ModelForm): def save(self, commit=True): profile = super(NewUserForm, self).save(False) pwletters = ascii_letters + digits - password = ''.join([random.choice(pwletters) for i in xrange(8)]) + password = ''.join([random.choice(pwletters) for _ in xrange(8)]) user = User.objects.create_user(username=self.cleaned_data['username'], email=self.cleaned_data['private_email'], password=password) user.first_name = self.cleaned_data['first_name'] @@ -342,7 +349,6 @@ def new_user_form(request): return direct_to_template(request, 'general_form.html', context) @user_passes_test(lambda u: u.is_superuser) -@never_cache def admin_log(request, username=None): user = None if username: diff --git a/main/middleware.py b/main/middleware.py deleted file mode 100644 index f417b545..00000000 --- a/main/middleware.py +++ /dev/null @@ -1,52 +0,0 @@ -# begin copy of stock Django UpdateCacheMiddleware -# this is to address feeds caching issue which makes it horribly -# unperformant - -from django.conf import settings -from django.core.cache import cache -from django.utils.cache import learn_cache_key, patch_response_headers, get_max_age - -class UpdateCacheMiddleware(object): - """ - Response-phase cache middleware that updates the cache if the response is - cacheable. - - Must be used as part of the two-part update/fetch cache middleware. - UpdateCacheMiddleware must be the first piece of middleware in - MIDDLEWARE_CLASSES so that it'll get called last during the response phase. - """ - def __init__(self): - self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS - self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX - self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) - - def process_response(self, request, response): - """Sets the cache, if needed.""" - if not hasattr(request, '_cache_update_cache') or not request._cache_update_cache: - # We don't need to update the cache, just return. - return response - if request.method != 'GET': - # This is a stronger requirement than above. It is needed - # because of interactions between this middleware and the - # HTTPMiddleware, which throws the body of a HEAD-request - # away before this middleware gets a chance to cache it. - return response - if not response.status_code == 200: - return response - # Try to get the timeout from the "max-age" section of the "Cache- - # Control" header before reverting to using the default cache_timeout - # length. - timeout = get_max_age(response) - if timeout == None: - timeout = self.cache_timeout - elif timeout == 0: - # max-age was set to 0, don't bother caching. - return response - patch_response_headers(response, timeout) - if timeout: - response.content = response.content - cache_key = learn_cache_key(request, response, timeout, self.key_prefix) - cache.set(cache_key, response, timeout) - return response - -# vim: set ts=4 sw=4 et: diff --git a/main/models.py b/main/models.py index cefebf76..d72f2c05 100644 --- a/main/models.py +++ b/main/models.py @@ -1,8 +1,6 @@ from django.db import models -from django.db.models.signals import pre_save from django.contrib.auth.models import User from django.contrib.sites.models import Site -from django.forms import ValidationError from .fields import PositiveBigIntegerField, PGPKeyField from .utils import cache_function, make_choice, set_created_field @@ -203,14 +201,14 @@ class Package(models.Model): def maintainers(self, maintainers): self._maintainers = maintainers - @cache_function(300) + @cache_function(1800) def applicable_arches(self): '''The list of (this arch) + (available agnostic arches).''' arches = set(Arch.objects.filter(agnostic=True)) arches.add(self.arch) return list(arches) - @cache_function(300) + @cache_function(119) def get_requiredby(self): """ Returns a list of package objects. An attempt will be made to keep this @@ -229,7 +227,7 @@ class Package(models.Model): pkg__arch__in=self.applicable_arches()) # sort out duplicate packages; this happens if something has a double # versioned dep such as a kernel module - requiredby = [list(vals)[0] for k, vals in + requiredby = [list(vals)[0] for _, vals in groupby(requiredby, lambda x: x.pkg.id)] # find another package by this name in the opposite testing setup @@ -244,7 +242,7 @@ class Package(models.Model): # for each unique package name, try to screen our package list down to # those packages in the same testing category (yes or no) iff there is # a package in the same testing category. - for name, dep_pkgs in groupby(requiredby, lambda x: x.pkg.pkgname): + for _, dep_pkgs in groupby(requiredby, lambda x: x.pkg.pkgname): dep_pkgs = list(dep_pkgs) dep = dep_pkgs[0] if len(dep_pkgs) > 1: @@ -256,7 +254,7 @@ class Package(models.Model): trimmed.append(dep) return trimmed - @cache_function(300) + @cache_function(121) def get_depends(self): """ Returns a list of dicts. Each dict contains ('dep', 'pkg', and @@ -281,7 +279,7 @@ class Package(models.Model): deps.append({'dep': dep, 'pkg': pkg, 'providers': providers}) return deps - @cache_function(300) + @cache_function(125) def base_package(self): """ Locate the base package for this package. It may be this very package, diff --git a/mirrors/templatetags/mirror_status.py b/mirrors/templatetags/mirror_status.py index 0031d83b..9a363fbe 100644 --- a/mirrors/templatetags/mirror_status.py +++ b/mirrors/templatetags/mirror_status.py @@ -10,7 +10,7 @@ def duration(value): return u'' # does not take microseconds into account total_secs = value.seconds + value.days * 24 * 3600 - mins, secs = divmod(total_secs, 60) + mins = total_secs // 60 hrs, mins = divmod(mins, 60) return '%d:%02d' % (hrs, mins) @@ -20,7 +20,7 @@ def hours(value): return u'' # does not take microseconds into account total_secs = value.seconds + value.days * 24 * 3600 - mins, secs = divmod(total_secs, 60) + mins = total_secs // 60 hrs, mins = divmod(mins, 60) if hrs == 1: return '%d hour' % hrs diff --git a/mirrors/utils.py b/mirrors/utils.py index 8518b3ba..f05ffc77 100644 --- a/mirrors/utils.py +++ b/mirrors/utils.py @@ -26,7 +26,7 @@ def annotate_url(url, delays): url.delay = None url.score = None -@cache_function(300) +@cache_function(123) def get_mirror_statuses(cutoff=default_cutoff): cutoff_time = datetime.datetime.utcnow() - cutoff protocols = list(MirrorProtocol.objects.filter(is_download=True)) @@ -40,8 +40,7 @@ def get_mirror_statuses(cutoff=default_cutoff): last_sync=Max('logs__last_sync'), last_check=Max('logs__check_time'), duration_avg=Avg('logs__duration'), - #duration_stddev=StdDev('logs__duration') - duration_stddev=Max('logs__duration') + duration_stddev=StdDev('logs__duration') ).order_by('-last_sync', '-duration_avg') # The Django ORM makes it really hard to get actual average delay in the @@ -81,7 +80,7 @@ def get_mirror_statuses(cutoff=default_cutoff): 'urls': urls, } -@cache_function(300) +@cache_function(117) def get_mirror_errors(cutoff=default_cutoff): cutoff_time = datetime.datetime.utcnow() - cutoff errors = MirrorLog.objects.filter( diff --git a/packages/admin.py b/packages/admin.py index 14fa8960..4c170247 100644 --- a/packages/admin.py +++ b/packages/admin.py @@ -1,21 +1,43 @@ from django.contrib import admin -from .models import PackageRelation, FlagRequest +from .models import PackageRelation, FlagRequest, Signoff, SignoffSpecification class PackageRelationAdmin(admin.ModelAdmin): - list_display = ('user', 'pkgbase', 'type', 'created') + list_display = ('pkgbase', 'user', 'type', 'created') list_filter = ('type', 'user') - search_fields = ('user__username', 'pkgbase') + search_fields = ('pkgbase', 'user__username') + ordering = ('pkgbase', 'user') date_hierarchy = 'created' class FlagRequestAdmin(admin.ModelAdmin): - list_display = ('pkgbase', 'created', 'who', 'is_spam', 'is_legitimate', - 'message') + list_display = ('pkgbase', 'version', 'created', 'who', 'is_spam', + 'is_legitimate', 'message') list_filter = ('is_spam', 'is_legitimate') search_fields = ('pkgbase', 'user_email', 'message') + ordering = ('-created',) date_hierarchy = 'created' + +class SignoffAdmin(admin.ModelAdmin): + list_display = ('pkgbase', 'full_version', 'arch', 'repo', + 'user', 'created', 'revoked') + list_filter = ('arch', 'repo', 'user') + search_fields = ('pkgbase', 'user__username') + ordering = ('-created',) + date_hierarchy = 'created' + +class SignoffSpecificationAdmin(admin.ModelAdmin): + list_display = ('pkgbase', 'full_version', 'arch', 'repo', + 'user', 'created', 'comments') + list_filter = ('arch', 'repo', 'user') + search_fields = ('pkgbase', 'user__username') + ordering = ('-created',) + date_hierarchy = 'created' + + admin.site.register(PackageRelation, PackageRelationAdmin) admin.site.register(FlagRequest, FlagRequestAdmin) +admin.site.register(Signoff, SignoffAdmin) +admin.site.register(SignoffSpecification, SignoffSpecificationAdmin) # vim: set ts=4 sw=4 et: diff --git a/packages/management/commands/populate_signoffs.py b/packages/management/commands/populate_signoffs.py index 42496e9d..97ba4146 100644 --- a/packages/management/commands/populate_signoffs.py +++ b/packages/management/commands/populate_signoffs.py @@ -15,7 +15,6 @@ import sys from xml.etree.ElementTree import XML from django.conf import settings -from django.contrib.auth.models import User from django.core.management.base import NoArgsCommand from ...models import SignoffSpecification diff --git a/packages/migrations/0013_auto__add_field_flagrequest_version.py b/packages/migrations/0013_auto__add_field_flagrequest_version.py new file mode 100644 index 00000000..ab33d5b3 --- /dev/null +++ b/packages/migrations/0013_auto__add_field_flagrequest_version.py @@ -0,0 +1,180 @@ +# encoding: 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('packages_flagrequest', 'version', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False) + + def backwards(self, orm): + db.delete_column('packages_flagrequest', 'version') + + 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'", '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'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': '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'", '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': '0'}), + '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'}) + }, + 'packages.conflict': { + 'Meta': {'ordering': "['name']", 'object_name': 'Conflict'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conflicts'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.license': { + 'Meta': {'ordering': "['name']", 'object_name': 'License'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'licenses'", 'to': "orm['main.Package']"}) + }, + 'packages.packagegroup': { + 'Meta': {'object_name': 'PackageGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Package']"}) + }, + 'packages.packagerelation': { + 'Meta': {'unique_together': "(('pkgbase', 'user', 'type'),)", 'object_name': 'PackageRelation'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_relations'", 'to': "orm['auth.User']"}) + }, + 'packages.provision': { + 'Meta': {'ordering': "['name']", 'object_name': 'Provision'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provides'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.replacement': { + 'Meta': {'ordering': "['name']", 'object_name': 'Replacement'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'replaces'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.signoff': { + 'Meta': {'object_name': 'Signoff'}, + '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', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + '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', [], {'to': "orm['main.Repo']"}), + 'revoked': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_signoffs'", 'to': "orm['auth.User']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + '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', [], {'to': "orm['main.Repo']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 77cade68..f63d6db0 100644 --- a/packages/models.py +++ b/packages/models.py @@ -1,7 +1,7 @@ from collections import namedtuple from django.db import models -from django.db.models.signals import pre_save, post_save +from django.db.models.signals import pre_save from django.contrib.auth.models import User from main.models import Arch, Repo @@ -167,11 +167,15 @@ class Signoff(models.Model): class FlagRequest(models.Model): + ''' + A notification the package is out-of-date submitted through the web site. + ''' user = models.ForeignKey(User, blank=True, null=True) user_email = models.EmailField('email address') created = models.DateTimeField(editable=False) ip_address = models.IPAddressField('IP address') pkgbase = models.CharField(max_length=255, db_index=True) + version = models.CharField(max_length=255, default='') repo = models.ForeignKey(Repo) num_packages = models.PositiveIntegerField('number of packages', default=1) message = models.TextField('message to developer', blank=True) diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index a8a8bd0f..d0c163ea 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -27,6 +27,7 @@ def url_unquote(original_url): class BuildQueryStringNode(template.Node): def __init__(self, sortfield): self.sortfield = sortfield + super(BuildQueryStringNode, self).__init__() def render(self, context): qs = parse_qs(context['current_query']) @@ -53,8 +54,8 @@ def do_buildsortqs(parser, token): @register.simple_tag def pkg_details_link(pkg): - template = '<a href="%s" title="View package details for %s">%s</a>' - return template % (pkg.get_absolute_url(), pkg.pkgname, pkg.pkgname) + link = '<a href="%s" title="View package details for %s">%s</a>' + return link % (pkg.get_absolute_url(), pkg.pkgname, pkg.pkgname) @register.simple_tag def multi_pkg_details(pkgs): diff --git a/packages/utils.py b/packages/utils.py index 5d0d0250..ab3c074f 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -11,7 +11,7 @@ from main.utils import cache_function, groupby_preserve_order, PackageStandin from .models import (PackageGroup, PackageRelation, SignoffSpecification, Signoff, DEFAULT_SIGNOFF_SPEC) -@cache_function(300) +@cache_function(127) def get_group_info(include_arches=None): raw_groups = PackageGroup.objects.values_list( 'name', 'pkg__arch__name').order_by('name').annotate( @@ -92,7 +92,7 @@ class Difference(object): return False -@cache_function(300) +@cache_function(127) def get_differences_info(arch_a, arch_b): # This is a monster. Join packages against itself, looking for packages in # our non-'any' architectures only, and not having a corresponding package @@ -381,7 +381,7 @@ SELECT DISTINCT p1.pkgbase, r.name AND r.testing = %s AND p2.repo_id IN ( """ - sql += ','.join(['%s' for r in repos]) + sql += ','.join(['%s' for _ in repos]) sql += ")" params = [False, False] diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 573e0c72..e66da179 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -1,10 +1,10 @@ from django.contrib import messages from django.contrib.auth.decorators import permission_required +from django.contrib.auth.models import User from django.core.serializers.json import DjangoJSONEncoder from django.http import HttpResponse, Http404 from django.shortcuts import get_object_or_404, redirect from django.utils import simplejson -from django.views.decorators.cache import never_cache from django.views.decorators.http import require_POST from django.views.decorators.vary import vary_on_headers from django.views.generic.simple import direct_to_template @@ -27,7 +27,8 @@ from .signoff import signoffs, signoff_package, signoff_options, signoffs_json class PackageJSONEncoder(DjangoJSONEncoder): pkg_attributes = [ 'pkgname', 'pkgbase', 'repo', 'arch', 'pkgver', 'pkgrel', 'epoch', 'pkgdesc', 'url', 'filename', 'compressed_size', - 'installed_size', 'build_date', 'last_update', 'flag_date' ] + 'installed_size', 'build_date', 'last_update', 'flag_date', + 'maintainers', 'packager' ] def default(self, obj): if hasattr(obj, '__iter__'): @@ -43,6 +44,8 @@ class PackageJSONEncoder(DjangoJSONEncoder): return obj.directory + filename if isinstance(obj, (Repo, Arch, PackageGroup)): return obj.name.lower() + elif isinstance(obj, User): + return obj.username return super(PackageJSONEncoder, self).default(obj) def opensearch(request): @@ -244,7 +247,6 @@ def arch_differences(request): return direct_to_template(request, 'packages/differences.html', context) @permission_required('main.change_package') -@never_cache def stale_relations(request): relations = PackageRelation.objects.select_related('user') pkgbases = Package.objects.all().values('pkgbase') diff --git a/packages/views/flag.py b/packages/views/flag.py index 2f5c9933..f74a718a 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -4,6 +4,7 @@ from django import forms from django.conf import settings from django.contrib.auth.decorators import permission_required from django.core.mail import send_mail +from django.db import transaction from django.shortcuts import get_object_or_404, redirect from django.template import loader, Context from django.views.generic.simple import direct_to_template @@ -46,17 +47,31 @@ def flag(request, name, repo, arch): if form.is_valid() and form.cleaned_data['website'] == '': # save the package list for later use flagged_pkgs = list(pkgs) - pkgs.update(flag_date=datetime.utcnow()) - - # store our flag request - flag_request = FlagRequest(user_email=form.cleaned_data['email'], - ip_address=request.META.get('REMOTE_ADDR', '127.0.0.1'), - pkgbase=pkg.pkgbase, repo=pkg.repo, - num_packages=len(flagged_pkgs), - message=form.cleaned_data['message']) - if request.user.is_authenticated(): - flag_request.user = request.user - flag_request.save() + + # find a common version if there is one available to store + versions = set(pkg.full_version for pkg in flagged_pkgs) + if len(versions) == 1: + version = versions.pop() + else: + version = '' + + email = form.cleaned_data['email'] + message = form.cleaned_data['message'] + ip_addr = request.META.get('REMOTE_ADDR') + + @transaction.commit_on_success + def perform_updates(): + pkgs.update(flag_date=datetime.utcnow()) + # store our flag request + flag_request = FlagRequest(user_email=email, message=message, + ip_address=ip_addr, pkgbase=pkg.pkgbase, + version=version, repo=pkg.repo, + num_packages=len(flagged_pkgs)) + if request.user.is_authenticated(): + flag_request.user = request.user + flag_request.save() + + perform_updates() maints = pkg.maintainers if not maints: @@ -75,8 +90,8 @@ def flag(request, name, repo, arch): # send notification email to the maintainers tmpl = loader.get_template('packages/outofdate.txt') ctx = Context({ - 'email': form.cleaned_data['email'], - 'message': form.cleaned_data['message'], + 'email': email, + 'message': message, 'pkg': pkg, 'packages': flagged_pkgs, }) diff --git a/packages/views/signoff.py b/packages/views/signoff.py index e57b4d9a..e3daf0dd 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -18,7 +18,6 @@ from ..utils import (get_signoff_groups, approved_by_signoffs, PackageSignoffGroup) @permission_required('main.change_package') -@never_cache def signoffs(request): signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase')) for group in signoff_groups: @@ -178,7 +177,6 @@ class SignoffJSONEncoder(DjangoJSONEncoder): return super(SignoffJSONEncoder, self).default(obj) @permission_required('main.change_package') -@never_cache def signoffs_json(request): signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase')) data = { diff --git a/public/utils.py b/public/utils.py index 6566b8c4..a40c9b53 100644 --- a/public/utils.py +++ b/public/utils.py @@ -49,7 +49,7 @@ class RecentUpdate(object): if package.arch not in arches and not arches.add(package.arch): yield PackageStandin(package) -@cache_function(300) +@cache_function(62) def get_recent_updates(number=15): # This is a bit of magic. We are going to show 15 on the front page, but we # want to try and eliminate cross-architecture wasted space. Pull enough diff --git a/public/views.py b/public/views.py index af46e343..493c45f6 100644 --- a/public/views.py +++ b/public/views.py @@ -2,6 +2,7 @@ from django.conf import settings from django.contrib.auth.models import User from django.http import Http404 from django.shortcuts import redirect +from django.views.decorators.cache import cache_control from django.views.generic import list_detail from django.views.generic.simple import direct_to_template @@ -9,8 +10,9 @@ from devel.models import MasterKey from main.models import Arch, Repo, Donor from mirrors.models import MirrorUrl from news.models import News -from utils import get_recent_updates +from .utils import get_recent_updates +@cache_control(max_age=300) def index(request): pkgs = get_recent_updates() context = { @@ -30,6 +32,7 @@ USER_LISTS = { }, } +@cache_control(max_age=300) def userlist(request, user_type='hackers'): users = User.objects.order_by( 'username').select_related('userprofile') @@ -45,15 +48,18 @@ def userlist(request, user_type='hackers'): context['users'] = users return direct_to_template(request, 'public/userlist.html', context) +@cache_control(max_age=300) def donate(request): context = { 'donors': Donor.objects.filter(visible=True).order_by('name'), } return direct_to_template(request, 'public/donate.html', context) +@cache_control(max_age=300) def download(request): return redirect('//wiki.parabolagnulinux.org/get', permanent=True) +@cache_control(max_age=300) def feeds(request): context = { 'arches': Arch.objects.all(), @@ -61,6 +67,7 @@ def feeds(request): } return direct_to_template(request, 'public/feeds.html', context) +@cache_control(max_age=300) def keys(request): context = { 'keys': MasterKey.objects.select_related('owner', 'revoker', diff --git a/settings.py b/settings.py index f7524d93..6c949a86 100644 --- a/settings.py +++ b/settings.py @@ -67,7 +67,6 @@ TEMPLATE_LOADERS = ( # This bug is a real bummer: # http://code.djangoproject.com/ticket/14105 MIDDLEWARE_CLASSES = ( - 'main.middleware.UpdateCacheMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', @@ -75,7 +74,6 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.http.ConditionalGetMiddleware', 'django.middleware.doc.XViewMiddleware', - 'django.middleware.cache.FetchFromCacheMiddleware', ) ROOT_URLCONF = 'urls' diff --git a/sitestatic/archweb.js b/sitestatic/archweb.js index 7b8230a4..f5a682d6 100644 --- a/sitestatic/archweb.js +++ b/sitestatic/archweb.js @@ -143,6 +143,26 @@ if (typeof $.tablesorter !== 'undefined') { }); } +(function($) { + $.fn.enableCheckboxRangeSelection = function() { + var lastCheckbox = null; + var lastElement = null; + + var spec = this; + spec.unbind("click.checkboxrange"); + spec.bind("click.checkboxrange", function(e) { + if (lastCheckbox != null && e.shiftKey) { + spec.slice( + Math.min(spec.index(lastCheckbox), spec.index(e.target)), + Math.max(spec.index(lastCheckbox), spec.index(e.target)) + 1 + ).attr({checked: e.target.checked ? "checked" : ""}); + } + lastCheckbox = e.target; + }); + + }; +})(jQuery); + /* news/add.html */ function enablePreview() { $('#news-preview-button').click(function(event) { diff --git a/templates/packages/stale_relations.html b/templates/packages/stale_relations.html index 3e635f56..4e814450 100644 --- a/templates/packages/stale_relations.html +++ b/templates/packages/stale_relations.html @@ -23,7 +23,7 @@ <tbody> {% for relation in inactive_user %} <tr class="{% cycle 'odd' 'even' %}"> - <td><input type="checkbox" name="relation_id" value="{{ relation.id }}" /></td> + <td><input type="checkbox" class="relation-checkbox" name="relation_id" value="{{ relation.id }}" /></td> <td>{{ relation.pkgbase }}</td> <td class="wrap">{% for pkg in relation.get_associated_packages %} <a href="{{ pkg.get_absolute_url }}" @@ -54,7 +54,7 @@ <tbody> {% for relation in missing_pkgbase %} <tr class="{% cycle 'odd' 'even' %}"> - <td><input type="checkbox" name="relation_id" value="{{ relation.id }}" /></td> + <td><input type="checkbox" class="relation-checkbox" name="relation_id" value="{{ relation.id }}" /></td> <td>{{ relation.pkgbase }}</td> <td>{{ relation.user.get_full_name }}</td> <td>{{ relation.get_type_display }}</td> @@ -83,7 +83,7 @@ <tbody> {% for relation in wrong_permissions %} <tr class="{% cycle 'odd' 'even' %}"> - <td><input type="checkbox" name="relation_id" value="{{ relation.id }}" /></td> + <td><input type="checkbox" class="relation-checkbox" name="relation_id" value="{{ relation.id }}" /></td> <td>{{ relation.pkgbase }}</td> <td class="wrap">{% for pkg in relation.get_associated_packages %} <a href="{{ pkg.get_absolute_url }}" @@ -114,5 +114,9 @@ $(document).ready(function() { $('#missing-pkgbase:not(:has(tbody tr.empty))').tablesorter({widgets: ['zebra'], headers: { 0: { sorter: false } }, sortList: [[1,0]]}); }); $('#wrong-permissions:not(:has(tbody tr.empty))').tablesorter({widgets: ['zebra'], headers: { 0: { sorter: false } }, sortList: [[3,0]]}); + $('table.results').bind('sortEnd', function() { + $('input.relation-checkbox').enableCheckboxRangeSelection(); + }); + $('input.relation-checkbox').enableCheckboxRangeSelection(); </script> {% endblock %} diff --git a/templates/public/index.html b/templates/public/index.html index 81c03090..971b5298 100644 --- a/templates/public/index.html +++ b/templates/public/index.html @@ -152,10 +152,10 @@ <ul> <li><a href="{% url page-keys %}" - title="Package/Database signing master keys">Master Keys</a></li> - <li><a href="/packages/" - title="View/search the package repository database">Packages</a> + title="Package/Database signing master keys">Master Keys</a> <img src="{% cdnprefix %}/media/new.png" alt="New"/></li> + <li><a href="/packages/" + title="View/search the package repository database">Packages</a></li> <li><a href="/groups/" title="View the available package groups">Package Groups</a></li> <li><a href="{% url visualize-index %}" diff --git a/templates/todolists/view.html b/templates/todolists/view.html index 14db4357..612f2902 100644 --- a/templates/todolists/view.html +++ b/templates/todolists/view.html @@ -23,6 +23,11 @@ <div>{{list.description|urlize|linebreaks}}</div> + <p>Link to lists of pkgbase values:</p> + <ul>{% for svn_root in svn_roots %} + <li><a href="pkgbases/{{ svn_root }}/">{{ svn_root }}</a></li> + {% endfor %}</ul> + <table id="dev-todo-pkglist" class="results todo-table"> <thead> <tr> diff --git a/todolists/urls.py b/todolists/urls.py index 2612a52e..0bd8817b 100644 --- a/todolists/urls.py +++ b/todolists/urls.py @@ -5,7 +5,8 @@ from .views import DeleteTodolist urlpatterns = patterns('todolists.views', (r'^$', 'todolist_list'), - (r'^(\d+)/$', 'view'), + (r'^(?P<list_id>\d+)/$', 'view'), + (r'^(?P<list_id>\d+)/pkgbases/(?P<svn_root>[a-z]+)/$', 'list_pkgbases'), (r'^add/$', 'add'), (r'^edit/(?P<list_id>\d+)/$', 'edit'), (r'^flag/(\d+)/(\d+)/$', 'flag'), diff --git a/todolists/views.py b/todolists/views.py index 233102cf..4903ec54 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -2,7 +2,7 @@ from django import forms from django.http import HttpResponse from django.core.mail import send_mail -from django.shortcuts import get_object_or_404, redirect +from django.shortcuts import get_list_or_404, get_object_or_404, redirect from django.contrib.auth.decorators import login_required, permission_required from django.db import transaction from django.views.decorators.cache import never_cache @@ -11,7 +11,7 @@ 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 +from main.models import Todolist, TodolistPkg, Package, Repo from packages.utils import attach_maintainers from .utils import get_annotated_todolists @@ -35,9 +35,9 @@ class TodoListForm(forms.ModelForm): @permission_required('main.change_todolistpkg') @never_cache -def flag(request, listid, pkgid): - todolist = get_object_or_404(Todolist, id=listid) - pkg = get_object_or_404(TodolistPkg, id=pkgid) +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() if request.is_ajax(): @@ -47,16 +47,29 @@ def flag(request, listid, pkgid): return redirect(todolist) @login_required -@never_cache -def view(request, listid): - todolist = get_object_or_404(Todolist, id=listid) +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() # 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', {'list': todolist}) + return direct_to_template(request, 'todolists/view.html', { + 'list': todolist, + 'svn_roots': svn_roots, + }) + +# 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) + 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) + return HttpResponse('\n'.join(sorted(pkgbases)), + mimetype='text/plain') @login_required -@never_cache def todolist_list(request): lists = get_annotated_todolists() return direct_to_template(request, 'todolists/list.html', {'lists': lists}) @@ -27,12 +27,12 @@ urlpatterns = [] # Feeds patterns, used later feeds_patterns = patterns('', (r'^$', 'public.views.feeds', {}, 'feeds-list'), - (r'^news/$', NewsFeed()), - (r'^packages/$', PackageFeed()), + (r'^news/$', cache_page(300)(NewsFeed())), + (r'^packages/$', cache_page(300)(PackageFeed())), (r'^packages/(?P<arch>[A-z0-9]+)/$', - PackageFeed()), + cache_page(300)(PackageFeed())), (r'^packages/(?P<arch>[A-z0-9]+)/(?P<repo>[A-z0-9\-]+)/$', - PackageFeed()), + cache_page(300)(PackageFeed())), ) # Sitemaps |