From 07a0b1925f5bd39d622d3912c02ebfccb579bf69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Reynolds?= Date: Sun, 6 Mar 2011 11:19:32 -0300 Subject: Removed AUR and pointed to the sources repo --- templates/packages/search.html | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/templates/packages/search.html b/templates/packages/search.html index 74f7ede3..ead009d0 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -147,18 +147,17 @@ {% else %}

We couldn't find any packages matching your query. Try searching again - using different criteria, or try - {% if search_form.q.data %} - searching the AUR - {% else %}searching the AUR{% endif %} - to see if the package can be found there.

+ using different criteria.

{% endif %}
-

You are browsing the Parabola package database. From here you can find - detailed information about packages located in the official supported repositories.

-
+

You are browsing the Parabola package database. From here you can + find detailed information about packages located in the official + supported repositories. If you need the sourceball from where a + package is built, you can look at our sources repo.

{% load adminmedia %} -- cgit v1.2.3-2-g168b From 30b2a1954d5bf63c78fe654a0c0bb98508e2f3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Reynolds?= Date: Sun, 6 Mar 2011 11:20:38 -0300 Subject: Removed AUR and Arch mention --- templates/public/feeds.html | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/templates/public/feeds.html b/templates/public/feeds.html index 5aa5df20..79e8a1aa 100644 --- a/templates/public/feeds.html +++ b/templates/public/feeds.html @@ -6,7 +6,7 @@

RSS Feeds

-

Several RSS feeds are available for consumption from the Arch website. +

Several RSS feeds are available for consumption from the Parabola website. The majority of these are package-related and allow feeds to be customized for the updates you care about.

@@ -58,12 +58,5 @@ -

A newest packages feed - is also available from the Arch User Repository (AUR), but be specially aware that sometimes, - unfree software gets uploaded there, at least until we found a solution - to it.

