From 0c6ee81eb469734dae380f428149f316fdbf75bd Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 7 Mar 2011 21:06:04 -0600 Subject: Work around unicode/str issue in pytz Signed-off-by: Dan McGee --- devel/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devel/views.py b/devel/views.py index 311922ca..c926c5a4 100644 --- a/devel/views.py +++ b/devel/views.py @@ -55,7 +55,9 @@ def clock(request): now = datetime.datetime.now() utc_now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) for dev in devs: - tz = pytz.timezone(dev.userprofile.time_zone) + # Work around https://bugs.launchpad.net/pytz/+bug/718673 + timezone = str(dev.userprofile.time_zone) + tz = pytz.timezone(timezone) dev.current_time = utc_now.astimezone(tz) page_dict = { -- cgit v1.2.3-2-g168b From e82a4d5b9c17fbc5ee63d14a1c87ef12718fa343 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 7 Mar 2011 21:23:42 -0600 Subject: Todo list styling and info cleanups Signed-off-by: Dan McGee --- media/archweb.css | 9 +++------ templates/todolists/list.html | 2 +- templates/todolists/public_list.html | 11 ++++++----- templates/todolists/view.html | 4 +++- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/media/archweb.css b/media/archweb.css index 47abba7b..aec78f87 100644 --- a/media/archweb.css +++ b/media/archweb.css @@ -248,18 +248,15 @@ table.dash-stats .key { width: 50%; } span.dash-click { font-weight: normal; font-size: 0.8em; color: #888; } div.dash-stats h3 { color: #07b; } -/* read only (public) todo lists */ -#public_todo_lists .todo_list { - margin-left: 2em; -} - /* dev dashboard: admin actions (add news items, todo list, etc) */ ul.admin-actions { float: right; list-style: none; margin-top: -2.5em; } ul.admin-actions li { display: inline; padding-left: 1.5em; } -/* dev: todo list */ +/* todo lists (public and private) */ .todo-table .complete { color: green; } .todo-table .incomplete { color: red; } +.todo-info { margin: 0; color: #999; } +.todo-list h4 { margin-top: 0; margin-bottom: 0.4em; } /* dev: signoff page */ #dev-signoffs ul { list-style: none; margin: 0; padding: 0; } diff --git a/templates/todolists/list.html b/templates/todolists/list.html index 2e75bdac..78acabfb 100644 --- a/templates/todolists/list.html +++ b/templates/todolists/list.html @@ -32,7 +32,7 @@ title="View todo list: {{ list.name }}">{{ list.name }} {{ list.date_added }} {{ list.creator.get_full_name }} - {{ list.description|safe }} + {{ list.description|urlize }} {{ list.pkg_count }} {{ list.incomplete_count }} {% ifequal list.incomplete_count 0 %}Complete diff --git a/templates/todolists/public_list.html b/templates/todolists/public_list.html index d84a548f..0d14250d 100644 --- a/templates/todolists/public_list.html +++ b/templates/todolists/public_list.html @@ -22,13 +22,14 @@ {% if todo_lists %} -
+
{% for list in todo_lists %}
- -

{{ list.name }}

-
-
{{ list.description|safe|linebreaks }}
+
+ +

{{ list.name }}

+

{{ list.date_added|date }} - {{ list.creator.get_full_name }}

+
{{ list.description|urlize|linebreaks }}
diff --git a/templates/todolists/view.html b/templates/todolists/view.html index 36ecb9bf..9a6e3c0f 100644 --- a/templates/todolists/view.html +++ b/templates/todolists/view.html @@ -17,7 +17,9 @@ {% endif %} -

{{list.description|safe|linebreaks}}

+

{{ list.date_added|date }} - {{ list.creator.get_full_name }}

+ +
{{list.description|urlize|linebreaks}}
-- cgit v1.2.3-2-g168b From 6a429a8d898d5f0789f5b5a3f2858c6578fa5227 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 7 Mar 2011 21:50:13 -0600 Subject: Clean up current_query and preserve multiple args When implementing search for multiple architectures or repositories, I didn't update this method to accomidate the new query parameters. Clean it up a bit by not appending/stripping the leading '?' anywhere but in the template itself, and ensure we can handle multiple of any parameter passed in. Fixes FS#23180. Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 16 +++++++++------- packages/views.py | 10 +++++----- templates/packages/search.html | 18 +++++++++--------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index d59a5562..24264af0 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -1,4 +1,6 @@ -import cgi, urllib +import urllib +import urlparse + from django import template from django.utils.html import escape @@ -9,15 +11,15 @@ class BuildQueryStringNode(template.Node): self.sortfield = sortfield def render(self, context): - qs = dict(cgi.parse_qsl(context['current_query'][1:])) - if qs.has_key('sort') and qs['sort'] == self.sortfield: + qs = urlparse.parse_qs(context['current_query']) + if qs.has_key('sort') and self.sortfield in qs['sort']: if self.sortfield.startswith('-'): - qs['sort'] = self.sortfield[1:] + qs['sort'] = [self.sortfield[1:]] else: - qs['sort'] = '-' + self.sortfield + qs['sort'] = ['-' + self.sortfield] else: - qs['sort'] = self.sortfield - return '?' + urllib.urlencode(qs) + qs['sort'] = [self.sortfield] + return urllib.urlencode(qs, True) @register.tag(name='buildsortqs') def do_buildsortqs(parser, token): diff --git a/packages/views.py b/packages/views.py index 59779fe4..3cef8226 100644 --- a/packages/views.py +++ b/packages/views.py @@ -168,12 +168,10 @@ class PackageSearchForm(forms.Form): [(m.username, m.get_full_name()) for m in maints] def search(request, page=None): - current_query = '?' limit = 50 packages = Package.objects.select_related('arch', 'repo') if request.GET: - current_query += request.GET.urlencode() form = PackageSearchForm(data=request.GET) if form.is_valid(): if form.cleaned_data['repo']: @@ -208,12 +206,14 @@ def search(request, page=None): else: form = PackageSearchForm() - page_dict = {'search_form': form, - 'current_query': current_query - } if packages.count() == 1: return redirect(packages[0]) + current_query = request.GET.urlencode() + page_dict = { + 'search_form': form, + 'current_query': current_query + } allowed_sort = ["arch", "repo", "pkgname", "last_update", "flag_date"] allowed_sort += ["-" + s for s in allowed_sort] sort = request.GET.get('sort', None) diff --git a/templates/packages/search.html b/templates/packages/search.html index dad05ffa..5d0de1f4 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -50,13 +50,13 @@

