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