diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2015-04-16 01:16:52 -0400 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2015-04-16 01:16:52 -0400 |
commit | 7aabbe788bb4e26ee9c7cfc0e18692b487e11099 (patch) | |
tree | e1032beb86e35caafb164e694d2aa7e2e9664de2 | |
parent | 046e6c8b6152a8142ed838e5c406cc04c18f2533 (diff) | |
parent | 155bf43a28e404f327a7bcff214c22e212627673 (diff) |
Merge branch 'archweb-generic'
45 files changed, 485 insertions, 213 deletions
diff --git a/README.BRANDING b/README.BRANDING index 55ef9d91..2dc186b0 100644 --- a/README.BRANDING +++ b/README.BRANDING @@ -3,7 +3,7 @@ Replacing logos is still a bit of work. Here is a summary of the text files that need to be changed to re-brand it, from the text side. -Files used to configure branding/url stuff +Files used to configure branding/URL stuff ------------------------------------------ * `settings.py` @@ -14,6 +14,9 @@ Files used to configure branding/url stuff Files with minor Arch stuff that's just easier to patch ------------------------------------------------------- +`devel/fixtures/staff_groups.json` + * Mentions of Arch in "description" fields + `templates/packages/flaghelp.html` * link to "arch-general" mailing list @@ -43,15 +43,15 @@ packages, you will probably want the following: 1. Run `virtualenv2`. - $ cd /path/to/archweb && virtualenv2 ./env/ + cd /path/to/archweb && virtualenv2 ./env/ 2. Activate the virtualenv. - $ source ./env/bin/activate + source ./env/bin/activate 2. Install dependencies through `pip`. - (archweb-env) $ pip install -r requirements.txt + pip install -r requirements.txt 3. Copy `local_settings.py.example` to `local_settings.py` and modify. Make sure to uncomment the appropriate database section (either sqlite or @@ -59,26 +59,29 @@ packages, you will probably want the following: 4. Sync the database to create it. - (archweb-env) $ ./manage.py syncdb + ./manage.py syncdb 5. Migrate changes. - (archweb-env) $ ./manage.py migrate + ./manage.py migrate 6. Load the fixtures to prepopulate some data. If you don't want some of the provided data, adjust the file glob accordingly. - (archweb-env) $ ./manage.py loaddata */fixtures/*.json + ./manage.py loaddata main/fixtures/*.json + ./manage.py loaddata devel/fixtures/*.json + ./manage.py loaddata mirrors/fixtures/*.json + ./manage.py loaddata releng/fixtures/*.json 7. Use the following commands to start a service instance - (archweb-env) $ ./manage.py runserver + ./manage.py runserver 8. To optionally populate the database with real data: - (archweb-env) $ wget ftp://ftp.archlinux.org/core/os/i686/core.db.tar.gz - (archweb-env) $ ./manage.py reporead i686 core.db.tar.gz - (archweb-env) $ ./manage.py syncisos + wget http://mirrors.kernel.org/archlinux/core/os/i686/core.db.tar.gz + ./manage.py reporead i686 core.db.tar.gz + ./manage.py syncisos Alter architecture and repo to get x86\_64 and packages from other repos if needed. diff --git a/devel/fixtures/staff_groups.json b/devel/fixtures/staff_groups.json new file mode 100644 index 00000000..f57baa26 --- /dev/null +++ b/devel/fixtures/staff_groups.json @@ -0,0 +1,58 @@ +[ +{ + "fields": { + "group": [ + "Hackers" + ], + "description": "This is a list of the current Parabola Hackers. They maintain the [libre] package repository and keep the [core], [extra], and [community] repositories clean of nonfree software, in addition to doing any other developer duties.", + "sort_order": 1, + "member_title": "Hacker", + "slug": "hackers", + "name": "Hackers" + }, + "model": "devel.staffgroup", + "pk": 1 +}, +{ + "fields": { + "group": [ + "Retired Hackers" + ], + "description": "Below you can find a list of ex-hackers (aka project fellows). These folks helped make Parabola what it is today. Thanks!", + "sort_order": 11, + "member_title": "Fellow", + "slug": "hacker-fellows", + "name": "Hacker Fellows" + }, + "model": "devel.staffgroup", + "pk": 3 +}, +{ + "fields": { + "group": [ + "Retired Trusted Users" + ], + "description": "Below you can find a list of ex-trusted users (aka fellows). These folks helped make Parabola what it is today. Thanks!", + "sort_order": 12, + "member_title": "Fellow", + "slug": "trusted-user-fellows", + "name": "Trusted User Fellows" + }, + "model": "devel.staffgroup", + "pk": 4 +}, +{ + "fields": { + "group": [ + "Support Staff" + ], + "description": "These are the unheralded people that keep things running behind the scenes. Forum moderators, wiki admins, IRC moderators, mirror maintenance, and everything else that keeps a Linux distro running smoothly.", + "sort_order": 5, + "member_title": "Staff", + "slug": "support-staff", + "name": "Support Staff" + }, + "model": "devel.staffgroup", + "pk": 5 +} +] diff --git a/devel/utils.py b/devel/utils.py index eaa7d07e..3326987a 100644 --- a/devel/utils.py +++ b/devel/utils.py @@ -6,13 +6,16 @@ from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned from django.db import connection from django.db.models import Count, Q +from devel.models import UserProfile from main.utils import cache_function from main.models import Package from packages.models import PackageRelation @cache_function(283) def get_annotated_maintainers(): - maintainers = User.objects.filter(is_active=True).order_by( + profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id') + maintainers = User.objects.filter( + is_active=True, userprofile__id__in=profile_ids).order_by( 'first_name', 'last_name') # annotate the maintainers with # of maintained and flagged packages @@ -43,7 +46,8 @@ SELECT pr.user_id, COUNT(*), COUNT(p.flag_date) m.flagged_count = flag_count.get(m.id, 0) m.updated_count = update_count.get(m.id, 0) - return maintainers + # force non-QS context, otherwise pickling doesn't work + return list(maintainers) def ignore_does_not_exist(func): diff --git a/main/fixtures/groups.json b/main/fixtures/groups.json index 67cab6cf..e1ef0556 100644 --- a/main/fixtures/groups.json +++ b/main/fixtures/groups.json @@ -206,6 +206,11 @@ "user" ], [ + "change_staffgroup", + "devel", + "staffgroup" + ], + [ "add_userprofile", "devel", "userprofile" @@ -471,5 +476,29 @@ }, "model": "auth.group", "pk": 8 +}, +{ + "fields": { + "name": "Retired Hackers", + "permissions": [] + }, + "model": "auth.group", + "pk": 9 +}, +{ + "fields": { + "name": "Retired Trusted Users", + "permissions": [] + }, + "model": "auth.group", + "pk": 10 +}, +{ + "fields": { + "name": "Support Staff", + "permissions": [] + }, + "model": "auth.group", + "pk": 11 } ] diff --git a/main/templatetags/pgp.py b/main/templatetags/pgp.py index 2417f688..455e8f9c 100644 --- a/main/templatetags/pgp.py +++ b/main/templatetags/pgp.py @@ -44,21 +44,14 @@ def pgp_key_link(key_id, link_text=None): return '<a href="%s" title="PGP key search for %s">%s</a>' % values -@cache_function(1741) -def name_for_key(normalized): - try: - matching_key = DeveloperKey.objects.select_related( - 'owner').get(key=normalized, owner_id__isnull=False) - return matching_key.owner.get_full_name() - except DeveloperKey.DoesNotExist: - return None - - @register.simple_tag -def user_pgp_key_link(key_id): +def user_pgp_key_link(dev_keys, key_id): normalized = key_id[-16:] - name = name_for_key(normalized) - return pgp_key_link(key_id, name) + found = dev_keys.get(normalized, None) + if found: + return pgp_key_link(key_id, found.owner.get_full_name()) + else: + return pgp_key_link(key_id, None) @register.filter(needs_autoescape=True) diff --git a/main/utils.py b/main/utils.py index cf156566..f94f314d 100644 --- a/main/utils.py +++ b/main/utils.py @@ -4,6 +4,8 @@ except ImportError: import pickle import hashlib +import markdown +from markdown.extensions import Extension from django.core.cache import cache from django.db import connections, router @@ -109,6 +111,19 @@ def database_vendor(model, mode='read'): return connections[database].vendor +class EscapeHtml(Extension): + def extendMarkdown(self, md, md_globals): + del md.preprocessors['html_block'] + del md.inlinePatterns['html'] + + +def parse_markdown(text, allow_html=False): + if allow_html: + return markdown.markdown(text, enable_attributes=False) + ext = [EscapeHtml()] + return markdown.markdown(text, extensions=ext, enable_attributes=False) + + def groupby_preserve_order(iterable, keyfunc): '''Take an iterable and regroup using keyfunc to determine whether items belong to the same group. The order of the iterable is preserved and diff --git a/mirrors/models.py b/mirrors/models.py index 820f3328..9743d177 100644 --- a/mirrors/models.py +++ b/mirrors/models.py @@ -165,6 +165,7 @@ class MirrorLog(models.Model): is_success = models.BooleanField(default=True) error = models.TextField(blank=True, default='') + @property def delay(self): if self.last_sync is None: return None diff --git a/mirrors/templatetags/jinja2.py b/mirrors/templatetags/jinja2.py new file mode 100644 index 00000000..04e50238 --- /dev/null +++ b/mirrors/templatetags/jinja2.py @@ -0,0 +1,53 @@ +from datetime import timedelta +from django_jinja import library +from markupsafe import Markup + + +@library.global_function +def country_flag(country): + if not country: + return '' + html = '<span class="fam-flag fam-flag-%s" title="%s"></span> ' % ( + unicode(country.code).lower(), unicode(country.name)) + return Markup(html) + + +@library.filter +def duration(value): + if not value and type(value) != timedelta: + return u'' + # does not take microseconds into account + total_secs = value.seconds + value.days * 24 * 3600 + mins = total_secs // 60 + hrs, mins = divmod(mins, 60) + return '%d:%02d' % (hrs, mins) + + +@library.filter +def hours(value): + if not value and type(value) != timedelta: + return u'' + # does not take microseconds into account + total_secs = value.seconds + value.days * 24 * 3600 + mins = total_secs // 60 + hrs, mins = divmod(mins, 60) + if hrs == 1: + return '%d hour' % hrs + return '%d hours' % hrs + + +@library.filter +def floatvalue(value, arg=2): + if value is None: + return u'' + return '%.*f' % (arg, value) + + +@library.filter +def percentage(value, arg=1): + if not value and type(value) != float: + return u'' + new_val = value * 100.0 + return '%.*f%%' % (arg, new_val) + +# vim: set ts=4 sw=4 et: diff --git a/mirrors/templatetags/mirror_status.py b/mirrors/templatetags/mirror_status.py index b3810d9a..c8004e4b 100644 --- a/mirrors/templatetags/mirror_status.py +++ b/mirrors/templatetags/mirror_status.py @@ -31,11 +31,4 @@ def floatvalue(value, arg=2): return u'' return '%.*f' % (arg, value) -@register.filter -def percentage(value, arg=1): - if not value and type(value) != float: - return u'' - new_val = value * 100.0 - return '%.*f%%' % (arg, new_val) - # vim: set ts=4 sw=4 et: diff --git a/news/models.py b/news/models.py index 985c1088..a66da8d4 100644 --- a/news/models.py +++ b/news/models.py @@ -1,11 +1,11 @@ -import markdown - from django.db import models from django.contrib.auth.models import User from django.contrib.sites.models import Site from django.utils.safestring import mark_safe from django.utils.timezone import now +from main.utils import parse_markdown + class News(models.Model): slug = models.SlugField(max_length=255, unique=True) @@ -22,8 +22,7 @@ class News(models.Model): return '/news/%s/' % self.slug def html(self): - return mark_safe(markdown.markdown( - self.content, safe_mode=self.safe_mode, enable_attributes=False)) + return mark_safe(parse_markdown(self.content, not self.safe_mode)) def __unicode__(self): return self.title diff --git a/news/urls.py b/news/urls.py index 0eec6d86..c13722d4 100644 --- a/news/urls.py +++ b/news/urls.py @@ -5,8 +5,7 @@ from .views import (NewsDetailView, NewsListView, urlpatterns = patterns('news.views', - (r'^$', - NewsListView.as_view(), {}, 'news-list'), + (r'^$', NewsListView.as_view(), {}, 'news-list'), (r'^preview/$', 'preview'), # old news URLs, permanent redirect view so we don't break all links diff --git a/news/views.py b/news/views.py index ca4fdf97..274ba75d 100644 --- a/news/views.py +++ b/news/views.py @@ -1,5 +1,3 @@ -import markdown - from django import forms from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect @@ -8,7 +6,7 @@ from django.views.generic import (DetailView, ListView, CreateView, UpdateView, DeleteView) from .models import News -from main.utils import find_unique_slug +from main.utils import find_unique_slug, parse_markdown class NewsForm(forms.ModelForm): @@ -62,7 +60,7 @@ def view_redirect(request, object_id): @require_POST def preview(request): data = request.POST.get('data', '') - markup = markdown.markdown(data, safe_mode=True, enable_attributes=False) + markup = parse_markdown(data) return HttpResponse(markup) # vim: set ts=4 sw=4 et: diff --git a/packages/templatetags/jinja2.py b/packages/templatetags/jinja2.py index a07f87bd..f0b42a09 100644 --- a/packages/templatetags/jinja2.py +++ b/packages/templatetags/jinja2.py @@ -88,5 +88,4 @@ def flag_unfree(package): } return link_encode(url, data) - # vim: set ts=4 sw=4 et: diff --git a/packages/views/search.py b/packages/views/search.py index 0b776d79..e4cd0423 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -6,6 +6,7 @@ from django.db.models import Q from django.http import HttpResponse from django.views.generic import ListView +from devel.models import UserProfile from main.models import Package, Arch, Repo from main.utils import empty_response, make_choice from ..models import PackageRelation @@ -36,14 +37,16 @@ class PackageSearchForm(forms.Form): self.fields['arch'].choices = make_choice( [arch.name for arch in Arch.objects.all()]) self.fields['q'].widget.attrs.update({"size": "30"}) - maints = User.objects.filter(is_active=True).order_by( + + profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id') + people = User.objects.filter( + is_active=True, userprofile__id__in=profile_ids).order_by( 'first_name', 'last_name') - self.fields['maintainer'].choices = \ - [('', 'All'), ('orphan', 'Orphan')] + \ - [(m.username, m.get_full_name()) for m in maints] - self.fields['packager'].choices = \ - [('', 'All'), ('unknown', 'Unknown')] + \ - [(m.username, m.get_full_name()) for m in maints] + people = [('', 'All'), ('orphan', 'Orphan')] + \ + [(p.username, p.get_full_name()) for p in people] + + self.fields['maintainer'].choices = people + self.fields['packager'].choices = people def exact_matches(self): # only do exact match search if 'q' is sole parameter diff --git a/public/views.py b/public/views.py index 1b79e7c8..088ee4de 100644 --- a/public/views.py +++ b/public/views.py @@ -9,7 +9,7 @@ from django.http import HttpResponse from django.shortcuts import get_object_or_404, render from django.views.decorators.cache import cache_control, cache_page -from devel.models import MasterKey, PGPSignature, StaffGroup +from devel.models import MasterKey, DeveloperKey, PGPSignature, StaffGroup, UserProfile from main.models import Arch, Repo, Donor from mirrors.models import MirrorUrl from news.models import News @@ -67,7 +67,9 @@ def feeds(request): @cache_control(max_age=307) def keys(request): - users = User.objects.filter(is_active=True).select_related( + profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id') + users = User.objects.filter( + is_active=True, userprofile__id__in=profile_ids).select_related( 'userprofile__pgp_key').order_by('first_name', 'last_name') user_key_ids = frozenset(user.userprofile.pgp_key[-16:] for user in users if user.userprofile.pgp_key) @@ -90,34 +92,39 @@ def keys(request): not_expired, revoked__isnull=True).values_list('signer', 'signee')) restrict = Q(signer__in=user_key_ids) & Q(signee__in=user_key_ids) - cross_signatures = PGPSignature.objects.filter(restrict, + cross_signatures = PGPSignature.objects.filter( not_expired, revoked__isnull=True).order_by('created') + # filter in python so we aren't sending a crazy long query to the DB + cross_signatures = [s for s in cross_signatures + if s.signer in user_key_ids and s.signee in user_key_ids] + + developer_keys = DeveloperKey.objects.select_related( + 'owner').filter(owner__isnull=False) + developer_keys = {key.key[-16:]: key for key in developer_keys} context = { 'keys': master_keys, 'active_users': users, 'signatures': signatures, 'cross_signatures': cross_signatures, + 'developer_keys': developer_keys, } return render(request, 'public/keys.html', context) @cache_page(1789) def keys_json(request): - node_list = [] + profile_ids = UserProfile.allowed_repos.through.objects.values('userprofile_id') + users = User.objects.filter( + is_active=True, userprofile__id__in=profile_ids).select_related( + 'userprofile__pgp_key').order_by('first_name', 'last_name') users = User.objects.filter(is_active=True).select_related('userprofile') - node_list.extend({ - 'name': dev.get_full_name(), - 'key': dev.userprofile.pgp_key, - 'group': 'dev' - } for dev in users.filter(groups__name='Developers')) - node_list.extend({ - 'name': tu.get_full_name(), - 'key': tu.userprofile.pgp_key, - 'group': 'tu' - } for tu in users.filter(groups__name='Trusted Users').exclude( - groups__name='Developers')) + node_list = [{ + 'name': user.get_full_name(), + 'key': user.userprofile.pgp_key, + 'group': 'packager' + } for user in users] master_keys = MasterKey.objects.select_related('owner').filter( revoked__isnull=True) diff --git a/releng/models.py b/releng/models.py index 2f9216bd..a4af81ab 100644 --- a/releng/models.py +++ b/releng/models.py @@ -2,7 +2,6 @@ from base64 import b64decode from bencode import bdecode, bencode from datetime import datetime import hashlib -import markdown from pytz import utc from django.conf import settings @@ -12,7 +11,7 @@ from django.db.models.signals import pre_save from django.utils.safestring import mark_safe from main.fields import PositiveBigIntegerField -from main.utils import set_created_field +from main.utils import set_created_field, parse_markdown class IsoOption(models.Model): @@ -154,8 +153,7 @@ class Release(models.Model): return "magnet:?%s" % '&'.join(['%s=%s' % (k, v) for k, v in query]) def info_html(self): - return mark_safe(markdown.markdown( - self.info, safe_mode=True, enable_attributes=False)) + return mark_safe(parse_markdown(self.info)) def torrent(self): try: diff --git a/requirements.txt b/requirements.txt index e1acd2e9..2a5dba96 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,9 +2,9 @@ Django==1.7.1 IPy==0.81 Jinja2==2.7.3 -Markdown==2.4.1 +Markdown==2.5.2 bencode==1.0 -django-jinja==1.0.4 +django-jinja==1.0.5 django_countries==3.0.1 jsmin==2.0.11 pgpdump==1.5 diff --git a/requirements_prod.txt b/requirements_prod.txt index ef535eb8..6edfa17f 100644 --- a/requirements_prod.txt +++ b/requirements_prod.txt @@ -2,9 +2,9 @@ Django==1.7.1 IPy==0.81 Jinja2==2.7.3 -Markdown==2.4.1 +Markdown==2.5.2 bencode==1.0 -django-jinja==1.0.4 +django-jinja==1.0.5 django_countries==3.0.1 jsmin==2.0.11 pgpdump==1.5 diff --git a/sitemaps.py b/sitemaps.py index eb4e05d9..03ad9254 100644 --- a/sitemaps.py +++ b/sitemaps.py @@ -8,6 +8,7 @@ from main.models import Package from news.models import News from packages.utils import get_group_info, get_split_packages_info from releng.models import Release +from todolists.models import Todolist class PackagesSitemap(Sitemap): @@ -98,6 +99,13 @@ class NewsSitemap(Sitemap): return 'yearly' +class RecentNewsSitemap(NewsSitemap): + def items(self): + now = datetime.utcnow().replace(tzinfo=utc) + cutoff = now - timedelta(days=30) + return super(RecentNewsSitemap, self).items().filter(postdate__gte=cutoff) + + class ReleasesSitemap(Sitemap): changefreq = "monthly" @@ -105,7 +113,7 @@ class ReleasesSitemap(Sitemap): return Release.objects.all().defer('info', 'torrent_data').order_by() def lastmod(self, obj): - return obj.created + return obj.last_modified def priority(self, obj): if obj.available: @@ -113,6 +121,25 @@ class ReleasesSitemap(Sitemap): return "0.2" +class TodolistSitemap(Sitemap): + priority = "0.4" + + def __init__(self): + now = datetime.utcnow().replace(tzinfo=utc) + self.two_weeks_ago = now - timedelta(days=14) + + def items(self): + return Todolist.objects.all().defer('raw').order_by() + + def lastmod(self, obj): + return obj.last_modified + + def changefreq(self, obj): + if obj.last_modified > self.two_weeks_ago: + return 'weekly' + return 'monthly' + + class BaseSitemap(Sitemap): DEFAULT_PRIORITY = 0.7 diff --git a/sitestatic/archweb.css b/sitestatic/archweb.css index 0a16687e..db053a52 100644 --- a/sitestatic/archweb.css +++ b/sitestatic/archweb.css @@ -631,6 +631,17 @@ div.news-article .article-info { width: 75%; } +/* todolists: list */ +.todolist-nav { + float: right; + margin-top: -2.2em; +} + + .todolist-nav .prev, + .todolist-nav .next { + margin: 0 1em; + } + /* donate: donor list */ #donor-list ul { width: 100%; diff --git a/sitestatic/archweb.js b/sitestatic/archweb.js index 43816c5b..dce9cd0c 100644 --- a/sitestatic/archweb.js +++ b/sitestatic/archweb.js @@ -303,12 +303,12 @@ function filter_packages() { var cells = $(this).children('td'); /* all this just to get the split version out of the table cell */ - var ver_a = cells.eq(2).find('span').text().match(pat); + var ver_a = cells.eq(2).text().match(pat); if (!ver_a) { return true; } - var ver_b = cells.eq(3).find('span').text().match(pat); + var ver_b = cells.eq(3).text().match(pat); if (!ver_b) { return true; } diff --git a/sitestatic/download.png b/sitestatic/download.png Binary files differnew file mode 100644 index 00000000..9ab858c2 --- /dev/null +++ b/sitestatic/download.png diff --git a/sitestatic/magnet.png b/sitestatic/magnet.png Binary files differnew file mode 100644 index 00000000..f67e69b9 --- /dev/null +++ b/sitestatic/magnet.png diff --git a/templates/devel/index.html b/templates/devel/index.html index 72f149a3..3a0fb9a9 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -157,8 +157,11 @@ </ul> </div>{# #dev-dashboard #} -<div id='stats-area'> - <p>Enable Javascript to get more useful info here.</p> +<div id="stats-area"> + <div class="box"> + <h2>Developer Stats</h2> + <p id="stats-message">Enable JavaScript to get more useful info here.</p> + </div> </div> {% endblock %} @@ -167,8 +170,12 @@ <script type="text/javascript" src="{% static "archweb.js" %}"></script> <script type="text/javascript"> $(document).ready(function() { - $("#stats-area").html('<p>Loading stats…</p>'); - $("#stats-area").load('stats/', function() { + $("#stats-message").html('Loading developer stats…'); + $("#stats-area").load('stats/', function(response, status, xhr) { + if (status === 'error' || status === 'timeout') { + $("#stats-message").html('Developer stats loading encountered an error. Sorry.'); + return; + } var settings = { widgets: ['zebra'], sortList: [[0,0]], diff --git a/templates/mirrors/error_table.html b/templates/mirrors/error_table.html.jinja index cd7265af..52f68135 100644 --- a/templates/mirrors/error_table.html +++ b/templates/mirrors/error_table.html.jinja @@ -1,5 +1,3 @@ -{% load cycle from future %} -{% load flags mirror_status %} <table id="errorlog_mirrors" class="results"> <thead> <tr> @@ -12,12 +10,12 @@ </tr> </thead> <tbody> - {% for log in error_logs %}<tr class="{% cycle 'odd' 'even' %}"> + {% for log in error_logs %}<tr class="{{ loop.cycle('odd', 'even') }}"> <td>{{ log.url__url }}</td> <td>{{ log.url__protocol__protocol }}</td> - <td class="country">{% country_flag log.country %}{{ log.country.name }}</td> + <td class="country">{{ country_flag(log.country) }}{{ log.country.name }}</td> <td class="wrap">{{ log.error|linebreaksbr }}</td> - <td>{{ log.last_occurred|date:'Y-m-d H:i' }}</td> + <td>{{ log.last_occurred|date('Y-m-d H:i') }}</td> <td>{{ log.error_count }}</td> </tr>{% endfor %} </tbody> diff --git a/templates/mirrors/mirror_details.html b/templates/mirrors/mirror_details.html index ffaacfe6..3220195d 100644 --- a/templates/mirrors/mirror_details.html +++ b/templates/mirrors/mirror_details.html @@ -90,46 +90,10 @@ </table> <h3>Available URLs</h3> - - <table id="available_urls" class="results"> - <thead> - <tr> - <th>Mirror URL</th> - <th>Protocol</th> - <th>Country</th> - <th>IPv4</th> - <th>IPv6</th> - <th>Last Sync</th> - <th>Completion %</th> - <th>μ Delay (hh:mm)</th> - <th>μ Duration (s)</th> - <th>σ Duration (s)</th> - <th>Score</th> - <th>Details</th> - </tr> - </thead> - <tbody> - {% for m_url in urls %} - <tr class="{% cycle 'odd' 'even' %}"> - <td>{% if m_url.protocol.is_download %}<a href="{{ m_url.url }}">{{ m_url.url }}</a>{% else %}{{ m_url.url }}{% endif %}</td> - <td>{{ m_url.protocol }}</td> - <td class="country">{% country_flag m_url.country %}{{ m_url.country.name }}</td> - <td>{{ m_url.has_ipv4|yesno|capfirst }}</td> - <td>{{ m_url.has_ipv6|yesno|capfirst }}</td> - <td>{{ m_url.last_sync|date:'Y-m-d H:i'|default:'unknown' }}</td> - <td>{{ m_url.completion_pct|percentage:1 }}</td> - <td>{{ m_url.delay|duration|default:'unknown' }}</td> - <td>{{ m_url.duration_avg|floatvalue:2 }}</td> - <td>{{ m_url.duration_stddev|floatvalue:2 }}</td> - <td>{{ m_url.score|floatvalue:1|default:'∞' }}</td> - <td><a href="{{ m_url.id }}/">Details</a></td> - </tr> - {% endfor %} - </tbody> - </table> + {% include "mirrors/mirror_details_urls.html.jinja" %} <h3>Error Log</h3> - {% include "mirrors/error_table.html" %} + {% include "mirrors/error_table.html.jinja" %} </div> <div class="box"> diff --git a/templates/mirrors/mirror_details_urls.html.jinja b/templates/mirrors/mirror_details_urls.html.jinja new file mode 100644 index 00000000..7ab1548b --- /dev/null +++ b/templates/mirrors/mirror_details_urls.html.jinja @@ -0,0 +1,36 @@ +<table id="available_urls" class="results"> + <thead> + <tr> + <th>Mirror URL</th> + <th>Protocol</th> + <th>Country</th> + <th>IPv4</th> + <th>IPv6</th> + <th>Last Sync</th> + <th>Completion %</th> + <th>μ Delay (hh:mm)</th> + <th>μ Duration (s)</th> + <th>σ Duration (s)</th> + <th>Score</th> + <th>Details</th> + </tr> + </thead> + <tbody> + {% for m_url in urls %} + <tr class="{{ loop.cycle('odd', 'even') }}"> + <td>{% if m_url.protocol.is_download %}<a href="{{ m_url.url }}">{{ m_url.url }}</a>{% else %}{{ m_url.url }}{% endif %}</td> + <td>{{ m_url.protocol }}</td> + <td class="country">{{ country_flag(m_url.country) }}{{ m_url.country.name }}</td> + <td>{{ m_url.has_ipv4|yesno|capfirst }}</td> + <td>{{ m_url.has_ipv6|yesno|capfirst }}</td> + <td>{{ m_url.last_sync|date('Y-m-d H:i')|default('unknown') }}</td> + <td>{{ m_url.completion_pct|percentage(1) }}</td> + <td>{{ m_url.delay|duration|default('unknown') }}</td> + <td>{{ m_url.duration_avg|floatvalue(2) }}</td> + <td>{{ m_url.duration_stddev|floatvalue(2) }}</td> + <td>{{ m_url.score|floatvalue(1)|default('∞') }}</td> + <td><a href="{{ m_url.id }}/">Details</a></td> + </tr> + {% endfor %} + </tbody> +</table> diff --git a/templates/mirrors/status.html b/templates/mirrors/status.html index 250d9bad..f11d57ca 100644 --- a/templates/mirrors/status.html +++ b/templates/mirrors/status.html @@ -60,18 +60,18 @@ <a name="outofsync" id="outofsync"></a> <h3>Out of Sync Mirrors</h3> {% with urls=bad_urls table_id='outofsync_mirrors' %} - {% include "mirrors/status_table.html" %} + {% include "mirrors/status_table.html.jinja" %} {% endwith %} <a name="successful" id="successful"></a> <h3>Successfully Syncing Mirrors</h3> {% with urls=good_urls table_id='successful_mirrors' %} - {% include "mirrors/status_table.html" %} + {% include "mirrors/status_table.html.jinja" %} {% endwith %} <a name="errorlog" id="errorlog"></a> <h3>Mirror Syncing Error Log</h3> - {% include "mirrors/error_table.html" %} + {% include "mirrors/error_table.html.jinja" %} </div> {% endblock %} diff --git a/templates/mirrors/status_table.html b/templates/mirrors/status_table.html deleted file mode 100644 index 83538303..00000000 --- a/templates/mirrors/status_table.html +++ /dev/null @@ -1,30 +0,0 @@ -{% load cycle from future %} -{% load flags mirror_status %} -<table id="{{ table_id }}" class="results"> - <thead> - <tr> - <th>Mirror URL</th> - <th>Protocol</th> - <th>Country</th> - <th>Completion %</th> - <th>μ Delay (hh:mm)</th> - <th>μ Duration (s)</th> - <th>σ Duration (s)</th> - <th>Mirror Score</th> - <th></th> - </tr> - </thead> - <tbody> - {% for m_url in urls %}<tr class="{% cycle 'odd' 'even' %}"> - <td>{{ m_url.url }}</td> - <td>{{ m_url.protocol }}</td> - <td class="country">{% country_flag m_url.country %}{{ m_url.country.name }}</td> - <td>{{ m_url.completion_pct|percentage:1 }}</td> - <td>{{ m_url.delay|duration|default:'unknown' }}</td> - <td>{{ m_url.duration_avg|floatvalue:2 }}</td> - <td>{{ m_url.duration_stddev|floatvalue:2 }}</td> - <td>{{ m_url.score|floatvalue:1|default:'∞' }}</td> - <td><a href="{{ m_url.get_absolute_url }}">details</a></td> - </tr>{% endfor %} - </tbody> -</table> diff --git a/templates/mirrors/status_table.html.jinja b/templates/mirrors/status_table.html.jinja new file mode 100644 index 00000000..598a1af0 --- /dev/null +++ b/templates/mirrors/status_table.html.jinja @@ -0,0 +1,28 @@ +<table id="{{ table_id }}" class="results"> + <thead> + <tr> + <th>Mirror URL</th> + <th>Protocol</th> + <th>Country</th> + <th>Completion %</th> + <th>μ Delay (hh:mm)</th> + <th>μ Duration (s)</th> + <th>σ Duration (s)</th> + <th>Mirror Score</th> + <th></th> + </tr> + </thead> + <tbody> + {% for m_url in urls %}<tr class="{{ loop.cycle('odd', 'even') }}"> + <td>{{ m_url.url }}</td> + <td>{{ m_url.protocol }}</td> + <td class="country">{{ country_flag(m_url.country) }}{{ m_url.country.name }}</td> + <td>{{ m_url.completion_pct|percentage(1) }}</td> + <td>{{ m_url.delay|duration|default('unknown') }}</td> + <td>{{ m_url.duration_avg|floatvalue(2) }}</td> + <td>{{ m_url.duration_stddev|floatvalue(2) }}</td> + <td>{{ m_url.score|floatvalue(1)|default('∞') }}</td> + <td><a href="{{ m_url.get_absolute_url() }}">details</a></td> + </tr>{% endfor %} + </tbody> +</table> diff --git a/templates/mirrors/url_details.html b/templates/mirrors/url_details.html index c47e0508..96fcc49d 100644 --- a/templates/mirrors/url_details.html +++ b/templates/mirrors/url_details.html @@ -1,5 +1,4 @@ {% extends "base.html" %} -{% load cycle from future %} {% load static from staticfiles %} {% load mirror_status %} {% load flags %} @@ -58,33 +57,7 @@ </table> <h3>Check Logs</h3> - - <table id="check_logs" class="results"> - <thead> - <tr> - <th>Check Time</th> - <th>Check Location</th> - <th>Check IP</th> - <th>Last Sync</th> - <th>Delay (hh:mm)</th> - <th>Duration (s)</th> - <th>Success?</th> - <th>Error Message</th> - </tr> - </thead> - <tbody> - {% for log in logs %}<tr class="{% cycle 'odd' 'even' %}"> - <td>{{ log.check_time|date:'Y-m-d H:i' }}</td> - <td class="country">{% country_flag log.location.country %}{{ log.location.country.name }}</td> - <td>{{ log.location.source_ip }}</td> - <td>{{ log.last_sync|date:'Y-m-d H:i' }}</td> - <td>{{ log.delay|duration }}</td> - <td>{{ log.duration|floatvalue }}</td> - <td>{{ log.is_success|yesno|capfirst }}</td> - <td class="wrap">{{ log.error|linebreaksbr }}</td> - </tr>{% endfor %} - </tbody> - </table> + {% include "mirrors/url_details_logs.html.jinja" %} </div> {% endblock %} diff --git a/templates/mirrors/url_details_logs.html.jinja b/templates/mirrors/url_details_logs.html.jinja new file mode 100644 index 00000000..58f179d8 --- /dev/null +++ b/templates/mirrors/url_details_logs.html.jinja @@ -0,0 +1,26 @@ +<table id="check_logs" class="results"> + <thead> + <tr> + <th>Check Time</th> + <th>Check Location</th> + <th>Check IP</th> + <th>Last Sync</th> + <th>Delay (hh:mm)</th> + <th>Duration (s)</th> + <th>Success?</th> + <th>Error Message</th> + </tr> + </thead> + <tbody> + {% for log in logs %}<tr class="{{ loop.cycle('odd', 'even') }}"> + <td>{{ log.check_time|date('Y-m-d H:i') }}</td> + <td class="country">{{ country_flag(log.location.country) }}{{ log.location.country.name }}</td> + <td>{{ log.location.source_ip }}</td> + <td>{{ log.last_sync|date('Y-m-d H:i') }}</td> + <td>{{ log.delay|duration }}</td> + <td>{{ log.duration|floatvalue }}</td> + <td>{{ log.is_success|yesno|capfirst }}</td> + <td class="wrap">{{ log.error|linebreaksbr }}</td> + </tr>{% endfor %} + </tbody> +</table> diff --git a/templates/news/list.html b/templates/news/list.html index 3295e333..8662a91b 100644 --- a/templates/news/list.html +++ b/templates/news/list.html @@ -10,7 +10,7 @@ {% block content %} <div id="news-article-list" class="box"> - <h2>News Archives</h2> + <h2>{{ BRANDING_DISTRONAME }} News Archives</h2> {% if perms.news.add_news %} <ul class="admin-actions"> @@ -54,6 +54,5 @@ </table> {% include "news/paginator.html" %} - </div> {% endblock %} diff --git a/templates/public/keys.html b/templates/public/keys.html index 0818719c..f15ec1a9 100644 --- a/templates/public/keys.html +++ b/templates/public/keys.html @@ -123,8 +123,8 @@ <tbody> {% for sig in cross_signatures %} <tr> - <td>{% user_pgp_key_link sig.signer %}</td> - <td>{% user_pgp_key_link sig.signee %}</td> + <td>{% user_pgp_key_link developer_keys sig.signer %}</td> + <td>{% user_pgp_key_link developer_keys sig.signee %}</td> <td>{{ sig.created }}</td> <td>{{ sig.expires|default:"" }}</td> </tr> diff --git a/templates/releng/release_detail.html b/templates/releng/release_detail.html index d04533b9..97017600 100644 --- a/templates/releng/release_detail.html +++ b/templates/releng/release_detail.html @@ -1,4 +1,5 @@ {% extends "base.html" %} +{% load static %} {% block title %}{{ BRANDING_DISTRONAME }} - Release: {{ release.version }}{% endblock %} @@ -9,11 +10,15 @@ <ul> <li><strong>Release Date:</strong> {{ release.release_date|date }}</li> {% if release.kernel_version %}<li><strong>Kernel Version:</strong> {{ release.kernel_version }}</li>{% endif %} - <li><strong>Available:</strong> {{ release.available|yesno }}</li> - {% if release.torrent_data %}<li><strong>Download:</strong> <a - href="{% url 'releng-release-torrent' release.version %}" - title="Download torrent for {{ release.version }}">Torrent</a>, - <a href="{{ release.magnet_uri }}">Magnet</a></li>{% endif %} + <li><strong>Available:</strong> {{ release.available|yesno|capfirst }}</li> + {% if release.torrent_data %} + <li><a href="{% url 'releng-release-torrent' release.version %}" + title="Download torrent for {{ release.version }}"> + Download via Torrent <img width="12" height="12" src="{% static "download.png" %}" alt=""/></a></li> + <li><a href="{{ release.magnet_uri }}" + title="Get magnet link for {{ release.version }}"> + Download via Magnet URI <img width="12" height="12" src="{% static "magnet.png" %}" alt=""/></a></li> + {% endif %} {% if release.md5_sum %}<li><strong>MD5:</strong> {{ release.md5_sum }}</li>{% endif %} {% if release.sha1_sum %}<li><strong>SHA1:</strong> {{ release.sha1_sum }}</li>{% endif %} </ul> diff --git a/templates/releng/release_list.html b/templates/releng/release_list.html index af8b8a61..3f442d07 100644 --- a/templates/releng/release_list.html +++ b/templates/releng/release_list.html @@ -12,28 +12,41 @@ <div id="release-list" class="box"> <h2>Releases</h2> + <p>This is a list of ISO releases made by the Arch Linux release + engineering team. These are typically done on a monthly cadence, containing + the latest kernel and base packages from the package repositories. Click + the version of each release to read any additional notes or details about + each release.</p> + <p>Torrents and magnet URIs are available to download the releases. + Releases listed as not available may still be seeded by peers, but are no + longer registered via the official Arch Linux torrent tracker. We always + recommend running the latest available release.</p> + <table id="release-table" class="results"> <thead> <tr> + <th style="width: 30px;"></th> <th>Release Date</th> <th>Version</th> <th>Kernel Version</th> <th>Available?</th> - <th>Torrent</th> - <th>Magnet</th> <th>Download Size</th> </tr> </thead> <tbody> {% for item in release_list %} <tr class="{% cycle 'odd' 'even' %}"> + <td>{% if item.torrent_data %} + <a href="{% url 'releng-release-torrent' item.version %}" + title="Download torrent for {{ item.version }}"><img width="12" height="12" src="{% static "download.png" %}" alt="Torrent"/></a> + + <a href="{{ item.magnet_uri }}" + title="Get magnet link for {{ item.version }}"><img width="12" height="12" src="{% static "magnet.png" %}" alt="Magnet"/></a> + {% endif %}</td> <td>{{ item.release_date|date }}</td> <td><a href="{{ item.get_absolute_url }}" title="Release details for {{ item.version }}">{{ item.version }}</a></td> <td>{{ item.kernel_version|default:"" }}</td> <td class="available-{{ item.available|yesno }}">{{ item.available|yesno|capfirst }}</td> - <td>{% if item.available %}<a href="{% url 'releng-release-torrent' item.version %}" - title="Download torrent for {{ item.version }}">Torrent</a>{% endif %}</td> - <td>{% if item.available %}<a href="{{ item.magnet_uri }}">Magnet</a>{% endif %}</td> <td>{% if item.torrent_data %}{{ item.torrent.file_length|filesizeformat }}{% endif %}</td> </tr> {% endfor %} @@ -50,7 +63,7 @@ $(document).ready(function() { $(".results").tablesorter({ widgets: ['zebra'], sortList: [[0,1], [1,1]], - headers: { 4: { sorter: false }, 5: { sorter: false } } + headers: { 0: { sorter: false } } }); }); </script> diff --git a/templates/sitemaps/news_sitemap.xml.jinja b/templates/sitemaps/news_sitemap.xml.jinja new file mode 100644 index 00000000..97dd17b5 --- /dev/null +++ b/templates/sitemaps/news_sitemap.xml.jinja @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"> +{% for url in urlset %}<url> +<loc>{{ url.location }}</loc> +{% if url.lastmod %}<lastmod>{{ url.lastmod|date("Y-m-d") }}</lastmod>{% endif %} +{% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %} +{% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %} +<news:news> + <news:publication><news:name>Arch Linux News</news:name><news:language>en</news:language></news:publication> + {% if url.item.postdate %}<news:publication_date>{{ url.item.postdate|date("c") }}</news:publication_date>{% endif %} + {% if url.item.title %}<news:title>{{ url.item.title }}</news:title>{% endif %} +</news:news> +</url>{% endfor %} +</urlset> diff --git a/templates/sitemaps/sitemap.xml.jinja b/templates/sitemaps/sitemap.xml.jinja new file mode 100644 index 00000000..0808a7de --- /dev/null +++ b/templates/sitemaps/sitemap.xml.jinja @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> +{% for url in urlset %}<url> +<loc>{{ url.location }}</loc> +{% if url.lastmod %}<lastmod>{{ url.lastmod|date("Y-m-d") }}</lastmod>{% endif %} +{% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %} +{% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %} +</url>{% endfor %} +</urlset> diff --git a/templates/todolists/list.html b/templates/todolists/list.html index 0f1ccfd7..5cfd6a02 100644 --- a/templates/todolists/list.html +++ b/templates/todolists/list.html @@ -16,7 +16,10 @@ <p>Todo lists are used by the developers when a rebuild of a set of packages is needed. This is common when a library has a version bump, during a toolchain rebuild, or a general cleanup of packages in the - repositories. The progress can be tracked here.</p> + repositories. The progress can be tracked here, and completed todo lists + can be browsed as well.</p> + + {% include "todolists/paginator.html" %} <table id="dev-todo-lists" class="results todo-table"> <thead> @@ -46,6 +49,8 @@ {% endfor %} </tbody> </table> + + {% include "todolists/paginator.html" %} </div> {% endblock %} diff --git a/templates/todolists/paginator.html b/templates/todolists/paginator.html new file mode 100644 index 00000000..3b077419 --- /dev/null +++ b/templates/todolists/paginator.html @@ -0,0 +1,22 @@ +{% if is_paginated %} +<div class="pagination"> + <p>{{ paginator.count }} todo lists, viewing page {{ page_obj.number }} of {{ paginator.num_pages }}.</p> + <p class="todolist-nav"> + {% if page_obj.has_previous %} + <a class="prev" href="?page={{ page_obj.previous_page_number }}" + title="Go to previous page">< Prev</a> + {% endif %} + {% for num in paginator.page_range %} + {% ifequal num page_obj.number %} + <span>{{ num }}</span> + {% else %} + <a href="?page={{ num }}" title="Go to page {{ num }}">{{ num }}</a> + {% endifequal %} + {% endfor %} + {% if page_obj.has_next %} + <a class="next" href="?page={{ page_obj.next_page_number }}" + title="Go to next page">Next ></a> + {% endif %} + </p> +</div> +{% endif %} diff --git a/todolists/urls.py b/todolists/urls.py index 6617d7dd..ed065f50 100644 --- a/todolists/urls.py +++ b/todolists/urls.py @@ -1,11 +1,11 @@ from django.conf.urls import patterns from django.contrib.auth.decorators import permission_required -from .views import (view_redirect, view, todolist_list, add, edit, flag, - list_pkgbases, DeleteTodolist) +from .views import (view_redirect, view, add, edit, flag, + list_pkgbases, DeleteTodolist, TodolistListView) urlpatterns = patterns('', - (r'^$', todolist_list), + (r'^$', TodolistListView.as_view(), {}, 'todolist-list'), # old todolists URLs, permanent redirect view so we don't break all links (r'^(?P<old_id>\d+)/$', view_redirect), diff --git a/todolists/views.py b/todolists/views.py index cdbfa702..db6f20f0 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -8,7 +8,7 @@ from django.shortcuts import (get_list_or_404, get_object_or_404, redirect, render) from django.db import transaction from django.views.decorators.cache import never_cache -from django.views.generic import DeleteView +from django.views.generic import DeleteView, ListView from django.template import Context, loader from django.utils.timezone import now @@ -92,10 +92,13 @@ def list_pkgbases(request, slug, svn_root): return HttpResponse('\n'.join(pkgbases), content_type='text/plain') -def todolist_list(request): - incomplete_only = request.user.is_anonymous() - lists = get_annotated_todolists(incomplete_only) - return render(request, 'todolists/list.html', {'lists': lists}) +class TodolistListView(ListView): + context_object_name = "lists" + template_name = "todolists/list.html" + paginate_by = 50 + + def get_queryset(self): + return get_annotated_todolists() @never_cache @@ -16,8 +16,11 @@ our_sitemaps = { 'package-groups': sitemaps.PackageGroupsSitemap, 'split-packages': sitemaps.SplitPackagesSitemap, 'releases': sitemaps.ReleasesSitemap, + 'todolists': sitemaps.TodolistSitemap, } +news_sitemaps = { 'news': sitemaps.RecentNewsSitemap } + urlpatterns = [] # Public pages @@ -78,7 +81,12 @@ urlpatterns += patterns('', {'sitemaps': our_sitemaps, 'sitemap_url_name': 'sitemaps'}), (r'^sitemap-(?P<section>.+)\.xml$', cache_page(1831)(sitemap_views.sitemap), - {'sitemaps': our_sitemaps}, 'sitemaps'), + {'sitemaps': our_sitemaps, 'template_name': 'sitemaps/sitemap.xml.jinja'}, + 'sitemaps'), + (r'^news-sitemap\.xml$', + cache_page(1831)(sitemap_views.sitemap), + {'sitemaps': news_sitemaps, 'template_name': 'sitemaps/news_sitemap.xml.jinja'}, + 'news-sitemap'), ) # Authentication / Admin diff --git a/visualize/static/visualize.js b/visualize/static/visualize.js index 6f254f29..32e2a304 100644 --- a/visualize/static/visualize.js +++ b/visualize/static/visualize.js @@ -195,7 +195,7 @@ function developer_keys(chart_id, data_url) { } }); jQuery.map(json.nodes, function(d, i) { - if (d.group === "dev" || d.group === "tu") { + if (d.group === "packager") { d.approved = d.master_sigs >= 3; } else { d.approved = null; @@ -220,8 +220,7 @@ function developer_keys(chart_id, data_url) { return r * 1.6 - 0.75; case "cacert": return r * 1.4 - 0.75; - case "dev": - case "tu": + case "packager": default: return r - 0.75; } |