{% if page_obj.has_previous %} - {% else %} < Prev {% endif %} {% if page_obj.has_next %} - {% else %} Next > @@ -74,17 +74,17 @@ {% if perms.main.change_package %}

{% endif %} - - - - - @@ -118,13 +118,13 @@

{% if page_obj.has_previous %} - {% else %} < Prev {% endif %} {% if page_obj.has_next %} - {% else %} Next > -- cgit v1.2.3-2-g168b From 3e73b5d7d291b20858b18ba7492b7f92501e01c7 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 10 Mar 2011 09:34:24 -0600 Subject: Make it very easy to use the Django debug toolbar Add a config option DEBUG_TOOLBAR that defaults to False. If set to True in local_settings, add the relevant application and middleware to the settings to enable it. Signed-off-by: Dan McGee --- settings.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/settings.py b/settings.py index 8e916e36..9565e6c4 100644 --- a/settings.py +++ b/settings.py @@ -4,6 +4,7 @@ import os ## Set the debug values DEBUG = False TEMPLATE_DEBUG = DEBUG +DEBUG_TOOLBAR = False ## Notification admins ADMINS = () @@ -121,4 +122,12 @@ if not TEMPLATE_DEBUG: ('django.template.loaders.cached.Loader', TEMPLATE_LOADERS), ) +# Enable the debug toolbar if requested +if DEBUG_TOOLBAR: + MIDDLEWARE_CLASSES = \ + [ 'debug_toolbar.middleware.DebugToolbarMiddleware' ] + \ + list(MIDDLEWARE_CLASSES) + + INSTALLED_APPS = list(INSTALLED_APPS) + [ 'debug_toolbar' ] + # vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From 1dc867587da6b66ca575eb26f4f65cb9d67ffdb3 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 4 Jan 2011 08:45:15 -0600 Subject: Add Admin log overview page This puts the admin log functionality to a bit more use and allows seeing the last 100 overall entries. You can also drill down to see actions on a per-user basis. Signed-off-by: Dan McGee --- devel/urls.py | 2 ++ devel/views.py | 16 ++++++++++- templates/admin/index.html | 8 ++++++ templates/devel/admin_log.html | 62 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 templates/devel/admin_log.html diff --git a/devel/urls.py b/devel/urls.py index bcf9c071..41be2b31 100644 --- a/devel/urls.py +++ b/devel/urls.py @@ -5,6 +5,8 @@ urlpatterns = patterns('devel.views', (r'^clock/$', 'clock'), (r'^profile/$', 'change_profile'), (r'^newuser/$', 'new_user_form'), + (r'^admin_log/(?P.*)/$','admin_log'), + (r'^admin_log/$','admin_log'), ) # vim: set ts=4 sw=4 et: diff --git a/devel/views.py b/devel/views.py index c926c5a4..46387f7a 100644 --- a/devel/views.py +++ b/devel/views.py @@ -1,9 +1,11 @@ from django import forms from django.http import HttpResponseRedirect -from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.decorators import \ + login_required, permission_required, user_passes_test from django.contrib.auth.models import User from django.contrib.sites.models import Site from django.core.mail import send_mail +from django.shortcuts import get_object_or_404 from django.template import loader, Context from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template @@ -169,4 +171,16 @@ 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: + user = get_object_or_404(User, username=username) + context = { + 'title': "Admin Action Log", + 'log_user': user, + } + return direct_to_template(request, 'devel/admin_log.html', context) + # vim: set ts=4 sw=4 et: diff --git a/templates/admin/index.html b/templates/admin/index.html index 6f7f98ee..93cf258d 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -14,10 +14,18 @@

 ArchRepoName Version DescriptionLast UpdatedFlag Date
+ {% if perms.auth.add_user %} + {% endif %} + {% if user.is_superuser %} + + + + + {% endif %}
Custom Admin Pages
Create New User
Admin Actions Log
diff --git a/templates/devel/admin_log.html b/templates/devel/admin_log.html new file mode 100644 index 00000000..2de15bad --- /dev/null +++ b/templates/devel/admin_log.html @@ -0,0 +1,62 @@ +{% extends "admin/base_site.html" %} +{% load i18n %} + +{% block extrastyle %}{{ block.super }}{% endblock %} + +{% block breadcrumbs %}{% endblock %} + +{% block content %} +
+
+{% load log %} +{% if log_user %} +{% get_admin_log 100 as admin_log for_user log_user %} +{% else %} +{% get_admin_log 100 as admin_log %} +{% endif %} +{% if not admin_log %} +

{% trans 'None available' %}

