diff options
-rw-r--r-- | README | 112 | ||||
-rw-r--r-- | devel/management/commands/reporead.py | 2 | ||||
-rw-r--r-- | main/models.py | 4 | ||||
-rw-r--r-- | main/templatetags/pgp.py | 8 | ||||
-rw-r--r-- | mirrors/views.py | 2 | ||||
-rw-r--r-- | packages/views/__init__.py | 28 | ||||
-rw-r--r-- | packages/views/signoff.py | 2 | ||||
-rw-r--r-- | public/views.py | 21 | ||||
-rw-r--r-- | requirements_prod.txt | 4 | ||||
-rw-r--r-- | sitestatic/archweb.css | 8 | ||||
-rw-r--r-- | templates/mirrors/status.html | 4 | ||||
-rw-r--r-- | templates/mirrors/status_table.html | 4 | ||||
-rw-r--r-- | templates/packages/search.html | 4 | ||||
-rw-r--r-- | templates/packages/signoff_cell.html | 2 | ||||
-rw-r--r-- | templates/packages/signoffs.html | 12 | ||||
-rw-r--r-- | templates/public/keys.html | 56 | ||||
-rw-r--r-- | templates/releng/result_section.html | 1 | ||||
-rw-r--r-- | urls.py | 16 | ||||
-rw-r--r-- | visualize/views.py | 2 |
19 files changed, 177 insertions, 115 deletions
@@ -1,4 +1,15 @@ # Parabolaweb README +It is recommended that you just install the (parabola) package +`parabolaweb-utils`, it + + - depends on the dependencies of parabolaweb + - offers initscripts and systemd service files for parabolaweb + - has a `parabolaweb-update` script that does most of the things here. + +Following is Archweb's readme, as I figure it might be useful for some people, +but I have given up on maintaining it for parabolaweb. + +# Archweb README To get a pretty version of this document, run @@ -15,97 +26,62 @@ See AUTHORS file. # Dependencies - python2 -- python2-virtualenv (if using pip to manage dependencies) +- python2-virtualenv # Python dependencies -We're going to use pip to handle python dependencies, m'kay? -Worry about that in step 3. - -If you really want to manage dependencies using something other than -pip, look at `requirements.txt`, and at the comments on other -dependiencies in step 3. - -# Testing Installation - -Throughout this, we assume that parabolaweb is installed in a -directory called `parabolaweb`. This is not necessarily true. On the -main server, it's in `/srv/http/web'. Wherever you see this in a -command, know that you should just replace it with the correct path -for your install. - -1. Run `virtualenv2`. - - $ cd /path/to/parabolaweb && virtualenv2 `pwd`-env - - Here I just had you use `pwd` to choose the environment - directory. You can use anything, but it is recommended that it not - be the same directory as the install. +More detail in `requirements.txt` and `requirements_prod.txt`; it is best to +use virtualenv and pip to handle these. But if you insist on (Arch Linux) +packages, you will probably want the following: -2. Activate the virtualenv. +- mysql-python or python-pysqlite +- django +- python-markdown +- python-south +- python-memcached - $ source `pwd`-env/bin/activate - -3. Fix symlink to the environment - - (parabolaweb-env) $ ln -sf ../../parabolaweb-env/lib/python2.7/site-packages/django/contrib/admin/media media/admin_media - - Of course change `../../parabolaweb-env` to the relative path to - your environment. Keep in mind that the path is relative from - inside the `media/` directory, not the current directory. - -4. Install dependencies through `pip`. - - To install base dependencies, run - - (parabolweb-env) $ pip install -r requirements.txt - - After that you will need to install a database engine for python - This means `MySQL-python==1.2.3`, `pysqlite` or `psycopg2` for - PostgreSQL. Eg: +# Testing Installation - (parabolweb-env) $ pip install MySQL-python==1.2.3 +1. Run `virtualenv2`. - You may also want to install memcached + $ cd /path/to/archweb && virtualenv2 ../archweb-env - (parabolweb-env) $ pip install python-memcached==1.47 +2. Activate the virtualenv. - Alternately, to have psycopg2 and memcached installed automatically, - run + $ source ../archweb-env/bin/activate - (parabolweb-env) $ pip install -r requirements_prod.txt +2. Install dependencies through `pip`. - We use PostgreSQL on the main server, and you may have problems - with other SQLs. + (archweb-env) $ pip install -r requirements.txt -5. Copy `local_settings.py.example` to `local_settings.py` and modify. - Make sure to uncomment the appropriate db section. +3. Copy `local_settings.py.example` to `local_settings.py` and modify. + Make sure to uncomment the appropriate db section (either sqlite or mysql). -6. Sync the database to create it. +4. Sync the database to create it. - (parabolaweb-env) $ ./manage.py syncdb + (archweb-env) $ ./manage.py syncdb -7. Migrate changes. +5. Migrate changes. - (parabolaweb-env) $ ./manage.py migrate + (archweb-env) $ ./manage.py migrate -8. Load the fixtures to prepopulate some data. If you don't want some - of the provided data, adjust the file glob accordingly. +6. Load the fixtures to prepopulate some data. If you don't want some of the + provided data, adjust the file glob accordingly. - (parabolaweb-env) $ ./manage.py loaddata */fixtures/*.json + (archweb-env) $ ./manage.py loaddata */fixtures/*.json -9. Use the following commands to start a service instance +7. Use the following commands to start a service instance - (parabolaweb-env) $ ./manage.py runserver + (archweb-env) $ ./manage.py runserver -10. To optionally populate the database with real data: +8. To optionally populate the database with real data: - $ wget https://repo.parabolagnulinux.org/core/os/i686/core.db.tar.gz - $ ./manage.py reporead i686 core.db.tar.gz - $ ./manage.py syncisos + (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 - Alter architecture and repo to get x86\_64 and packages from - other repos if needed. +Alter architecture and repo to get x86\_64 and packages from other repos if +needed. # Production Installation diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 57c8715a..cf98f004 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -234,7 +234,7 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): populate_files(dbpkg, repopkg, force=force) - dbpkg.packagedepend_set.all().delete() + dbpkg.depends.all().delete() deps = [create_depend(dbpkg, y) for y in repopkg.depends] deps += [create_depend(dbpkg, y, True) for y in repopkg.optdepends] PackageDepend.objects.bulk_create(deps) diff --git a/main/models.py b/main/models.py index 289cbb84..34cbcd17 100644 --- a/main/models.py +++ b/main/models.py @@ -288,7 +288,7 @@ class Package(models.Model): if not self.arch.agnostic: arches = self.applicable_arches() # TODO: we can use list comprehension and an 'in' query to make this more effective - for dep in self.packagedepend_set.order_by('optional', 'depname'): + for dep in self.depends.order_by('optional', 'depname'): pkg = dep.get_best_satisfier(arches, testing=self.repo.testing, staging=self.repo.staging) providers = None @@ -391,7 +391,7 @@ class PackageFile(models.Model): db_table = 'package_files' class PackageDepend(models.Model): - pkg = models.ForeignKey(Package) + pkg = models.ForeignKey(Package, related_name='depends') depname = models.CharField(max_length=255, db_index=True) depvcmp = models.CharField(max_length=255, default='') optional = models.BooleanField(default=False) diff --git a/main/templatetags/pgp.py b/main/templatetags/pgp.py index 1ffc5241..50b1aa17 100644 --- a/main/templatetags/pgp.py +++ b/main/templatetags/pgp.py @@ -50,4 +50,12 @@ def pgp_fingerprint(key_id, autoescape=True): return mark_safe(format_key(esc(key_id))) pgp_fingerprint.needs_autoescape = True + +@register.assignment_tag +def signature_exists(signatures, signer, signee): + if not signer or not signee: + return False + lookup = (signer[-16:], signee[-16:]) + return lookup in signatures + # vim: set ts=4 sw=4 et: diff --git a/mirrors/views.py b/mirrors/views.py index 2ef8654d..e93097a3 100644 --- a/mirrors/views.py +++ b/mirrors/views.py @@ -169,7 +169,7 @@ class MirrorStatusJSONEncoder(DjangoJSONEncoder): def status_json(request): status_info = get_mirror_statuses() data = status_info.copy() - data['version'] = 1 + data['version'] = 2 to_json = simplejson.dumps(data, ensure_ascii=False, cls=MirrorStatusJSONEncoder) response = HttpResponse(to_json, mimetype='application/json') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 08e0286c..aa2721af 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -1,3 +1,6 @@ +from string import Template +from urllib import urlencode + from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import User @@ -9,12 +12,10 @@ from django.views.decorators.http import require_POST from django.views.decorators.vary import vary_on_headers from django.views.generic.simple import direct_to_template -from string import Template -from urllib import urlencode - -from main.models import Package, PackageFile, Arch, Repo +from main.models import Package, PackageFile, PackageDepend, Arch, Repo from mirrors.models import MirrorUrl -from ..models import PackageRelation, PackageGroup +from ..models import (PackageRelation, PackageGroup, License, + Conflict, Provision, Replacement) from ..utils import (get_group_info, get_differences_info, multilib_differences, get_wrong_permissions) @@ -29,6 +30,8 @@ class PackageJSONEncoder(DjangoJSONEncoder): 'pkgrel', 'epoch', 'pkgdesc', 'url', 'filename', 'compressed_size', 'installed_size', 'build_date', 'last_update', 'flag_date', 'maintainers', 'packager' ] + pkg_list_attributes = [ 'groups', 'licenses', 'conflicts', + 'provides', 'replaces', 'depends' ] def default(self, obj): if hasattr(obj, '__iter__'): @@ -37,13 +40,18 @@ class PackageJSONEncoder(DjangoJSONEncoder): if isinstance(obj, Package): data = dict((attr, getattr(obj, attr)) for attr in self.pkg_attributes) - data['groups'] = obj.groups.all() + for attr in self.pkg_list_attributes: + data[attr] = getattr(obj, attr).all() return data if isinstance(obj, PackageFile): filename = obj.filename or '' return obj.directory + filename - if isinstance(obj, (Repo, Arch, PackageGroup)): + if isinstance(obj, (Repo, Arch)): return obj.name.lower() + if isinstance(obj, (PackageGroup, License)): + return obj.name + if isinstance(obj, (Conflict, Provision, Replacement, PackageDepend)): + return unicode(obj) elif isinstance(obj, User): return obj.username return super(PackageJSONEncoder, self).default(obj) @@ -177,7 +185,8 @@ def group_details(request, arch, name): def files(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) - fileslist = PackageFile.objects.filter(pkg=pkg).order_by('directory', 'filename') + # files are inserted in sorted order, so preserve that + fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') dir_count = sum(1 for f in fileslist if f.is_directory) files_count = len(fileslist) - dir_count context = { @@ -201,7 +210,8 @@ def details_json(request, name, repo, arch): def files_json(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) - fileslist = PackageFile.objects.filter(pkg=pkg).order_by('directory', 'filename') + # files are inserted in sorted order, so preserve that + fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') data = { 'pkgname': pkg.pkgname, 'repo': pkg.repo.name.lower(), diff --git a/packages/views/signoff.py b/packages/views/signoff.py index cf00b0b9..63341a1d 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -180,7 +180,7 @@ class SignoffJSONEncoder(DjangoJSONEncoder): def signoffs_json(request): signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase')) data = { - 'version': 1, + 'version': 2, 'signoff_groups': signoff_groups, } to_json = simplejson.dumps(data, ensure_ascii=False, diff --git a/public/views.py b/public/views.py index a8ce2fa7..e031201e 100644 --- a/public/views.py +++ b/public/views.py @@ -1,6 +1,8 @@ +from datetime import datetime + from django.conf import settings from django.contrib.auth.models import User -from django.db.models import Count +from django.db.models import Count, Q from django.http import Http404 from django.shortcuts import redirect from django.views.decorators.cache import cache_control @@ -71,17 +73,30 @@ def feeds(request): @cache_control(max_age=300) def keys(request): + not_expired = Q(expires__gt=datetime.utcnow) | Q(expires__isnull=True) master_keys = MasterKey.objects.select_related('owner', 'revoker', 'owner__userprofile', 'revoker__userprofile').filter( revoked__isnull=True) - sig_counts = PGPSignature.objects.filter(valid=True, - expires__isnull=True).values_list('signer').annotate( + + sig_counts = PGPSignature.objects.filter( + not_expired, valid=True).values_list('signer').annotate( Count('signer')) sig_counts = dict((key_id[-16:], ct) for key_id, ct in sig_counts) + for key in master_keys: key.signature_count = sig_counts.get(key.pgp_key[-16:], 0) + + users = User.objects.filter(is_active=True).select_related( + 'userprofile__pgp_key').order_by('first_name', 'last_name') + + # frozenset because we are going to do lots of __contains__ lookups + signatures = frozenset(PGPSignature.objects.filter( + not_expired, valid=True).values_list('signer', 'signee')) + context = { 'keys': master_keys, + 'active_users': users, + 'signatures': signatures, } return direct_to_template(request, 'public/keys.html', context) diff --git a/requirements_prod.txt b/requirements_prod.txt index 80fa2b5b..bb67fc5b 100644 --- a/requirements_prod.txt +++ b/requirements_prod.txt @@ -1,8 +1,8 @@ -Django==1.4 +Django>=1.4 Markdown>=2.1.1 -psycopg2 South>=0.7.4 pgpdump>=1.1 +psycopg2>=2.4.4 pyinotify>=0.9.2 python-memcached>=1.48 pytz>=2012b diff --git a/sitestatic/archweb.css b/sitestatic/archweb.css index 180cb05a..ced1fd2a 100644 --- a/sitestatic/archweb.css +++ b/sitestatic/archweb.css @@ -957,6 +957,14 @@ ul.signoff-list { color: red; } +#key-status .signed-yes { + color: green; +} + +#key-status .signed-no { + color: red; +} + /* highlight current website in the navbar */ #archnavbar.anb-home ul li#anb-home a, #archnavbar.anb-packages ul li#anb-packages a, diff --git a/templates/mirrors/status.html b/templates/mirrors/status.html index 307b96dd..cce6d983 100644 --- a/templates/mirrors/status.html +++ b/templates/mirrors/status.html @@ -89,7 +89,7 @@ </thead> <tbody> {% for log in error_logs %} - <tr class="{% cycle 'odd' 'even' %}"> + {% spaceless %}<tr class="{% cycle 'odd' 'even' %}"> <td>{{ log.url__url }}</td> <td>{{ log.url__protocol__protocol }}</td> <td>{{ log.country }}</td> @@ -97,7 +97,7 @@ <td>{{ log.last_occurred|date:'Y-m-d H:i' }}</td> <td>{{ log.error_count }}</td> </tr> - {% endfor %} + {% endspaceless %}{% endfor %} </tbody> </table> diff --git a/templates/mirrors/status_table.html b/templates/mirrors/status_table.html index 72de25dc..bd70115c 100644 --- a/templates/mirrors/status_table.html +++ b/templates/mirrors/status_table.html @@ -15,7 +15,7 @@ </thead> <tbody> {% for m_url in urls %} - <tr class="{% cycle 'odd' 'even' %}"> + {% spaceless %}<tr class="{% cycle 'odd' 'even' %}"> <td>{{ m_url.url }}</td> <td>{{ m_url.protocol }}</td> <td>{{ m_url.real_country }}</td> @@ -26,6 +26,6 @@ <td>{{ m_url.duration_stddev|floatformat:2 }}</td> <td>{{ m_url.score|floatformat:1|default:'∞' }}</td> </tr> - {% endfor %} + {% endspaceless %}{% endfor %} </tbody> </table> diff --git a/templates/packages/search.html b/templates/packages/search.html index b344af1f..974c190b 100644 --- a/templates/packages/search.html +++ b/templates/packages/search.html @@ -77,7 +77,7 @@ </thead> <tbody> {% for pkg in package_list %} - <tr class="{% cycle 'odd' 'even' %}"> + {% spaceless %}<tr class="{% cycle 'odd' 'even' %}"> {% if perms.main.change_package %} <td><input type="checkbox" name="pkgid" value="{{ pkg.id }}" /></td> {% endif %} @@ -94,7 +94,7 @@ <td>{{ pkg.last_update|date }}</td> <td>{{ pkg.flag_date|date }}</td> </tr> - {% endfor %} + {% endspaceless %}{% endfor %} </tbody> </table> {% include "packages/search_paginator.html" %} diff --git a/templates/packages/signoff_cell.html b/templates/packages/signoff_cell.html index 01a5d58d..7d9e1972 100644 --- a/templates/packages/signoff_cell.html +++ b/templates/packages/signoff_cell.html @@ -1,4 +1,3 @@ -{% spaceless %} {% if group.signoffs %} <ul class="signoff-list"> {% for signoff in group.signoffs %} @@ -22,4 +21,3 @@ <a class="signoff-options" href="{{ group.package.get_absolute_url }}signoff/options/">Signoff Options</a> </div> {% endif %} -{% endspaceless %} diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 2d7b04cd..7b79c35b 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -52,6 +52,7 @@ <tbody id="tbody_signoffs"> {% for group in signoff_groups %} <tr class="{% cycle 'odd' 'even' %} {{ group.arch.name }} {{ group.target_repo|lower }}"> + {% spaceless %} <td>{% pkg_details_link group.package %} {{ group.version }}</td> <td>{{ group.arch.name }}</td> <td>{{ group.target_repo }}</td> @@ -68,12 +69,13 @@ {% endif %} {% endif %} <td>{% include "packages/signoff_cell.html" %}</td> - <td class="wrap">{% if not group.default_spec %}{% with group.specification as spec %} - {% if spec.required != 2 %}Required signoffs: {{ spec.required }}<br/>{% endif %} - {% if not spec.enabled %}Signoffs are not currently enabled<br/>{% endif %} - {% if spec.known_bad %}Package is known to be bad<br/>{% endif %} - {{ spec.comments|default:""|linebreaksbr }} + <td class="wrap">{% if not group.default_spec %}{% with group.specification as spec %}{% comment %} + {% endcomment %}{% if spec.required != 2 %}Required signoffs: {{ spec.required }}<br/>{% endif %}{% comment %} + {% endcomment %}{% if not spec.enabled %}Signoffs are not currently enabled<br/>{% endif %}{% comment %} + {% endcomment %}{% if spec.known_bad %}Package is known to be bad<br/>{% endif %}{% comment %} + {% endcomment %}{{ spec.comments|default:""|linebreaksbr }} {% endwith %}{% endif %}</td> + {% endspaceless %} </tr> {% endfor %} </tbody> diff --git a/templates/public/keys.html b/templates/public/keys.html index 43b1b067..f0aa310e 100644 --- a/templates/public/keys.html +++ b/templates/public/keys.html @@ -15,15 +15,6 @@ <p>The {{ keys|length }} key{{ keys|pluralize }} listed below should be regarded as the current set of master keys. They are available on public keyservers and should be signed by the owner of the key.</p> - <p>All official Arch Linux developers and trusted users should have their - key signed by at least three of these master keys. This is in accordance - with the PGP <em>web of trust</em> concept. If a user is willing to - marginally trust all of the master keys, three signatures from different - master keys will consider a given developer's key as valid. For more - information on trust, please consult the - <a href="http://www.gnupg.org/gph/en/manual.html">GNU Privacy Handbook</a> - and <a href="http://www.gnupg.org/gph/en/manual.html#AEN385">Using trust to - validate keys</a>.</p> <table class="pretty2"> <thead> @@ -55,5 +46,52 @@ {% endfor %} </tbody> </table> + + <p>The following table shows all active developers and trusted users along + with the status of their personal signing key. A 'Yes' indicates that the + personal key of the developer is signed by the given master key. A 'No' + indicates it has not been signed; however, this does not necessarily mean + the key should not be trusted.</p> + <p>All official Arch Linux developers and trusted users should have their + key signed by at least three master keys if they are responsible for + packaging software in the repositories. This is in accordance with the PGP + <em>web of trust</em> concept. If a user is willing to marginally trust all + of the master keys, three signatures from different master keys will + consider a given developer's key as valid. For more information on trust, + please consult the + <a href="http://www.gnupg.org/gph/en/manual.html">GNU Privacy Handbook</a> + and <a href="http://www.gnupg.org/gph/en/manual.html#AEN385">Using trust to + validate keys</a>.</p> + + <table class="pretty2" id="key-status"> + <thead> + <tr> + <th></th> + <th>PGP Key</th> + {% for key in keys %} + <th>{{ key.owner.get_full_name }}</th> + {% endfor %} + </tr> + <tr> + <th></th> + <th></th> + {% for key in keys %} + <th>{% pgp_key_link key.pgp_key %}</th> + {% endfor %} + </tr> + </thead> + <tbody> + {% for user in active_users %} + <tr> + <th>{{ user.get_full_name }}</th> + <td>{% pgp_key_link user.userprofile.pgp_key %}</td> + {% spaceless %}{% for key in keys %} + {% signature_exists signatures key.pgp_key user.userprofile.pgp_key as signed %} + <td class="signed-{{ signed|yesno }}">{{ signed|yesno|capfirst }}</td> + {% endfor %}{% endspaceless %} + </tr> + {% endfor %} + </tbody> + </table> </div> {% endblock %} diff --git a/templates/releng/result_section.html b/templates/releng/result_section.html index 45838b86..91a75613 100644 --- a/templates/releng/result_section.html +++ b/templates/releng/result_section.html @@ -7,7 +7,6 @@ {% for item in option.values %} <tr> <td> - <a href="{% url releng-results-for option.field_name item.value.pk %}"> <a href="{% url 'releng-results-for' option.field_name item.value.pk %}"> {{ item.value.name|lower }} </a> @@ -93,9 +93,17 @@ urlpatterns += patterns('', (r'^todolists/$','todolists.views.public_list'), ) -if settings.DEBUG == True: - urlpatterns += patterns('', - (r'^media/(.*)$', 'django.views.static.serve', - {'document_root': os.path.join(settings.DEPLOY_PATH, 'media')})) +legacy_urls = ( + ('^about.php', '/about/'), + ('^changelog.php', '/packages/?sort=-last_update'), + ('^download.php', '/download/'), + ('^index.php', '/'), + ('^logos.php', '/art/'), + ('^news.php', '/news/'), +) + +for old_url, new_url in legacy_urls: + urlpatterns += patterns('django.views.generic.simple', + (old_url, 'redirect_to', {'url': new_url})) # vim: set ts=4 sw=4 et: diff --git a/visualize/views.py b/visualize/views.py index be6057b2..afc5429d 100644 --- a/visualize/views.py +++ b/visualize/views.py @@ -102,7 +102,7 @@ def pgp_keys(request): 'group': 'cacert', }) - not_expired = Q(expires__gt=datetime.now) | Q(expires__isnull=True) + not_expired = Q(expires__gt=datetime.utcnow) | Q(expires__isnull=True) signatures = PGPSignature.objects.filter(not_expired, valid=True) edge_list = [{ 'signee': sig.signee, 'signer': sig.signer } for sig in signatures] |