From d80908e21e88c78262563f5852d81f2754fb5d82 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 23 Dec 2013 09:44:08 -0600 Subject: Change import location of django_countries fields This will work with both the newer and older versions. Signed-off-by: Dan McGee --- devel/models.py | 2 +- mirrors/models.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/devel/models.py b/devel/models.py index 44bbc66e..5c4d4fe7 100644 --- a/devel/models.py +++ b/devel/models.py @@ -4,7 +4,7 @@ import pytz from django.db import models from django.db.models.signals import pre_save from django.contrib.auth.models import User -from django_countries import CountryField +from django_countries.fields import CountryField from .fields import PGPKeyField from main.utils import make_choice, set_created_field diff --git a/mirrors/models.py b/mirrors/models.py index 57664562..0b053043 100644 --- a/mirrors/models.py +++ b/mirrors/models.py @@ -5,7 +5,7 @@ from urlparse import urlparse from django.core.exceptions import ValidationError from django.db import models from django.db.models.signals import pre_save -from django_countries import CountryField +from django_countries.fields import CountryField from .fields import IPNetworkField from main.utils import set_created_field -- cgit v1.2.2 From 5eff1ab9bf0c3820c8b90e18efee1dfc773d08fc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 23 Dec 2013 11:51:04 -0600 Subject: Set all attributes to default values on status URL fetch We were missing two duration-related attributes here, causing some 500 errors to happen if we had cached status_data around that didn't agree with our current list of checked mirrors. Don't blow up on the JSON data fetch by ensuring we provide a value, even if it is out of date. Signed-off-by: Dan McGee --- mirrors/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mirrors/utils.py b/mirrors/utils.py index 0dd26ae0..be44121a 100644 --- a/mirrors/utils.py +++ b/mirrors/utils.py @@ -88,6 +88,8 @@ def annotate_url(url, url_data): ('success_count', 0), ('check_count', 0), ('completion_pct', None), + ('duration_avg', None), + ('duration_stddev', None), ('last_check', None), ('last_sync', None), ('delay', None), -- cgit v1.2.2 From 0e29ce68fc0aa899a7e02954943642381d9531e5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 23 Dec 2013 12:03:14 -0600 Subject: A few more tweaks to the URL details template Signed-off-by: Dan McGee --- templates/mirrors/url_details.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/templates/mirrors/url_details.html b/templates/mirrors/url_details.html index 0b9d2916..54960a0d 100644 --- a/templates/mirrors/url_details.html +++ b/templates/mirrors/url_details.html @@ -15,7 +15,7 @@ - + @@ -42,6 +42,14 @@ + + + + + + + + {% endif %}
URL:{{ url.url }}{% if url.protocol.is_download %}{{ url.url }}{% else %}{{ url.url }}{% endif %}
Protocol:Created: {{ url.created }}
First Check:{{ url.logs.earliest.check_time }}
Last Check:{{ url.logs.latest.check_time }}
-- cgit v1.2.2 From 67a0c0ac088ed2d48fc785f13097557ed6ad25cf Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 9 Jan 2014 08:40:34 -0600 Subject: Remove remaining references to release file_size field Signed-off-by: Dan McGee --- feeds.py | 5 ++++- templates/public/download.html | 2 +- templates/releng/release_list.html | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/feeds.py b/feeds.py index 678bebcf..ecdb7d9a 100644 --- a/feeds.py +++ b/feeds.py @@ -194,7 +194,10 @@ class ReleaseFeed(Feed): return "%s://%s/%s.torrent" % (proto, domain, item.iso_url()) def item_enclosure_length(self, item): - return item.file_size or "" + if item.torrent_data: + torrent = item.torrent() + return torrent['file_length'] or "" + return "" item_enclosure_mime_type = 'application/x-bittorrent' diff --git a/templates/public/download.html b/templates/public/download.html index 71da1c17..00db9bed 100644 --- a/templates/public/download.html +++ b/templates/public/download.html @@ -27,7 +27,7 @@
    {% if release.version %}
  • Current Release: {{ release.version }}
  • {% endif %} {% if release.kernel_version %}
  • Included Kernel: {{ release.kernel_version }}
  • {% endif %} - {% if release.file_size %}
  • ISO Size: {{ release.file_size|filesizeformat }}
  • {% endif %} + {% if release.torrent_data %}
  • ISO Size: {{ release.torrent.file_length|filesizeformat }}
  • {% endif %}
  • Installation Guide
  • Resources:
      diff --git a/templates/releng/release_list.html b/templates/releng/release_list.html index f7e90377..be257f9e 100644 --- a/templates/releng/release_list.html +++ b/templates/releng/release_list.html @@ -34,7 +34,7 @@ {% if item.available %}Torrent{% endif %} {% if item.available %}Magnet{% endif %} - {% if item.file_size %}{{ item.file_size|filesizeformat }}{% endif %} + {% if item.torrent_data %}{{ item.torrent.file_length|filesizeformat }}{% endif %} {% endfor %} -- cgit v1.2.2 From b47b4e73b856b34afa07b1d72367dba4a7c6c005 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 7 Jan 2014 14:16:00 -0600 Subject: Remove release fields we can pull from the torrent This makes entering new releases a bit less cumbersome as we don't really need to enter either the file size or the torrent infohash. Signed-off-by: Dan McGee --- ...orrent_infohash__del_field_release_file_size.py | 118 +++++++++++++++++++++ releng/models.py | 10 +- templates/releng/release_detail.html | 6 +- 3 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 releng/migrations/0008_auto__del_field_release_torrent_infohash__del_field_release_file_size.py diff --git a/releng/migrations/0008_auto__del_field_release_torrent_infohash__del_field_release_file_size.py b/releng/migrations/0008_auto__del_field_release_torrent_infohash__del_field_release_file_size.py new file mode 100644 index 00000000..4a80fd8e --- /dev/null +++ b/releng/migrations/0008_auto__del_field_release_torrent_infohash__del_field_release_file_size.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.delete_column(u'releng_release', 'torrent_infohash') + db.delete_column(u'releng_release', 'file_size') + + def backwards(self, orm): + db.add_column(u'releng_release', 'torrent_infohash', + self.gf('django.db.models.fields.CharField')(default='', max_length=40, blank=True), + keep_default=False) + db.add_column(u'releng_release', 'file_size', + self.gf('main.fields.PositiveBigIntegerField')(null=True, blank=True), + keep_default=False) + + models = { + u'releng.architecture': { + 'Meta': {'object_name': 'Architecture'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.bootloader': { + 'Meta': {'object_name': 'Bootloader'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.boottype': { + 'Meta': {'object_name': 'BootType'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.clockchoice': { + 'Meta': {'object_name': 'ClockChoice'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.filesystem': { + 'Meta': {'object_name': 'Filesystem'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.hardwaretype': { + 'Meta': {'object_name': 'HardwareType'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.installtype': { + 'Meta': {'object_name': 'InstallType'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.iso': { + 'Meta': {'object_name': 'Iso'}, + 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'removed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}) + }, + u'releng.isotype': { + 'Meta': {'object_name': 'IsoType'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.module': { + 'Meta': {'object_name': 'Module'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.release': { + 'Meta': {'ordering': "('-release_date', '-version')", 'object_name': 'Release'}, + 'available': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'info': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'kernel_version': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'md5_sum': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'release_date': ('django.db.models.fields.DateField', [], {'db_index': 'True'}), + 'sha1_sum': ('django.db.models.fields.CharField', [], {'max_length': '40', 'blank': 'True'}), + 'torrent_data': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'version': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '50'}) + }, + u'releng.source': { + 'Meta': {'object_name': 'Source'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}) + }, + u'releng.test': { + 'Meta': {'object_name': 'Test'}, + 'architecture': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.Architecture']"}), + 'boot_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.BootType']"}), + 'bootloader': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.Bootloader']"}), + 'clock_choice': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.ClockChoice']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'filesystem': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.Filesystem']"}), + 'hardware_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.HardwareType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'install_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.InstallType']"}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'iso': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.Iso']"}), + 'iso_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.IsoType']"}), + 'modules': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': u"orm['releng.Module']", 'null': 'True', 'blank': 'True'}), + 'rollback_filesystem': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'rollback_test_set'", 'null': 'True', 'to': u"orm['releng.Filesystem']"}), + 'rollback_modules': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rollback_test_set'", 'null': 'True', 'symmetrical': 'False', 'to': u"orm['releng.Module']"}), + 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['releng.Source']"}), + 'success': ('django.db.models.fields.BooleanField', [], {}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '500'}) + } + } + + complete_apps = ['releng'] diff --git a/releng/models.py b/releng/models.py index 5ee2f325..c73e28d8 100644 --- a/releng/models.py +++ b/releng/models.py @@ -119,14 +119,13 @@ class Release(models.Model): release_date = models.DateField(db_index=True) version = models.CharField(max_length=50, unique=True) kernel_version = models.CharField(max_length=50, blank=True) - torrent_infohash = models.CharField(max_length=40, blank=True) md5_sum = models.CharField('MD5 digest', max_length=32, blank=True) sha1_sum = models.CharField('SHA1 digest', max_length=40, blank=True) - file_size = PositiveBigIntegerField(null=True, blank=True) created = models.DateTimeField(editable=False) available = models.BooleanField(default=True) info = models.TextField('Public information', blank=True) - torrent_data = models.TextField(blank=True) + torrent_data = models.TextField(blank=True, + help_text="base64-encoded torrent file") class Meta: get_latest_by = 'release_date' @@ -150,8 +149,9 @@ class Release(models.Model): ] if settings.TORRENT_TRACKERS: query.extend(('tr', uri) for uri in settings.TORRENT_TRACKERS) - if self.torrent_infohash: - query.insert(0, ('xt', "urn:btih:%s" % self.torrent_infohash)) + metadata = self.torrent() + if metadata and 'info_hash' in metadata: + query.insert(0, ('xt', "urn:btih:%s" % metadata['info_hash'])) return "magnet:?%s" % '&'.join(['%s=%s' % (k, v) for k, v in query]) def info_html(self): diff --git a/templates/releng/release_detail.html b/templates/releng/release_detail.html index 09507536..5cd1c432 100644 --- a/templates/releng/release_detail.html +++ b/templates/releng/release_detail.html @@ -10,14 +10,12 @@
    • Release Date: {{ release.release_date|date }}
    • {% if release.kernel_version %}
    • Kernel Version: {{ release.kernel_version }}
    • {% endif %}
    • Available: {{ release.available|yesno }}
    • - {% if release.available %}
    • Download: Download: Torrent, Magnet
    • {% endif %} - {% if release.torrent_infohash %}
    • Torrent Info Hash: {{ release.torrent_infohash }}
    • {% endif %} {% if release.md5_sum %}
    • MD5: {{ release.md5_sum }}
    • {% endif %} {% if release.sha1_sum %}
    • SHA1: {{ release.sha1_sum }}
    • {% endif %} -
    • Download Size: {% if release.file_size %}{{ release.file_size|filesizeformat }}{% else %}Unknown{% endif %}
    {% if release.info %} @@ -38,7 +36,7 @@
  • File Length: {{ torrent.file_length|filesizeformat }}
  • Piece Count: {{ torrent.piece_count }} pieces
  • Piece Length: {{ torrent.piece_length|filesizeformat }}
  • -
  • Computed Info Hash: {{ torrent.info_hash }}
  • +
  • Info Hash: {{ torrent.info_hash }}
  • URL List Length: {{ torrent.url_list|length }} URLs
{% endwith %}{% endif %} -- cgit v1.2.2 From f4d49590153a5c39d4b60ba0a9c2901c344ff45a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 7 Jan 2014 23:18:18 -0600 Subject: Bump psycopg2 version Signed-off-by: Dan McGee --- requirements_prod.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_prod.txt b/requirements_prod.txt index e609c5d6..7210d3bd 100644 --- a/requirements_prod.txt +++ b/requirements_prod.txt @@ -7,7 +7,7 @@ bencode==1.0 django-countries==1.5 jsmin==2.0.8 pgpdump==1.4 -psycopg2==2.5.1 +psycopg2==2.5.2 pyinotify==0.9.4 python-memcached==1.53 pytz>=2013.8 -- cgit v1.2.2 From 3827215fa3335f8da3c82d4d098eb402b6d29dbc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 11 Jan 2014 13:07:40 -0600 Subject: Speed up feeds generation by batching writes The XML generation underlying our package feeds was doing 1600+ calls to the write() method on the outfile. For some reason, the Python standard library insists on calling flush() after every write, which really makes performance take a nosedive. Wrap the write calls and do them in batches to remove some of the overhead and make feed generation a bit snappier. Signed-off-by: Dan McGee --- feeds.py | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/feeds.py b/feeds.py index ecdb7d9a..feb8a84a 100644 --- a/feeds.py +++ b/feeds.py @@ -14,6 +14,22 @@ from news.models import News from releng.models import Release +class BatchWritesWrapper(object): + def __init__(self, outfile, chunks=20): + self.outfile = outfile + self.chunks = chunks + self.buf = [] + def write(self, s): + buf = self.buf + buf.append(s) + if len(buf) >= self.chunks: + self.outfile.write(''.join(buf)) + self.buf = [] + def flush(self): + self.outfile.write(''.join(self.buf)) + self.outfile.flush() + + class GuidNotPermalinkFeed(Rss201rev2Feed): @staticmethod def check_for_unique_id(f): @@ -26,13 +42,26 @@ class GuidNotPermalinkFeed(Rss201rev2Feed): return wrapper def write_items(self, handler): - # Totally disgusting. Monkey-patch the hander so if it sees a - # 'unique-id' field come through, add an isPermalink="false" attribute. - # Workaround for http://code.djangoproject.com/ticket/9800 + ''' + Totally disgusting. Monkey-patch the handler so if it sees a + 'unique-id' field come through, add an isPermalink="false" attribute. + Workaround for http://code.djangoproject.com/ticket/9800 + ''' handler.addQuickElement = self.check_for_unique_id( handler.addQuickElement) super(GuidNotPermalinkFeed, self).write_items(handler) + def write(self, outfile, encoding): + ''' + Batch the underlying 'write' calls on the outfile because Python's + default saxutils XmlGenerator is a POS that insists on unbuffered + write/flush calls. This sucks when it is making 1-byte calls to write + '>' closing tags and over 1600 write calls in our package feed. + ''' + wrapper = BatchWritesWrapper(outfile) + super(GuidNotPermalinkFeed, self).write(wrapper, encoding) + wrapper.flush() + def package_etag(request, *args, **kwargs): latest = retrieve_latest(Package) -- cgit v1.2.2 From fe37b46c50b60a8302e70ccef3ab34306fd8ea39 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 11 Jan 2014 13:37:12 -0600 Subject: Add a 'last_modified' function for mirror status pages We can use this on both the HTML and JSON views of this data to prevent recomputation for smart clients that respect the modified date header. Signed-off-by: Dan McGee --- mirrors/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mirrors/views.py b/mirrors/views.py index 34336165..5429cea2 100644 --- a/mirrors/views.py +++ b/mirrors/views.py @@ -11,6 +11,7 @@ from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.utils.timezone import now from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import condition from django_countries.countries import COUNTRIES from .models import (Mirror, MirrorUrl, MirrorProtocol, MirrorLog, @@ -220,6 +221,11 @@ def url_details(request, name, url_id): return render(request, 'mirrors/url_details.html', context) +def status_last_modified(request, *args, **kwargs): + return MirrorLog.objects.values_list('check_time', flat=True).latest() + + +@condition(last_modified_func=status_last_modified) def status(request, tier=None): if tier is not None: tier = int(tier) @@ -297,6 +303,7 @@ class ExtendedMirrorStatusJSONEncoder(MirrorStatusJSONEncoder): return super(ExtendedMirrorStatusJSONEncoder, self).default(obj) +@condition(last_modified_func=status_last_modified) def status_json(request, tier=None): if tier is not None: tier = int(tier) -- cgit v1.2.2 From faf196aa521e794aed905122600d12f1c9ab1f0e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 21 Jan 2014 08:06:56 -0600 Subject: Flag out-of-date message is no longer optional Changed way back in commit ef9d1c1e, but I didn't update the actual text at the same time. FS#38126. Signed-off-by: Dan McGee --- templates/packages/flag.html | 2 +- templates/packages/flaghelp.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/packages/flag.html b/templates/packages/flag.html index d1226295..a83f37ff 100644 --- a/templates/packages/flag.html +++ b/templates/packages/flag.html @@ -20,7 +20,7 @@ {% endfor %} -