+{% else %} + + + + + + + + + + + + {% for entry in admin_log %} + + + {% if log_user %} + + {% else %} + + {% endif %} + + + + + {% endfor %} + +
{% trans 'Date/time' %}{% trans 'User' %}TypeObject{% trans 'Action' %}
{{ entry.action_time|date:"DATETIME_FORMAT" }}{{ entry.user.username }}{% if entry.user.get_full_name %} ({{ entry.user.get_full_name }}){% endif %}{{ entry.user.username }}{% if entry.user.get_full_name %} ({{ entry.user.get_full_name }}){% endif %} + {% if entry.content_type %} + {% filter capfirst %}{% trans entry.content_type.name %}{% endfilter %} + {% else %} + {% trans 'Unknown content' %} + {% endif %} + + + {% if entry.is_deletion %} + {{ entry.object_repr }} + {% else %} + {{ entry.object_repr }} + {% endif %} + {{ entry.change_message }}
+{% endif %} +
+
+{% endblock %} -- cgit v1.2.3-2-g168b From 9730be60a8ef4a04358b0a026ce6b706de21d4e8 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 13 Mar 2011 11:29:05 -0500 Subject: Add package epoch support This comes with pacman 3.5, replacing the old "force" PKGBUILD option. We parse it and store it for now, but don't display it anywhere just yet. Also update a few queries relying on version differences in any of the multiple parts. Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 24 ++- .../0043_auto__add_field_package_epoch.py | 162 +++++++++++++++++++++ main/models.py | 4 +- packages/utils.py | 2 + 4 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 main/migrations/0043_auto__add_field_package_epoch.py diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 09e48559..f826abea 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -88,11 +88,13 @@ class Pkg(object): bare = ( 'name', 'base', 'arch', 'desc', 'filename', 'md5sum', 'url', 'builddate', 'packager' ) number = ( 'csize', 'isize' ) + version_re = re.compile(r'^((\d+):)?(.+)-([^-]+)$') def __init__(self, repo): self.repo = repo self.ver = None self.rel = None + self.epoch = 0 for k in self.bare + self.number: setattr(self, k, None) @@ -106,9 +108,11 @@ class Pkg(object): elif k == 'force': setattr(self, k, True) elif k == 'version': - ver, rel = v[0].rsplit('-') - setattr(self, 'ver', ver) - setattr(self, 'rel', rel) + match = self.version_re.match(v[0]) + self.ver = match.group(3) + self.rel = match.group(4) + if match.group(2): + self.epoch = int(match.group(2)) else: # files, depends, etc. setattr(self, k, v) @@ -184,6 +188,7 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): dbpkg.pkgbase = repopkg.name dbpkg.pkgver = repopkg.ver dbpkg.pkgrel = repopkg.rel + dbpkg.epoch = repopkg.epoch dbpkg.pkgdesc = repopkg.desc dbpkg.url = repopkg.url dbpkg.filename = repopkg.filename @@ -230,10 +235,12 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): def populate_files(dbpkg, repopkg, force=False): if not force: - if dbpkg.pkgver != repopkg.ver or dbpkg.pkgrel != repopkg.rel: - logger.info("db version (%s-%s) didn't match repo version (%s-%s) " - "for package %s, skipping file list addition", - dbpkg.pkgver, dbpkg.pkgrel, repopkg.ver, repopkg.rel, + if dbpkg.pkgver != repopkg.ver or dbpkg.pkgrel != repopkg.rel \ + or dbpkg.epoch != repopkg.epoch: + logger.info("db version (%s:%s-%s) didn't match repo version " + "(%s:%s-%s) for package %s, skipping file list addition", + dbpkg.epoch, dbpkg.pkgver, dbpkg.pkgrel, + repopkg.epoch, repopkg.ver, repopkg.rel, dbpkg.pkgname) return if not dbpkg.files_last_update or not dbpkg.last_update: @@ -334,7 +341,8 @@ def db_update(archname, reponame, pkgs, options): # for a non-force, we don't want to do anything at all. if filesonly: pass - elif p.ver == dbp.pkgver and p.rel == dbp.pkgrel: + elif p.ver == dbp.pkgver and p.rel == dbp.pkgrel \ + and p.epoch == dbp.epoch: if not force: continue else: diff --git a/main/migrations/0043_auto__add_field_package_epoch.py b/main/migrations/0043_auto__add_field_package_epoch.py new file mode 100644 index 00000000..77cd9b49 --- /dev/null +++ b/main/migrations/0043_auto__add_field_package_epoch.py @@ -0,0 +1,162 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding field 'Package.epoch' + db.add_column('packages', 'epoch', self.gf('django.db.models.fields.PositiveIntegerField')(default=0), keep_default=False) + + + def backwards(self, orm): + + # Deleting field 'Package.epoch' + db.delete_column('packages', 'epoch') + + + 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'"}, + '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',)", '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': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + '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': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('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', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.packagedepend': { + 'Meta': {'object_name': 'PackageDepend', 'db_table': "'package_depends'"}, + 'depname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'depvcmp': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + '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_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'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'main.signoff': { + 'Meta': {'object_name': 'Signoff'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'main.todolist': { + 'Meta': {'object_name': 'Todolist', 'db_table': "'todolists'"}, + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'date_added': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': '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']"}) + }, + 'main.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'user_profiles'"}, + 'alias': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'allowed_repos': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Repo']", 'symmetrical': 'False', 'blank': 'True'}), + 'favorite_distros': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'interests': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'languages': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'other_contact': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'picture': ('django.db.models.fields.files.FileField', [], {'default': "'devs/silhouette.png'", 'max_length': '100'}), + 'public_email': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'roles': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'time_zone': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'userprofile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'website': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'yob': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['main'] diff --git a/main/models.py b/main/models.py index a7cc2335..d0ccb12a 100644 --- a/main/models.py +++ b/main/models.py @@ -107,6 +107,7 @@ class Package(models.Model): pkgbase = models.CharField(max_length=255, db_index=True) pkgver = models.CharField(max_length=255) pkgrel = models.CharField(max_length=255) + epoch = models.PositiveIntegerField(default=0) pkgdesc = models.CharField(max_length=255, null=True) url = models.CharField(max_length=255, null=True) filename = models.CharField(max_length=255) @@ -286,7 +287,8 @@ class Package(models.Model): 'is this package similar, name and version-wise, to another' return self.pkgname == other.pkgname \ and self.pkgver == other.pkgver \ - and self.pkgrel == other.pkgrel + and self.pkgrel == other.pkgrel \ + and self.epoch == other.epoch def in_testing(self): '''attempt to locate this package in a testing repo; if we are in diff --git a/packages/utils.py b/packages/utils.py index 8d9f13ab..29a3087f 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -96,6 +96,8 @@ SELECT p.id, q.id p.pkgver != q.pkgver OR p.pkgrel != q.pkgrel + OR + p.epoch != q.epoch ) """ cursor = connection.cursor() -- cgit v1.2.3-2-g168b From 5abe0727bfa9a4fab24afcccb66da7d8832bf6b9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 13 Mar 2011 11:41:36 -0500 Subject: Show epoch in package version if != 0 Add a full_version property method to our package object that does the version formatting, and switch all templates displaying package versions over to this new method. Signed-off-by: Dan McGee --- main/models.py | 6 ++++++ templates/feeds/packages_title.html | 2 +- templates/packages/details.html | 8 ++++---- templates/packages/differences.html | 4 ++-- templates/packages/files.html | 4 ++-- templates/packages/group_details.html | 4 ++-- templates/packages/search.html | 4 ++-- templates/packages/signoffs.html | 2 +- templates/public/index.html | 2 +- 9 files changed, 21 insertions(+), 15 deletions(-) diff --git a/main/models.py b/main/models.py index d0ccb12a..727dc010 100644 --- a/main/models.py +++ b/main/models.py @@ -131,6 +131,12 @@ class Package(models.Model): def __unicode__(self): return self.pkgname + @property + def full_version(self): + if self.epoch > 0: + return u'%d:%s-%s' % (self.epoch, self.pkgver, self.pkgrel) + return u'%s-%s' % (self.pkgver, self.pkgrel) + def get_absolute_url(self): return '/packages/%s/%s/%s/' % (self.repo.name.lower(), self.arch.name, self.pkgname) diff --git a/templates/feeds/packages_title.html b/templates/feeds/packages_title.html index 910c6207..5c54ba65 100644 --- a/templates/feeds/packages_title.html +++ b/templates/feeds/packages_title.html @@ -1 +1 @@ -{{ obj.pkgname }} {{ obj.pkgver }}-{{ obj.pkgrel }} {{ obj.arch.name }} +{{ obj.pkgname }} {{ obj.full_version }} {{ obj.arch.name }} diff --git a/templates/packages/details.html b/templates/packages/details.html index f73e9d7c..051726bd 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -1,14 +1,14 @@ {% extends "base.html" %} {% load cache %} -{% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.pkgver }}-{{ pkg.pkgrel }} - Package Details{% endblock %} +{% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.full_version }} - Package Details{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% load package_extras %} {% block content %}
-