- - {% endblock %} -- cgit v1.2.3-2-g168b 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 From b9c6451f88caa35ab39b6468a99b147d7d7f4937 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 20 Mar 2011 15:51:02 -0500 Subject: Fix news preview with CSRF and AJAX in Django 1.2.5 Signed-off-by: Dan McGee --- media/archweb.js | 14 ++++++++------ templates/news/add.html | 8 ++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/media/archweb.js b/media/archweb.js index 52e817a4..03358fa9 100644 --- a/media/archweb.js +++ b/media/archweb.js @@ -71,16 +71,18 @@ if (typeof $.tablesorter !== 'undefined') { /* news/add.html */ function enablePreview() { - $('#previewbtn').click(function(event) { + $('#news-preview-button').click(function(event) { event.preventDefault(); - $.post('/news/preview/', - { data: $('#id_content').val() }, + $.post('/news/preview/', { + data: $('#id_content').val(), + csrfmiddlewaretoken: $('#newsform input[name=csrfmiddlewaretoken]').val() + }, function(data) { - $('#previewdata').html(data); - $('.news-article').show(); + $('#news-preview-data').html(data); + $('#news-preview').show(); } ); - $('#previewtitle').html($('#id_title').val()); + $('#news-preview-title').html($('#id_title').val()); }); } diff --git a/templates/news/add.html b/templates/news/add.html index f580e0d0..bb866dc4 100644 --- a/templates/news/add.html +++ b/templates/news/add.html @@ -23,14 +23,14 @@

- +

- -{% load cdn %}{% jquery %} - - -{% endblock %} diff --git a/templates/packages/packages_list.html b/templates/packages/packages_list.html new file mode 100644 index 00000000..c897aac5 --- /dev/null +++ b/templates/packages/packages_list.html @@ -0,0 +1,47 @@ +{% extends "base.html" %} +{% block title %}Arch Linux - {{ name }} ({{ arch.name }}) - {{ list_title }}{% endblock %} +{% block navbarclass %}anb-packages{% endblock %} + +{% block content %} +
+

{{ list_title }} - {{ name }} ({{ arch.name }})

+ + + + + + + + + + + + + + {% for pkg in packages %} + + + + + {% if pkg.flag_date %} + + {% else %} + + {% endif %} + + + + + {% endfor %} + +
ArchRepoNameVersionDescriptionLast UpdatedFlag Date
{{ pkg.arch.name }}{{ pkg.repo.name|capfirst }}{{ pkg.pkgname }}{{ pkg.full_version }}{{ pkg.full_version }}{{ pkg.pkgdesc }}{{ pkg.last_update|date }}{{ pkg.flag_date|date }}
+
+{% load cdn %}{% jquery %} + + +{% endblock %} -- cgit v1.2.3-2-g168b From a52ddb5c48dd2cb7856779f64611679aca7d660d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Mar 2011 12:47:26 -0500 Subject: Allow virtual base packages to display in web interface Repurpose the old group details page to show a listing of all packages built from a particular pkgbase value, even if this value is not an actual package. Signed-off-by: Dan McGee --- packages/views.py | 26 +++++++++++++++++++++++--- templates/packages/details.html | 3 ++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/views.py b/packages/views.py index 374a1a20..263165fd 100644 --- a/packages/views.py +++ b/packages/views.py @@ -83,9 +83,29 @@ def update(request): def details(request, name='', repo='', arch=''): if all([name, repo, arch]): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - return direct_to_template(request, 'packages/details.html', {'pkg': pkg, }) + try: + pkg = Package.objects.get(pkgname=name, + repo__name__iexact=repo, arch__name=arch) + return direct_to_template(request, 'packages/details.html', + {'pkg': pkg, }) + except Package.DoesNotExist: + arch = get_object_or_404(Arch, name=arch) + arches = [ arch ] + arches.extend(Arch.objects.filter(agnostic=True)) + repo = get_object_or_404(Repo, name__iexact=repo) + pkgs = Package.objects.filter(pkgbase=name, + repo__testing=repo.testing, arch__in=arches) + pkgs = pkgs.select_related('arch', 'repo').order_by('pkgname') + if len(pkgs) == 0: + raise Http404 + context = { + 'list_title': 'Split Package Details', + 'name': name, + 'arch': arch, + 'packages': pkgs, + } + return direct_to_template(request, 'packages/packages_list.html', + context) else: return redirect("/packages/?arch=%s&repo=%s&q=%s" % ( arch.lower(), repo.title(), name)) diff --git a/templates/packages/details.html b/templates/packages/details.html index 051726bd..4253f0b3 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -96,7 +96,8 @@ {{ pkg.pkgbase }} {% else %} - {{ pkg.pkgbase }} + {{ pkg.pkgbase }} {% endif %} {% endifequal %} -- cgit v1.2.3-2-g168b From 5f7fbcb7133b1c04824d1aa4ded7af8feef13c2e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Mar 2011 13:01:21 -0500 Subject: Add basic todo list admin site Signed-off-by: Dan McGee --- main/admin.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/main/admin.py b/main/admin.py index a2c5b2a9..45bc5ab2 100644 --- a/main/admin.py +++ b/main/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from django.contrib.auth.models import User from django.contrib.auth.admin import UserAdmin -from main.models import Arch, Donor, Package, Repo, UserProfile +from main.models import Arch, Donor, Package, Repo, Todolist, UserProfile class DonorAdmin(admin.ModelAdmin): list_display = ('name', 'visible') @@ -23,6 +23,10 @@ class PackageAdmin(admin.ModelAdmin): list_filter = ('repo', 'arch') search_fields = ('pkgname',) +class TodolistAdmin(admin.ModelAdmin): + list_display = ('name', 'date_added', 'creator', 'description') + search_fields = ('name', 'description') + admin.site.unregister(User) class UserProfileInline(admin.StackedInline): model = UserProfile @@ -40,4 +44,6 @@ admin.site.register(Package, PackageAdmin) admin.site.register(Arch, ArchAdmin) admin.site.register(Repo, RepoAdmin) +admin.site.register(Todolist, TodolistAdmin) + # vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From 97437d27b7b3d01bfd2e2ae628e6612c149324b4 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 16 Feb 2011 18:09:46 -0600 Subject: Add new package parts models This allows us to store conflicts, provisions (provides), and replacements in the database, things we weren't capturing before. All can be multivalued, just like License and PackageGroup. Signed-off-by: Dan McGee --- ...add_provision__add_conflict__add_replacement.py | 167 +++++++++++++++++++++ packages/models.py | 44 ++++++ 2 files changed, 211 insertions(+) create mode 100644 packages/migrations/0006_auto__add_provision__add_conflict__add_replacement.py diff --git a/packages/migrations/0006_auto__add_provision__add_conflict__add_replacement.py b/packages/migrations/0006_auto__add_provision__add_conflict__add_replacement.py new file mode 100644 index 00000000..c764ce77 --- /dev/null +++ b/packages/migrations/0006_auto__add_provision__add_conflict__add_replacement.py @@ -0,0 +1,167 @@ +# 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 model 'Provision' + db.create_table('packages_provision', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('pkg', self.gf('django.db.models.fields.related.ForeignKey')(related_name='provides', to=orm['main.Package'])), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('version', self.gf('django.db.models.fields.CharField')(default='', max_length=255)), + )) + db.send_create_signal('packages', ['Provision']) + + # Adding model 'Conflict' + db.create_table('packages_conflict', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('pkg', self.gf('django.db.models.fields.related.ForeignKey')(related_name='conflicts', to=orm['main.Package'])), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('comparison', self.gf('django.db.models.fields.CharField')(default='', max_length=255)), + ('version', self.gf('django.db.models.fields.CharField')(default='', max_length=255)), + )) + db.send_create_signal('packages', ['Conflict']) + + # Adding model 'Replacement' + db.create_table('packages_replacement', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('pkg', self.gf('django.db.models.fields.related.ForeignKey')(related_name='replaces', to=orm['main.Package'])), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('comparison', self.gf('django.db.models.fields.CharField')(default='', max_length=255)), + ('version', self.gf('django.db.models.fields.CharField')(default='', max_length=255)), + )) + db.send_create_signal('packages', ['Replacement']) + + + def backwards(self, orm): + # Deleting model 'Provision' + db.delete_table('packages_provision') + # Deleting model 'Conflict' + db.delete_table('packages_conflict') + # Deleting model 'Replacement' + db.delete_table('packages_replacement') + + + 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',)", '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.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'}), + '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.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'}), + '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'}, + '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'}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 5dbdea45..63a7255b 100644 --- a/packages/models.py +++ b/packages/models.py @@ -57,6 +57,50 @@ class License(models.Model): class Meta: ordering = ['name'] +class Conflict(models.Model): + pkg = models.ForeignKey('main.Package', related_name='conflicts') + name = models.CharField(max_length=255, db_index=True) + comparison = models.CharField(max_length=255, default='') + version = models.CharField(max_length=255, default='') + + def __unicode__(self): + if self.version: + return u'%s%s%s' % (self.name, self.comparison, self.version) + return self.name + + class Meta: + ordering = ['name'] + +class Provision(models.Model): + pkg = models.ForeignKey('main.Package', related_name='provides') + name = models.CharField(max_length=255, db_index=True) + # comparison must be '=' for provides + comparison = '=' + version = models.CharField(max_length=255, default='') + + def __unicode__(self): + if self.version: + return u'%s=%s' % (self.name, self.version) + return self.name + + class Meta: + ordering = ['name'] + +class Replacement(models.Model): + pkg = models.ForeignKey('main.Package', related_name='replaces') + name = models.CharField(max_length=255, db_index=True) + comparison = models.CharField(max_length=255, default='') + version = models.CharField(max_length=255, default='') + + def __unicode__(self): + if self.version: + return u'%s%s%s' % (self.name, self.comparison, self.version) + return self.name + + class Meta: + ordering = ['name'] + + def remove_inactive_maintainers(sender, instance, created, **kwargs): # instance is an auth.models.User; we want to remove any existing # maintainer relations if the user is no longer active -- cgit v1.2.3-2-g168b From 857cae2e96aa0d23b0702d5ad9db572787f657a9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 16 Feb 2011 18:22:14 -0600 Subject: reporead: refactor multivalued attribute creation This will come in more handy with our new models, but we can adapt groups and licenses to use it first. Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 231cad01..0c835555 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -188,6 +188,17 @@ def create_depend(package, dep_str, optional=False): depend.save(force_insert=True) return depend +def create_multivalued(dbpkg, repopkg, db_attr, repo_attr): + '''Populate the simplest of multivalued attributes. These are those that + only deal with a 'name' attribute, such as licenses, groups, etc. The input + and output objects and attribute names are specified, and everything is + done via hasattr()/getattr().''' + collection = getattr(dbpkg, db_attr) + collection.all().delete() + if hasattr(repopkg, repo_attr): + for name in getattr(repopkg, repo_attr): + collection.create(name=name) + def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): if repopkg.base: dbpkg.pkgbase = repopkg.base @@ -229,15 +240,8 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): for y in repopkg.optdepends: dep = create_depend(dbpkg, y, True) - dbpkg.groups.all().delete() - if hasattr(repopkg, 'groups'): - for y in repopkg.groups: - dbpkg.groups.create(name=y) - - dbpkg.licenses.all().delete() - if hasattr(repopkg, 'license'): - for y in repopkg.license: - dbpkg.licenses.create(name=y) + create_multivalued(dbpkg, repopkg, 'groups', 'groups') + create_multivalued(dbpkg, repopkg, 'licenses', 'license') def populate_files(dbpkg, repopkg, force=False): -- cgit v1.2.3-2-g168b From 1942a9da6c8165c10c0914bcab522a59d15a1cc5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Mar 2011 16:43:10 -0500 Subject: Fix package group unicode method --- packages/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/models.py b/packages/models.py index 63a7255b..79e8abca 100644 --- a/packages/models.py +++ b/packages/models.py @@ -45,7 +45,7 @@ class PackageGroup(models.Model): name = models.CharField(max_length=255) def __unicode__(self): - return "%s: %s" % (name, pkg) + return "%s: %s" % (self.name, self.pkg) class License(models.Model): pkg = models.ForeignKey('main.Package', related_name='licenses') -- cgit v1.2.3-2-g168b From 151eafb0192957c489fa5b8010c081eb6db5f925 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Mar 2011 16:43:39 -0500 Subject: Remove need to import each individual logger constant Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 0c835555..8ce6eab8 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -35,12 +35,10 @@ try: except ImportError: pass -from logging import ERROR, WARNING, INFO, DEBUG - from main.models import Arch, Package, PackageDepend, PackageFile, Repo logging.basicConfig( - level=WARNING, + level=logging.WARNING, format='%(asctime)s -> %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', stream=sys.stderr) @@ -69,11 +67,11 @@ class Command(BaseCommand): v = int(options.get('verbosity', 0)) if v == 0: - logger.level = ERROR + logger.level = logging.ERROR elif v == 1: - logger.level = INFO + logger.level = logging.INFO elif v == 2: - logger.level = DEBUG + logger.level = logging.DEBUG import signal, traceback handler = lambda sig, stack: traceback.print_stack(stack) -- cgit v1.2.3-2-g168b From d6b148779f3a1119e30fa75c63a2a0e46938098c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Mar 2011 16:44:17 -0500 Subject: reporead: read in provisions, conflicts, and replacements Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 45 ++++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 8ce6eab8..708b8a59 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -36,6 +36,7 @@ except ImportError: pass from main.models import Arch, Package, PackageDepend, PackageFile, Repo +from packages.models import Conflict, Provision, Replacement logging.basicConfig( level=logging.WARNING, @@ -172,20 +173,48 @@ def find_user(userstring): # lookup more than strictly necessary. find_user.cache = {} +DEPEND_RE = re.compile(r"^(.+?)((>=|<=|=|>|<)(.*))?$") + def create_depend(package, dep_str, optional=False): depend = PackageDepend(pkg=package, optional=optional) # lop off any description first parts = dep_str.split(':', 1) if len(parts) > 1: depend.description = parts[1].strip() - match = re.match(r"^(.+?)((>=|<=|=|>|<)(.*))?$", parts[0].strip()) + match = DEPEND_RE.match(parts[0].strip()) if match: depend.depname = match.group(1) if match.group(2): depend.depvcmp = match.group(2) + else: + logger.warning('Package %s had unparsable depend string %s', + package.pkgname, dep_str) + return None depend.save(force_insert=True) return depend +def create_related(model, package, rel_str, equals_only=False): + related = model(pkg=package) + match = DEPEND_RE.match(rel_str) + if match: + related.name = match.group(1) + if match.group(3): + comp = match.group(3) + if not equals_only: + related.comparison = comp + elif comp != '=': + logger.warning( + 'Package %s had unexpected comparison operator %s for %s in %s', + package.pkgname, comp, model.__name__, rel_str) + if match.group(4): + related.version = match.group(4) + else: + logger.warning('Package %s had unparsable %s string %s', + package.pkgname, model.___name__, rel_str) + return None + related.save(force_insert=True) + return related + def create_multivalued(dbpkg, repopkg, db_attr, repo_attr): '''Populate the simplest of multivalued attributes. These are those that only deal with a 'name' attribute, such as licenses, groups, etc. The input @@ -233,10 +262,20 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): dbpkg.packagedepend_set.all().delete() if hasattr(repopkg, 'depends'): for y in repopkg.depends: - dep = create_depend(dbpkg, y) + create_depend(dbpkg, y) if hasattr(repopkg, 'optdepends'): for y in repopkg.optdepends: - dep = create_depend(dbpkg, y, True) + create_depend(dbpkg, y, True) + + if hasattr(repopkg, 'conflicts'): + for y in repopkg.conflicts: + create_related(Conflict, dbpkg, y) + if hasattr(repopkg, 'provides'): + for y in repopkg.provides: + create_related(Provision, dbpkg, y, equals_only=True) + if hasattr(repopkg, 'replaces'): + for y in repopkg.replaces: + create_related(Replacement, dbpkg, y) create_multivalued(dbpkg, repopkg, 'groups', 'groups') create_multivalued(dbpkg, repopkg, 'licenses', 'license') -- cgit v1.2.3-2-g168b From dad2ca8b3e42cbf0ad5a67be7016426ec4835a19 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Mar 2011 20:19:00 -0500 Subject: Clear out package relation sets before adding new values Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 708b8a59..277196e2 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -267,12 +267,15 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): for y in repopkg.optdepends: create_depend(dbpkg, y, True) + dbpkg.conflicts.all().delete() if hasattr(repopkg, 'conflicts'): for y in repopkg.conflicts: create_related(Conflict, dbpkg, y) + dbpkg.provides.all().delete() if hasattr(repopkg, 'provides'): for y in repopkg.provides: create_related(Provision, dbpkg, y, equals_only=True) + dbpkg.replaces.all().delete() if hasattr(repopkg, 'replaces'): for y in repopkg.replaces: create_related(Replacement, dbpkg, y) -- cgit v1.2.3-2-g168b From ca370499d863dc2ce86fbeb26cf810d3808239f6 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Mar 2011 20:32:55 -0500 Subject: reporead: remove the need for hasattr() checks Ensure all our multivalued attributes already exist on the object beforehand, and add some special sauce to handle the difference between a package without files and a database without files entries. Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 51 ++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 277196e2..35bb7da3 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -87,6 +87,9 @@ class Pkg(object): bare = ( 'name', 'base', 'arch', 'desc', 'filename', 'md5sum', 'url', 'builddate', 'packager' ) number = ( 'csize', 'isize' ) + collections = ( 'depends', 'optdepends', 'conflicts', + 'replaces', 'groups', 'license', 'files' ) + version_re = re.compile(r'^((\d+):)?(.+)-([^-]+)$') def __init__(self, repo): @@ -96,6 +99,11 @@ class Pkg(object): self.epoch = 0 for k in self.bare + self.number: setattr(self, k, None) + for k in self.collections: + setattr(self, k, ()) + # So we can tell the diffence between a package with no files, and a DB + # without files entries + self.has_files = False def populate(self, values): for k, v in values.iteritems(): @@ -104,16 +112,17 @@ class Pkg(object): setattr(self, k, v[0][:254]) elif k in self.number: setattr(self, k, long(v[0])) - elif k == 'force': - setattr(self, k, True) elif k == 'version': 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)) + elif k == 'files': + self.files = v + self.has_files = True else: - # files, depends, etc. + # anything left in collections setattr(self, k, v) @property @@ -219,12 +228,11 @@ def create_multivalued(dbpkg, repopkg, db_attr, repo_attr): '''Populate the simplest of multivalued attributes. These are those that only deal with a 'name' attribute, such as licenses, groups, etc. The input and output objects and attribute names are specified, and everything is - done via hasattr()/getattr().''' + done via getattr().''' collection = getattr(dbpkg, db_attr) collection.all().delete() - if hasattr(repopkg, repo_attr): - for name in getattr(repopkg, repo_attr): - collection.create(name=name) + for name in getattr(repopkg, repo_attr): + collection.create(name=name) def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): if repopkg.base: @@ -260,25 +268,20 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): populate_files(dbpkg, repopkg, force=force) dbpkg.packagedepend_set.all().delete() - if hasattr(repopkg, 'depends'): - for y in repopkg.depends: - create_depend(dbpkg, y) - if hasattr(repopkg, 'optdepends'): - for y in repopkg.optdepends: - create_depend(dbpkg, y, True) + for y in repopkg.depends: + create_depend(dbpkg, y) + for y in repopkg.optdepends: + create_depend(dbpkg, y, True) dbpkg.conflicts.all().delete() - if hasattr(repopkg, 'conflicts'): - for y in repopkg.conflicts: - create_related(Conflict, dbpkg, y) + for y in repopkg.conflicts: + create_related(Conflict, dbpkg, y) dbpkg.provides.all().delete() - if hasattr(repopkg, 'provides'): - for y in repopkg.provides: - create_related(Provision, dbpkg, y, equals_only=True) + for y in repopkg.provides: + create_related(Provision, dbpkg, y, equals_only=True) dbpkg.replaces.all().delete() - if hasattr(repopkg, 'replaces'): - for y in repopkg.replaces: - create_related(Replacement, dbpkg, y) + for y in repopkg.replaces: + create_related(Replacement, dbpkg, y) create_multivalued(dbpkg, repopkg, 'groups', 'groups') create_multivalued(dbpkg, repopkg, 'licenses', 'license') @@ -297,7 +300,7 @@ def populate_files(dbpkg, repopkg, force=False): elif dbpkg.files_last_update > dbpkg.last_update: return # only delete files if we are reading a DB that contains them - if hasattr(repopkg, 'files'): + if repopkg.has_files: dbpkg.packagefile_set.all().delete() logger.info("adding %d files for package %s", len(repopkg.files), dbpkg.pkgname) @@ -311,7 +314,7 @@ def populate_files(dbpkg, repopkg, force=False): is_directory=(filename is None), directory=dirname + '/', filename=filename) - pkgfile.save() + pkgfile.save(force_insert=True) dbpkg.files_last_update = datetime.now() dbpkg.save() -- cgit v1.2.3-2-g168b From 73ea5b8abe5de6457834c2981efaa70985dfa3e6 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 24 Mar 2011 00:43:15 -0500 Subject: Remove dead merchandise link Addresses FS#23399. Signed-off-by: Dan McGee --- templates/public/index.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/templates/public/index.html b/templates/public/index.html index 91a8414c..1f4b2ce5 100644 --- a/templates/public/index.html +++ b/templates/public/index.html @@ -133,8 +133,6 @@ title="T-shirts, mugs, mouse pads, hoodies, posters, skateboards, shoes, etc.">Products via Zazzle
  • T-shirts via Freewear
  • -
  • Merchandise via ProstoPrint
  • Tools

    -- cgit v1.2.3-2-g168b From f6464da0e14047e9db803f4b40c18ba644e75f24 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 24 Mar 2011 00:47:23 -0500 Subject: Add provides to collections list Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 35bb7da3..e26bb800 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -88,7 +88,7 @@ class Pkg(object): 'md5sum', 'url', 'builddate', 'packager' ) number = ( 'csize', 'isize' ) collections = ( 'depends', 'optdepends', 'conflicts', - 'replaces', 'groups', 'license', 'files' ) + 'provides', 'replaces', 'groups', 'license', 'files' ) version_re = re.compile(r'^((\d+):)?(.+)-([^-]+)$') -- cgit v1.2.3-2-g168b From 5f0916a8df8fa3498b7ec3f395e8c3ed49139b31 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 28 Mar 2011 18:26:23 -0500 Subject: Move country filter last in mirror admin Signed-off-by: Dan McGee --- mirrors/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mirrors/admin.py b/mirrors/admin.py index b9c2876a..b7b478de 100644 --- a/mirrors/admin.py +++ b/mirrors/admin.py @@ -60,7 +60,7 @@ class MirrorAdminForm(forms.ModelForm): class MirrorAdmin(admin.ModelAdmin): form = MirrorAdminForm list_display = ('name', 'tier', 'country', 'active', 'public', 'isos', 'admin_email', 'supported_protocols') - list_filter = ('tier', 'country', 'active', 'public') + list_filter = ('tier', 'active', 'public', 'country') search_fields = ('name',) inlines = [ MirrorUrlInlineAdmin, -- cgit v1.2.3-2-g168b From 5df255a5b61117f5e48a638b11470a092b8326c6 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 28 Mar 2011 18:26:55 -0500 Subject: Enhance the mirror details page Get the URLs with their performance data showing up, and simplify the top part for non-authenticated users while adding more detail for logged-in users. Signed-off-by: Dan McGee --- mirrors/views.py | 13 +++- templates/mirrors/mirror_details.html | 111 ++++++++++++++++++++++++++-------- templates/mirrors/mirrors.html | 2 - 3 files changed, 96 insertions(+), 30 deletions(-) diff --git a/mirrors/views.py b/mirrors/views.py index a2b94de8..032a4700 100644 --- a/mirrors/views.py +++ b/mirrors/views.py @@ -104,10 +104,19 @@ def mirror_details(request, name): mirror = get_object_or_404(Mirror, name=name) if not request.user.is_authenticated() and \ (not mirror.public or not mirror.active): - # TODO: maybe this should be 403? but that would leak existence raise Http404 + + status_info = get_mirror_statuses() + checked_urls = [url for url in status_info['urls'] \ + if url.mirror_id == mirror.id] + all_urls = mirror.urls.select_related('protocol') + # get each item from checked_urls and supplement with anything in all_urls + # if it wasn't there + all_urls = set(checked_urls).union(all_urls) + all_urls = sorted(all_urls, key=lambda x: x.url) + return direct_to_template(request, 'mirrors/mirror_details.html', - {'mirror': mirror}) + {'mirror': mirror, 'urls': all_urls}) def status(request): bad_timedelta = datetime.timedelta(days=3) diff --git a/templates/mirrors/mirror_details.html b/templates/mirrors/mirror_details.html index 90baa75d..ae7ec63f 100644 --- a/templates/mirrors/mirror_details.html +++ b/templates/mirrors/mirror_details.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load mirror_status %} {% block title %}Arch Linux - {{ mirror.name }} - Mirror Details{% endblock %} @@ -12,47 +13,105 @@ Name: {{ mirror.name }} - + + Tier: {{ mirror.get_tier_display }} - + + + Country: + {{ mirror.country }} + + + Has ISOs: + {{ mirror.isos|yesno }} + + {% if user.is_authenticated %} + + Public: + {{ mirror.public|yesno }} + + + Active: + {{ mirror.active|yesno }} + + + Rsync IPs: + {{mirror.rsync_ips.all|join:', '}} + + + Admin Email: + {{ mirror.admin_email }} + + + Notes: + {{ mirror.notes|linebreaks }} + + Upstream: - {% if mirror.upstream %} {{ mirror.upstream.name }} {% else %}None{% endif %} - + + Downstream: {% with mirror.downstream as ds_mirrors %} {% if ds_mirrors %} {% for ds in ds_mirrors %} {{ ds.name }}
    + title="Mirror details for {{ ds.name }}">{{ ds.name }} + {% if not ds.active %}(inactive){% endif %} + {% if not ds.public %}(private){% endif %} +
    {% endfor %} - {% else %}None{% endif %} - - {% endwith %} - - Country: - {{ mirror.country }} - - Has ISOs: - {{ mirror.isos|yesno }} - - Protocols: - {{ mirror.supported_protocols }} - - Mirror URLs: - {% with mirror.urls.all as urls %} - {% if urls %} - {% for u in urls %} - {{ u.url }}
    - {% endfor %} - {% else %}None{% endif %} - + {% else %}None{% endif %} {% endwith %} + {% endif %} + + +

    Available URLs

    + + + + + + + + + + + + + + + + + {% for m_url in urls %} + + + + + + + + + + + + {% endfor %} +
    Mirror URLIPv4IPv6Last SyncCompletion %μ Delay (hh:mm)μ Duration (secs)σ Duration (secs)Mirror Score
    {% if m_url.protocol.is_download %}{{ m_url.url }}{% else %}{{ m_url.url }}{% endif %}{{ m_url.has_ipv4|yesno }}{{ m_url.has_ipv6|yesno }}{{ m_url.last_sync|date:'Y-m-d H:i'|default:'unknown' }}{{ m_url.completion_pct|percentage:1 }}{{ m_url.delay|duration|default:'unknown' }}{{ m_url.duration_avg|floatformat:2 }}{{ m_url.duration_stddev|floatformat:2 }}{{ m_url.score|floatformat:1|default:'∞' }}
    +{% load cdn %}{% jquery %} + + + {% endblock %} diff --git a/templates/mirrors/mirrors.html b/templates/mirrors/mirrors.html index 56f23db5..67a678d9 100644 --- a/templates/mirrors/mirrors.html +++ b/templates/mirrors/mirrors.html @@ -15,7 +15,6 @@ {% if user.is_authenticated %} Public Active - Rsync IPs Admin Email Notes {% endif %} @@ -33,7 +32,6 @@ {% if user.is_authenticated %} {{mirror.public|yesno}} {{mirror.active|yesno}} - {{mirror.rsync_ips.all|join:', '}} {{mirror.admin_email}} {{mirror.notes|linebreaks}} {% endif %} -- cgit v1.2.3-2-g168b From 262cea74c16427f47aab80dd59cbf0cb59eeb8c1 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 28 Mar 2011 18:28:09 -0500 Subject: Small package details template cleanup Signed-off-by: Dan McGee --- templates/packages/details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/packages/details.html b/templates/packages/details.html index 4253f0b3..6c8aee3a 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -103,7 +103,7 @@ {% endifequal %} Description: - {% if pkg.pkgdesc %}{{ pkg.pkgdesc }}{% endif %} + {{ pkg.pkgdesc|default:"" }} Upstream URL: {% if pkg.url %} Date: Tue, 29 Mar 2011 02:14:29 -0500 Subject: Ensure durations are sorted correctly The automatic detection started using the builtin time parser instead of our duration parser, causing it to barf on anything > 60 minutes. Signed-off-by: Dan McGee --- templates/mirrors/status.html | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/templates/mirrors/status.html b/templates/mirrors/status.html index 28b2ffea..5724c71c 100644 --- a/templates/mirrors/status.html +++ b/templates/mirrors/status.html @@ -106,12 +106,11 @@ -- cgit v1.2.3-2-g168b From 9fd0995aa5567bc3b2df939cebb02cc6efeaa3b6 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 16 Apr 2011 09:34:31 -0500 Subject: Only include known values in generated search query Signed-off-by: Dan McGee --- packages/views.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/views.py b/packages/views.py index 1587563b..73692d37 100644 --- a/packages/views.py +++ b/packages/views.py @@ -18,6 +18,7 @@ from django.views.generic.simple import direct_to_template from datetime import datetime import string +from urllib import urlencode from main.models import Package, PackageFile from main.models import Arch, Repo, Signoff @@ -108,8 +109,14 @@ def details(request, name='', repo='', arch=''): return direct_to_template(request, 'packages/packages_list.html', context) else: - return redirect("/packages/?arch=%s&repo=%s&q=%s" % ( - arch.lower(), repo.title(), name)) + pkg_data = [ + ('arch', arch.lower()), + ('repo', repo.lower()), + ('q', name), + ] + # only include non-blank values in the query we generate + pkg_data = [(x, y) for x, y in pkg_data if y] + return redirect("/packages/?%s" % urlencode(pkg_data)) def groups(request, arch=None): arches = [] -- cgit v1.2.3-2-g168b From 43964627a31bbf7e583a3aeb0ab6bc5a5a108396 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 18 Apr 2011 13:48:44 -0500 Subject: Update out of date flag screen and email Now that multiple packages get marked out of date whenever this form is processed, have the page and email itself reflect this fact. Signed-off-by: Dan McGee --- packages/urls.py | 1 + packages/views.py | 37 +++++++++++++++++++++++++--------- templates/packages/flag.html | 28 ++++++++++++------------- templates/packages/flag_confirmed.html | 19 +++++++++++++++++ templates/packages/outofdate.txt | 8 +++----- 5 files changed, 64 insertions(+), 29 deletions(-) create mode 100644 templates/packages/flag_confirmed.html diff --git a/packages/urls.py b/packages/urls.py index bfe9f76c..e0362fa2 100644 --- a/packages/urls.py +++ b/packages/urls.py @@ -5,6 +5,7 @@ package_patterns = patterns('packages.views', (r'^files/$', 'files'), (r'^maintainer/$', 'getmaintainer'), (r'^flag/$', 'flag'), + (r'^flag/done/$', 'flag_confirmed', {}, 'package-flag-confirmed'), (r'^unflag/$', 'unflag'), (r'^unflag/all/$', 'unflag_all'), (r'^download/$', 'download'), diff --git a/packages/views.py b/packages/views.py index 73692d37..bcb66413 100644 --- a/packages/views.py +++ b/packages/views.py @@ -367,17 +367,20 @@ class FlagForm(forms.Form): def flag(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) - context = {'pkg': pkg} if pkg.flag_date is not None: # already flagged. do nothing. return direct_to_template(request, 'packages/flagged.html', context) + # find all packages from (hopefully) the same PKGBUILD + pkgs = Package.objects.select_related('arch', 'repo').filter( + pkgbase=pkg.pkgbase, flag_date__isnull=True, + repo__testing=pkg.repo.testing).order_by( + 'pkgname', 'repo__name', 'arch__name') if request.POST: form = FlagForm(request.POST) if form.is_valid() and form.cleaned_data['website'] == '': - # find all packages from (hopefully) the same PKGBUILD - pkgs = Package.objects.filter( - pkgbase=pkg.pkgbase, repo__testing=pkg.repo.testing) + # save the package list for later use + flagged_pkgs = list(pkgs) pkgs.update(flag_date=datetime.utcnow()) maints = pkg.maintainers @@ -394,13 +397,13 @@ def flag(request, name, repo, arch): toemail.append(maint.email) if toemail: - # send notification email to the maintainer + # send notification email to the maintainers t = loader.get_template('packages/outofdate.txt') c = Context({ 'email': form.cleaned_data['email'], 'message': form.cleaned_data['usermessage'], 'pkg': pkg, - 'weburl': pkg.get_full_url(), + 'packages': flagged_pkgs, }) send_mail(subject, t.render(c), @@ -408,14 +411,30 @@ def flag(request, name, repo, arch): toemail, fail_silently=True) - context['confirmed'] = True + return redirect('package-flag-confirmed', name=name, repo=repo, + arch=arch) else: form = FlagForm() - context['form'] = form - + context = { + 'package': pkg, + 'packages': pkgs, + 'form': form + } return direct_to_template(request, 'packages/flag.html', context) +def flag_confirmed(request, name, repo, arch): + pkg = get_object_or_404(Package, + pkgname=name, repo__name__iexact=repo, arch__name=arch) + pkgs = Package.objects.select_related('arch', 'repo').filter( + pkgbase=pkg.pkgbase, flag_date=pkg.flag_date, + repo__testing=pkg.repo.testing).order_by( + 'pkgname', 'repo__name', 'arch__name') + + context = {'package': pkg, 'packages': pkgs} + + return direct_to_template(request, 'packages/flag_confirmed.html', context) + def download(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) diff --git a/templates/packages/flag.html b/templates/packages/flag.html index 35198dff..4a3c6966 100644 --- a/templates/packages/flag.html +++ b/templates/packages/flag.html @@ -1,23 +1,22 @@ {% extends "base.html" %} -{% block title %}Arch Linux - Flag Package - {{ pkg.pkgname }}{% endblock %} +{% block title %}Arch Linux - Flag Package - {{ package.pkgname }}{% endblock %} {% block navbarclass %}anb-packages{% endblock %} {% block content %}
    -{% if confirmed %} -

    Package Flagged

    - -

    Thank you, the maintainers have been notified about {{ pkg.pkgname }}.

    - -

    You can return to the package details page for - {{pkg.pkgname}}.

    -{% else %} -

    Flag Package: {{ pkg.pkgname }}

    +

    Flag Package: {{ package.pkgname }}

    If you notice a package is out-of-date (i.e., there is a newer stable release available), then please notify us using the form below.

    +

    Note that all of the following packages will be marked out of date:

    +
      + {% for pkg in packages %} +
    • {{ pkg.pkgname }} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})
    • + {% endfor %} +
    +

    The message box portion of the flag utility is optional, and meant for short messages only. If you need more than 200 characters for your message, then file a bug report, email the maintainer directly, or send @@ -26,17 +25,16 @@ with your additional text.

    Note: Please do not use this facility if the - package is broken! Use the bug tracker instead.

    + package is broken! Please file a bug instead.

    -

    Please confirm your flag request for {{pkg.pkgname}}:

    +

    Please confirm your flag request for {{package.pkgname}}:

    {% csrf_token %}
    {{ form.as_p }}
    -

    +

    -{% endif %}
    {% endblock %} diff --git a/templates/packages/flag_confirmed.html b/templates/packages/flag_confirmed.html new file mode 100644 index 00000000..02c24f72 --- /dev/null +++ b/templates/packages/flag_confirmed.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% block title %}Arch Linux - Package Flagged - {{ package.pkgname }}{% endblock %} +{% block navbarclass %}anb-packages{% endblock %} + +{% block content %} +
    +

    Package Flagged - {{ package.pkgname }}

    + +

    Thank you, the maintainers have been notified the following packages are out-of-date:

    +
      + {% for pkg in packages %} +
    • {{ pkg.pkgname }} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})
    • + {% endfor %} +
    + +

    You can return to the package details page for + {{package.pkgname}}.

    +
    +{% endblock %} diff --git a/templates/packages/outofdate.txt b/templates/packages/outofdate.txt index 93abea03..4876c316 100644 --- a/templates/packages/outofdate.txt +++ b/templates/packages/outofdate.txt @@ -1,9 +1,7 @@ -{% autoescape off %}{{ email }} wants to notify you that the following package may be out-of-date: +{% autoescape off %}{{ email }} wants to notify you that the following packages may be out-of-date: - Package Name: {{ pkg.pkgname }} - Architecture: {{ pkg.arch.name }} - Repository: {{ pkg.repo.name }} - ({{ weburl }}) +{% for p in packages %} +* {{ p.pkgname }} {{ p.full_version }} [{{ p.repo.name|lower }}] ({{ p.arch.name }}): {{ p.get_full_url }}{% endfor %} {% if message %} The user provided the following additional text: -- cgit v1.2.3-2-g168b From 1e3191ff566bf085f3deefddaa5986cee944dceb Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 18 Apr 2011 14:49:28 -0500 Subject: Never parse generated lastsync file We should be encouraging our mirrors to serve us the original file, not something they create and come up with. Signed-off-by: Dan McGee --- mirrors/management/commands/mirrorcheck.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/mirrors/management/commands/mirrorcheck.py b/mirrors/management/commands/mirrorcheck.py index 51be71ea..44971c4f 100644 --- a/mirrors/management/commands/mirrorcheck.py +++ b/mirrors/management/commands/mirrorcheck.py @@ -52,22 +52,6 @@ class Command(NoArgsCommand): return check_current_mirrors() -def parse_rfc3339_datetime(time_string): - # '2010-09-02 11:05:06+02:00' - m = re.match('^(\d{4})-(\d{2})-(\d{2}) ' - '(\d{2}):(\d{2}):(\d{2})([-+])(\d{2}):(\d{2})', time_string) - if m: - vals = m.groups() - parsed = datetime(int(vals[0]), int(vals[1]), int(vals[2]), - int(vals[3]), int(vals[4]), int(vals[5])) - # now account for time zone offset - sign = vals[6] - offset = timedelta(hours=int(sign + vals[7]), - minutes=int(sign + vals[8])) - # subtract the offset, e.g. '-04:00' should be moved up 4 hours - return parsed - offset - return None - def check_mirror_url(mirror_url): url = mirror_url.url + 'lastsync' logger.info("checking URL %s", url) @@ -78,18 +62,14 @@ def check_mirror_url(mirror_url): data = result.read() result.close() end = time.time() - # lastsync should be an epoch value, but some mirrors - # are creating their own in RFC-3339 format: - # '2010-09-02 11:05:06+02:00' + # lastsync should be an epoch value created by us parsed_time = None try: parsed_time = datetime.utcfromtimestamp(int(data)) except ValueError: # it is bad news to try logging the lastsync value; # sometimes we get a crazy-encoded web page. - logger.info("attempting to parse generated lastsync file" - " from mirror %s", url) - parsed_time = parse_rfc3339_datetime(data) + pass log.last_sync = parsed_time # if we couldn't parse a time, this is a failure -- cgit v1.2.3-2-g168b From 1b91de94357a60ad372abe4213d1fa52f5fc9c9e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 18 Apr 2011 15:10:20 -0500 Subject: mirrors: pylint discovered cleanups Signed-off-by: Dan McGee --- mirrors/management/commands/mirrorcheck.py | 6 ++--- mirrors/management/commands/mirrorresolv.py | 2 +- mirrors/utils.py | 42 ++++++++++++++++------------- mirrors/views.py | 7 +++-- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/mirrors/management/commands/mirrorcheck.py b/mirrors/management/commands/mirrorcheck.py index 44971c4f..ea43d558 100644 --- a/mirrors/management/commands/mirrorcheck.py +++ b/mirrors/management/commands/mirrorcheck.py @@ -13,7 +13,7 @@ from django.core.management.base import NoArgsCommand from django.db import transaction from collections import deque -from datetime import datetime, timedelta +from datetime import datetime import logging import re import socket @@ -134,8 +134,8 @@ class MirrorCheckPool(object): @transaction.commit_on_success def run(self): logger.debug("starting threads") - for t in self.threads: - t.start() + for thread in self.threads: + thread.start() logger.debug("joining on all threads") self.tasks.join() logger.debug("processing log entries") diff --git a/mirrors/management/commands/mirrorresolv.py b/mirrors/management/commands/mirrorresolv.py index 8a628bd4..4e812f2d 100644 --- a/mirrors/management/commands/mirrorresolv.py +++ b/mirrors/management/commands/mirrorresolv.py @@ -49,6 +49,6 @@ def resolve_mirrors(): mirrorurl.has_ipv4, mirrorurl.has_ipv6) mirrorurl.save(force_update=True) except socket.error, e: - logger.warn("error resolving %s: %s", hostname, e) + logger.warn("error resolving %s: %s", mirrorurl.hostname, e) # vim: set ts=4 sw=4 et: diff --git a/mirrors/utils.py b/mirrors/utils.py index 2d88f125..686ec581 100644 --- a/mirrors/utils.py +++ b/mirrors/utils.py @@ -7,6 +7,25 @@ import datetime default_cutoff = datetime.timedelta(hours=24) +def annotate_url(url, delays): + '''Given a MirrorURL object, add a few more attributes to it regarding + status, including completion_pct, delay, and score.''' + url.completion_pct = float(url.success_count) / url.check_count + if url.id in delays: + url_delays = delays[url.id] + url.delay = sum(url_delays, datetime.timedelta()) / len(url_delays) + hours = url.delay.days * 24.0 + url.delay.seconds / 3600.0 + + if url.completion_pct > 0: + divisor = url.completion_pct + else: + # arbitrary small value + divisor = 0.005 + url.score = (hours + url.duration_avg + url.duration_stddev) / divisor + else: + url.delay = None + url.score = None + @cache_function(300) def get_mirror_statuses(cutoff=default_cutoff): cutoff_time = datetime.datetime.utcnow() - cutoff @@ -31,8 +50,8 @@ def get_mirror_statuses(cutoff=default_cutoff): check_time__gte=cutoff_time) delays = {} for log in times: - d = log.check_time - log.last_sync - delays.setdefault(log.url_id, []).append(d) + delay = log.check_time - log.last_sync + delays.setdefault(log.url_id, []).append(delay) if urls: last_check = max([u.last_check for u in urls]) @@ -44,29 +63,14 @@ def get_mirror_statuses(cutoff=default_cutoff): check_frequency = (check_info['mx'] - check_info['mn']) \ / (num_checks - 1) else: - check_frequency = None; + check_frequency = None else: last_check = None num_checks = 0 check_frequency = None for url in urls: - url.completion_pct = float(url.success_count) / url.check_count - if url.id in delays: - url_delays = delays[url.id] - d = sum(url_delays, datetime.timedelta()) / len(url_delays) - url.delay = d - hours = d.days * 24.0 + d.seconds / 3600.0 - - if url.completion_pct > 0: - divisor = url.completion_pct - else: - # arbitrary small value - divisor = 0.005 - url.score = (hours + url.duration_avg + url.duration_stddev) / divisor - else: - url.delay = None - url.score = None + annotate_url(url, delays) return { 'cutoff': cutoff, diff --git a/mirrors/views.py b/mirrors/views.py index 69592146..f03a2e8a 100644 --- a/mirrors/views.py +++ b/mirrors/views.py @@ -1,6 +1,5 @@ from django import forms from django.core.serializers.json import DjangoJSONEncoder -from django.db.models import Avg, Count, Max, Min, StdDev from django.db.models import Q from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 @@ -96,11 +95,11 @@ def find_mirrors(request, countries=None, protocols=None, use_status=False, mimetype='text/plain') def mirrors(request): - mirrors = Mirror.objects.select_related().order_by('tier', 'country') + mirror_list = Mirror.objects.select_related().order_by('tier', 'country') if not request.user.is_authenticated(): - mirrors = mirrors.filter(public=True, active=True) + mirror_list = mirror_list.filter(public=True, active=True) return direct_to_template(request, 'mirrors/mirrors.html', - {'mirror_list': mirrors}) + {'mirror_list': mirror_list}) def mirror_details(request, name): mirror = get_object_or_404(Mirror, name=name) -- cgit v1.2.3-2-g168b From 08ce9c5cd9a5d2dc0c15ee8c88ce7b78748339e5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 18 Apr 2011 15:19:24 -0500 Subject: packages: pylint suggested cleanups Signed-off-by: Dan McGee --- packages/views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/views.py b/packages/views.py index bcb66413..59c685bd 100644 --- a/packages/views.py +++ b/packages/views.py @@ -171,7 +171,7 @@ class LimitTypedChoiceField(forms.TypedChoiceField): try: coerce_limit_value(value) return True - except ValueError, TypeError: + except (ValueError, TypeError): return False class PackageSearchForm(forms.Form): @@ -226,7 +226,7 @@ def search(request, page=None): packages = packages.filter(pkgbase__in=inner_q) if form.cleaned_data['flagged'] == 'Flagged': - packages=packages.filter(flag_date__isnull=False) + packages = packages.filter(flag_date__isnull=False) elif form.cleaned_data['flagged'] == 'Not Flagged': packages = packages.filter(flag_date__isnull=True) @@ -369,7 +369,7 @@ def flag(request, name, repo, arch): pkgname=name, repo__name__iexact=repo, arch__name=arch) if pkg.flag_date is not None: # already flagged. do nothing. - return direct_to_template(request, 'packages/flagged.html', context) + return direct_to_template(request, 'packages/flagged.html', {'pkg': pkg}) # find all packages from (hopefully) the same PKGBUILD pkgs = Package.objects.select_related('arch', 'repo').filter( pkgbase=pkg.pkgbase, flag_date__isnull=True, @@ -445,13 +445,13 @@ def download(request, name, repo, arch): if pkg.arch.agnostic: # grab the first non-any arch to fake the download path arch = Arch.objects.exclude(agnostic=True)[0].name - details = { + values = { 'host': mirrorurl.url, 'arch': arch, 'repo': pkg.repo.name.lower(), 'file': pkg.filename, } - url = string.Template('${host}${repo}/os/${arch}/${file}').substitute(details) + url = string.Template('${host}${repo}/os/${arch}/${file}').substitute(values) return redirect(url) def arch_differences(request): -- cgit v1.2.3-2-g168b From d8022fd5720a8367a03bbff58668ed701a0bebcf Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 18 Apr 2011 23:00:30 -0500 Subject: Add a "Report a Bug" link We need Flyspray category data to make this more useful, and we can prefill the Subject and Category fields (along with putting it on the right project). Implements FS#23751. Signed-off-by: Dan McGee --- main/admin.py | 3 +- .../0048_auto__add_field_repo_bugs_category.py | 158 +++++++++++++++++++++ main/models.py | 11 ++ templates/packages/details.html | 1 + 4 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 main/migrations/0048_auto__add_field_repo_bugs_category.py diff --git a/main/admin.py b/main/admin.py index f93838d3..e86e5cab 100644 --- a/main/admin.py +++ b/main/admin.py @@ -14,7 +14,8 @@ class ArchAdmin(admin.ModelAdmin): search_fields = ('name',) class RepoAdmin(admin.ModelAdmin): - list_display = ('name', 'testing', 'staging', 'bugs_project', 'svn_root') + list_display = ('name', 'testing', 'staging', 'bugs_project', + 'bugs_category', 'svn_root') list_filter = ('testing', 'staging') search_fields = ('name',) diff --git a/main/migrations/0048_auto__add_field_repo_bugs_category.py b/main/migrations/0048_auto__add_field_repo_bugs_category.py new file mode 100644 index 00000000..30575126 --- /dev/null +++ b/main/migrations/0048_auto__add_field_repo_bugs_category.py @@ -0,0 +1,158 @@ +# 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.add_column('repos', 'bugs_category', self.gf('django.db.models.fields.SmallIntegerField')(default=0), keep_default=False) + + def backwards(self, orm): + db.delete_column('repos', 'bugs_category') + + 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_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'}) + }, + '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', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'main.todolistpkg': { + 'Meta': {'unique_together': "(('list', 'pkg'),)", 'object_name': 'TodolistPkg', 'db_table': "'todolist_pkgs'"}, + 'complete': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Todolist']"}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + '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 772d85bb..d84a6af3 100644 --- a/main/models.py +++ b/main/models.py @@ -8,6 +8,7 @@ from packages.models import PackageRelation from datetime import datetime from itertools import groupby import pytz +from urllib import urlencode class UserProfile(models.Model): notify = models.BooleanField( @@ -89,6 +90,8 @@ class Repo(models.Model): help_text="Is this repo meant for package staging?") bugs_project = models.SmallIntegerField(default=1, help_text="Flyspray project ID for this repository.") + bugs_category = models.SmallIntegerField(default=0, + help_text="Flyspray category ID for this repository.") svn_root = models.CharField(max_length=64, help_text="SVN root (e.g. path) for this repository.") @@ -294,6 +297,14 @@ class Package(models.Model): return "https://bugs.archlinux.org/?project=%d&string=%s" % \ (self.repo.bugs_project, self.pkgname) + def get_bug_report_link(self): + data = { + 'project': self.repo.bugs_project, + 'product_category': self.repo.bugs_category, + 'item_summary': '[%s]' % self.pkgname, + } + return "https://bugs.archlinux.org/newtask?%s" % urlencode(data) + def is_same_version(self, other): 'is this package similar, name and version-wise, to another' return self.pkgname == other.pkgname \ diff --git a/templates/packages/details.html b/templates/packages/details.html index 6c8aee3a..4c669892 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -18,6 +18,7 @@
  • SVN Entries ({{pkg.repo|lower}}-{{pkg.arch}})
  • SVN Entries (trunk)
  • Bug Reports
  • +
  • Report a Bug
  • {% if pkg.flag_date %}
  • Flagged out-of-date on {{ pkg.flag_date|date }}
  • {% with pkg.in_testing as tp %}{% if tp %} -- cgit v1.2.3-2-g168b From f1f01ecf0216441dd66f3bc6afc14fe104de291f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 18 Apr 2011 23:26:10 -0500 Subject: Reimplement links code as template tags These were starting to get a bit too much inside the model itself, and they don't really belong there as they are view layer concerns anyway. Signed-off-by: Dan McGee --- main/models.py | 24 --------------------- packages/templatetags/package_extras.py | 37 +++++++++++++++++++++++++++++++-- templates/packages/details.html | 8 +++---- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/main/models.py b/main/models.py index d84a6af3..e8f189e7 100644 --- a/main/models.py +++ b/main/models.py @@ -8,7 +8,6 @@ from packages.models import PackageRelation from datetime import datetime from itertools import groupby import pytz -from urllib import urlencode class UserProfile(models.Model): notify = models.BooleanField( @@ -282,29 +281,6 @@ class Package(models.Model): return Package.objects.filter(arch__in=self.applicable_arches(), repo__testing=self.repo.testing, pkgbase=self.pkgbase).exclude(id=self.id) - def get_svn_link(self, svnpath): - linkbase = "http://projects.archlinux.org/svntogit/%s.git/tree/%s/%s/" - return linkbase % (self.repo.svn_root, self.pkgbase, svnpath) - - def get_arch_svn_link(self): - repo = self.repo.name.lower() - return self.get_svn_link("repos/%s-%s" % (repo, self.arch.name)) - - def get_trunk_svn_link(self): - return self.get_svn_link("trunk") - - def get_bugs_link(self): - return "https://bugs.archlinux.org/?project=%d&string=%s" % \ - (self.repo.bugs_project, self.pkgname) - - def get_bug_report_link(self): - data = { - 'project': self.repo.bugs_project, - 'product_category': self.repo.bugs_category, - 'item_summary': '[%s]' % self.pkgname, - } - return "https://bugs.archlinux.org/newtask?%s" % urlencode(data) - def is_same_version(self, other): 'is this package similar, name and version-wise, to another' return self.pkgname == other.pkgname \ diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index dd5b9347..e089b723 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -1,4 +1,4 @@ -import urllib +from urllib import urlencode, quote as urlquote try: from urlparse import parse_qs except ImportError: @@ -22,7 +22,7 @@ class BuildQueryStringNode(template.Node): qs['sort'] = ['-' + self.sortfield] else: qs['sort'] = [self.sortfield] - return urllib.urlencode(qs, True) + return urlencode(qs, True) @register.tag(name='buildsortqs') def do_buildsortqs(parser, token): @@ -48,4 +48,37 @@ def userpkgs(user): ) return '' + +def svn_link(package, svnpath): + '''Helper function for the two real SVN link methods.''' + parts = (package.repo.svn_root, package.pkgbase, svnpath) + linkbase = "http://projects.archlinux.org/svntogit/%s.git/tree/%s/%s/" + return linkbase % tuple(urlquote(part) for part in parts) + +@register.simple_tag +def svn_arch(package): + repo = package.repo.name.lower() + return svn_link(package, "repos/%s-%s" % (repo, package.arch.name)) + +@register.simple_tag +def svn_trunk(package): + return svn_link(package, "trunk") + +@register.simple_tag +def bugs_list(package): + data = { + 'project': package.repo.bugs_project, + 'string': package.pkgname, + } + return "https://bugs.archlinux.org/?%s" % urlencode(data) + +@register.simple_tag +def bug_report(package): + data = { + 'project': package.repo.bugs_project, + 'product_category': package.repo.bugs_category, + 'item_summary': '[%s]' % package.pkgname, + } + return "https://bugs.archlinux.org/newtask?%s" % urlencode(data) + # vim: set ts=4 sw=4 et: diff --git a/templates/packages/details.html b/templates/packages/details.html index 4c669892..1926abc2 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -15,10 +15,10 @@

    Package Actions

      -
    • SVN Entries ({{pkg.repo|lower}}-{{pkg.arch}})
    • -
    • SVN Entries (trunk)
    • -
    • Bug Reports
    • -
    • Report a Bug
    • +
    • SVN Entries ({{pkg.repo|lower}}-{{pkg.arch}})
    • +
    • SVN Entries (trunk)
    • +
    • Bug Reports
    • +
    • Report a Bug
    • {% if pkg.flag_date %}
    • Flagged out-of-date on {{ pkg.flag_date|date }}
    • {% with pkg.in_testing as tp %}{% if tp %} -- cgit v1.2.3-2-g168b From e6717510a0a7976fca1ccd3e5aaf1a16123a1ad4 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 23 Apr 2011 14:29:29 -0500 Subject: Update repos fixture with new fields Signed-off-by: Dan McGee --- main/fixtures/repos.json | 58 ++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/main/fixtures/repos.json b/main/fixtures/repos.json index fae96f85..5fd918bb 100644 --- a/main/fixtures/repos.json +++ b/main/fixtures/repos.json @@ -3,70 +3,84 @@ "pk": 5, "model": "main.repo", "fields": { - "svn_root": "community", - "testing": false, + "bugs_category": 33, + "staging": false, "name": "Community", - "bugs_project": 5 + "bugs_project": 5, + "svn_root": "community", + "testing": false } }, { "pk": 6, "model": "main.repo", "fields": { - "svn_root": "community", - "testing": true, + "bugs_category": 41, + "staging": false, "name": "Community-Testing", - "bugs_project": 5 + "bugs_project": 5, + "svn_root": "community", + "testing": true } }, { "pk": 1, "model": "main.repo", "fields": { - "svn_root": "packages", - "testing": false, + "bugs_category": 31, + "staging": false, "name": "Core", - "bugs_project": 1 + "bugs_project": 1, + "svn_root": "packages", + "testing": false } }, { "pk": 2, "model": "main.repo", "fields": { - "svn_root": "packages", - "testing": false, + "bugs_category": 2, + "staging": false, "name": "Extra", - "bugs_project": 1 + "bugs_project": 1, + "svn_root": "packages", + "testing": false } }, { "pk": 7, "model": "main.repo", "fields": { - "svn_root": "community", - "testing": false, + "bugs_category": 46, + "staging": false, "name": "Multilib", - "bugs_project": 5 + "bugs_project": 5, + "svn_root": "community", + "testing": false } }, { "pk": 8, "model": "main.repo", "fields": { - "svn_root": "community", - "testing": true, + "bugs_category": 46, + "staging": false, "name": "Multilib-Testing", - "bugs_project": 5 + "bugs_project": 5, + "svn_root": "community", + "testing": true } }, { "pk": 3, "model": "main.repo", "fields": { - "svn_root": "packages", - "testing": true, + "bugs_category": 10, + "staging": false, "name": "Testing", - "bugs_project": 1 + "bugs_project": 1, + "svn_root": "packages", + "testing": true } } -] \ No newline at end of file +] -- cgit v1.2.3-2-g168b From 381e0a787205af530ae11bac1b1a17e567eecc84 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 25 Apr 2011 18:09:39 -0500 Subject: Developer reports This commit adds four initial developer reports that are hopefully useful to developers and packages in checking up on the state of things. They include: * big : the 100 biggest packages in the repos * old : packages built > 2 years ago * uncompressed-man : self-explanatory * uncompressed-info : self-explanatory There should obviously be some sort of index page to access all of these, so that will be coming soon. Signed-off-by: Dan McGee --- devel/urls.py | 9 ++++--- devel/views.py | 54 +++++++++++++++++++++++++++++++++++---- main/templatetags/attributes.py | 21 ++++++++++++++++ templates/devel/packages.html | 56 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 main/templatetags/attributes.py create mode 100644 templates/devel/packages.html diff --git a/devel/urls.py b/devel/urls.py index 41be2b31..9bf50f45 100644 --- a/devel/urls.py +++ b/devel/urls.py @@ -1,12 +1,13 @@ from django.conf.urls.defaults import patterns urlpatterns = patterns('devel.views', - (r'^$', 'index'), + (r'^admin_log/$','admin_log'), + (r'^admin_log/(?P.*)/$','admin_log'), (r'^clock/$', 'clock'), - (r'^profile/$', 'change_profile'), + (r'^$', 'index'), (r'^newuser/$', 'new_user_form'), - (r'^admin_log/(?P.*)/$','admin_log'), - (r'^admin_log/$','admin_log'), + (r'^profile/$', 'change_profile'), + (r'^reports/(?P.*)/$', 'report'), ) # vim: set ts=4 sw=4 et: diff --git a/devel/views.py b/devel/views.py index b61e605f..01d54e6f 100644 --- a/devel/views.py +++ b/devel/views.py @@ -6,19 +6,22 @@ from django.contrib.auth.models import User, Group from django.contrib.sites.models import Site from django.core.mail import send_mail from django.db import transaction +from django.db.models import Q +from django.http import Http404 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 -from main.models import Package, TodolistPkg +from main.models import Package, PackageFile, TodolistPkg from main.models import Arch, Repo from main.models import UserProfile from packages.models import PackageRelation from todolists.utils import get_annotated_todolists from .utils import get_annotated_maintainers -import datetime +from datetime import datetime, timedelta +import operator import pytz import random from string import ascii_letters, digits @@ -26,7 +29,7 @@ from string import ascii_letters, digits @login_required @never_cache def index(request): - '''the Developer dashboard''' + '''the developer dashboard''' inner_q = PackageRelation.objects.filter(user=request.user).values('pkgbase') flagged = Package.objects.select_related('arch', 'repo').filter( flag_date__isnull=False, pkgbase__in=inner_q).order_by('pkgname') @@ -70,8 +73,8 @@ def clock(request): 'username').select_related('userprofile') # now annotate each dev object with their current time - now = datetime.datetime.now() - utc_now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc) + now = datetime.now() + utc_now = datetime.utcnow().replace(tzinfo=pytz.utc) for dev in devs: # Work around https://bugs.launchpad.net/pytz/+bug/718673 timezone = str(dev.userprofile.time_zone) @@ -123,6 +126,47 @@ def change_profile(request): return direct_to_template(request, 'devel/profile.html', {'form': form, 'profile_form': profile_form}) +@login_required +def report(request, report): + title = "Developer Report" + packages = Package.objects.select_related('arch', 'repo') + names = attrs = None + if report == "old": + title = "Packages last built more than two years ago" + cutoff = datetime.now() - timedelta(days=730) + packages = packages.filter(build_date__lt=cutoff).order_by('build_date') + elif report == "big": + title = "100 largest compressed packages" + packages = packages.order_by('-compressed_size')[:100] + names = [ 'Compressed Size', 'Installed Size' ] + attrs = [ 'compressed_size', 'installed_size' ] + elif report == "uncompressed-man": + title = "Packages with uncompressed manpages" + # magic going on here! Checking for all '.1'...'.9' extensions + invalid_endings = [Q(filename__endswith='.%d' % n) for n in range(1,10)] + invalid_endings.append(Q(filename__endswith='.n')) + bad_files = PackageFile.objects.filter(Q(directory__contains='man') & ( + reduce(operator.or_, invalid_endings)) + ).values_list('pkg_id', flat=True).distinct() + packages = packages.filter(id__in=set(bad_files)) + elif report == "uncompressed-info": + title = "Packages with uncompressed infopages" + bad_files = PackageFile.objects.filter(directory__contains='/info', + filename__endswith='.info').values_list( + 'pkg_id', flat=True).distinct() + packages = packages.filter(id__in=set(bad_files)) + else: + raise Http404 + + context = { + 'title': title, + 'packages': packages, + 'column_names': names, + 'column_attrs': attrs, + } + return direct_to_template(request, 'devel/packages.html', context) + + class NewUserForm(forms.ModelForm): username = forms.CharField(max_length=30) private_email = forms.EmailField() diff --git a/main/templatetags/attributes.py b/main/templatetags/attributes.py new file mode 100644 index 00000000..bd4ccf3d --- /dev/null +++ b/main/templatetags/attributes.py @@ -0,0 +1,21 @@ +import re +from django import template +from django.conf import settings + +numeric_test = re.compile("^\d+$") +register = template.Library() + +def attribute(value, arg): + """Gets an attribute of an object dynamically from a string name""" + if hasattr(value, str(arg)): + return getattr(value, arg) + elif hasattr(value, 'has_key') and value.has_key(arg): + return value[arg] + elif numeric_test.match(str(arg)) and len(value) > int(arg): + return value[int(arg)] + else: + return settings.TEMPLATE_STRING_IF_INVALID + +register.filter('attribute', attribute) + +# vim: set ts=4 sw=4 et: diff --git a/templates/devel/packages.html b/templates/devel/packages.html new file mode 100644 index 00000000..3b511c98 --- /dev/null +++ b/templates/devel/packages.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} +{% load attributes %} + +{% block title %}Arch Linux - {{ title }}{% endblock %} + +{% block content %} +
      +

      {{ title }}

      + + + + + + + + + + + + {% for name in column_names %} + + {% endfor %} + + + + {% for pkg in packages %} + + + + + {% if pkg.flag_date %} + + {% else %} + + {% endif %} + + + + + {% for attr in column_attrs %} + + {% endfor %} + + {% endfor %} + +
      ArchRepoNameVersionDescriptionLast UpdatedBuild DateFlag Date{{ name }}
      {{ pkg.arch.name }}{{ pkg.repo.name|capfirst }}{{ pkg.pkgname }}{{ pkg.full_version }}{{ pkg.full_version }}{{ pkg.pkgdesc }}{{ pkg.last_update|date }}{{ pkg.build_date|date }}{{ pkg.flag_date|date }}{{ pkg|attribute:attr }}
      +
      +{% load cdn %}{% jquery %} + + +{% endblock %} -- cgit v1.2.3-2-g168b From 174d04ad0334b1c441bc0237e3e2ed8f581575ef Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 25 Apr 2011 18:17:56 -0500 Subject: Add links to developer reports Signed-off-by: Dan McGee --- templates/devel/index.html | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/templates/devel/index.html b/templates/devel/index.html index 2eae0815..601de752 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -98,6 +98,14 @@ +

      Developer Reports

      + +
    {% cache 60 dev-dash-by-arch %} -- cgit v1.2.3-2-g168b From 1547c7c49a1852852ffbac0737d0ffdf54addda9 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 1 Mar 2011 18:47:03 +0100 Subject: isotests: entry and listing of release engineering tests Add a new project for entry and listing of testing results for our release ISOs. This will assist the release engineering team with determining a good ISO to make into the real deal. Signed-off-by: Dan McGee --- isotests/__init__.py | 0 isotests/admin.py | 10 +++++ isotests/fixtures/bootloaders.json | 23 ++++++++++ isotests/fixtures/filesystems.json | 23 ++++++++++ isotests/fixtures/hardware.json | 44 +++++++++++++++++++ isotests/fixtures/installtype.json | 30 +++++++++++++ isotests/fixtures/modules.json | 37 ++++++++++++++++ isotests/fixtures/source.json | 23 ++++++++++ isotests/models.py | 87 ++++++++++++++++++++++++++++++++++++++ isotests/templates/iso_list.html | 2 + isotests/tests.py | 23 ++++++++++ isotests/urls.py | 16 +++++++ isotests/views.py | 24 +++++++++++ settings.py | 1 + templates/isotests/add.html | 13 ++++++ templates/isotests/iso_list.html | 18 ++++++++ templates/isotests/test_list.html | 18 ++++++++ 17 files changed, 392 insertions(+) create mode 100644 isotests/__init__.py create mode 100644 isotests/admin.py create mode 100644 isotests/fixtures/bootloaders.json create mode 100644 isotests/fixtures/filesystems.json create mode 100644 isotests/fixtures/hardware.json create mode 100644 isotests/fixtures/installtype.json create mode 100644 isotests/fixtures/modules.json create mode 100644 isotests/fixtures/source.json create mode 100644 isotests/models.py create mode 100644 isotests/templates/iso_list.html create mode 100644 isotests/tests.py create mode 100644 isotests/urls.py create mode 100644 isotests/views.py create mode 100644 templates/isotests/add.html create mode 100644 templates/isotests/iso_list.html create mode 100644 templates/isotests/test_list.html diff --git a/isotests/__init__.py b/isotests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/isotests/admin.py b/isotests/admin.py new file mode 100644 index 00000000..03b5fbab --- /dev/null +++ b/isotests/admin.py @@ -0,0 +1,10 @@ +from isotests.models import * +from django.contrib import admin + +admin.site.register(Iso) +admin.site.register(Hardware) +admin.site.register(InstallType) +admin.site.register(Source) +admin.site.register(Filesystem) +admin.site.register(Module) +admin.site.register(Bootloader) diff --git a/isotests/fixtures/bootloaders.json b/isotests/fixtures/bootloaders.json new file mode 100644 index 00000000..545b5c0c --- /dev/null +++ b/isotests/fixtures/bootloaders.json @@ -0,0 +1,23 @@ +[ + { + "pk": 1, + "model": "isotests.bootloader", + "fields": { + "name": "grub" + } + }, + { + "pk": 2, + "model": "isotests.bootloader", + "fields": { + "name": "syslinux" + } + }, + { + "pk": 3, + "model": "isotests.bootloader", + "fields": { + "name": "other/manual" + } + } +] diff --git a/isotests/fixtures/filesystems.json b/isotests/fixtures/filesystems.json new file mode 100644 index 00000000..4d3f1bc4 --- /dev/null +++ b/isotests/fixtures/filesystems.json @@ -0,0 +1,23 @@ +[ + { + "pk": 1, + "model": "isotests.filesystem", + "fields": { + "name": "autoprepare (check the installed system, incl fstab)" + } + }, + { + "pk": 2, + "model": "isotests.filesystem", + "fields": { + "name": "manual" + } + }, + { + "pk": 3, + "model": "isotests.filesystem", + "fields": { + "name": "from config file" + } + } +] diff --git a/isotests/fixtures/hardware.json b/isotests/fixtures/hardware.json new file mode 100644 index 00000000..c9169146 --- /dev/null +++ b/isotests/fixtures/hardware.json @@ -0,0 +1,44 @@ +[ + { + "pk": 1, + "model": "isotests.hardware", + "fields": { + "name": "virtualbox" + } + }, + { + "pk": 2, + "model": "isotests.hardware", + "fields": { + "name": "qemu" + } + }, + { + "pk": 3, + "model": "isotests.hardware", + "fields": { + "name": "intel i686" + } + }, + { + "pk": 4, + "model": "isotests.hardware", + "fields": { + "name": "intel x86_64" + } + }, + { + "pk": 5, + "model": "isotests.hardware", + "fields": { + "name": "amd i686" + } + }, + { + "pk": 6, + "model": "isotests.hardware", + "fields": { + "name": "amd x86_64" + } + } +] diff --git a/isotests/fixtures/installtype.json b/isotests/fixtures/installtype.json new file mode 100644 index 00000000..d23bd4b2 --- /dev/null +++ b/isotests/fixtures/installtype.json @@ -0,0 +1,30 @@ +[ + { + "pk": 1, + "model": "isotests.installtype", + "fields": { + "name": "automatic install generic example" + } + }, + { + "pk": 2, + "model": "isotests.installtype", + "fields": { + "name": "automatic install fancy example" + } + }, + { + "pk": 3, + "model": "isotests.installtype", + "fields": { + "name": "automatic install custom config (specify in comments)" + } + }, + { + "pk": 4, + "model": "isotests.installtype", + "fields": { + "name": "interactive install" + } + } +] diff --git a/isotests/fixtures/modules.json b/isotests/fixtures/modules.json new file mode 100644 index 00000000..27d04c7a --- /dev/null +++ b/isotests/fixtures/modules.json @@ -0,0 +1,37 @@ +[ + { + "pk": 1, + "model": "isotests.module", + "fields": { + "name": "lvm2" + } + }, + { + "pk": 2, + "model": "isotests.module", + "fields": { + "name": "dm_crypt" + } + }, + { + "pk": 3, + "model": "isotests.module", + "fields": { + "name": "softraid" + } + }, + { + "pk": 4, + "model": "isotests.module", + "fields": { + "name": "nilfs2" + } + }, + { + "pk": 5, + "model": "isotests.module", + "fields": { + "name": "btrfs" + } + } +] diff --git a/isotests/fixtures/source.json b/isotests/fixtures/source.json new file mode 100644 index 00000000..1bf835a1 --- /dev/null +++ b/isotests/fixtures/source.json @@ -0,0 +1,23 @@ +[ + { + "pk": 1, + "model": "isotests.source", + "fields": { + "name": "net install manual networking config (Check that it works + rc.conf, resolv.conf, mirrorlist)" + } + }, + { + "pk": 2, + "model": "isotests.source", + "fields": { + "name": "net install dhcp (Check that it works + rc.conf)" + } + }, + { + "pk": 3, + "model": "isotests.source", + "fields": { + "name": "core" + } + } +] diff --git a/isotests/models.py b/isotests/models.py new file mode 100644 index 00000000..1eaca163 --- /dev/null +++ b/isotests/models.py @@ -0,0 +1,87 @@ +from django.db import models + +# Create your models here. +class Iso(models.Model): + date = models.DateField() + + def __unicode__(self): + return str(self.date) + +class Hardware(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + +class InstallType(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + +class Source(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + +class Filesystem(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + +class Module(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + +class Bootloader(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + +class Test(models.Model): + ARCH_CHOICES = ( + ('d86', 'dual, option i686'), + ('d64', 'dual, option x86_64'), + ('x86', 'i686'), + ('x64', 'x86_64') + ) + + ISOTYPE_CHOICES = ( + ('c', 'core'), + ('n', 'net') + ) + + BOOTTYPE_CHOICES = ( + ('o', 'optical'), + ('u', 'usb'), + ('p', 'pxe') + ) + + CLOCK_CHOICES = ( + ('d', 'default'), + ('m', 'configured manually'), + ('n', 'NTP') + ) + + user_name = models.CharField(max_length=500) + user_email = models.EmailField() + iso = models.ForeignKey(Iso) + arch = models.CharField(max_length=3, choices=ARCH_CHOICES) + isotype = models.CharField(max_length=1, choices=ISOTYPE_CHOICES) + boottype = models.CharField(max_length=1, choices=BOOTTYPE_CHOICES) + hardwaretype = models.ForeignKey(Hardware) + installtype = models.ForeignKey(InstallType) + source = models.ForeignKey(Source) + clock = models.CharField(max_length=1, choices=CLOCK_CHOICES) + filesystem = models.ForeignKey(Filesystem) + ms = models.ManyToManyField(Module) + rollback = models.BooleanField() + rollback_filesystem = models.ForeignKey(Filesystem, related_name="rollback_test") + rollback_modules = models.ManyToManyField(Module, related_name="rollback_test") + success = models.BooleanField() + comments = models.TextField() diff --git a/isotests/templates/iso_list.html b/isotests/templates/iso_list.html new file mode 100644 index 00000000..06572739 --- /dev/null +++ b/isotests/templates/iso_list.html @@ -0,0 +1,2 @@ +hello there +bla diff --git a/isotests/tests.py b/isotests/tests.py new file mode 100644 index 00000000..2247054b --- /dev/null +++ b/isotests/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/isotests/urls.py b/isotests/urls.py new file mode 100644 index 00000000..fdde9e3b --- /dev/null +++ b/isotests/urls.py @@ -0,0 +1,16 @@ +from django.conf.urls.defaults import patterns +from isotests.models import Test + +info_dict = { + 'queryset': Test.objects.all() +} + +urlpatterns = patterns('isotests.views', + (r'^add/$', 'add_result') +) + +urlpatterns += patterns('', + (r'^$', 'django.views.generic.list_detail.object_list', info_dict) +) + +# vim: set ts=4 sw=4 et: diff --git a/isotests/views.py b/isotests/views.py new file mode 100644 index 00000000..742be8ff --- /dev/null +++ b/isotests/views.py @@ -0,0 +1,24 @@ +# Create your views here. +from django.http import HttpResponse, HttpResponseRedirect +from django.forms import ModelForm +from isotests.models import Test +from django.shortcuts import render_to_response +from django.template import RequestContext + +class TestForm(ModelForm): + class Meta: + model = Test + +def add_result(request): + if request.method == 'POST': # If the form has been submitted... + form = TestForm(request.POST) # A form bound to the post data + if form.is_valid(): # All validation rules pass + form.save() + return HttpResponseRedirect('/isotests') # Redirect after POST + else: + form = TestForm() # An unbound form + + return render_to_response('isotests/add.html', { + 'form': form, + }, + context_instance=RequestContext(request)) diff --git a/settings.py b/settings.py index 1d26d9eb..107baa17 100644 --- a/settings.py +++ b/settings.py @@ -104,6 +104,7 @@ INSTALLED_APPS = ( 'devel', 'public', 'south', # database migration support + 'isotests', ) ## Import local settings diff --git a/templates/isotests/add.html b/templates/isotests/add.html new file mode 100644 index 00000000..07d3ed81 --- /dev/null +++ b/templates/isotests/add.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% block title %}Arch Linux - Test Result Entry{% endblock %} + +{% block content %} +
    +

    Arch releng iso build test result entry

    +
    {% csrf_token %} + {{ form.as_p }} + +
    +
    +{% endblock %} diff --git a/templates/isotests/iso_list.html b/templates/isotests/iso_list.html new file mode 100644 index 00000000..f94bbe1a --- /dev/null +++ b/templates/isotests/iso_list.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Arch Linux - Testresults{% endblock %} + +{% block content %} +{% if object_list %} +
    +

    Arch releng iso buid test results

    +
      + {% for iso in object_list %} +
    • {{ iso }}
    • + {% endfor %} +
    + {% else %} +

    No tests are available.

    + {% endif %} +
    +{% endblock %} diff --git a/templates/isotests/test_list.html b/templates/isotests/test_list.html new file mode 100644 index 00000000..1ef39a4c --- /dev/null +++ b/templates/isotests/test_list.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} + +{% block title %}Arch Linux - Testresults{% endblock %} + +{% block content %} +
    +

    Arch releng iso build test results

    + {% if object_list %} + + {% else %} +

    No test results are available.

    + {% endif %} +
    +{% endblock %} -- cgit v1.2.3-2-g168b From f4229daac60fa90cbf8d77bfdffd88a467869b3c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 1 Mar 2011 20:43:37 +0100 Subject: isotests: view updates, choices->models, show results, admin * Started changing the view portion * Changed choices to models * Show the latest failed/succeeded tests on results page * Added some more admin pages Signed-off-by: Dan McGee --- isotests/admin.py | 4 + isotests/fixtures/architecture.json | 30 ++++++ isotests/fixtures/boottype.json | 23 +++++ isotests/fixtures/clockchoices.json | 23 +++++ isotests/fixtures/isotypes.json | 16 +++ isotests/models.py | 110 +++++++++++++++------ isotests/urls.py | 6 +- isotests/views.py | 42 ++++++-- templates/isotests/results.html | 190 ++++++++++++++++++++++++++++++++++++ 9 files changed, 402 insertions(+), 42 deletions(-) create mode 100644 isotests/fixtures/architecture.json create mode 100644 isotests/fixtures/boottype.json create mode 100644 isotests/fixtures/clockchoices.json create mode 100644 isotests/fixtures/isotypes.json create mode 100644 templates/isotests/results.html diff --git a/isotests/admin.py b/isotests/admin.py index 03b5fbab..b03ddea3 100644 --- a/isotests/admin.py +++ b/isotests/admin.py @@ -2,9 +2,13 @@ from isotests.models import * from django.contrib import admin admin.site.register(Iso) +admin.site.register(Architecture) +admin.site.register(Isotype) +admin.site.register(Boottype) admin.site.register(Hardware) admin.site.register(InstallType) admin.site.register(Source) +admin.site.register(Clockchoice) admin.site.register(Filesystem) admin.site.register(Module) admin.site.register(Bootloader) diff --git a/isotests/fixtures/architecture.json b/isotests/fixtures/architecture.json new file mode 100644 index 00000000..a21100ba --- /dev/null +++ b/isotests/fixtures/architecture.json @@ -0,0 +1,30 @@ +[ + { + "pk": 1, + "model": "isotests.architecture", + "fields": { + "name": "dual, option i686" + } + }, + { + "pk": 2, + "model": "isotests.architecture", + "fields": { + "name": "dual, option x86_64" + } + }, + { + "pk": 3, + "model": "isotests.architecture", + "fields": { + "name": "i686" + } + }, + { + "pk": 4, + "model": "isotests.architecture", + "fields": { + "name": "x86_64" + } + } +] diff --git a/isotests/fixtures/boottype.json b/isotests/fixtures/boottype.json new file mode 100644 index 00000000..5d87ef15 --- /dev/null +++ b/isotests/fixtures/boottype.json @@ -0,0 +1,23 @@ +[ + { + "pk": 1, + "model": "isotests.boottype", + "fields": { + "name": "optical" + } + }, + { + "pk": 2, + "model": "isotests.boottype", + "fields": { + "name": "usb" + } + }, + { + "pk": 3, + "model": "isotests.boottype", + "fields": { + "name": "pxe" + } + } +] diff --git a/isotests/fixtures/clockchoices.json b/isotests/fixtures/clockchoices.json new file mode 100644 index 00000000..2c078128 --- /dev/null +++ b/isotests/fixtures/clockchoices.json @@ -0,0 +1,23 @@ +[ + { + "pk": 1, + "model": "isotests.clockchoice", + "fields": { + "name": "default" + } + }, + { + "pk": 2, + "model": "isotests.clockchoice", + "fields": { + "name": "configured manually" + } + }, + { + "pk": 3, + "model": "isotests.clockchoice", + "fields": { + "name": "NTP" + } + } +] diff --git a/isotests/fixtures/isotypes.json b/isotests/fixtures/isotypes.json new file mode 100644 index 00000000..760e3738 --- /dev/null +++ b/isotests/fixtures/isotypes.json @@ -0,0 +1,16 @@ +[ + { + "pk": 1, + "model": "isotests.isotype", + "fields": { + "name": "core" + } + }, + { + "pk": 2, + "model": "isotests.isotype", + "fields": { + "name": "net" + } + } +] diff --git a/isotests/models.py b/isotests/models.py index 1eaca163..d9cfc78c 100644 --- a/isotests/models.py +++ b/isotests/models.py @@ -1,4 +1,6 @@ from django.db import models +from django.db.models import Max +from datetime import datetime # Create your models here. class Iso(models.Model): @@ -7,81 +9,125 @@ class Iso(models.Model): def __unicode__(self): return str(self.date) +class Architecture(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] + +class Isotype(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] + +class Boottype(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] + class Hardware(models.Model): name = models.CharField(max_length=200) def __unicode__(self): return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] class InstallType(models.Model): name = models.CharField(max_length=200) def __unicode__(self): return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] class Source(models.Model): name = models.CharField(max_length=200) def __unicode__(self): return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] + +class Clockchoice(models.Model): + name = models.CharField(max_length=200) + + def __unicode__(self): + return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] class Filesystem(models.Model): name = models.CharField(max_length=200) def __unicode__(self): return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] class Module(models.Model): name = models.CharField(max_length=200) def __unicode__(self): return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] class Bootloader(models.Model): name = models.CharField(max_length=200) def __unicode__(self): return self.name + def get_success_test(self): + return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] + def get_failed_test(self): + return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] class Test(models.Model): - ARCH_CHOICES = ( - ('d86', 'dual, option i686'), - ('d64', 'dual, option x86_64'), - ('x86', 'i686'), - ('x64', 'x86_64') - ) - - ISOTYPE_CHOICES = ( - ('c', 'core'), - ('n', 'net') - ) - - BOOTTYPE_CHOICES = ( - ('o', 'optical'), - ('u', 'usb'), - ('p', 'pxe') - ) - - CLOCK_CHOICES = ( - ('d', 'default'), - ('m', 'configured manually'), - ('n', 'NTP') - ) - user_name = models.CharField(max_length=500) user_email = models.EmailField() iso = models.ForeignKey(Iso) - arch = models.CharField(max_length=3, choices=ARCH_CHOICES) - isotype = models.CharField(max_length=1, choices=ISOTYPE_CHOICES) - boottype = models.CharField(max_length=1, choices=BOOTTYPE_CHOICES) + arch = models.ForeignKey(Architecture) + isotype = models.ForeignKey(Isotype) + boottype = models.ForeignKey(Boottype) hardwaretype = models.ForeignKey(Hardware) installtype = models.ForeignKey(InstallType) source = models.ForeignKey(Source) - clock = models.CharField(max_length=1, choices=CLOCK_CHOICES) + clock = models.ForeignKey(Clockchoice) filesystem = models.ForeignKey(Filesystem) - ms = models.ManyToManyField(Module) + ms = models.ManyToManyField(Module, null=True, blank=True) rollback = models.BooleanField() - rollback_filesystem = models.ForeignKey(Filesystem, related_name="rollback_test") - rollback_modules = models.ManyToManyField(Module, related_name="rollback_test") + rollback_filesystem = models.ForeignKey(Filesystem, + related_name="rollback_test", null=True, blank=True) + rollback_modules = models.ManyToManyField(Module, + related_name="rollback_test", null=True, blank=True) + bootloader = models.ForeignKey(Bootloader) success = models.BooleanField() - comments = models.TextField() + comments = models.TextField(null=True, blank=True) diff --git a/isotests/urls.py b/isotests/urls.py index fdde9e3b..e28497aa 100644 --- a/isotests/urls.py +++ b/isotests/urls.py @@ -6,11 +6,11 @@ info_dict = { } urlpatterns = patterns('isotests.views', - (r'^add/$', 'add_result') -) + (r'^$', 'view_results'), + (r'^add/$', 'add_result') + ) urlpatterns += patterns('', - (r'^$', 'django.views.generic.list_detail.object_list', info_dict) ) # vim: set ts=4 sw=4 et: diff --git a/isotests/views.py b/isotests/views.py index 742be8ff..738fa90d 100644 --- a/isotests/views.py +++ b/isotests/views.py @@ -1,9 +1,9 @@ # Create your views here. from django.http import HttpResponse, HttpResponseRedirect -from django.forms import ModelForm -from isotests.models import Test +from django.forms import ModelForm, DateField +from isotests.models import * from django.shortcuts import render_to_response -from django.template import RequestContext +from django.template import RequestContext, Context, loader class TestForm(ModelForm): class Meta: @@ -18,7 +18,35 @@ def add_result(request): else: form = TestForm() # An unbound form - return render_to_response('isotests/add.html', { - 'form': form, - }, - context_instance=RequestContext(request)) + return render_to_response('isotests/add.html', { 'form': form, }, + context_instance=RequestContext(request)) + +def view_results(request): + result_success_list = Test.objects.filter(success=True) + result_failed_list = Test.objects.filter(success=False) + + architecture_list = Architecture.objects.all() + isotype_list = Isotype.objects.all() + boottype_list = Boottype.objects.all() + hardware_list = Hardware.objects.all() + installtype_list = InstallType.objects.all() + source_list = Source.objects.all() + clockchoice_list = Clockchoice.objects.all() + module_list = Module.objects.all() + filesystem_list = Filesystem.objects.all() + bootloader_list = Bootloader.objects.all() + + t = loader.get_template("isotests/results.html") + c = Context({ + 'arch_choices': architecture_list, + 'isotype_choices': isotype_list, + 'boottype_choices': boottype_list, + 'hardware_list': hardware_list, + 'installtype_list': installtype_list, + 'source_list': source_list, + 'clock_choices': clockchoice_list, + 'filesystem_list': filesystem_list, + 'module_list': module_list, + 'bootloader_list': bootloader_list, + }) + return HttpResponse(t.render(c)) diff --git a/templates/isotests/results.html b/templates/isotests/results.html new file mode 100644 index 00000000..3e43ae47 --- /dev/null +++ b/templates/isotests/results.html @@ -0,0 +1,190 @@ +{% extends "base.html" %} + +{% block title %}Arch Linux - Testresults{% endblock %} + +{% block content %} +
    +

    Arch releng iso build test results

    + + + + + {% if arch_choices %} + {% for arch in arch_choices %} + + + + + + {% endfor %} + {% endif %} + + + + {% if isotype_choices %} + {% for isotype in isotype_choices %} + + + + + + {% endfor %} + {% endif %} + + + + {% if boottype_choices %} + {% for boottype in boottype_choices %} + + + + + + {% endfor %} + {% endif %} + + + + {% if hardware_list %} + {% for hardware in hardware_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if installtype_list %} + {% for installtype in installtype_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if source_list %} + {% for source in source_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if clock_choices %} + {% for clock in clock_choices %} + + + + + + {% endfor %} + {% endif %} + + + + {% if filesystem_list %} + {% for filesystem in filesystem_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if module_list %} + {% for module in module_list %} + + + + + + {% endfor %} + {% endif %} + + + + + + + + + + + + + {% if filesystem_list %} + {% for filesystem in filesystem_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if module_list %} + {% for module in module_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if bootloader_list %} + {% for bootloader in bootloader_list %} + + + + + + {% endfor %} + {% endif %} +
    +

    image arch

    +
    {{ arch.name }}{{ arch.get_success_test|default_if_none:"Never succeeded" }}{{ arch.get_failed_test|default_if_none:"Never failed" }}
    +

    image type

    +
    {{ isotype.name }}{{ isotype.get_success_test|default_if_none:"Never succeeded" }}{{ isotype.get_failed_test|default_if_none:"Never failed" }}
    +

    image boot

    +
    {{ boottype.name }}{{ boottype.get_success_test|default_if_none:"Never succeeded" }}{{ boottype.get_failed_test|default_if_none:"Never failed" }}
    +

    hardware type

    +
    {{ hardware.name }}{{ hardware.get_success_test|default_if_none:"Never succeeded" }}{{ hardware.get_failed_test|default_if_none:"Never failed" }}
    +

    install type

    +
    {{ installtype.name }}{{ installtype.get_success_test|default_if_none:"Never succeeded" }}{{ installtype.get_failed_test|default_if_none:"Never failed" }}
    +

    source selection

    +
    {{ source.name }}{{ source.get_success_test|default_if_none:"Never succeeded" }}{{ source.get_failed_test|default_if_none:"Never failed" }}
    +

    clock

    +
    {{ clock.name }}{{ clock.get_success_test|default_if_none:"Never succeeded" }}{{ clock.get_failed_test|default_if_none:"Never failed" }}
    +

    partitioning/filesystems

    +
    {{ filesystem.name }}{{ filesystem.get_success_test|default_if_none:"Never succeeded" }}{{ filesystem.get_failed_test|default_if_none:"Never failed" }}
    +

    fancy stuff

    +
    {{ module.name }}{{ module.get_success_test|default_if_none:"Never succeeded" }}{{ module.get_failed_test|default_if_none:"Never failed" }}
    +

    rollback

    +
    yes
    no
    +

    rollback: partitioning/filesystems

    +
    {{ filesystem.name }}{{ filesystem.get_success_test|default_if_none:"Never succeeded" }}{{ filesystem.get_failed_test|default_if_none:"Never failed" }}
    +

    rollback: fancy stuff

    +
    {{ module.name }}{{ module.get_success_test|default_if_none:"Never succeeded" }}{{ module.get_failed_test|default_if_none:"Never failed" }}
    +

    bootloader

    +
    {{ bootloader.name }}{{ bootloader.get_success_test|default_if_none:"Never succeeded" }}{{ bootloader.get_failed_test|default_if_none:"Never failed" }}
    +
    +{% endblock %} -- cgit v1.2.3-2-g168b From 00e096ddf0654d32e67ac8bc47f3de01ed7e740b Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 28 Apr 2011 13:00:27 -0500 Subject: isotests: style cleanup, ui improvements * Using radio buttons for widgets is smarter. * Model names cleanup. * Test.ms: totally un-descriptive field name, should be modules. * models, Iso: Likely need more than a date field here. Removed date and added name. * get_success_test/get_failed_test: now on abstract superclass * tests.py: I wasn't using these, so I might as well remove it. * admin.py: convention is not to use * imports. * models.py: "# Create your models here." -> not needed. * urls.py: I wasn't using info_dict anymore; I had a blank second pattern definition, and I should follow indentation patterns from elsewhere in the project. * views.py, add: switched to using mostly direct_to_template to avoid some of the boilerplate. * isotest/templates: was old, not used. * I had 4 + 1 templates, but only two views- these other ones were old, unnecessary and not wired up. Signed-off-by: Dan McGee --- isotests/admin.py | 12 +- isotests/fixtures/hardware.json | 12 +- isotests/models.py | 135 +++++--------- isotests/templates/iso_list.html | 2 - isotests/tests.py | 23 --- isotests/urls.py | 12 +- isotests/views.py | 66 ++++--- templates/isotests/iso_list.html | 18 -- templates/isotests/results.html | 364 +++++++++++++++++++------------------- templates/isotests/test_list.html | 18 -- 10 files changed, 279 insertions(+), 383 deletions(-) delete mode 100644 isotests/templates/iso_list.html delete mode 100644 isotests/tests.py delete mode 100644 templates/isotests/iso_list.html delete mode 100644 templates/isotests/test_list.html diff --git a/isotests/admin.py b/isotests/admin.py index b03ddea3..0cde0f83 100644 --- a/isotests/admin.py +++ b/isotests/admin.py @@ -1,14 +1,16 @@ -from isotests.models import * +from isotests.models import Iso, Architecture, IsoType, BootType +from isotests.models import HardwareType, InstallType, Source +from isotests.models import ClockChoice, Filesystem, Module, Bootloader from django.contrib import admin admin.site.register(Iso) admin.site.register(Architecture) -admin.site.register(Isotype) -admin.site.register(Boottype) -admin.site.register(Hardware) +admin.site.register(IsoType) +admin.site.register(BootType) +admin.site.register(HardwareType) admin.site.register(InstallType) admin.site.register(Source) -admin.site.register(Clockchoice) +admin.site.register(ClockChoice) admin.site.register(Filesystem) admin.site.register(Module) admin.site.register(Bootloader) diff --git a/isotests/fixtures/hardware.json b/isotests/fixtures/hardware.json index c9169146..335a50f6 100644 --- a/isotests/fixtures/hardware.json +++ b/isotests/fixtures/hardware.json @@ -1,42 +1,42 @@ [ { "pk": 1, - "model": "isotests.hardware", + "model": "isotests.hardwaretype", "fields": { "name": "virtualbox" } }, { "pk": 2, - "model": "isotests.hardware", + "model": "isotests.hardwaretype", "fields": { "name": "qemu" } }, { "pk": 3, - "model": "isotests.hardware", + "model": "isotests.hardwaretype", "fields": { "name": "intel i686" } }, { "pk": 4, - "model": "isotests.hardware", + "model": "isotests.hardwaretype", "fields": { "name": "intel x86_64" } }, { "pk": 5, - "model": "isotests.hardware", + "model": "isotests.hardwaretype", "fields": { "name": "amd i686" } }, { "pk": 6, - "model": "isotests.hardware", + "model": "isotests.hardwaretype", "fields": { "name": "amd x86_64" } diff --git a/isotests/models.py b/isotests/models.py index d9cfc78c..bffb2d94 100644 --- a/isotests/models.py +++ b/isotests/models.py @@ -1,128 +1,77 @@ from django.db import models from django.db.models import Max -from datetime import datetime -# Create your models here. -class Iso(models.Model): - date = models.DateField() - - def __unicode__(self): - return str(self.date) +class IsoOption(models.Model): + class Meta: + abstract = True -class Architecture(models.Model): name = models.CharField(max_length=200) def __unicode__(self): - return self.name + return str(self.name) def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] - -class Isotype(models.Model): - name = models.CharField(max_length=200) + test = self.test_set.filter(success=True).annotate(Max('iso__id')) + if test: + return test[0].iso.name + return None - def __unicode__(self): - return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] + test = self.test_set.filter(success=False).annotate(Max('iso__id')) + if test: + return test[0].iso.name + return None -class Boottype(models.Model): - name = models.CharField(max_length=200) +class Iso(models.Model): + name = models.CharField(max_length=500) + active = models.BooleanField(default=True) def __unicode__(self): return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] -class Hardware(models.Model): - name = models.CharField(max_length=200) +class Architecture(IsoOption): + pass - def __unicode__(self): - return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] +class IsoType(IsoOption): + pass -class InstallType(models.Model): - name = models.CharField(max_length=200) +class BootType(IsoOption): + pass - def __unicode__(self): - return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] +class HardwareType(IsoOption): + pass -class Source(models.Model): - name = models.CharField(max_length=200) +class InstallType(IsoOption): + pass - def __unicode__(self): - return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] - -class Clockchoice(models.Model): - name = models.CharField(max_length=200) - - def __unicode__(self): - return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] - -class Filesystem(models.Model): - name = models.CharField(max_length=200) - - def __unicode__(self): - return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] +class Source(IsoOption): + pass -class Module(models.Model): - name = models.CharField(max_length=200) +class ClockChoice(IsoOption): + pass - def __unicode__(self): - return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] +class Filesystem(IsoOption): + pass -class Bootloader(models.Model): - name = models.CharField(max_length=200) +class Module(IsoOption): + pass - def __unicode__(self): - return self.name - def get_success_test(self): - return self.test_set.filter(success=True).aggregate(Max('iso__date'))['iso__date__max'] - def get_failed_test(self): - return self.test_set.filter(success=False).aggregate(Max('iso__date'))['iso__date__max'] +class Bootloader(IsoOption): + pass class Test(models.Model): user_name = models.CharField(max_length=500) user_email = models.EmailField() iso = models.ForeignKey(Iso) - arch = models.ForeignKey(Architecture) - isotype = models.ForeignKey(Isotype) - boottype = models.ForeignKey(Boottype) - hardwaretype = models.ForeignKey(Hardware) - installtype = models.ForeignKey(InstallType) + architecture = models.ForeignKey(Architecture) + iso_type = models.ForeignKey(IsoType) + boot_type = models.ForeignKey(BootType) + hardware_type = models.ForeignKey(HardwareType) + install_type = models.ForeignKey(InstallType) source = models.ForeignKey(Source) - clock = models.ForeignKey(Clockchoice) + clock_choice = models.ForeignKey(ClockChoice) filesystem = models.ForeignKey(Filesystem) - ms = models.ManyToManyField(Module, null=True, blank=True) + modules = models.ManyToManyField(Module, null=True, blank=True) rollback = models.BooleanField() rollback_filesystem = models.ForeignKey(Filesystem, related_name="rollback_test", null=True, blank=True) diff --git a/isotests/templates/iso_list.html b/isotests/templates/iso_list.html deleted file mode 100644 index 06572739..00000000 --- a/isotests/templates/iso_list.html +++ /dev/null @@ -1,2 +0,0 @@ -hello there -bla diff --git a/isotests/tests.py b/isotests/tests.py deleted file mode 100644 index 2247054b..00000000 --- a/isotests/tests.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". - -Replace these with more appropriate tests for your application. -""" - -from django.test import TestCase - -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.failUnlessEqual(1 + 1, 2) - -__test__ = {"doctest": """ -Another way to test that 1 + 1 is equal to 2. - ->>> 1 + 1 == 2 -True -"""} - diff --git a/isotests/urls.py b/isotests/urls.py index e28497aa..f60f0bc9 100644 --- a/isotests/urls.py +++ b/isotests/urls.py @@ -1,16 +1,8 @@ from django.conf.urls.defaults import patterns -from isotests.models import Test - -info_dict = { - 'queryset': Test.objects.all() -} urlpatterns = patterns('isotests.views', - (r'^$', 'view_results'), - (r'^add/$', 'add_result') - ) - -urlpatterns += patterns('', + (r'^$', 'view_results'), + (r'^add/$', 'add_result') ) # vim: set ts=4 sw=4 et: diff --git a/isotests/views.py b/isotests/views.py index 738fa90d..cb7f23c5 100644 --- a/isotests/views.py +++ b/isotests/views.py @@ -1,50 +1,64 @@ -# Create your views here. from django.http import HttpResponse, HttpResponseRedirect -from django.forms import ModelForm, DateField -from isotests.models import * -from django.shortcuts import render_to_response -from django.template import RequestContext, Context, loader +from django.forms import ModelForm, RadioSelect, CheckboxSelectMultiple +from django.forms import ModelChoiceField +from isotests.models import Iso, Architecture, IsoType, BootType +from isotests.models import HardwareType, InstallType, Source, Test +from isotests.models import ClockChoice, Filesystem, Module, Bootloader +from django.template import Context, loader +from django.views.generic.simple import direct_to_template class TestForm(ModelForm): class Meta: model = Test + widgets = { + "architecture": RadioSelect(), + "iso_type": RadioSelect(), + "boot_type": RadioSelect(), + "hardware_type": RadioSelect(), + "install_type": RadioSelect(), + "source": RadioSelect(), + "clock_choice": RadioSelect(), + "filesystem": RadioSelect(), + "rollback_filesystem": RadioSelect(), + "bootloader": RadioSelect(), + "modules": CheckboxSelectMultiple(), + "rollback_modules": CheckboxSelectMultiple(), + } + iso = ModelChoiceField(queryset=Iso.objects.filter(active=True)) def add_result(request): - if request.method == 'POST': # If the form has been submitted... - form = TestForm(request.POST) # A form bound to the post data - if form.is_valid(): # All validation rules pass + if request.method == 'POST': + form = TestForm(request.POST) + if form.is_valid(): form.save() - return HttpResponseRedirect('/isotests') # Redirect after POST + return HttpResponseRedirect('/isotests') else: - form = TestForm() # An unbound form + form = TestForm() - return render_to_response('isotests/add.html', { 'form': form, }, - context_instance=RequestContext(request)) + context = {'form': form} + return direct_to_template(request, 'isotests/add.html', context) def view_results(request): - result_success_list = Test.objects.filter(success=True) - result_failed_list = Test.objects.filter(success=False) - architecture_list = Architecture.objects.all() - isotype_list = Isotype.objects.all() - boottype_list = Boottype.objects.all() - hardware_list = Hardware.objects.all() - installtype_list = InstallType.objects.all() + iso_type_list = IsoType.objects.all() + boot_type_list = BootType.objects.all() + hardware_type_list = HardwareType.objects.all() + install_type_list = InstallType.objects.all() source_list = Source.objects.all() - clockchoice_list = Clockchoice.objects.all() + clock_choice_list = ClockChoice.objects.all() module_list = Module.objects.all() filesystem_list = Filesystem.objects.all() bootloader_list = Bootloader.objects.all() t = loader.get_template("isotests/results.html") c = Context({ - 'arch_choices': architecture_list, - 'isotype_choices': isotype_list, - 'boottype_choices': boottype_list, - 'hardware_list': hardware_list, - 'installtype_list': installtype_list, + 'architecture_list': architecture_list, + 'iso_type_list': iso_type_list, + 'boot_type_list': boot_type_list, + 'hardware_type_list': hardware_type_list, + 'install_type_list': install_type_list, 'source_list': source_list, - 'clock_choices': clockchoice_list, + 'clock_choices_list': clock_choice_list, 'filesystem_list': filesystem_list, 'module_list': module_list, 'bootloader_list': bootloader_list, diff --git a/templates/isotests/iso_list.html b/templates/isotests/iso_list.html deleted file mode 100644 index f94bbe1a..00000000 --- a/templates/isotests/iso_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Arch Linux - Testresults{% endblock %} - -{% block content %} -{% if object_list %} -
    -

    Arch releng iso buid test results

    -
      - {% for iso in object_list %} -
    • {{ iso }}
    • - {% endfor %} -
    - {% else %} -

    No tests are available.

    - {% endif %} -
    -{% endblock %} diff --git a/templates/isotests/results.html b/templates/isotests/results.html index 3e43ae47..b773056a 100644 --- a/templates/isotests/results.html +++ b/templates/isotests/results.html @@ -4,187 +4,187 @@ {% block content %}
    -

    Arch releng iso build test results

    - - - - - {% if arch_choices %} - {% for arch in arch_choices %} - - - - - - {% endfor %} - {% endif %} - - - - {% if isotype_choices %} - {% for isotype in isotype_choices %} - - - - - - {% endfor %} - {% endif %} - - - - {% if boottype_choices %} - {% for boottype in boottype_choices %} - - - - - - {% endfor %} - {% endif %} - - - - {% if hardware_list %} - {% for hardware in hardware_list %} - - - - - - {% endfor %} - {% endif %} - - - - {% if installtype_list %} - {% for installtype in installtype_list %} - - - - - - {% endfor %} - {% endif %} - - - - {% if source_list %} - {% for source in source_list %} - - - - - - {% endfor %} - {% endif %} - - - - {% if clock_choices %} - {% for clock in clock_choices %} - - - - - - {% endfor %} - {% endif %} - - - - {% if filesystem_list %} - {% for filesystem in filesystem_list %} - - - - - - {% endfor %} - {% endif %} - - - - {% if module_list %} - {% for module in module_list %} - - - - - - {% endfor %} - {% endif %} - - - - - - - - - - - - - {% if filesystem_list %} - {% for filesystem in filesystem_list %} - - - - - - {% endfor %} - {% endif %} - - - - {% if module_list %} - {% for module in module_list %} - - - - - - {% endfor %} - {% endif %} - - - - {% if bootloader_list %} - {% for bootloader in bootloader_list %} - - - - - - {% endfor %} - {% endif %} -
    -

    image arch

    -
    {{ arch.name }}{{ arch.get_success_test|default_if_none:"Never succeeded" }}{{ arch.get_failed_test|default_if_none:"Never failed" }}
    -

    image type

    -
    {{ isotype.name }}{{ isotype.get_success_test|default_if_none:"Never succeeded" }}{{ isotype.get_failed_test|default_if_none:"Never failed" }}
    -

    image boot

    -
    {{ boottype.name }}{{ boottype.get_success_test|default_if_none:"Never succeeded" }}{{ boottype.get_failed_test|default_if_none:"Never failed" }}
    -

    hardware type

    -
    {{ hardware.name }}{{ hardware.get_success_test|default_if_none:"Never succeeded" }}{{ hardware.get_failed_test|default_if_none:"Never failed" }}
    -

    install type

    -
    {{ installtype.name }}{{ installtype.get_success_test|default_if_none:"Never succeeded" }}{{ installtype.get_failed_test|default_if_none:"Never failed" }}
    -

    source selection

    -
    {{ source.name }}{{ source.get_success_test|default_if_none:"Never succeeded" }}{{ source.get_failed_test|default_if_none:"Never failed" }}
    -

    clock

    -
    {{ clock.name }}{{ clock.get_success_test|default_if_none:"Never succeeded" }}{{ clock.get_failed_test|default_if_none:"Never failed" }}
    -

    partitioning/filesystems

    -
    {{ filesystem.name }}{{ filesystem.get_success_test|default_if_none:"Never succeeded" }}{{ filesystem.get_failed_test|default_if_none:"Never failed" }}
    -

    fancy stuff

    -
    {{ module.name }}{{ module.get_success_test|default_if_none:"Never succeeded" }}{{ module.get_failed_test|default_if_none:"Never failed" }}
    -

    rollback

    -
    yes
    no
    -

    rollback: partitioning/filesystems

    -
    {{ filesystem.name }}{{ filesystem.get_success_test|default_if_none:"Never succeeded" }}{{ filesystem.get_failed_test|default_if_none:"Never failed" }}
    -

    rollback: fancy stuff

    -
    {{ module.name }}{{ module.get_success_test|default_if_none:"Never succeeded" }}{{ module.get_failed_test|default_if_none:"Never failed" }}
    -

    bootloader

    -
    {{ bootloader.name }}{{ bootloader.get_success_test|default_if_none:"Never succeeded" }}{{ bootloader.get_failed_test|default_if_none:"Never failed" }}
    +

    Arch releng iso build test results

    + + + + + {% if architecture_list %} + {% for architecture in architecture_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if iso_type_list %} + {% for iso_type in iso_type_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if boot_type_list %} + {% for boot_type in boot_type_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if hardware_type_list %} + {% for hardware_type in hardware_type_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if install_type_list %} + {% for install_type in install_type_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if source_list %} + {% for source in source_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if clock_choices_list %} + {% for clock_choice in clock_choices_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if filesystem_list %} + {% for filesystem in filesystem_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if module_list %} + {% for module in module_list %} + + + + + + {% endfor %} + {% endif %} + + + + + + + + + + + + + {% if filesystem_list %} + {% for filesystem in filesystem_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if module_list %} + {% for module in module_list %} + + + + + + {% endfor %} + {% endif %} + + + + {% if bootloader_list %} + {% for bootloader in bootloader_list %} + + + + + + {% endfor %} + {% endif %} +
    +

    image arch

    +
    {{ architecture.name }}{{ architecture.get_success_test|default_if_none:"Never succeeded" }}{{ architecture.get_failed_test|default_if_none:"Never failed" }}
    +

    image type

    +
    {{ iso_type.name }}{{ iso_type.get_success_test|default_if_none:"Never succeeded" }}{{ iso_type.get_failed_test|default_if_none:"Never failed" }}
    +

    image boot

    +
    {{ boot_type.name }}{{ boot_type.get_success_test|default_if_none:"Never succeeded" }}{{ boot_type.get_failed_test|default_if_none:"Never failed" }}
    +

    hardware type

    +
    {{ hardware_type.name }}{{ hardware_type.get_success_test|default_if_none:"Never succeeded" }}{{ hardware_type.get_failed_test|default_if_none:"Never failed" }}
    +

    install type

    +
    {{ install_type.name }}{{ install_type.get_success_test|default_if_none:"Never succeeded" }}{{ install_type.get_failed_test|default_if_none:"Never failed" }}
    +

    source selection

    +
    {{ source.name }}{{ source.get_success_test|default_if_none:"Never succeeded" }}{{ source.get_failed_test|default_if_none:"Never failed" }}
    +

    clock

    +
    {{ clock_choice.name }}{{ clock_choice.get_success_test|default_if_none:"Never succeeded" }}{{ clock_choice.get_failed_test|default_if_none:"Never failed" }}
    +

    partitioning/filesystems

    +
    {{ filesystem.name }}{{ filesystem.get_success_test|default_if_none:"Never succeeded" }}{{ filesystem.get_failed_test|default_if_none:"Never failed" }}
    +

    fancy stuff

    +
    {{ module.name }}{{ module.get_success_test|default_if_none:"Never succeeded" }}{{ module.get_failed_test|default_if_none:"Never failed" }}
    +

    rollback

    +
    yes
    no
    +

    rollback: partitioning/filesystems

    +
    {{ filesystem.name }}{{ filesystem.get_success_test|default_if_none:"Never succeeded" }}{{ filesystem.get_failed_test|default_if_none:"Never failed" }}
    +

    rollback: fancy stuff

    +
    {{ module.name }}{{ module.get_success_test|default_if_none:"Never succeeded" }}{{ module.get_failed_test|default_if_none:"Never failed" }}
    +

    bootloader

    +
    {{ bootloader.name }}{{ bootloader.get_success_test|default_if_none:"Never succeeded" }}{{ bootloader.get_failed_test|default_if_none:"Never failed" }}
    {% endblock %} diff --git a/templates/isotests/test_list.html b/templates/isotests/test_list.html deleted file mode 100644 index 1ef39a4c..00000000 --- a/templates/isotests/test_list.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base.html" %} - -{% block title %}Arch Linux - Testresults{% endblock %} - -{% block content %} -
    -

    Arch releng iso build test results

    - {% if object_list %} - - {% else %} -

    No test results are available.

    - {% endif %} -
    -{% endblock %} -- cgit v1.2.3-2-g168b From db137d4db607461dd32c46e40bee9084eb508da9 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 31 Mar 2011 22:39:40 +0200 Subject: isotests: add syncisos command * Installtype fixture places 'interactive' at the top now. * Added a syncisos command to isotests to get new iso names from http://releng.archlinux.org/isos/ and add them to the database. Signed-off-by: Dan McGee --- isotests/fixtures/installtype.json | 8 ++--- isotests/management/__init__.py | 0 isotests/management/commands/__init__.py | 0 isotests/management/commands/syncisos.py | 51 ++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 isotests/management/__init__.py create mode 100644 isotests/management/commands/__init__.py create mode 100644 isotests/management/commands/syncisos.py diff --git a/isotests/fixtures/installtype.json b/isotests/fixtures/installtype.json index d23bd4b2..cc5c62a0 100644 --- a/isotests/fixtures/installtype.json +++ b/isotests/fixtures/installtype.json @@ -3,28 +3,28 @@ "pk": 1, "model": "isotests.installtype", "fields": { - "name": "automatic install generic example" + "name": "interactive install" } }, { "pk": 2, "model": "isotests.installtype", "fields": { - "name": "automatic install fancy example" + "name": "automatic install generic example" } }, { "pk": 3, "model": "isotests.installtype", "fields": { - "name": "automatic install custom config (specify in comments)" + "name": "automatic install fancy example" } }, { "pk": 4, "model": "isotests.installtype", "fields": { - "name": "interactive install" + "name": "automatic install custom config (specify in comments)" } } ] diff --git a/isotests/management/__init__.py b/isotests/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/isotests/management/commands/__init__.py b/isotests/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/isotests/management/commands/syncisos.py b/isotests/management/commands/syncisos.py new file mode 100644 index 00000000..975104d9 --- /dev/null +++ b/isotests/management/commands/syncisos.py @@ -0,0 +1,51 @@ +import re +import urllib +from HTMLParser import HTMLParser, HTMLParseError + +from django.core.management.base import BaseCommand, CommandError + +from isotests.models import Iso +from settings import ISOLISTURL + +class IsoListParser(HTMLParser): + def __init__(self): + HTMLParser.__init__(self) + + self.hyperlinks = [] + self.url_re = re.compile('(?!\.{2})/$') + + def handle_starttag(self, tag, attrs): + if tag == 'a': + for name, value in attrs: + if name == "href": + if value != '../' and self.url_re.search(value) != None: + self.hyperlinks.append(value[:len(value)-1]) + + def parse(self, url): + try: + f = urllib.urlopen(url) + + s = f.read() + f.close() + + self.feed(s) + self.close() + + return self.hyperlinks + except HTMLParseError: + raise CommandError('Couldn\'t parse "%s"' % url) + +class Command(BaseCommand): + help = 'Gets new isos from http://releng.archlinux.org/isos/' + + def handle(self, *args, **options): + parser = IsoListParser() + isonames = Iso.objects.values_list('name', flat=True) + new_isos = parser.parse(ISOLISTURL) + + for iso in new_isos: + if iso not in isonames: + new = Iso(name=iso) + new.save() + +# vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From c292dcfc6bf96ebf5f34342beb1367aa5361f7c4 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 28 Apr 2011 13:19:42 -0500 Subject: isotests: various changes and updates * isotests/fixtures/clockchoices.json: changed 'default' to 'unchanged' * isotests/fixtures/filesystems.json: removed 'check the installed system' line from one of the options * isotests/fixtures/modules.json: added 'ext2','ext3','ext4','swap','xfs','jfs','reiserFS' * isotests/models.py: * Added RollbackOption abstract class that adds the functions get_rollback_success_test and get_rollback_failed_test on top of the IsoOption abstract class for use with the Filesystem and Module classes since Test uses these both in 2 ways (regular and rollback). This keeps them seperated. * renamed the related names of these properties from rollback_test to rollback_test_set (seems more in-tune with the other relations) * isotests/views.py: * changed the order of the fields, the automatic order makes no sense. * Added help texts to the fields success, filesystem, rollback_filesystem and rollback_modules. * Removed help text from modules (made no sense) * Added a website field, should remain empty, a simplistic way to hopefully reduce spambot entries. * templates/isotests/results.html: * Removed the rollback yes/no section * The rollback labels should check get_rollback_success_test and get_rollback_failed_test. * Rollback checkbox removed. * Clearly tell users that success must only be selected if everything works right. * Clearly tell users to only fill in the rollback options if they did a rollback. * Added a thanks page that tells people thanks. * Added links between the pages. * Added links to lists with tests of either a specific iso or of any iso where a specific option was selected. Signed-off-by: Dan McGee Conflicts: templates/isotests/results.html --- isotests/fixtures/clockchoices.json | 2 +- isotests/fixtures/filesystems.json | 2 +- isotests/fixtures/modules.json | 49 +++ isotests/models.py | 51 +++- isotests/urls.py | 8 +- isotests/views.py | 64 +++- templates/isotests/add.html | 2 + templates/isotests/result_list.html | 34 +++ templates/isotests/results.html | 595 +++++++++++++++++++++++++----------- templates/isotests/thanks.html | 14 + templates/public/index.html | 2 + 11 files changed, 616 insertions(+), 207 deletions(-) create mode 100644 templates/isotests/result_list.html create mode 100644 templates/isotests/thanks.html diff --git a/isotests/fixtures/clockchoices.json b/isotests/fixtures/clockchoices.json index 2c078128..6dfd06e1 100644 --- a/isotests/fixtures/clockchoices.json +++ b/isotests/fixtures/clockchoices.json @@ -3,7 +3,7 @@ "pk": 1, "model": "isotests.clockchoice", "fields": { - "name": "default" + "name": "unchanged" } }, { diff --git a/isotests/fixtures/filesystems.json b/isotests/fixtures/filesystems.json index 4d3f1bc4..5386c391 100644 --- a/isotests/fixtures/filesystems.json +++ b/isotests/fixtures/filesystems.json @@ -3,7 +3,7 @@ "pk": 1, "model": "isotests.filesystem", "fields": { - "name": "autoprepare (check the installed system, incl fstab)" + "name": "autoprepare" } }, { diff --git a/isotests/fixtures/modules.json b/isotests/fixtures/modules.json index 27d04c7a..ae8a1683 100644 --- a/isotests/fixtures/modules.json +++ b/isotests/fixtures/modules.json @@ -33,5 +33,54 @@ "fields": { "name": "btrfs" } + }, + { + "pk": 6, + "model": "isotests.module", + "fields": { + "name": "ext2" + } + }, + { + "pk": 7, + "model": "isotests.module", + "fields": { + "name": "ext3" + } + }, + { + "pk": 8, + "model": "isotests.module", + "fields": { + "name": "ext4" + } + }, + { + "pk": 9, + "model": "isotests.module", + "fields": { + "name": "swap" + } + }, + { + "pk": 10, + "model": "isotests.module", + "fields": { + "name": "xfs" + } + }, + { + "pk": 11, + "model": "isotests.module", + "fields": { + "name": "jfs" + } + }, + { + "pk": 12, + "model": "isotests.module", + "fields": { + "name": "reiserFS" + } } ] diff --git a/isotests/models.py b/isotests/models.py index bffb2d94..ae5bf96f 100644 --- a/isotests/models.py +++ b/isotests/models.py @@ -7,19 +7,49 @@ class IsoOption(models.Model): name = models.CharField(max_length=200) + success_tests = None + failed_tests = None + def __unicode__(self): return str(self.name) def get_success_test(self): - test = self.test_set.filter(success=True).annotate(Max('iso__id')) - if test: - return test[0].iso.name + if not self.success_tests: + self.success_tests = self.test_set.filter(success=True).annotate(Max('iso__id')) + + if self.success_tests: + return self.success_tests[0].iso return None def get_failed_test(self): - test = self.test_set.filter(success=False).annotate(Max('iso__id')) - if test: - return test[0].iso.name + if not self.failed_tests: + self.failed_tests = self.test_set.filter(success=False).annotate(Max('iso__id')) + + if self.failed_tests: + return self.failed_tests[0].iso + return None + +class RollbackOption(IsoOption): + class Meta: + abstract = True + + success_rollback_tests = None + failed_rollback_tests = None + + def get_rollback_success_test(self): + if not self.success_rollback_tests: + self.success_rollback_tests = self.rollback_test_set.filter(success=True).annotate(Max('iso__id')) + + if self.success_rollback_tests: + return self.success_rollback_tests[0].iso + return None + + def get_rollback_failed_test(self): + if not self.failed_rollback_tests: + self.failed_rollback_tests = self.rollback_test_set.filter(success=False).annotate(Max('iso__id')) + + if self.failed_rollback_tests: + return self.failed_rollback_tests[0].iso return None class Iso(models.Model): @@ -50,10 +80,10 @@ class Source(IsoOption): class ClockChoice(IsoOption): pass -class Filesystem(IsoOption): +class Filesystem(RollbackOption): pass -class Module(IsoOption): +class Module(RollbackOption): pass class Bootloader(IsoOption): @@ -72,11 +102,10 @@ class Test(models.Model): clock_choice = models.ForeignKey(ClockChoice) filesystem = models.ForeignKey(Filesystem) modules = models.ManyToManyField(Module, null=True, blank=True) - rollback = models.BooleanField() rollback_filesystem = models.ForeignKey(Filesystem, - related_name="rollback_test", null=True, blank=True) + related_name="rollback_test_set", null=True, blank=True) rollback_modules = models.ManyToManyField(Module, - related_name="rollback_test", null=True, blank=True) + related_name="rollback_test_set", null=True, blank=True) bootloader = models.ForeignKey(Bootloader) success = models.BooleanField() comments = models.TextField(null=True, blank=True) diff --git a/isotests/urls.py b/isotests/urls.py index f60f0bc9..7f438368 100644 --- a/isotests/urls.py +++ b/isotests/urls.py @@ -1,8 +1,12 @@ from django.conf.urls.defaults import patterns urlpatterns = patterns('isotests.views', - (r'^$', 'view_results'), - (r'^add/$', 'add_result') + (r'^$', 'view_results'), + (r'^add/$', 'add_result'), + (r'^thanks/$', 'thanks'), + (r'^results/$', 'view_results'), + (r'^results/(?P