The message box portion of the flag utility is optional, and meant +

The message box portion is 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 an email to the -

The message box portion of the flag utility is optional, and meant +

The message box portion of the flag utility is 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 an email to the Date: Sat, 25 Jan 2014 09:16:48 -0600 Subject: Add 'Latest Update' column to stale package relations This helps when doing the irregular cleanup of these things and making sure a relation has been stale for some time and not just a couple minutes or hours. Signed-off-by: Dan McGee --- packages/models.py | 9 ++++++--- packages/utils.py | 3 ++- templates/packages/stale_relations.html | 2 ++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/models.py b/packages/models.py index 6477d412..da8adc56 100644 --- a/packages/models.py +++ b/packages/models.py @@ -28,6 +28,9 @@ class PackageRelation(models.Model): type = models.PositiveIntegerField(choices=TYPE_CHOICES, default=MAINTAINER) created = models.DateTimeField(editable=False) + class Meta: + unique_together = (('pkgbase', 'user', 'type'),) + def get_associated_packages(self): return Package.objects.normal().filter(pkgbase=self.pkgbase) @@ -35,13 +38,13 @@ class PackageRelation(models.Model): packages = self.get_associated_packages() return sorted({p.repo for p in packages}) + def last_update(self): + return Update.objects.filter(pkgbase=self.pkgbase).latest() + def __unicode__(self): return u'%s: %s (%s)' % ( self.pkgbase, self.user, self.get_type_display()) - class Meta: - unique_together = (('pkgbase', 'user', 'type'),) - class SignoffSpecificationManager(models.Manager): def get_from_package(self, pkg): diff --git a/packages/utils.py b/packages/utils.py index 6ec39483..0f47f170 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -243,7 +243,8 @@ SELECT DISTINCT id cursor = connection.cursor() cursor.execute(sql, [PackageRelation.MAINTAINER]) to_fetch = [row[0] for row in cursor.fetchall()] - relations = PackageRelation.objects.select_related('user').filter( + relations = PackageRelation.objects.select_related( + 'user', 'user__userprofile').filter( id__in=to_fetch) return relations diff --git a/templates/packages/stale_relations.html b/templates/packages/stale_relations.html index 76f32052..f1ad4373 100644 --- a/templates/packages/stale_relations.html +++ b/templates/packages/stale_relations.html @@ -52,6 +52,7 @@ User Type Created + Latest Update @@ -62,6 +63,7 @@ {{ relation.user.get_full_name }} {{ relation.get_type_display }} {{ relation.created }} + {{ relation.last_update.created }} {% empty %} No non-existent pkgbase relations. -- cgit v1.2.2 From 4a5714fba769c83724f29578d573f0b4096eb332 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 26 Jan 2014 12:55:33 -0600 Subject: Revert "Change old packages report from two years to one year" This reverts commit 8d3a1a1c504a70dd23d36c3ed5be0ebcd2f7a86d. Turns out we aren't updating packages quite as often anymore. There are currently 1900+ packages in the repos built more than one year ago. Signed-off-by: Dan McGee Conflicts: devel/views.py --- devel/views.py | 4 ++-- templates/devel/index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devel/views.py b/devel/views.py index 29954b51..46feece2 100644 --- a/devel/views.py +++ b/devel/views.py @@ -194,8 +194,8 @@ def report(request, report_name, username=None): type=PackageRelation.MAINTAINER).values('user')) if report_name == 'old': - title = 'Packages last built more than one year ago' - cutoff = now() - timedelta(days=365) + title = 'Packages last built more than two years ago' + cutoff = now() - timedelta(days=365 * 2) packages = packages.filter( build_date__lt=cutoff).order_by('build_date') elif report_name == 'long-out-of-date': diff --git a/templates/devel/index.html b/templates/devel/index.html index c0c437a6..a4503102 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -150,7 +150,7 @@

Developer Reports

  • Old: - Packages last built more than one year ago + Packages last built more than two years ago (yours only)
  • Long Out-of-date: Packages marked out-of-date more than 90 days ago -- cgit v1.2.2 From 8a3553a7d23b541912ce55f24054f99fa586cfd2 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 27 Jan 2014 10:17:56 -0600 Subject: Bump pgpdump required version Signed-off-by: Dan McGee --- requirements.txt | 2 +- requirements_prod.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index de8a04e9..2d3ee1d2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,5 +6,5 @@ South==0.8.4 bencode==1.0 django-countries==1.5 jsmin==2.0.8 -pgpdump==1.4 +pgpdump==1.5 pytz>=2013.8 diff --git a/requirements_prod.txt b/requirements_prod.txt index 7210d3bd..4740893a 100644 --- a/requirements_prod.txt +++ b/requirements_prod.txt @@ -6,7 +6,7 @@ South==0.8.4 bencode==1.0 django-countries==1.5 jsmin==2.0.8 -pgpdump==1.4 +pgpdump==1.5 psycopg2==2.5.2 pyinotify==0.9.4 python-memcached==1.53 -- cgit v1.2.2 From 8af4d27cd67157b4f53286913d886d8896f7010a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 27 Jan 2014 13:17:49 -0600 Subject: Fix linebreak issues in preformatted code blocks In text blocks that have preformatted code, such as news items, we don't handle the overflow case very well. Let the browser do the job and wrap things if absolutely required. Fixes FS#35649. Signed-off-by: Dan McGee --- sitestatic/archweb.css | 1 + 1 file changed, 1 insertion(+) diff --git a/sitestatic/archweb.css b/sitestatic/archweb.css index b7d6e1ee..53fa6274 100644 --- a/sitestatic/archweb.css +++ b/sitestatic/archweb.css @@ -79,6 +79,7 @@ pre { pre code { display: block; background: none; + overflow: auto; } blockquote { -- cgit v1.2.2 From 7c70083ed5b7cf0fbd1f66551c088a3c963b258c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 27 Jan 2014 17:27:54 -0600 Subject: Cleanups and enhancments to JS package search typeahead Remove the need to press enter twice when using this typeahead box. Submit the form on enter, regardless of whether an item is selected or not. Signed-off-by: Dan McGee --- templates/public/index.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/templates/public/index.html b/templates/public/index.html index 58dd3729..515ce582 100644 --- a/templates/public/index.html +++ b/templates/public/index.html @@ -224,8 +224,18 @@ function setupTypeahead() { matcher: function(item) { return true; }, sorter: function(items) { return items; }, menu: '
      ', - items: 10 + items: 10, + updater: function(item) { + $('#pkgsearch-form').submit(); + return item; + } }).attr('autocomplete', 'off'); + $('#pkgsearch-field').keyup(function(e) { + if (e.keyCode === 13 && + $('ul.pkgsearch-typeahead li.active').size() === 0) { + $('#pkgsearch-form').submit(); + } + }); } function setupKonami() { var konami = new Konami(function() { -- cgit v1.2.2 From 35a44f7ca75a9cc25e5502df9b9a6844f3eb49fe Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 10 May 2013 18:22:19 -0500 Subject: Releng release JSON view FS#35049. Signed-off-by: Dan McGee --- releng/urls.py | 2 ++ releng/views.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/releng/urls.py b/releng/urls.py index 76c36345..ca76eb25 100644 --- a/releng/urls.py +++ b/releng/urls.py @@ -14,6 +14,8 @@ feedback_patterns = patterns('releng.views', releases_patterns = patterns('releng.views', (r'^$', ReleaseListView.as_view(), {}, 'releng-release-list'), + (r'^json/$', + 'releases_json', {}, 'releng-release-list-json'), (r'^(?P[-.\w]+)/$', ReleaseDetailView.as_view(), {}, 'releng-release-detail'), (r'^(?P[-.\w]+)/torrent/$', diff --git a/releng/views.py b/releng/views.py index b1c76a4a..ef81a65c 100644 --- a/releng/views.py +++ b/releng/views.py @@ -1,7 +1,10 @@ from base64 import b64decode +import json from django import forms from django.conf import settings +from django.core.serializers.json import DjangoJSONEncoder +from django.core.urlresolvers import reverse from django.db.models import Count, Max from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404, redirect, render @@ -238,4 +241,46 @@ def release_torrent(request, version): response['Content-Disposition'] = 'attachment; filename=%s' % filename return response + +class ReleaseJSONEncoder(DjangoJSONEncoder): + release_attributes = ('release_date', 'version', 'kernel_version', + 'created', 'md5_sum', 'sha1_sum') + + def default(self, obj): + if hasattr(obj, '__iter__'): + # mainly for queryset serialization + return list(obj) + if isinstance(obj, Release): + data = {attr: getattr(obj, attr) or None + for attr in self.release_attributes} + data['available'] = obj.available + data['iso_url'] = '/' + obj.iso_url() + data['magnet_uri'] = obj.magnet_uri() + data['torrent_url'] = reverse('releng-release-torrent', args=[obj.version]) + data['info'] = obj.info_html() + torrent_data = obj.torrent() + if torrent_data: + torrent_data.pop('url_list', None) + data['torrent'] = torrent_data + return data + return super(ReleaseJSONEncoder, self).default(obj) + + +def releases_json(request): + releases = Release.objects.all() + try: + latest_version = Release.objects.filter(available=True).values_list( + 'version', flat=True).latest() + except Release.DoesNotExist: + latest_version = None + + data = { + 'version': 1, + 'releases': releases, + 'latest_version': latest_version, + } + to_json = json.dumps(data, ensure_ascii=False, cls=ReleaseJSONEncoder) + response = HttpResponse(to_json, content_type='application/json') + return response + # vim: set ts=4 sw=4 et: -- cgit v1.2.2 From c47230a091b9084553ea7c54f9cddc92472e2191 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 6 Feb 2014 18:09:36 -0600 Subject: Bump requirements including Django version Signed-off-by: Dan McGee --- requirements.txt | 4 ++-- requirements_prod.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2d3ee1d2..8a8bab4e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -e git+git://github.com/fredj/cssmin.git@master#egg=cssmin -Django==1.6.1 +Django==1.6.2 IPy==0.81 Markdown==2.3.1 South==0.8.4 bencode==1.0 django-countries==1.5 -jsmin==2.0.8 +jsmin==2.0.9 pgpdump==1.5 pytz>=2013.8 diff --git a/requirements_prod.txt b/requirements_prod.txt index 4740893a..f3381ffd 100644 --- a/requirements_prod.txt +++ b/requirements_prod.txt @@ -1,11 +1,11 @@ -e git+git://github.com/fredj/cssmin.git@master#egg=cssmin -Django==1.6.1 +Django==1.6.2 IPy==0.81 Markdown==2.3.1 South==0.8.4 bencode==1.0 django-countries==1.5 -jsmin==2.0.8 +jsmin==2.0.9 pgpdump==1.5 psycopg2==2.5.2 pyinotify==0.9.4 -- cgit v1.2.2 From cd21694652383a49077e15439adf717f631cdd50 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 16 Feb 2014 11:27:05 -0600 Subject: Ensure correct value gets submitted on package search typeahead Populate the form field with the chosen item before submitting the form. Signed-off-by: Dan McGee --- templates/public/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/public/index.html b/templates/public/index.html index 515ce582..ab836569 100644 --- a/templates/public/index.html +++ b/templates/public/index.html @@ -226,6 +226,7 @@ function setupTypeahead() { menu: '
        ', items: 10, updater: function(item) { + $('#pkgsearch-field').val(item); $('#pkgsearch-form').submit(); return item; } -- cgit v1.2.2 From 4d311163ed1b83d719218099425c818485d35181 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 22 Feb 2014 08:41:23 -0600 Subject: Upgrade markdown version Signed-off-by: Dan McGee --- requirements.txt | 2 +- requirements_prod.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8a8bab4e..4d9d636d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -e git+git://github.com/fredj/cssmin.git@master#egg=cssmin Django==1.6.2 IPy==0.81 -Markdown==2.3.1 +Markdown==2.4 South==0.8.4 bencode==1.0 django-countries==1.5 diff --git a/requirements_prod.txt b/requirements_prod.txt index f3381ffd..5feb890d 100644 --- a/requirements_prod.txt +++ b/requirements_prod.txt @@ -1,7 +1,7 @@ -e git+git://github.com/fredj/cssmin.git@master#egg=cssmin Django==1.6.2 IPy==0.81 -Markdown==2.3.1 +Markdown==2.4 South==0.8.4 bencode==1.0 django-countries==1.5 -- cgit v1.2.2 From 5fcc6af4ddd0d6f9fbe491a1fd5a9a0edc5b52a5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 22 Feb 2014 08:42:07 -0600 Subject: Change long out-of-date report to 30 days At the request of barthalion. This bumps the report from currently showing 36 packages to 109 packages. Signed-off-by: Dan McGee --- devel/views.py | 4 ++-- templates/devel/index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devel/views.py b/devel/views.py index 46feece2..1e20a437 100644 --- a/devel/views.py +++ b/devel/views.py @@ -199,8 +199,8 @@ def report(request, report_name, username=None): packages = packages.filter( build_date__lt=cutoff).order_by('build_date') elif report_name == 'long-out-of-date': - title = 'Packages marked out-of-date more than 90 days ago' - cutoff = now() - timedelta(days=90) + title = 'Packages marked out-of-date more than 30 days ago' + cutoff = now() - timedelta(days=30) packages = packages.filter( flag_date__lt=cutoff).order_by('flag_date') elif report_name == 'big': diff --git a/templates/devel/index.html b/templates/devel/index.html index a4503102..f432e6b9 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -153,7 +153,7 @@ Packages last built more than two years ago (yours only)
      • Long Out-of-date: - Packages marked out-of-date more than 90 days ago + Packages marked out-of-date more than 30 days ago (yours only)
      • Uncompressed Manpages: Self-explanatory -- cgit v1.2.2 From 16b22e4bfc2e271a58a79a9fb4ccb5e059c6d62a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 22 Feb 2014 08:49:58 -0600 Subject: Upgrade django-countries to 2.0 Signed-off-by: Dan McGee --- mirrors/migrations/0015_assign_country_codes.py | 4 ++-- mirrors/utils.py | 2 +- mirrors/views.py | 2 +- requirements.txt | 2 +- requirements_prod.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mirrors/migrations/0015_assign_country_codes.py b/mirrors/migrations/0015_assign_country_codes.py index c1b0f969..5d83e02c 100644 --- a/mirrors/migrations/0015_assign_country_codes.py +++ b/mirrors/migrations/0015_assign_country_codes.py @@ -4,12 +4,12 @@ from south.db import db from south.v2 import DataMigration from django.db import models -from django_countries.countries import OFFICIAL_COUNTRIES +from django_countries.data import COUNTRIES class Migration(DataMigration): def forwards(self, orm): - reverse_map = dict((v, k) for k, v in OFFICIAL_COUNTRIES.items()) + reverse_map = dict((v.upper(), k) for k, v in COUNTRIES.items()) # add a few special cases to the list that we know might exist reverse_map['GREAT BRITAIN'] = 'GB' reverse_map['KOREA'] = 'KR' diff --git a/mirrors/utils.py b/mirrors/utils.py index be44121a..fe18cd6a 100644 --- a/mirrors/utils.py +++ b/mirrors/utils.py @@ -179,7 +179,7 @@ def get_mirror_errors(cutoff=DEFAULT_CUTOFF, mirror_id=None, show_all=False): errors = list(errors) for err in errors: - err['country'] = Country(err['url__country']) + err['country'] = Country(err['url__country'], flag_url='') return errors diff --git a/mirrors/views.py b/mirrors/views.py index 5429cea2..26b5b802 100644 --- a/mirrors/views.py +++ b/mirrors/views.py @@ -12,7 +12,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.utils.timezone import now from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import condition -from django_countries.countries import COUNTRIES +from django_countries.data import COUNTRIES from .models import (Mirror, MirrorUrl, MirrorProtocol, MirrorLog, CheckLocation) diff --git a/requirements.txt b/requirements.txt index 4d9d636d..81070449 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ IPy==0.81 Markdown==2.4 South==0.8.4 bencode==1.0 -django-countries==1.5 +django-countries==2.0 jsmin==2.0.9 pgpdump==1.5 pytz>=2013.8 diff --git a/requirements_prod.txt b/requirements_prod.txt index 5feb890d..840e0396 100644 --- a/requirements_prod.txt +++ b/requirements_prod.txt @@ -4,7 +4,7 @@ IPy==0.81 Markdown==2.4 South==0.8.4 bencode==1.0 -django-countries==1.5 +django-countries==2.0 jsmin==2.0.9 pgpdump==1.5 psycopg2==2.5.2 -- cgit v1.2.2 From 7947d36c4e509a917941a34c576fde3a207a4439 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 22 Feb 2014 13:38:59 -0600 Subject: Break out developer reports into a separate module This code was getting quite unwieldy, and wasn't very modular. Introduce a DeveloperReport class that contains the content for a single report, and utilize it to create our various report metadata and package filtering operations. Utilize these report objects in the reports view, vastly simplifying it. We don't yet dynamically generate the list of reports on the developer index page; that will be coming soon. Signed-off-by: Dan McGee --- devel/reports.py | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ devel/views.py | 137 +++++-------------------------------------- 2 files changed, 191 insertions(+), 122 deletions(-) create mode 100644 devel/reports.py diff --git a/devel/reports.py b/devel/reports.py new file mode 100644 index 00000000..f1ffab3f --- /dev/null +++ b/devel/reports.py @@ -0,0 +1,176 @@ +from datetime import timedelta +import pytz + +from django.db.models import F +from django.template.defaultfilters import filesizeformat +from django.utils.timezone import now + +from .models import DeveloperKey, UserProfile +from main.models import PackageFile +from packages.models import PackageRelation, Depend + +class DeveloperReport(object): + def __init__(self, slug, name, desc, packages_func, + names=None, attrs=None): + self.slug = slug + self.name = name + self.description = desc + self.packages = packages_func + self.names = names + self.attrs = attrs + + +def old(packages, username): + cutoff = now() - timedelta(days=365 * 2) + return packages.filter( + build_date__lt=cutoff).order_by('build_date') + + +def outofdate(packages, username): + cutoff = now() - timedelta(days=30) + return packages.filter( + flag_date__lt=cutoff).order_by('flag_date') + + +def big(packages, username): + cutoff = 50 * 1024 * 1024 + packages = packages.filter( + compressed_size__gte=cutoff).order_by('-compressed_size') + # Format the compressed and installed sizes with MB/GB/etc suffixes + for package in packages: + package.compressed_size_pretty = filesizeformat( + package.compressed_size) + package.installed_size_pretty = filesizeformat( + package.installed_size) + return packages + + +def badcompression(packages, username): + cutoff = 0.90 * F('installed_size') + packages = packages.filter(compressed_size__gt=0, installed_size__gt=0, + compressed_size__gte=cutoff).order_by('-compressed_size') + + # Format the compressed and installed sizes with MB/GB/etc suffixes + for package in packages: + package.compressed_size_pretty = filesizeformat( + package.compressed_size) + package.installed_size_pretty = filesizeformat( + package.installed_size) + ratio = package.compressed_size / float(package.installed_size) + package.ratio = '%.3f' % ratio + package.compress_type = package.filename.split('.')[-1] + + return packages + + +def uncompressed_man(packages, username): + # checking for all '.0'...'.9' + '.n' extensions + bad_files = PackageFile.objects.filter(is_directory=False, + directory__contains='/man/', + filename__regex=r'\.[0-9n]').exclude( + filename__endswith='.gz').exclude( + filename__endswith='.xz').exclude( + filename__endswith='.bz2').exclude( + filename__endswith='.html') + if username: + pkg_ids = set(packages.values_list('id', flat=True)) + bad_files = bad_files.filter(pkg__in=pkg_ids) + bad_files = bad_files.values_list( + 'pkg_id', flat=True).order_by().distinct() + return packages.filter(id__in=set(bad_files)) + + +def uncompressed_info(packages, username): + # we don't worry about looking for '*.info-1', etc., given that an + # uncompressed root page probably exists in the package anyway + bad_files = PackageFile.objects.filter(is_directory=False, + directory__endswith='/info/', filename__endswith='.info') + if username: + pkg_ids = set(packages.values_list('id', flat=True)) + bad_files = bad_files.filter(pkg__in=pkg_ids) + bad_files = bad_files.values_list( + 'pkg_id', flat=True).order_by().distinct() + return packages.filter(id__in=set(bad_files)) + + +def unneeded_orphans(packages, username): + owned = PackageRelation.objects.all().values('pkgbase') + required = Depend.objects.all().values('name') + # The two separate calls to exclude is required to do the right thing + return packages.exclude(pkgbase__in=owned).exclude( + pkgname__in=required) + + +def mismatched_signature(packages, username): + cutoff = timedelta(hours=24) + filtered = [] + packages = packages.select_related( + 'arch', 'repo', 'packager').filter(signature_bytes__isnull=False) + known_keys = DeveloperKey.objects.select_related( + 'owner').filter(owner__isnull=False) + known_keys = {dk.key: dk for dk in known_keys} + for package in packages: + bad = False + sig = package.signature + sig_date = sig.creation_time.replace(tzinfo=pytz.utc) + package.sig_date = sig_date.date() + dev_key = known_keys.get(sig.key_id, None) + if dev_key: + package.sig_by = dev_key.owner + if dev_key.owner_id != package.packager_id: + bad = True + else: + package.sig_by = sig.key_id + bad = True + + if sig_date > package.build_date + cutoff: + bad = True + + if bad: + filtered.append(package) + return filtered + + +REPORT_OLD = DeveloperReport('old', 'Old', + 'Packages last built more than two years ago', old) + +REPORT_OUTOFDATE = DeveloperReport('long-out-of-date', 'Long Out-of-date', + 'Packages marked out-of-date more than 30 days ago', outofdate) + +REPORT_BIG = DeveloperReport('big', 'Big', + 'Packages with compressed size > 50 MiB', big, + ['Compressed Size', 'Installed Size'], + ['compressed_size_pretty', 'installed_size_pretty']) + +REPORT_BADCOMPRESS = DeveloperReport('badcompression', 'Bad Compression', + 'Packages that have little need for compression', badcompression, + ['Compressed Size', 'Installed Size', 'Ratio', 'Type'], + ['compressed_size_pretty', 'installed_size_pretty','ratio', 'compress_type']) + + +REPORT_MAN = DeveloperReport('uncompressed-man', 'Uncompressed Manpages', + 'Packages with uncompressed manpages', uncompressed_man) + +REPORT_INFO = DeveloperReport('uncompressed-info', 'Uncompressed Info Pages', + 'Packages with uncompressed infopages', uncompressed_info) + +REPORT_ORPHANS = DeveloperReport('unneeded-orphans', 'Unneeded Orphans', + 'Orphan packages required by no other packages', unneeded_orphans) + +REPORT_SIGNATURE = DeveloperReport('mismatched-signature', 'Mismatched Signatures', + 'Packages with mismatched signatures', mismatched_signature, + ['Signature Date', 'Signed By', 'Packager'], + ['sig_date', 'sig_by', 'packager']) + + +def available_reports(): + return ( + REPORT_OLD, + REPORT_OUTOFDATE, + REPORT_BIG, + REPORT_BADCOMPRESS, + REPORT_MAN, + REPORT_INFO, + REPORT_ORPHANS, + REPORT_SIGNATURE, + ) diff --git a/devel/views.py b/devel/views.py index 1e20a437..cd2e25f8 100644 --- a/devel/views.py +++ b/devel/views.py @@ -1,6 +1,5 @@ from datetime import timedelta import operator -import pytz import time from django.http import HttpResponseRedirect @@ -10,21 +9,21 @@ from django.contrib.admin.models import LogEntry, ADDITION from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.db import transaction -from django.db.models import F, Count, Max +from django.db.models import Count, Max from django.http import Http404 from django.shortcuts import get_object_or_404, render -from django.template.defaultfilters import filesizeformat from django.views.decorators.cache import never_cache from django.utils.encoding import force_unicode from django.utils.http import http_date from django.utils.timezone import now from .forms import ProfileForm, UserProfileForm, NewUserForm -from .models import DeveloperKey, UserProfile -from main.models import Package, PackageFile +from .models import UserProfile +from .reports import available_reports +from main.models import Package from main.models import Arch, Repo from news.models import News -from packages.models import PackageRelation, Signoff, FlagRequest, Depend +from packages.models import PackageRelation, Signoff, FlagRequest from packages.utils import get_signoff_groups from todolists.models import TodolistPackage from todolists.utils import get_annotated_todolists @@ -180,10 +179,13 @@ def change_profile(request): @login_required def report(request, report_name, username=None): - title = 'Developer Report' - packages = Package.objects.normal() - names = attrs = user = None + available = {report.slug: report for report in available_reports()} + report = available.get(report_name, None) + if report is None: + raise Http404 + packages = Package.objects.normal() + user = None if username: user = get_object_or_404(User, username=username, is_active=True) maintained = PackageRelation.objects.filter(user=user, @@ -193,126 +195,17 @@ def report(request, report_name, username=None): maints = User.objects.filter(id__in=PackageRelation.objects.filter( type=PackageRelation.MAINTAINER).values('user')) - if report_name == 'old': - title = 'Packages last built more than two years ago' - cutoff = now() - timedelta(days=365 * 2) - packages = packages.filter( - build_date__lt=cutoff).order_by('build_date') - elif report_name == 'long-out-of-date': - title = 'Packages marked out-of-date more than 30 days ago' - cutoff = now() - timedelta(days=30) - packages = packages.filter( - flag_date__lt=cutoff).order_by('flag_date') - elif report_name == 'big': - title = 'Packages with compressed size > 50 MiB' - cutoff = 50 * 1024 * 1024 - packages = packages.filter( - compressed_size__gte=cutoff).order_by('-compressed_size') - names = [ 'Compressed Size', 'Installed Size' ] - attrs = [ 'compressed_size_pretty', 'installed_size_pretty' ] - # Format the compressed and installed sizes with MB/GB/etc suffixes - for package in packages: - package.compressed_size_pretty = filesizeformat( - package.compressed_size) - package.installed_size_pretty = filesizeformat( - package.installed_size) - elif report_name == 'badcompression': - title = 'Packages that have little need for compression' - cutoff = 0.90 * F('installed_size') - packages = packages.filter(compressed_size__gt=0, installed_size__gt=0, - compressed_size__gte=cutoff).order_by('-compressed_size') - names = [ 'Compressed Size', 'Installed Size', 'Ratio', 'Type' ] - attrs = [ 'compressed_size_pretty', 'installed_size_pretty', - 'ratio', 'compress_type' ] - # Format the compressed and installed sizes with MB/GB/etc suffixes - for package in packages: - package.compressed_size_pretty = filesizeformat( - package.compressed_size) - package.installed_size_pretty = filesizeformat( - package.installed_size) - ratio = package.compressed_size / float(package.installed_size) - package.ratio = '%.3f' % ratio - package.compress_type = package.filename.split('.')[-1] - elif report_name == 'uncompressed-man': - title = 'Packages with uncompressed manpages' - # checking for all '.0'...'.9' + '.n' extensions - bad_files = PackageFile.objects.filter(is_directory=False, - directory__contains='/man/', - filename__regex=r'\.[0-9n]').exclude( - filename__endswith='.gz').exclude( - filename__endswith='.xz').exclude( - filename__endswith='.bz2').exclude( - filename__endswith='.html') - if username: - pkg_ids = set(packages.values_list('id', flat=True)) - bad_files = bad_files.filter(pkg__in=pkg_ids) - bad_files = bad_files.values_list( - 'pkg_id', flat=True).order_by().distinct() - packages = packages.filter(id__in=set(bad_files)) - elif report_name == 'uncompressed-info': - title = 'Packages with uncompressed infopages' - # we don't worry about looking for '*.info-1', etc., given that an - # uncompressed root page probably exists in the package anyway - bad_files = PackageFile.objects.filter(is_directory=False, - directory__endswith='/info/', filename__endswith='.info') - if username: - pkg_ids = set(packages.values_list('id', flat=True)) - bad_files = bad_files.filter(pkg__in=pkg_ids) - bad_files = bad_files.values_list( - 'pkg_id', flat=True).order_by().distinct() - packages = packages.filter(id__in=set(bad_files)) - elif report_name == 'unneeded-orphans': - title = 'Orphan packages required by no other packages' - owned = PackageRelation.objects.all().values('pkgbase') - required = Depend.objects.all().values('name') - # The two separate calls to exclude is required to do the right thing - packages = packages.exclude(pkgbase__in=owned).exclude( - pkgname__in=required) - elif report_name == 'mismatched-signature': - title = 'Packages with mismatched signatures' - names = [ 'Signature Date', 'Signed By', 'Packager' ] - attrs = [ 'sig_date', 'sig_by', 'packager' ] - cutoff = timedelta(hours=24) - filtered = [] - packages = packages.select_related( - 'arch', 'repo', 'packager').filter(signature_bytes__isnull=False) - known_keys = DeveloperKey.objects.select_related( - 'owner').filter(owner__isnull=False) - known_keys = {dk.key: dk for dk in known_keys} - for package in packages: - bad = False - sig = package.signature - sig_date = sig.creation_time.replace(tzinfo=pytz.utc) - package.sig_date = sig_date.date() - dev_key = known_keys.get(sig.key_id, None) - if dev_key: - package.sig_by = dev_key.owner - if dev_key.owner_id != package.packager_id: - bad = True - else: - package.sig_by = sig.key_id - bad = True - - if sig_date > package.build_date + cutoff: - bad = True - - if bad: - filtered.append(package) - packages = filtered - else: - raise Http404 - arches = {pkg.arch for pkg in packages} repos = {pkg.repo for pkg in packages} context = { 'all_maintainers': maints, - 'title': title, + 'title': report.description, 'maintainer': user, - 'packages': packages, + 'packages': report.packages(packages, username), 'arches': sorted(arches), 'repos': sorted(repos), - 'column_names': names, - 'column_attrs': attrs, + 'column_names': report.names, + 'column_attrs': report.attrs, } return render(request, 'devel/packages.html', context) -- cgit v1.2.2 From 871b284dd76f01d8a297f43ae86d869e2b7d2b3c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 22 Feb 2014 13:48:52 -0600 Subject: Generate list of reports dynamically Signed-off-by: Dan McGee --- devel/reports.py | 15 ++++++++++----- devel/views.py | 3 ++- templates/devel/index.html | 30 +++++------------------------- 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/devel/reports.py b/devel/reports.py index f1ffab3f..53cf9e69 100644 --- a/devel/reports.py +++ b/devel/reports.py @@ -11,13 +11,14 @@ from packages.models import PackageRelation, Depend class DeveloperReport(object): def __init__(self, slug, name, desc, packages_func, - names=None, attrs=None): + names=None, attrs=None, personal=True): self.slug = slug self.name = name self.description = desc self.packages = packages_func self.names = names self.attrs = attrs + self.personal = personal def old(packages, username): @@ -143,7 +144,7 @@ REPORT_BIG = DeveloperReport('big', 'Big', ['compressed_size_pretty', 'installed_size_pretty']) REPORT_BADCOMPRESS = DeveloperReport('badcompression', 'Bad Compression', - 'Packages that have little need for compression', badcompression, + 'Packages with a compression ratio of less than 10%', badcompression, ['Compressed Size', 'Installed Size', 'Ratio', 'Type'], ['compressed_size_pretty', 'installed_size_pretty','ratio', 'compress_type']) @@ -152,13 +153,17 @@ REPORT_MAN = DeveloperReport('uncompressed-man', 'Uncompressed Manpages', 'Packages with uncompressed manpages', uncompressed_man) REPORT_INFO = DeveloperReport('uncompressed-info', 'Uncompressed Info Pages', - 'Packages with uncompressed infopages', uncompressed_info) + 'Packages with uncompressed info pages', uncompressed_info) REPORT_ORPHANS = DeveloperReport('unneeded-orphans', 'Unneeded Orphans', - 'Orphan packages required by no other packages', unneeded_orphans) + 'Packages that have no maintainer and are not required by any ' + + 'other package in any repository', unneeded_orphans, + personal=False) REPORT_SIGNATURE = DeveloperReport('mismatched-signature', 'Mismatched Signatures', - 'Packages with mismatched signatures', mismatched_signature, + 'Packages where 1) signing key is unknown, 2) signer != packager, ' + + 'or 3) signature timestamp more than 24 hours after build timestamp', + mismatched_signature, ['Signature Date', 'Signed By', 'Packager'], ['sig_date', 'sig_by', 'packager']) diff --git a/devel/views.py b/devel/views.py index cd2e25f8..c53da668 100644 --- a/devel/views.py +++ b/devel/views.py @@ -57,7 +57,8 @@ def index(request): 'todos': todolists, 'flagged': flagged, 'todopkgs': todopkgs, - 'signoffs': signoffs + 'signoffs': signoffs, + 'reports': available_reports(), } return render(request, 'devel/index.html', page_dict) diff --git a/templates/devel/index.html b/templates/devel/index.html index f432e6b9..147917a0 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -149,31 +149,11 @@

        Developer Reports

        {# #dev-dashboard #} -- cgit v1.2.2 From e395bef7241a71cd72d24362bf233251746c8c8b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 22 Feb 2014 13:56:51 -0600 Subject: Split signature report into two reports Signed-off-by: Dan McGee --- devel/reports.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/devel/reports.py b/devel/reports.py index 53cf9e69..ca1a3321 100644 --- a/devel/reports.py +++ b/devel/reports.py @@ -103,7 +103,6 @@ def unneeded_orphans(packages, username): def mismatched_signature(packages, username): - cutoff = timedelta(hours=24) filtered = [] packages = packages.select_related( 'arch', 'repo', 'packager').filter(signature_bytes__isnull=False) @@ -113,8 +112,6 @@ def mismatched_signature(packages, username): for package in packages: bad = False sig = package.signature - sig_date = sig.creation_time.replace(tzinfo=pytz.utc) - package.sig_date = sig_date.date() dev_key = known_keys.get(sig.key_id, None) if dev_key: package.sig_by = dev_key.owner @@ -124,14 +121,26 @@ def mismatched_signature(packages, username): package.sig_by = sig.key_id bad = True - if sig_date > package.build_date + cutoff: - bad = True - if bad: filtered.append(package) return filtered +def signature_time(packages, username): + cutoff = timedelta(hours=24) + filtered = [] + packages = packages.select_related( + 'arch', 'repo', 'packager').filter(signature_bytes__isnull=False) + for package in packages: + sig = package.signature + sig_date = sig.creation_time.replace(tzinfo=pytz.utc) + package.sig_date = sig_date.date() + if sig_date > package.build_date + cutoff: + filtered.append(package) + + return filtered + + REPORT_OLD = DeveloperReport('old', 'Old', 'Packages last built more than two years ago', old) @@ -160,12 +169,19 @@ REPORT_ORPHANS = DeveloperReport('unneeded-orphans', 'Unneeded Orphans', + 'other package in any repository', unneeded_orphans, personal=False) -REPORT_SIGNATURE = DeveloperReport('mismatched-signature', 'Mismatched Signatures', - 'Packages where 1) signing key is unknown, 2) signer != packager, ' - + 'or 3) signature timestamp more than 24 hours after build timestamp', +REPORT_SIGNATURE = DeveloperReport('mismatched-signature', + 'Mismatched Signatures', + 'Packages where the signing key is unknown or signer != packager', mismatched_signature, - ['Signature Date', 'Signed By', 'Packager'], - ['sig_date', 'sig_by', 'packager']) + ['Signed By', 'Packager'], + ['sig_by', 'packager']) + +REPORT_SIG_TIME = DeveloperReport('signature-time', 'Signature Time', + 'Packages where the signature timestamp is more than 24 hours ' + + 'after the build timestamp', + signature_time, + ['Signature Date', 'Packager'], + ['sig_date', 'packager']) def available_reports(): @@ -178,4 +194,5 @@ def available_reports(): REPORT_INFO, REPORT_ORPHANS, REPORT_SIGNATURE, + REPORT_SIG_TIME, ) -- cgit v1.2.2 From 81a2051e346bb41fb9756695566f735d5035bfd8 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 9 Mar 2014 11:12:40 -0500 Subject: Allow filesize sorter to match   character This gets the sorter working correctly again on the developer reports pages where we show file sizes. Apparently the Django filesizeformat tag now uses non-breaking spaces. Signed-off-by: Dan McGee --- sitestatic/archweb.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sitestatic/archweb.js b/sitestatic/archweb.js index 800c5add..209d6f67 100644 --- a/sitestatic/archweb.js +++ b/sitestatic/archweb.js @@ -103,7 +103,7 @@ if (typeof $ !== 'undefined' && typeof $.tablesorter !== 'undefined') { $.tablesorter.addParser({ id: 'filesize', - re: /^(\d+(?:\.\d+)?) (bytes?|[KMGTPEZY]i?B)$/, + re: /^(\d+(?:\.\d+)?)[ \u00a0](bytes?|[KMGTPEZY]i?B)$/, is: function(s) { return this.re.test(s); }, -- cgit v1.2.2 From 2e06e74d3a2a09cbb2a2521cdd55d6543cb56674 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 9 Mar 2014 11:40:58 -0500 Subject: Use localStorage to save/restore signoffs filters Signed-off-by: Dan McGee --- sitestatic/archweb.js | 18 ++++++++++++++++++ templates/packages/signoffs.html | 1 + 2 files changed, 19 insertions(+) diff --git a/sitestatic/archweb.js b/sitestatic/archweb.js index 209d6f67..8212e85b 100644 --- a/sitestatic/archweb.js +++ b/sitestatic/archweb.js @@ -444,6 +444,7 @@ function filter_signoffs() { $('#filter-count').text(rows.length); /* make sure we update the odd/even styling from sorting */ $('.results').trigger('applyWidgets', [false]); + filter_signoffs_save(); } function filter_signoffs_reset() { $('#signoffs_filter .arch_filter').attr('checked', 'checked'); @@ -451,6 +452,23 @@ function filter_signoffs_reset() { $('#id_pending').removeAttr('checked'); filter_signoffs(); } +function filter_signoffs_save() { + var state = $('#signoffs_filter').serializeArray(); + localStorage['filter_signoffs'] = JSON.stringify(state); +} +function filter_signoffs_load() { + var state = localStorage['filter_signoffs']; + if (!state) + return; + state = JSON.parse(state); + $('#signoffs_filter .arch_filter').removeAttr('checked'); + $('#signoffs_filter .repo_filter').removeAttr('checked'); + $('#id_pending').removeAttr('checked'); + $.each(state, function (i, v) { + // this assumes our only filters are checkboxes + $('#signoffs_filter input[name="' + v['name'] + '"]').attr('checked', 'checked'); + }); +} function collapseNotes(elements) { // Remove any trailing
        tags from the note contents diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 83f81d39..1c133f15 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -93,6 +93,7 @@ $(document).ready(function() { $('#signoffs_filter input').change(filter_signoffs); $('#criteria_reset').click(filter_signoffs_reset); // fire function on page load to ensure the current form selections take effect + filter_signoffs_load(); filter_signoffs(); }); $(document).ready(function() { -- cgit v1.2.2 From 697a2b15c1e402c1c624af1ffeaf5123d7fb0e9f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 9 Mar 2014 11:49:22 -0500 Subject: Use localStorage to save/restore todolist filters Signed-off-by: Dan McGee --- sitestatic/archweb.js | 19 +++++++++++++++++++ templates/todolists/view.html | 6 +++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/sitestatic/archweb.js b/sitestatic/archweb.js index 8212e85b..457e334c 100644 --- a/sitestatic/archweb.js +++ b/sitestatic/archweb.js @@ -367,6 +367,25 @@ function filter_pkgs_reset(callback) { callback(); } +function filter_todolist_save(list_id) { + var state = $('#todolist_filter').serializeArray(); + localStorage['filter_todolist_' + list_id] = JSON.stringify(state); +} +function filter_todolist_load(list_id) { + var state = localStorage['filter_todolist_' + list_id]; + if (!state) + return; + state = JSON.parse(state); + $('#todolist_filter .arch_filter').removeAttr('checked'); + $('#todolist_filter .repo_filter').removeAttr('checked'); + $('#id_incomplete').removeAttr('checked'); + $('#id_mine_only').removeAttr('checked'); + $.each(state, function (i, v) { + // this assumes our only filters are checkboxes + $('#todolist_filter input[name="' + v['name'] + '"]').attr('checked', 'checked'); + }); +} + /* signoffs.html */ function signoff_package() { // TODO: fix usage of this diff --git a/templates/todolists/view.html b/templates/todolists/view.html index a3ee5479..0045390c 100644 --- a/templates/todolists/view.html +++ b/templates/todolists/view.html @@ -118,10 +118,14 @@ $(document).ready(function() { }); $(document).ready(function() { $('a.status-link').click(todolist_flag); - var filter_func = function() { filter_pkgs_list('#todolist_filter', '#dev-todo-pkglist tbody'); }; + var filter_func = function() { + filter_pkgs_list('#todolist_filter', '#dev-todo-pkglist tbody'); + filter_todolist_save({{ list.id }}); + }; $('#todolist_filter input').change(filter_func); $('#criteria_reset').click(function() { filter_pkgs_reset(filter_func); }); // fire function on page load to ensure the current form selections take effect + filter_todolist_load({{ list.id }}); filter_func(); }); -- cgit v1.2.2 From 268317dd331bcdbe1c2828583034c0274a19eea3 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 9 Mar 2014 11:58:51 -0500 Subject: Use localStorage to save/restore developer report filters Signed-off-by: Dan McGee --- devel/views.py | 1 + sitestatic/archweb.js | 16 ++++++++++++++++ templates/devel/packages.html | 6 +++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/devel/views.py b/devel/views.py index c53da668..972d0abb 100644 --- a/devel/views.py +++ b/devel/views.py @@ -201,6 +201,7 @@ def report(request, report_name, username=None): context = { 'all_maintainers': maints, 'title': report.description, + 'report': report, 'maintainer': user, 'packages': report.packages(packages, username), 'arches': sorted(arches), diff --git a/sitestatic/archweb.js b/sitestatic/archweb.js index 457e334c..0a979494 100644 --- a/sitestatic/archweb.js +++ b/sitestatic/archweb.js @@ -386,6 +386,22 @@ function filter_todolist_load(list_id) { }); } +function filter_report_save(report_id) { + var state = $('#report_filter').serializeArray(); + localStorage['filter_report_' + report_id] = JSON.stringify(state); +} +function filter_report_load(report_id) { + var state = localStorage['filter_report_' + report_id]; + if (!state) + return; + state = JSON.parse(state); + $('#report_filter input[type="checkbox"]').removeAttr('checked') + $.each(state, function (i, v) { + // this assumes our only filters are checkboxes + $('#report_filter input[name="' + v['name'] + '"]').attr('checked', 'checked'); + }); +} + /* signoffs.html */ function signoff_package() { // TODO: fix usage of this diff --git a/templates/devel/packages.html b/templates/devel/packages.html index 74aebf20..c190ab08 100644 --- a/templates/devel/packages.html +++ b/templates/devel/packages.html @@ -84,10 +84,14 @@ $(document).ready(function() { $(".results").tablesorter({widgets: ['zebra']}); }); $(document).ready(function() { - var filter_func = function() { filter_pkgs_list('#report_filter', '#dev-report-results tbody'); }; + var filter_func = function() { + filter_pkgs_list('#report_filter', '#dev-report-results tbody'); + filter_report_save('{{ report.slug }}'); + }; $('#report_filter input').change(filter_func); $('#criteria_reset').click(function() { filter_pkgs_reset(filter_func); }); // run on page load to ensure current form selections take effect + filter_report_load('{{ report.slug }}'); filter_func(); }); -- cgit v1.2.2 From 3f1ca4da2a0e29850eadec7a8d69444081da099c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 9 Mar 2014 12:02:00 -0500 Subject: Simplify filter reload code Signed-off-by: Dan McGee --- sitestatic/archweb.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/sitestatic/archweb.js b/sitestatic/archweb.js index 0a979494..0c4059a9 100644 --- a/sitestatic/archweb.js +++ b/sitestatic/archweb.js @@ -376,10 +376,7 @@ function filter_todolist_load(list_id) { if (!state) return; state = JSON.parse(state); - $('#todolist_filter .arch_filter').removeAttr('checked'); - $('#todolist_filter .repo_filter').removeAttr('checked'); - $('#id_incomplete').removeAttr('checked'); - $('#id_mine_only').removeAttr('checked'); + $('#todolist_filter input[type="checkbox"]').removeAttr('checked'); $.each(state, function (i, v) { // this assumes our only filters are checkboxes $('#todolist_filter input[name="' + v['name'] + '"]').attr('checked', 'checked'); @@ -395,7 +392,7 @@ function filter_report_load(report_id) { if (!state) return; state = JSON.parse(state); - $('#report_filter input[type="checkbox"]').removeAttr('checked') + $('#report_filter input[type="checkbox"]').removeAttr('checked'); $.each(state, function (i, v) { // this assumes our only filters are checkboxes $('#report_filter input[name="' + v['name'] + '"]').attr('checked', 'checked'); @@ -496,9 +493,7 @@ function filter_signoffs_load() { if (!state) return; state = JSON.parse(state); - $('#signoffs_filter .arch_filter').removeAttr('checked'); - $('#signoffs_filter .repo_filter').removeAttr('checked'); - $('#id_pending').removeAttr('checked'); + $('#signoffs_filter input[type="checkbox"]').removeAttr('checked'); $.each(state, function (i, v) { // this assumes our only filters are checkboxes $('#signoffs_filter input[name="' + v['name'] + '"]').attr('checked', 'checked'); -- cgit v1.2.2 From cd7222047b41ac3efa389a22fdd22148d03c1d61 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 22 Mar 2014 12:25:09 -0500 Subject: Make filename and directory name fields accept longer values Signed-off-by: Dan McGee --- ...le_directory__chg_field_packagefile_filename.py | 112 +++++++++++++++++++++ main/models.py | 4 +- 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 main/migrations/0068_auto__chg_field_packagefile_directory__chg_field_packagefile_filename.py diff --git a/main/migrations/0068_auto__chg_field_packagefile_directory__chg_field_packagefile_filename.py b/main/migrations/0068_auto__chg_field_packagefile_directory__chg_field_packagefile_filename.py new file mode 100644 index 00000000..5e359806 --- /dev/null +++ b/main/migrations/0068_auto__chg_field_packagefile_directory__chg_field_packagefile_filename.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.alter_column('package_files', 'directory', self.gf('django.db.models.fields.CharField')(max_length=1024)) + db.alter_column('package_files', 'filename', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True)) + + def backwards(self, orm): + db.alter_column('package_files', 'directory', self.gf('django.db.models.fields.CharField')(max_length=255)) + db.alter_column('package_files', 'filename', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)) + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'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'}), + u'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'}) + }, + u'main.arch': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'required_signoffs': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}) + }, + u'main.donor': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Donor', 'db_table': "'donors'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + u'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'}) + }, + u'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': u"orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + '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', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': '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.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + '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'", 'on_delete': 'models.PROTECT', 'to': u"orm['main.Repo']"}), + 'signature_bytes': ('django.db.models.fields.BinaryField', [], {'null': 'True'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + u'main.packagefile': { + 'Meta': {'object_name': 'PackageFile', 'db_table': "'package_files'"}, + 'directory': ('django.db.models.fields.CharField', [], {'max_length': '1024'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}), + u'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': u"orm['main.Package']"}) + }, + u'main.repo': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + u'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'}) + } + } + + complete_apps = ['main'] diff --git a/main/models.py b/main/models.py index bf7a9409..09b1adc0 100644 --- a/main/models.py +++ b/main/models.py @@ -433,8 +433,8 @@ class Package(models.Model): class PackageFile(models.Model): pkg = models.ForeignKey(Package) is_directory = models.BooleanField(default=False) - directory = models.CharField(max_length=255) - filename = models.CharField(max_length=255, null=True, blank=True) + directory = models.CharField(max_length=1024) + filename = models.CharField(max_length=1024, null=True, blank=True) def __unicode__(self): return "%s%s" % (self.directory, self.filename or '') -- cgit v1.2.2