Package Details: {{ pkg.pkgname }} {{ pkg.pkgver }}-{{ pkg.pkgrel }}

+

Package Details: {{ pkg.pkgname }} {{ pkg.full_version }}

diff --git a/templates/packages/differences.html b/templates/packages/differences.html index b2a1be02..dd1046bc 100644 --- a/templates/packages/differences.html +++ b/templates/packages/differences.html @@ -45,12 +45,12 @@ {% if diff.pkg_a %} - {{ diff.pkg_a.pkgver }}-{{ diff.pkg_a.pkgrel }} + {{ diff.pkg_a.full_version }} {% else %}-{% endif %} {% if diff.pkg_b %} - {{ diff.pkg_b.pkgver }}-{{ diff.pkg_b.pkgrel }} + {{ diff.pkg_b.full_version }} {% else %}-{% endif %} {% endfor %} diff --git a/templates/packages/files.html b/templates/packages/files.html index 5e0ceb07..362e62cd 100644 --- a/templates/packages/files.html +++ b/templates/packages/files.html @@ -1,11 +1,11 @@ {% extends "base.html" %} -{% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.pkgver }}-{{ pkg.pkgrel }} - Package File List{% endblock %} +{% block title %}Arch Linux - {{ pkg.pkgname }} {{ pkg.full_version }} - Package File List{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% block content %}
-

Package File List: {{ pkg.pkgname }} {{ pkg.pkgver }}-{{ pkg.pkgrel }}

+

Package File List: {{ pkg.pkgname }} {{ pkg.full_version }}

Back to Package

{% include "packages/files-list.html" %} diff --git a/templates/packages/group_details.html b/templates/packages/group_details.html index 9cfa7b73..b40c21d0 100644 --- a/templates/packages/group_details.html +++ b/templates/packages/group_details.html @@ -24,9 +24,9 @@ {{ pkg.pkgname }} {% if pkg.flag_date %} - {{ pkg.pkgver }}-{{ pkg.pkgrel }} + {{ pkg.full_version }} {% else %} - {{ pkg.pkgver }}-{{ pkg.pkgrel }} + {{ pkg.full_version }} {% endif %} {{ pkg.pkgdesc }} {{ pkg.last_update|date }} diff --git a/templates/packages/search.html b/templates/packages/search.html index 5d0de1f4..8ea53b8e 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -99,9 +99,9 @@ {{ pkg.pkgname }} {% if pkg.flag_date %} - {{ pkg.pkgver }}-{{ pkg.pkgrel }} + {{ pkg.full_version }} {% else %} - {{ pkg.pkgver }}-{{ pkg.pkgrel }} + {{ pkg.full_version }} {% endif %} {{ pkg.pkgdesc }} {{ pkg.last_update|date }} diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 0cb7679c..b7184039 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -26,7 +26,7 @@ {{ pkg.arch.name }} {{ pkg.pkgname }} - {{ pkg.pkgver }}-{{ pkg.pkgrel }} + {{ pkg.full_version }} {{ pkg.last_update }} {{ target }} diff --git a/templates/public/index.html b/templates/public/index.html index c903b232..91a8414c 100644 --- a/templates/public/index.html +++ b/templates/public/index.html @@ -82,7 +82,7 @@ {% for update in pkg_updates %} {% with update|first as fpkg %} - {{ fpkg.pkgname }} {{ fpkg.pkgver }}-{{ fpkg.pkgrel }} + {{ fpkg.pkgname }} {{ fpkg.full_version }} {% for pkg in update %}{{ pkg.arch }}{% if not forloop.last %}/{% endif %}{% endfor %} -- cgit v1.2.3-2-g168b From ba1ca7db1e16400651bb746b8b80f2b30cf88a2f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 13 Mar 2011 12:02:59 -0500 Subject: Ensure PyPy compatibility Unfortunately I was relying on all python environments having this method defined, which is not true yet. Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index 24264af0..dd5b9347 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -1,5 +1,8 @@ import urllib -import urlparse +try: + from urlparse import parse_qs +except ImportError: + from cgi import parse_qs from django import template from django.utils.html import escape @@ -11,7 +14,7 @@ class BuildQueryStringNode(template.Node): self.sortfield = sortfield def render(self, context): - qs = urlparse.parse_qs(context['current_query']) + qs = parse_qs(context['current_query']) if qs.has_key('sort') and self.sortfield in qs['sort']: if self.sortfield.startswith('-'): qs['sort'] = [self.sortfield[1:]] -- cgit v1.2.3-2-g168b From 9d17938782d661a313b766bb6a3713fbfb5bb155 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 13 Mar 2011 12:03:37 -0500 Subject: Move magic numbers into a function argument Instead of having the '15 most recent' bit hardcoded, specify it as the default but allow value to be overridden by any caller. We don't make use of this yet, but it is not hard to do. Signed-off-by: Dan McGee --- public/utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/public/utils.py b/public/utils.py index 81f589f7..8ce2af45 100644 --- a/public/utils.py +++ b/public/utils.py @@ -4,19 +4,20 @@ from main.models import Arch, Package from main.utils import cache_function @cache_function(300) -def get_recent_updates(): +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 # packages that we can later do some screening and trim out the fat. pkgs = [] + # grab a few extra so we can hopefully catch everything we need + fetch = number * 4 for arch in Arch.objects.all(): - # grab a few extra so we can hopefully catch everything we need pkgs += list(Package.objects.select_related( - 'arch', 'repo').filter(arch=arch).order_by('-last_update')[:50]) + 'arch', 'repo').filter(arch=arch).order_by('-last_update')[:fetch]) pkgs.sort(key=attrgetter('last_update')) updates = [] ctr = 0 - while ctr < 15 and len(pkgs) > 0: + while ctr < number and len(pkgs) > 0: # not particularly happy with this logic, but it works. p = pkgs.pop() is_same = lambda q: p.is_same_version(q) and p.repo == q.repo -- cgit v1.2.3-2-g168b From 16890b7d8822ae7f82852f01274b11df6aa2ef6a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 10 Mar 2011 10:53:51 -0600 Subject: Fix relative URL links in admin_log template Signed-off-by: Dan McGee --- templates/devel/admin_log.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/devel/admin_log.html b/templates/devel/admin_log.html index 2de15bad..0f22ba2b 100644 --- a/templates/devel/admin_log.html +++ b/templates/devel/admin_log.html @@ -48,7 +48,7 @@ {% if entry.is_deletion %} {{ entry.object_repr }} {% else %} - {{ entry.object_repr }} + {{ entry.object_repr }} {% endif %} {{ entry.change_message }} -- cgit v1.2.3-2-g168b From c4a335d5da78ca4558cea8500ae78db9e3be74ff Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 10 Mar 2011 12:00:59 -0600 Subject: Remove duplicate TEMPLATE_LOADERS config Whoops. This has been there a while. Signed-off-by: Dan McGee --- settings.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/settings.py b/settings.py index 9565e6c4..1d26d9eb 100644 --- a/settings.py +++ b/settings.py @@ -45,13 +45,6 @@ LOGIN_REDIRECT_URL = '/' # Set django's User stuff to use our profile model AUTH_PROFILE_MODULE = 'main.UserProfile' -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.load_template_source', - 'django.template.loaders.eggs.load_template_source', - 'django.template.loaders.app_directories.load_template_source', -) - # We add a processor to determine if the request is secure or not TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', @@ -62,6 +55,18 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'main.context_processors.secure', ) +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates". + # Always use forward slashes, even on Windows. + '%s/templates' % DEPLOY_PATH, +) + +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.eggs.Loader', + 'django.template.loaders.app_directories.Loader', +) + # This bug is a real bummer: # http://code.djangoproject.com/ticket/14105 MIDDLEWARE_CLASSES = ( @@ -78,18 +83,6 @@ MIDDLEWARE_CLASSES = ( ROOT_URLCONF = 'urls' -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates". - # Always use forward slashes, even on Windows. - '%s/templates' % DEPLOY_PATH, -) - -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.eggs.Loader', - 'django.template.loaders.app_directories.Loader', -) - # Configure where sessions and messages should reside MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' -- cgit v1.2.3-2-g168b From ad2a9ac23af0d4610e079741070c4408a4c6ce16 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 11 Mar 2011 17:13:25 -0600 Subject: Show orphan package counts in maintainer dashboard table Signed-off-by: Dan McGee --- devel/views.py | 11 +++++++++++ templates/devel/index.html | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/devel/views.py b/devel/views.py index 46387f7a..5b8965d8 100644 --- a/devel/views.py +++ b/devel/views.py @@ -36,11 +36,22 @@ def index(request): maintainers = get_annotated_maintainers() + maintained = PackageRelation.objects.filter( + type=PackageRelation.MAINTAINER).values('pkgbase') + total_orphans = Package.objects.exclude(pkgbase__in=maintained).count() + total_flagged_orphans = Package.objects.filter( + flag_date__isnull=False).exclude(pkgbase__in=maintained).count() + orphan = { + 'package_count': total_orphans, + 'flagged_count': total_flagged_orphans, + } + page_dict = { 'todos': Todolist.objects.incomplete().order_by('-date_added'), 'repos': Repo.objects.all(), 'arches': Arch.objects.all(), 'maintainers': maintainers, + 'orphan': orphan, 'flagged' : flagged, 'todopkgs' : todopkgs, } diff --git a/templates/devel/index.html b/templates/devel/index.html index 08ca249a..fb4ed4ab 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -166,6 +166,15 @@ # Packages # Flagged + + Orphan + + {{ orphan.package_count }} packages + + {{ orphan.flagged_count }} packages + {% for maint in maintainers %} -- cgit v1.2.3-2-g168b From dfab07f5abc5dcc99aa521027e29434224cb0a57 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 14 Mar 2011 23:55:52 -0500 Subject: Make todo list DateField a DateTimeField Signed-off-by: Dan McGee --- .../0044_auto__chg_field_todolist_date_added.py | 156 +++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 main/migrations/0044_auto__chg_field_todolist_date_added.py diff --git a/main/migrations/0044_auto__chg_field_todolist_date_added.py b/main/migrations/0044_auto__chg_field_todolist_date_added.py new file mode 100644 index 00000000..d4099891 --- /dev/null +++ b/main/migrations/0044_auto__chg_field_todolist_date_added.py @@ -0,0 +1,156 @@ +# encoding: utf-8 +import datetime +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', 'date_added', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True)) + + def backwards(self, orm): + db.alter_column('todolists', 'date_added', self.gf('django.db.models.fields.DateField')(auto_now_add=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.donor': { + 'Meta': {'ordering': "['name']", 'object_name': 'Donor', 'db_table': "'donors'"}, + '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',)", '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': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + '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': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('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', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.packagedepend': { + 'Meta': {'object_name': 'PackageDepend', 'db_table': "'package_depends'"}, + 'depname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'depvcmp': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + '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_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'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'main.signoff': { + 'Meta': {'object_name': 'Signoff'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'main.todolist': { + 'Meta': {'object_name': 'Todolist', 'db_table': "'todolists'"}, + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'date_added': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': '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']"}) + }, + 'main.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'user_profiles'"}, + 'alias': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'allowed_repos': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Repo']", 'symmetrical': 'False', 'blank': 'True'}), + 'favorite_distros': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'interests': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'languages': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'other_contact': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'picture': ('django.db.models.fields.files.FileField', [], {'default': "'devs/silhouette.png'", 'max_length': '100'}), + 'public_email': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'roles': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'time_zone': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'userprofile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'website': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'yob': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['main'] -- cgit v1.2.3-2-g168b From 9fd7174029de87e58e25db6362f6e1c5349f0599 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 15 Mar 2011 00:00:12 -0500 Subject: Use date template filter on datetime field display Signed-off-by: Dan McGee --- templates/devel/index.html | 6 +++--- templates/todolists/list.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/devel/index.html b/templates/devel/index.html index fb4ed4ab..f4a81102 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -27,8 +27,8 @@ {{ pkg.repo.name }} {{ pkg.pkgver }} {{ pkg.arch.name }} - {{ pkg.flag_date }} - {{ pkg.last_update }} + {{ pkg.flag_date|date }} + {{ pkg.last_update|date }} {% empty %} No flagged packages to display @@ -80,7 +80,7 @@ {{ todo.name }} - {{ todo.date_added }} + {{ todo.date_added|date }} {{ todo.description|safe }} {% empty %} diff --git a/templates/todolists/list.html b/templates/todolists/list.html index 78acabfb..7d229271 100644 --- a/templates/todolists/list.html +++ b/templates/todolists/list.html @@ -30,7 +30,7 @@ {{ list.name }} - {{ list.date_added }} + {{ list.date_added|date }} {{ list.creator.get_full_name }} {{ list.description|urlize }} {{ list.pkg_count }} -- cgit v1.2.3-2-g168b From 3d6392391b3a0b37bea06f36f1998344ae170df2 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 15 Mar 2011 09:00:20 -0500 Subject: Check in actual models update for DateTime migration Signed-off-by: Dan McGee --- main/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/models.py b/main/models.py index 727dc010..24befddd 100644 --- a/main/models.py +++ b/main/models.py @@ -349,7 +349,7 @@ class Todolist(models.Model): creator = models.ForeignKey(User) name = models.CharField(max_length=255) description = models.TextField() - date_added = models.DateField(auto_now_add=True) + date_added = models.DateTimeField(auto_now_add=True) objects = TodolistManager() def __unicode__(self): return self.name -- cgit v1.2.3-2-g168b From a0ef88770f5fe318f38eaa7dc794727a507c797b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 15 Mar 2011 09:02:22 -0500 Subject: Ensure package search form correctly handles errors We were silently eating errors and just showing a normal package list if the form didn't validate. Rather than do that, make sure we return no packages at all and display the form errors back to the user in a sane fashion. Adjust the validation methods on the 'limit' parameter so any integer is acceptable. Signed-off-by: Dan McGee --- packages/views.py | 40 +++++++++++++++++++------------- templates/packages/search.html | 52 ++++++++++++++++++++++++------------------ 2 files changed, 54 insertions(+), 38 deletions(-) diff --git a/packages/views.py b/packages/views.py index 3cef8226..6239f01a 100644 --- a/packages/views.py +++ b/packages/views.py @@ -126,6 +126,23 @@ def getmaintainer(request, name, repo, arch): return HttpResponse(str('\n'.join(names)), mimetype='text/plain') +def coerce_limit_value(value): + if not value: + return 50 + if value == 'all': + return None + value = int(value) + if value < 0: + raise ValueError + return value + +class LimitTypedChoiceField(forms.TypedChoiceField): + def valid_value(self, value): + try: + return coerce_limit_value(value) + except ValueError, TypeError: + return False + class PackageSearchForm(forms.Form): repo = forms.MultipleChoiceField(required=False) arch = forms.MultipleChoiceField(required=False) @@ -136,25 +153,12 @@ class PackageSearchForm(forms.Form): flagged = forms.ChoiceField( choices=[('', 'All')] + make_choice(['Flagged', 'Not Flagged']), required=False) - limit = forms.ChoiceField( + limit = LimitTypedChoiceField( choices=make_choice([50, 100, 250]) + [('all', 'All')], + coerce=coerce_limit_value, required=False, initial=50) - def clean_limit(self): - limit = self.cleaned_data['limit'] - if limit == 'all': - limit = None - elif limit: - try: - limit = int(limit) - except: - raise forms.ValidationError("Should be an integer") - else: - limit = 50 - return limit - - def __init__(self, *args, **kwargs): super(PackageSearchForm, self).__init__(*args, **kwargs) self.fields['repo'].choices = make_choice( @@ -186,7 +190,8 @@ def search(request, page=None): inner_q = PackageRelation.objects.all().values('pkgbase') packages = packages.exclude(pkgbase__in=inner_q) elif form.cleaned_data['maintainer']: - inner_q = PackageRelation.objects.filter(user__username=form.cleaned_data['maintainer']).values('pkgbase') + inner_q = PackageRelation.objects.filter( + user__username=form.cleaned_data['maintainer']).values('pkgbase') packages = packages.filter(pkgbase__in=inner_q) if form.cleaned_data['flagged'] == 'Flagged': @@ -203,6 +208,9 @@ def search(request, page=None): packages = packages.filter(last_update__gte= datetime(lu.year, lu.month, lu.day, 0, 0)) limit = form.cleaned_data['limit'] + else: + # Form had errors, don't return any results, just the busted form + packages = Package.objects.none() else: form = PackageSearchForm() diff --git a/templates/packages/search.html b/templates/packages/search.html index 8ea53b8e..381ebb01 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -14,28 +14,36 @@

Package Search

- +
-- cgit v1.2.3-2-g168b From 2360e2c4bcb0fc873a60599165e7f24f20465786 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 15 Mar 2011 11:06:34 -0500 Subject: Only set limit if we have one, else default to 50 This was the cause of some pretty awesome performance headaches this morning. Signed-off-by: Dan McGee --- packages/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/views.py b/packages/views.py index 6239f01a..70b3a84b 100644 --- a/packages/views.py +++ b/packages/views.py @@ -207,7 +207,9 @@ def search(request, page=None): lu = form.cleaned_data['last_update'] packages = packages.filter(last_update__gte= datetime(lu.year, lu.month, lu.day, 0, 0)) - limit = form.cleaned_data['limit'] + + if form.cleaned_data['limit']: + limit = form.cleaned_data['limit'] else: # Form had errors, don't return any results, just the busted form packages = Package.objects.none() -- cgit v1.2.3-2-g168b From b0061f5bd99ef49733b4b66ef67dfd8f156785b1 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 16 Mar 2011 08:32:40 -0500 Subject: Various reporead small cleanups Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 53 +++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index f826abea..3f1e9ddf 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -117,6 +117,13 @@ class Pkg(object): # files, depends, etc. setattr(self, k, v) + @property + def full_version(self): + '''Very similar to the main.models.Package method.''' + if self.epoch > 0: + return u'%d:%s-%s' % (self.epoch, self.ver, self.rel) + return u'%s-%s' % (self.ver, self.rel) + def find_user(userstring): ''' @@ -237,11 +244,9 @@ def populate_files(dbpkg, repopkg, force=False): if not force: if dbpkg.pkgver != repopkg.ver or dbpkg.pkgrel != repopkg.rel \ or dbpkg.epoch != repopkg.epoch: - logger.info("db version (%s:%s-%s) didn't match repo version " - "(%s:%s-%s) for package %s, skipping file list addition", - dbpkg.epoch, dbpkg.pkgver, dbpkg.pkgrel, - repopkg.epoch, repopkg.ver, repopkg.rel, - dbpkg.pkgname) + logger.info("DB version (%s) didn't match repo version " + "(%s) for package %s, skipping file list addition", + dbpkg.full_version, repopkg.full_version, dbpkg.pkgname) return if not dbpkg.files_last_update or not dbpkg.last_update: pass @@ -280,25 +285,23 @@ def db_update(archname, reponame, pkgs, options): filesonly = options.get('filesonly', False) repository = Repo.objects.get(name__iexact=reponame) architecture = Arch.objects.get(name__iexact=archname) - dbpkgs = Package.objects.filter(arch=architecture, repo=repository) - # It makes sense to fully evaluate our DB query now because we will - # be using 99% of the objects in our "in both sets" loop. Force eval - # by calling list() on the QuerySet. - list(dbpkgs) + # no-arg order_by() removes even the default ordering; we don't need it + dbpkgs = Package.objects.filter( + arch=architecture, repo=repository).order_by() # This makes our inner loop where we find packages by name *way* more # efficient by not having to go to the database for each package to # SELECT them by name. dbdict = dict([(pkg.pkgname, pkg) for pkg in dbpkgs]) logger.debug("Creating sets") - dbset = set([pkg.pkgname for pkg in dbpkgs]) + dbset = set(dbdict.keys()) syncset = set([pkg.name for pkg in pkgs]) logger.info("%d packages in current web DB", len(dbset)) logger.info("%d packages in new updating db", len(syncset)) in_sync_not_db = syncset - dbset logger.info("%d packages in sync not db", len(in_sync_not_db)) - # Try to catch those random orphaning issues that make Eric so unhappy. + # Try to catch those random package deletions that make Eric so unhappy. if len(dbset): dbpercent = 100.0 * len(syncset) / len(dbset) else: @@ -309,12 +312,14 @@ def db_update(archname, reponame, pkgs, options): # means we expect the repo to fluctuate a lot. msg = "Package database has %.1f%% the number of packages in the " \ "web database" % dbpercent - if not filesonly and \ + if len(dbset) == 0 and len(syncset) == 0: + pass + elif not filesonly and \ len(dbset) > 20 and dbpercent < 50.0 and \ not repository.testing: logger.error(msg) raise Exception(msg) - if dbpercent < 75.0: + elif dbpercent < 75.0: logger.warning(msg) if not filesonly: @@ -328,8 +333,8 @@ def db_update(archname, reponame, pkgs, options): in_db_not_sync = dbset - syncset for p in in_db_not_sync: logger.info("Removing package %s from database", p) - Package.objects.get( - pkgname=p, arch=architecture, repo=repository).delete() + dbp = dbdict[p] + dbp.delete() # packages in both database and in syncdb (update in database) pkg_in_both = syncset & dbset @@ -429,10 +434,9 @@ def parse_repo(repopath): logger.info("Finished repo parsing, %d total packages", len(pkgs)) return (reponame, pkgs.values()) -def validate_arch(arch): +def validate_arch(archname): "Check if arch is valid." - available_arches = [x.name for x in Arch.objects.all()] - return arch in available_arches + return Arch.objects.filter(name__iexact=archname).exists() def read_repo(primary_arch, repo_file, options): """ @@ -440,21 +444,22 @@ def read_repo(primary_arch, repo_file, options): """ repo, packages = parse_repo(repo_file) - # sort packages by arch -- to handle noarch stuff + # group packages by arch -- to handle noarch stuff packages_arches = {} - packages_arches['any'] = [] + for arch in Arch.objects.filter(agnostic=True): + packages_arches[arch.name] = [] packages_arches[primary_arch] = [] for package in packages: - if package.arch in ('any', primary_arch): + if package.arch in packages_arches: packages_arches[package.arch].append(package) else: # we don't include mis-arched packages logger.warning("Package %s arch = %s", package.name,package.arch) logger.info('Starting database updates.') - for (arch, pkgs) in packages_arches.items(): - db_update(arch, repo, pkgs, options) + for arch in sorted(packages_arches.keys()): + db_update(arch, repo, packages_arches[arch], options) logger.info('Finished database updates.') return 0 -- cgit v1.2.3-2-g168b From aca7700dd7519d45e677e18b1a0199f3712ce140 Mon Sep 17 00:00:00 2001 From: Lukas Fleischer Date: Wed, 16 Mar 2011 09:03:38 +0100 Subject: Fix valid_value() in "LimitTypedChoiceField" class. We just returned the coerced value in valid_value() which may become None if the valid value "all" was passed, resulting in valid_value() evaluating to False. Explicitly returning True if the value can be coerced without an error fixes this. Signed-off-by: Lukas Fleischer Signed-off-by: Dan McGee --- packages/views.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/views.py b/packages/views.py index 70b3a84b..4101c538 100644 --- a/packages/views.py +++ b/packages/views.py @@ -128,9 +128,10 @@ def getmaintainer(request, name, repo, arch): def coerce_limit_value(value): if not value: - return 50 - if value == 'all': return None + if value == 'all': + # negative value indicates show all results + return -1 value = int(value) if value < 0: raise ValueError @@ -139,7 +140,8 @@ def coerce_limit_value(value): class LimitTypedChoiceField(forms.TypedChoiceField): def valid_value(self, value): try: - return coerce_limit_value(value) + coerce_limit_value(value) + return True except ValueError, TypeError: return False @@ -208,8 +210,11 @@ def search(request, page=None): packages = packages.filter(last_update__gte= datetime(lu.year, lu.month, lu.day, 0, 0)) - if form.cleaned_data['limit']: - limit = form.cleaned_data['limit'] + asked_limit = form.cleaned_data['limit'] + if asked_limit and asked_limit < 0: + limit = None + elif asked_limit: + limit = asked_limit else: # Form had errors, don't return any results, just the busted form packages = Package.objects.none() -- cgit v1.2.3-2-g168b