From cda9c5bb34b450b391500636c8d3f5ff4bfe51ab Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 15 Jun 2011 16:55:03 -0500 Subject: Add a base pages sitemap Signed-off-by: Dan McGee --- packages/urls.py | 4 ++-- sitemaps.py | 34 +++++++++++++++++++++++++++++++++- urls.py | 1 + 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/urls.py b/packages/urls.py index 3c2c87b0..d408e6cf 100644 --- a/packages/urls.py +++ b/packages/urls.py @@ -19,10 +19,10 @@ 'signoff_package'), (r'^update/$', 'update'), - (r'^$', 'search'), + (r'^$', 'search', {}, 'packages-search'), (r'^(?P\d+)/$', 'search'), - (r'^differences/$', 'arch_differences'), + (r'^differences/$', 'arch_differences', {}, 'packages-differences'), (r'^stale_relations/$', 'stale_relations'), (r'^stale_relations/update/$','stale_relations_update'), diff --git a/sitemaps.py b/sitemaps.py index 37387f15..8ac5bc4f 100644 --- a/sitemaps.py +++ b/sitemaps.py @@ -1,4 +1,6 @@ from django.contrib.sitemaps import Sitemap +from django.core.urlresolvers import reverse + from main.models import Package from news.models import News from packages.utils import get_group_info @@ -41,7 +43,7 @@ def location(self, obj): class NewsSitemap(Sitemap): changefreq = "never" - priority = "0.7" + priority = "0.8" def items(self): return News.objects.all() @@ -49,4 +51,34 @@ def items(self): def lastmod(self, obj): return obj.last_modified + +class BaseSitemap(Sitemap): + base_viewnames = ( + ('index', 1.0, 'hourly'), + ('packages-search', 0.8, 'hourly'), + 'page-about', 'page-art', 'page-svn', 'page-devs', 'page-tus', + 'page-fellows', 'page-donate', 'page-download', 'news-list', + 'feeds-list', 'groups-list', 'mirror-list', 'mirror-status', + 'mirrorlist', 'packages-differences', 'releng-test-overview', + ) + + def items(self): + return self.base_viewnames + + def location(self, obj): + name = obj + if isinstance(obj, tuple): + name = obj[0] + return reverse(name) + + def priority(self, obj): + if isinstance(obj, tuple): + return obj[1] + return 0.7 + + def changefreq(self, obj): + if isinstance(obj, tuple): + return obj[2] + return 'monthly' + # vim: set ts=4 sw=4 et: diff --git a/urls.py b/urls.py index 36a2d79f..c9faf165 100644 --- a/urls.py +++ b/urls.py @@ -10,6 +10,7 @@ import sitemaps sitemaps = { + 'base': sitemaps.BaseSitemap, 'news': sitemaps.NewsSitemap, 'packages': sitemaps.PackagesSitemap, 'package-files': sitemaps.PackageFilesSitemap, -- cgit v1.2.3-54-g00ecf From 00eb8f426a8ddb905fcf8b241eb29b687b9db8ed Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 16 Jun 2011 12:41:40 -0500 Subject: Remove automatic search redirection on count = 1 This is really annoying. Signed-off-by: Dan McGee --- packages/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/views.py b/packages/views.py index 5f559d6a..486fb945 100644 --- a/packages/views.py +++ b/packages/views.py @@ -262,9 +262,6 @@ def search(request, page=None): else: form = PackageSearchForm() - if packages.count() == 1: - return redirect(packages[0]) - current_query = request.GET.urlencode() page_dict = { 'search_form': form, -- cgit v1.2.3-54-g00ecf From 92e13e0670661a542db646c668780cb4620c0b13 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 20 Jun 2011 01:09:20 -0500 Subject: Differentiate between no filelist and empty filelist We had these two cases munged together before; some packages have seen filelist updates but simply don't have any files ('firefox-i18n' for example). Signed-off-by: Dan McGee --- packages/views.py | 7 +++++-- templates/packages/files-list.html | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/views.py b/packages/views.py index 486fb945..7b8c2e67 100644 --- a/packages/views.py +++ b/packages/views.py @@ -290,11 +290,14 @@ 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') + context = { + 'pkg': pkg, + 'files': fileslist, + } template = 'packages/files.html' if request.is_ajax(): template = 'packages/files-list.html' - return direct_to_template(request, template, - {'pkg':pkg, 'files':fileslist}) + return direct_to_template(request, template, context) def details_json(request, name, repo, arch): pkg = get_object_or_404(Package, diff --git a/templates/packages/files-list.html b/templates/packages/files-list.html index bb89b663..95a85d24 100644 --- a/templates/packages/files-list.html +++ b/templates/packages/files-list.html @@ -2,12 +2,16 @@

Note: This file list was generated from a previous version of the package; it may be out of date.

{% endif %} -{% if files.count %} +{% if pkg.files_last_update %} +{% if files|length %}
    {% for file in files %}
  • {{ file.directory }}{{ file.filename|default:'' }}
  • {% endfor %}
{% else %} +

Package has no files.

+{% endif %} +{% else %}

No file list available.

{% endif %} -- cgit v1.2.3-54-g00ecf From dcbee21c1ba281b061db302056c771936925c824 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 21 Jun 2011 08:56:50 -0500 Subject: Nicer formatting of PGP key display value Signed-off-by: Dan McGee --- main/templatetags/pgp.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/main/templatetags/pgp.py b/main/templatetags/pgp.py index d9126dbd..956de892 100644 --- a/main/templatetags/pgp.py +++ b/main/templatetags/pgp.py @@ -3,6 +3,17 @@ register = template.Library() +def format_key(key_id): + print len(key_id) + if len(key_id) in (8, 20): + return u'0x%s' % key_id + elif len(key_id) == 40: + # normal display format is 5 groups of 4 hex chars seperated by spaces, + # double space, then 5 more groups of 4 hex chars + split = tuple(key_id[i:i+4] for i in range(0, 40, 4)) + return u'%s  %s' % (' '.join(split[0:5]), ' '.join(split[5:10])) + return u'0x%s' % key_id + @register.simple_tag def pgp_key_link(key_id): if not key_id: @@ -10,10 +21,10 @@ def pgp_key_link(key_id): # Something like 'pgp.mit.edu:11371' pgp_server = getattr(settings, 'PGP_SERVER', None) if not pgp_server: - return "0x%s" % key_id + return format_key(key_id) url = 'http://%s/pks/lookup?op=vindex&fingerprint=on&exact=on&search=0x%s' % \ (pgp_server, key_id) - values = (url, key_id, key_id) - return '0x%s' % values + values = (url, key_id, format_key(key_id)) + return '%s' % values # vim: set ts=4 sw=4 et: -- cgit v1.2.3-54-g00ecf From 388973d6fd082148c85db0df44e0ee5ed3b6a54b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 18:48:39 -0500 Subject: Revert "Remove unused flagged() manager method" Far from unneeded, this is used on the developer dashboard. Silly me. Document this fact as well in the code so we don't screw it up again. This reverts commit 2a44855556531a27e949884d4084c6e5d37082e1. --- main/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main/models.py b/main/models.py index a5d6c42e..05e63fed 100644 --- a/main/models.py +++ b/main/models.py @@ -73,6 +73,10 @@ def incomplete(self): return self.filter(todolistpkg__complete=False).distinct() class PackageManager(models.Manager): + def flagged(self): + """Used by dev dashboard.""" + return self.filter(flag_date__isnull=False) + def normal(self): return self.select_related('arch', 'repo') -- cgit v1.2.3-54-g00ecf From da20949c8cc185e91dbaae1b8369fcffa3447081 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 19:11:12 -0500 Subject: Move find_user method to devel utils This could be handy elsewhere as well, and it is loosely coupled to anything else in reporead. Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 51 +----------------------------- devel/utils.py | 58 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index baf7fee1..138931ff 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -16,7 +16,6 @@ from django.core.management.base import BaseCommand, CommandError from django.contrib.auth.models import User from django.db import transaction -from django.db.models import Q from collections import defaultdict import io @@ -28,6 +27,7 @@ from datetime import datetime from optparse import make_option +from devel.utils import find_user from main.models import Arch, Package, PackageDepend, PackageFile, Repo from packages.models import Conflict, Provision, Replacement @@ -130,55 +130,6 @@ def full_version(self): return u'%s-%s' % (self.ver, self.rel) -def find_user(userstring): - ''' - Attempt to find the corresponding User object for a standard - packager string, e.g. something like - 'A. U. Thor '. - We start by searching for a matching email address; we then move onto - matching by first/last name. If we cannot find a user, then return None. - ''' - if userstring in find_user.cache: - return find_user.cache[userstring] - matches = re.match(r'^([^<]+)? ?<([^>]*)>', userstring) - if not matches: - return None - - user = None - name = matches.group(1) - email = matches.group(2) - - def user_email(): - return User.objects.get(email=email) - def profile_email(): - return User.objects.get(userprofile__public_email=email) - def user_name(): - # yes, a bit odd but this is the easiest way since we can't always be - # sure how to split the name. Ensure every 'token' appears in at least - # one of the two name fields. - name_q = Q() - for token in name.split(): - # ignore quoted parts; e.g. nicknames in strings - if re.match(r'^[\'"].*[\'"]$', token): - continue - name_q &= (Q(first_name__icontains=token) | - Q(last_name__icontains=token)) - return User.objects.get(name_q) - - for matcher in (user_email, profile_email, user_name): - try: - user = matcher() - break - except (User.DoesNotExist, User.MultipleObjectsReturned): - pass - - find_user.cache[userstring] = user - return user - -# cached mappings of user strings -> User objects so we don't have to do the -# lookup more than strictly necessary. -find_user.cache = {} - DEPEND_RE = re.compile(r"^(.+?)((>=|<=|=|>|<)(.*))?$") def create_depend(package, dep_str, optional=False): diff --git a/devel/utils.py b/devel/utils.py index abfdabe5..9d7dfb2d 100644 --- a/devel/utils.py +++ b/devel/utils.py @@ -1,7 +1,11 @@ +import re + from django.contrib.auth.models import User from django.db import connection +from django.db.models import Count, Q from main.utils import cache_function +from main.models import Package from packages.models import PackageRelation @cache_function(300) @@ -28,10 +32,64 @@ def get_annotated_maintainers(): pkg_count[k] = total flag_count[k] = flagged + update_count = Package.objects.values_list('packager').annotate( + Count('packager')) + update_count = dict(update_count) + for m in maintainers: m.package_count = pkg_count.get(m.id, 0) m.flagged_count = flag_count.get(m.id, 0) + m.updated_count = update_count.get(m.id, 0) return maintainers +def find_user(userstring): + ''' + Attempt to find the corresponding User object for a standard + packager string, e.g. something like + 'A. U. Thor '. + We start by searching for a matching email address; we then move onto + matching by first/last name. If we cannot find a user, then return None. + ''' + if userstring in find_user.cache: + return find_user.cache[userstring] + matches = re.match(r'^([^<]+)? ?<([^>]*)>', userstring) + if not matches: + return None + + user = None + name = matches.group(1) + email = matches.group(2) + + def user_email(): + return User.objects.get(email=email) + def profile_email(): + return User.objects.get(userprofile__public_email=email) + def user_name(): + # yes, a bit odd but this is the easiest way since we can't always be + # sure how to split the name. Ensure every 'token' appears in at least + # one of the two name fields. + name_q = Q() + for token in name.split(): + # ignore quoted parts; e.g. nicknames in strings + if re.match(r'^[\'"].*[\'"]$', token): + continue + name_q &= (Q(first_name__icontains=token) | + Q(last_name__icontains=token)) + return User.objects.get(name_q) + + for matcher in (user_email, profile_email, user_name): + try: + user = matcher() + break + except (User.DoesNotExist, User.MultipleObjectsReturned): + pass + + find_user.cache[userstring] = user + return user + +# cached mappings of user strings -> User objects so we don't have to do the +# lookup more than strictly necessary. +find_user.cache = {} + # vim: set ts=4 sw=4 et: -- cgit v1.2.3-54-g00ecf From 26c54d017185b1c409dbd6ed4c09fb14986df0b3 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 19:35:47 -0500 Subject: find_user: add tests and fix no email address case If a packager string was passed in without an email address, we would blow up on the matcher and not try to find a user. Signed-off-by: Dan McGee --- devel/tests.py | 45 ++++++++++++++++++++++++++++++++++++++++++++- devel/utils.py | 26 ++++++++++++++++++-------- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/devel/tests.py b/devel/tests.py index 604da74c..33b02582 100644 --- a/devel/tests.py +++ b/devel/tests.py @@ -1,8 +1,10 @@ from django.test import TestCase +from django.contrib.auth.models import User +from devel.utils import find_user +from main.models import UserProfile class DevelTest(TestCase): - def test_index(self): response = self.client.get('/devel/') self.assertEqual(response.status_code, 302) @@ -27,3 +29,44 @@ def test_newuser(self): def test_mirrors(self): response = self.client.get('/mirrors/') self.assertEqual(response.status_code, 200) + +class FindUserTest(TestCase): + + def setUp(self): + self.user1 = User.objects.create(username="joeuser", first_name="Joe", + last_name="User", email="user1@example.com") + self.user2 = User.objects.create(username="john", first_name="John", + last_name="", email="user2@example.com") + self.user3 = User.objects.create(username="bjones", first_name="Bob", + last_name="Jones", email="user3@example.com") + + for user in (self.user1, self.user2, self.user3): + email_addr = "%s@awesome.com" % user.username + UserProfile.objects.create(user=user, public_email=email_addr) + + def test_not_matching(self): + self.assertIsNone(find_user(None)) + self.assertIsNone(find_user("")) + self.assertIsNone(find_user("Bogus")) + self.assertIsNone(find_user("Bogus ")) + self.assertIsNone(find_user("")) + self.assertIsNone(find_user("bogus@example.com")) + self.assertIsNone(find_user("Unknown Packager")) + + def test_by_email(self): + self.assertEqual(self.user1, find_user("XXX YYY ")) + self.assertEqual(self.user2, find_user("YYY ZZZ ")) + + def test_by_profile_email(self): + self.assertEqual(self.user1, find_user("XXX ")) + self.assertEqual(self.user2, find_user("YYY ")) + self.assertEqual(self.user3, find_user("ZZZ ")) + + def test_by_name(self): + self.assertEqual(self.user1, find_user("Joe User ")) + self.assertEqual(self.user1, find_user("Joe User")) + self.assertEqual(self.user2, find_user("John ")) + self.assertEqual(self.user3, find_user("Bob Jones ")) + +# vim: set ts=4 sw=4 et: diff --git a/devel/utils.py b/devel/utils.py index 9d7dfb2d..acdda959 100644 --- a/devel/utils.py +++ b/devel/utils.py @@ -51,24 +51,32 @@ def find_user(userstring): We start by searching for a matching email address; we then move onto matching by first/last name. If we cannot find a user, then return None. ''' + if not userstring: + return None if userstring in find_user.cache: return find_user.cache[userstring] matches = re.match(r'^([^<]+)? ?<([^>]*)>', userstring) if not matches: - return None - - user = None - name = matches.group(1) - email = matches.group(2) + name = userstring + email = None + else: + name = matches.group(1) + email = matches.group(2) def user_email(): - return User.objects.get(email=email) + if email: + return User.objects.get(email=email) + return None def profile_email(): - return User.objects.get(userprofile__public_email=email) + if email: + return User.objects.get(userprofile__public_email=email) + return None def user_name(): # yes, a bit odd but this is the easiest way since we can't always be # sure how to split the name. Ensure every 'token' appears in at least # one of the two name fields. + if not name: + return None name_q = Q() for token in name.split(): # ignore quoted parts; e.g. nicknames in strings @@ -78,10 +86,12 @@ def user_name(): Q(last_name__icontains=token)) return User.objects.get(name_q) + user = None for matcher in (user_email, profile_email, user_name): try: user = matcher() - break + if user != None: + break except (User.DoesNotExist, User.MultipleObjectsReturned): pass -- cgit v1.2.3-54-g00ecf From 9156003d2d93de57c663901c39ac66316a3d969e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 19:50:46 -0500 Subject: Turn find_user into UserFinder class This moves the cache inside an instance. Also add a few more tests. Signed-off-by: Dan McGee --- devel/management/commands/reporead.py | 6 ++- devel/tests.py | 69 ++++++++++++++++++++-------- devel/utils.py | 86 +++++++++++++++++++---------------- 3 files changed, 103 insertions(+), 58 deletions(-) diff --git a/devel/management/commands/reporead.py b/devel/management/commands/reporead.py index 138931ff..470b785d 100644 --- a/devel/management/commands/reporead.py +++ b/devel/management/commands/reporead.py @@ -27,7 +27,7 @@ from datetime import datetime from optparse import make_option -from devel.utils import find_user +from devel.utils import UserFinder from main.models import Arch, Package, PackageDepend, PackageFile, Repo from packages.models import Conflict, Provision, Replacement @@ -182,6 +182,8 @@ def create_multivalued(dbpkg, repopkg, db_attr, repo_attr): for name in getattr(repopkg, repo_attr): collection.create(name=name) +finder = UserFinder() + def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): db_score = 1 @@ -200,7 +202,7 @@ def populate_pkg(dbpkg, repopkg, force=False, timestamp=None): dbpkg.build_date = repopkg.builddate dbpkg.packager_str = repopkg.packager # attempt to find the corresponding django user for this string - dbpkg.packager = find_user(repopkg.packager) + dbpkg.packager = finder.find(repopkg.packager) if timestamp: dbpkg.flag_date = None diff --git a/devel/tests.py b/devel/tests.py index 33b02582..c982e502 100644 --- a/devel/tests.py +++ b/devel/tests.py @@ -1,7 +1,7 @@ from django.test import TestCase from django.contrib.auth.models import User -from devel.utils import find_user +from devel.utils import UserFinder from main.models import UserProfile class DevelTest(TestCase): @@ -33,6 +33,8 @@ def test_mirrors(self): class FindUserTest(TestCase): def setUp(self): + self.finder = UserFinder() + self.user1 = User.objects.create(username="joeuser", first_name="Joe", last_name="User", email="user1@example.com") self.user2 = User.objects.create(username="john", first_name="John", @@ -44,29 +46,60 @@ def setUp(self): email_addr = "%s@awesome.com" % user.username UserProfile.objects.create(user=user, public_email=email_addr) + self.user4 = User.objects.create(username="tim1", first_name="Tim", + last_name="One", email="tim@example.com") + self.user5 = User.objects.create(username="tim2", first_name="Tim", + last_name="Two", email="timtwo@example.com") + def test_not_matching(self): - self.assertIsNone(find_user(None)) - self.assertIsNone(find_user("")) - self.assertIsNone(find_user("Bogus")) - self.assertIsNone(find_user("Bogus ")) - self.assertIsNone(find_user("")) - self.assertIsNone(find_user("bogus@example.com")) - self.assertIsNone(find_user("Unknown Packager")) + self.assertIsNone(self.finder.find(None)) + self.assertIsNone(self.finder.find("")) + self.assertIsNone(self.finder.find("Bogus")) + self.assertIsNone(self.finder.find("Bogus ")) + self.assertIsNone(self.finder.find("")) + self.assertIsNone(self.finder.find("bogus@example.com")) + self.assertIsNone(self.finder.find("Unknown Packager")) def test_by_email(self): - self.assertEqual(self.user1, find_user("XXX YYY ")) - self.assertEqual(self.user2, find_user("YYY ZZZ ")) + self.assertEqual(self.user1, + self.finder.find("XXX YYY ")) + self.assertEqual(self.user2, + self.finder.find("YYY ZZZ ")) def test_by_profile_email(self): - self.assertEqual(self.user1, find_user("XXX ")) - self.assertEqual(self.user2, find_user("YYY ")) - self.assertEqual(self.user3, find_user("ZZZ ")) + self.assertEqual(self.user1, + self.finder.find("XXX ")) + self.assertEqual(self.user2, + self.finder.find("YYY ")) + self.assertEqual(self.user3, + self.finder.find("ZZZ ")) def test_by_name(self): - self.assertEqual(self.user1, find_user("Joe User ")) - self.assertEqual(self.user1, find_user("Joe User")) - self.assertEqual(self.user2, find_user("John ")) - self.assertEqual(self.user3, find_user("Bob Jones ")) + self.assertEqual(self.user1, + self.finder.find("Joe User ")) + self.assertEqual(self.user1, + self.finder.find("Joe User")) + self.assertEqual(self.user2, + self.finder.find("John ")) + self.assertEqual(self.user2, + self.finder.find("John")) + self.assertEqual(self.user3, + self.finder.find("Bob Jones ")) + + def test_cache(self): + # simply look two of them up, but then do it repeatedly + for i in range(50): + self.assertEqual(self.user1, + self.finder.find("XXX YYY ")) + self.assertEqual(self.user3, + self.finder.find("Bob Jones ")) + + def test_ambiguous(self): + self.assertEqual(self.user4, + self.finder.find("Tim One ")) + self.assertEqual(self.user5, + self.finder.find("Tim Two ")) + self.assertIsNone(self.finder.find("Tim ")) # vim: set ts=4 sw=4 et: diff --git a/devel/utils.py b/devel/utils.py index acdda959..3a6ad699 100644 --- a/devel/utils.py +++ b/devel/utils.py @@ -43,35 +43,25 @@ def get_annotated_maintainers(): return maintainers -def find_user(userstring): - ''' - Attempt to find the corresponding User object for a standard - packager string, e.g. something like - 'A. U. Thor '. - We start by searching for a matching email address; we then move onto - matching by first/last name. If we cannot find a user, then return None. - ''' - if not userstring: - return None - if userstring in find_user.cache: - return find_user.cache[userstring] - matches = re.match(r'^([^<]+)? ?<([^>]*)>', userstring) - if not matches: - name = userstring - email = None - else: - name = matches.group(1) - email = matches.group(2) - - def user_email(): + +class UserFinder(object): + def __init__(self): + self.cache = {} + + @staticmethod + def user_email(name, email): if email: return User.objects.get(email=email) return None - def profile_email(): + + @staticmethod + def profile_email(name, email): if email: return User.objects.get(userprofile__public_email=email) return None - def user_name(): + + @staticmethod + def user_name(name, email): # yes, a bit odd but this is the easiest way since we can't always be # sure how to split the name. Ensure every 'token' appears in at least # one of the two name fields. @@ -86,20 +76,40 @@ def user_name(): Q(last_name__icontains=token)) return User.objects.get(name_q) - user = None - for matcher in (user_email, profile_email, user_name): - try: - user = matcher() - if user != None: - break - except (User.DoesNotExist, User.MultipleObjectsReturned): - pass - - find_user.cache[userstring] = user - return user - -# cached mappings of user strings -> User objects so we don't have to do the -# lookup more than strictly necessary. -find_user.cache = {} + def find(self, userstring): + ''' + Attempt to find the corresponding User object for a standard + packager string, e.g. something like + 'A. U. Thor '. + We start by searching for a matching email address; we then move onto + matching by first/last name. If we cannot find a user, then return None. + ''' + if not userstring: + return None + if userstring in self.cache: + return self.cache[userstring] + matches = re.match(r'^([^<]+)? ?<([^>]*)>', userstring) + if not matches: + name = userstring + email = None + else: + name = matches.group(1) + email = matches.group(2) + + user = None + find_methods = (self.user_email, self.profile_email, self.user_name) + for matcher in find_methods: + try: + user = matcher(name, email) + if user != None: + break + except (User.DoesNotExist, User.MultipleObjectsReturned): + pass + + self.cache[userstring] = user + return user + + def clear_cache(self): + self.cache = {} # vim: set ts=4 sw=4 et: -- cgit v1.2.3-54-g00ecf From 82289ebb4432b3372b959430581afa0a2158acb9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 20:11:07 -0500 Subject: Add a rematch_packager management command This allows quick resolution of all unmatched packages, especially after tweaking the way find_user works. Signed-off-by: Dan McGee --- devel/management/commands/rematch_packager.py | 64 +++++++++++++++++++++++++++ devel/tests.py | 12 ++++- devel/utils.py | 8 ++-- 3 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 devel/management/commands/rematch_packager.py diff --git a/devel/management/commands/rematch_packager.py b/devel/management/commands/rematch_packager.py new file mode 100644 index 00000000..ba6e6a54 --- /dev/null +++ b/devel/management/commands/rematch_packager.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +""" +rematch_packager command + +Match all packages with a packager_str but NULL packager_id to a packager if we +can find one. + +Usage: ./manage.py rematch_packager +""" + +from django.core.management.base import NoArgsCommand + +import sys +import logging + +from devel.utils import UserFinder +from main.models import Package + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s -> %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + stream=sys.stderr) +logger = logging.getLogger() + +class Command(NoArgsCommand): + help = "Runs a check on all active mirror URLs to determine if they are reachable via IPv4 and/or v6." + + def handle_noargs(self, **options): + v = int(options.get('verbosity', None)) + if v == 0: + logger.level = logging.ERROR + elif v == 1: + logger.level = logging.INFO + elif v == 2: + logger.level = logging.DEBUG + + return match_packager() + +def match_packager(): + finder = UserFinder() + logger.info("getting all unmatched packages") + package_count = matched_count = 0 + unknown = set() + + for package in Package.objects.filter(packager__isnull=True): + logger.debug("package %s, packager string %s", + package.pkgname, package.packager_str) + package_count += 1 + user = finder.find(package.packager_str) + if user: + package.packager = user + logger.debug(" found user %s" % user.username) + package.save() + matched_count += 1 + else: + unknown.add(package.packager_str) + + logger.info("%d packages checked, %d newly matched", + package_count, matched_count) + logger.debug("unknown packagers:\n%s", + "\n".join(unknown)) + +# vim: set ts=4 sw=4 et: diff --git a/devel/tests.py b/devel/tests.py index c982e502..36691179 100644 --- a/devel/tests.py +++ b/devel/tests.py @@ -1,7 +1,7 @@ from django.test import TestCase from django.contrib.auth.models import User -from devel.utils import UserFinder +from devel.utils import UserFinder from main.models import UserProfile class DevelTest(TestCase): @@ -87,6 +87,16 @@ def test_by_name(self): self.assertEqual(self.user3, self.finder.find("Bob Jones ")) + def test_by_invalid(self): + self.assertEqual(self.user1, + self.finder.find("Joe User ]*)>', userstring) + + name = email = None + + matches = re.match(r'^([^<]+)? ?<([^>]*)>?', userstring) if not matches: - name = userstring - email = None + name = userstring.strip() else: name = matches.group(1) email = matches.group(2) -- cgit v1.2.3-54-g00ecf From dcbb859a259082bf8d0587a63385ece44c697e45 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 20:13:01 -0500 Subject: Add (hidden) ability to search by last packager This is used from the developer dashboard to add a new column to the stats of # of packages for a given developer where they were the last to do the packaging. Signed-off-by: Dan McGee --- devel/views.py | 2 ++ packages/views.py | 10 ++++++++++ templates/devel/index.html | 41 ++++++++++++++++++++++++++++------------- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/devel/views.py b/devel/views.py index 1827f2ac..4399b73f 100644 --- a/devel/views.py +++ b/devel/views.py @@ -50,9 +50,11 @@ def index(request): total_orphans = Package.objects.exclude(pkgbase__in=maintained).count() total_flagged_orphans = Package.objects.filter( flag_date__isnull=False).exclude(pkgbase__in=maintained).count() + total_updated = Package.objects.filter(packager__isnull=True).count() orphan = { 'package_count': total_orphans, 'flagged_count': total_flagged_orphans, + 'updated_count': total_updated, } page_dict = { diff --git a/packages/views.py b/packages/views.py index 7b8c2e67..02b9f93b 100644 --- a/packages/views.py +++ b/packages/views.py @@ -191,6 +191,7 @@ class PackageSearchForm(forms.Form): arch = forms.MultipleChoiceField(required=False) q = forms.CharField(required=False) maintainer = forms.ChoiceField(required=False) + packager = forms.ChoiceField(required=False) last_update = forms.DateField(required=False, widget=AdminDateWidget(), label='Last Updated After') flagged = forms.ChoiceField( @@ -213,6 +214,9 @@ def __init__(self, *args, **kwargs): self.fields['maintainer'].choices = \ [('', 'All'), ('orphan', 'Orphan')] + \ [(m.username, m.get_full_name()) for m in maints] + self.fields['packager'].choices = \ + [('', 'All'), ('unknown', 'Unknown')] + \ + [(m.username, m.get_full_name()) for m in maints] def search(request, page=None): limit = 50 @@ -237,6 +241,12 @@ def search(request, page=None): user__username=form.cleaned_data['maintainer']).values('pkgbase') packages = packages.filter(pkgbase__in=inner_q) + if form.cleaned_data['packager'] == 'unknown': + packages = packages.filter(packager__isnull=True) + elif form.cleaned_data['packager']: + packages = packages.filter( + packager__username=form.cleaned_data['packager']) + if form.cleaned_data['flagged'] == 'Flagged': packages = packages.filter(flag_date__isnull=False) elif form.cleaned_data['flagged'] == 'Not Flagged': diff --git a/templates/devel/index.html b/templates/devel/index.html index 2a0058d3..f9ca2135 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -182,10 +182,10 @@

Stats by Repository

{# dash-by-arch #} {% endcache %} -{% cache 60 dev-dash-by-maintainer %} -
+{% cache 60 dev-dash-by-developer %} +
-

Stats by Maintainer

+

Stats by Developer

{% if perms.main.change_package %}

Look for stale relations

@@ -195,17 +195,24 @@

Stats by Maintainer

Maintainer - # Packages + # Maintained # Flagged + # Last Packager - Orphan + Orphan/Unknown - {{ orphan.package_count }} packages + {{ orphan.package_count }} packages + - {{ orphan.flagged_count }} packages + {{ orphan.flagged_count }} packages + + + {{ orphan.updated_count }} packages + @@ -214,15 +221,21 @@

Stats by Maintainer

{{ maint.get_full_name }} - {{ maint.package_count }} packages + {{ maint.package_count }} packages + - {{ maint.flagged_count }} packages + {{ maint.flagged_count }} packages + + + {{ maint.updated_count }} packages + {% endfor %} -
{# #dash-by-maintainer #} +
{# #dash-by-developer #} {% endcache %} {% load cdn %}{% jquery %} @@ -236,9 +249,11 @@

Stats by Maintainer

{widgets: ['zebra'], sortList: [[0,0], [1,0]]}); $("#dash-todo:not(:has(tbody tr.empty))").tablesorter( {widgets: ['zebra'], sortList: [[1,1]]}); - $(".dash-stats").tablesorter( - {widgets: ['zebra'], sortList: [[0,0]], - headers: { 1: { sorter: 'pkgcount' }, 2: { sorter: 'pkgcount' } } }); + $(".dash-stats").tablesorter({ + widgets: ['zebra'], + sortList: [[0,0]], + headers: { 1: { sorter: 'pkgcount' }, 2: { sorter: 'pkgcount' }, 3: { sorter: 'pkgcount' } } + }); }); {% endblock %} -- cgit v1.2.3-54-g00ecf From 55f6ad0c95323a5bfeca2c322918d21f413d1075 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 20:19:49 -0500 Subject: Set up queries for staging repos This treats repo.staging special in much the way we already have to treat repo.testing as special. Signed-off-by: Dan McGee --- main/models.py | 17 +++++++++++------ packages/views.py | 13 ++++++++----- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/main/models.py b/main/models.py index 05e63fed..e0017069 100644 --- a/main/models.py +++ b/main/models.py @@ -226,6 +226,7 @@ def get_requiredby(self): groupby(requiredby, lambda x: x.pkg.id)] # find another package by this name in the opposite testing setup + # TODO: figure out staging exclusions too if not Package.objects.filter(pkgname=self.pkgname, arch=self.arch).exclude(id=self.id).exclude( repo__testing=self.repo.testing).exists(): @@ -241,7 +242,8 @@ def get_requiredby(self): dep = dep_pkgs[0] if len(dep_pkgs) > 1: dep_pkgs = [d for d in dep_pkgs - if d.pkg.repo.testing == self.repo.testing] + if d.pkg.repo.testing == self.repo.testing and + d.pkg.repo.staging = self.repo.staging] if len(dep_pkgs) > 0: dep = dep_pkgs[0] trimmed.append(dep) @@ -273,7 +275,8 @@ def get_depends(self): # grab the first though in case we fail pkg = pkgs[0] # prevents yet more DB queries, these lists should be short - pkgs = [p for p in pkgs if p.repo.testing == self.repo.testing] + pkgs = [p for p in pkgs if p.repo.testing == self.repo.testing + and p.repo.staging = self.repo.staging] if len(pkgs) > 0: pkg = pkgs[0] deps.append({'dep': dep, 'pkg': pkg}) @@ -293,7 +296,8 @@ def base_package(self): # this package might be split across repos? just find one # that matches the correct [testing] repo flag pkglist = Package.objects.filter(arch=self.arch, - repo__testing=self.repo.testing, pkgname=self.pkgbase) + repo__testing=self.repo.testing, + repo__staging=self.repo.staging, pkgname=self.pkgbase) if len(pkglist) > 0: return pkglist[0] return None @@ -303,11 +307,12 @@ def split_packages(self): Return all packages that were built with this one (e.g. share a pkgbase value). The package this method is called on will never be in the list, and we will never return a package that does not have the same - repo.testing flag. For any non-split packages, the return value will be - an empty list. + repo.testing and repo.staging flags. For any non-split packages, the + return value will be an empty list. """ return Package.objects.filter(arch__in=self.applicable_arches(), - repo__testing=self.repo.testing, pkgbase=self.pkgbase).exclude(id=self.id) + repo__testing=self.repo.testing, repo__staging=self.repo.staging, + pkgbase=self.pkgbase).exclude(id=self.id) def is_same_version(self, other): 'is this package similar, name and version-wise, to another' diff --git a/packages/views.py b/packages/views.py index 02b9f93b..01d01e20 100644 --- a/packages/views.py +++ b/packages/views.py @@ -118,7 +118,8 @@ def details(request, name='', repo='', arch=''): arches.extend(Arch.objects.filter(agnostic=True)) repo = get_object_or_404(Repo, name__iexact=repo) pkgs = Package.objects.normal().filter(pkgbase=name, - repo__testing=repo.testing, arch__in=arches).order_by('pkgname') + repo__testing=repo.testing, repo__staging=repo.staging, + arch__in=arches).order_by('pkgname') if len(pkgs) == 0: raise Http404 context = { @@ -343,8 +344,8 @@ def unflag_all(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) # find all packages from (hopefully) the same PKGBUILD - pkgs = Package.objects.filter( - pkgbase=pkg.pkgbase, repo__testing=pkg.repo.testing) + pkgs = Package.objects.filter(pkgbase=pkg.pkgbase, + repo__testing=pkg.repo.testing, repo__staging=pkg.repo.staging) pkgs.update(flag_date=None) return redirect(pkg) @@ -416,7 +417,8 @@ def flag(request, name, repo, arch): # find all packages from (hopefully) the same PKGBUILD pkgs = Package.objects.normal().filter( pkgbase=pkg.pkgbase, flag_date__isnull=True, - repo__testing=pkg.repo.testing).order_by( + repo__testing=pkg.repo.testing, + repo__staging=pkg.repo.staging).order_by( 'pkgname', 'repo__name', 'arch__name') if request.POST: @@ -471,7 +473,8 @@ def flag_confirmed(request, name, repo, arch): pkgname=name, repo__name__iexact=repo, arch__name=arch) pkgs = Package.objects.normal().filter( pkgbase=pkg.pkgbase, flag_date=pkg.flag_date, - repo__testing=pkg.repo.testing).order_by( + repo__testing=pkg.repo.testing, + repo__staging=pkg.repo.staging).order_by( 'pkgname', 'repo__name', 'arch__name') context = {'package': pkg, 'packages': pkgs} -- cgit v1.2.3-54-g00ecf From 5bf28d1cabc54dfa68ff64804a31ead419780fdf Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 20:24:15 -0500 Subject: Add Package.in_staging method Signed-off-by: Dan McGee --- main/models.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/main/models.py b/main/models.py index e0017069..b52c45fb 100644 --- a/main/models.py +++ b/main/models.py @@ -332,6 +332,17 @@ def in_testing(self): except Package.DoesNotExist: return None + def in_staging(self): + '''attempt to locate this package in a staging repo; if we are in + a staging repo we will always return None.''' + if self.repo.staging: + return None + try: + return Package.objects.normal().get(repo__staging=True, + pkgname=self.pkgname, arch=self.arch) + except Package.DoesNotExist: + return None + def elsewhere(self): '''attempt to locate this package anywhere else, regardless of architecture or repository. Excludes this package from the list.''' -- cgit v1.2.3-54-g00ecf From 8f3c60c8dec4c162ad5dc753b97ce5177fb12ef1 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 20:27:21 -0500 Subject: Don't include staging packages in todolists Signed-off-by: Dan McGee --- todolists/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/todolists/views.py b/todolists/views.py index 9376ed68..8ad7be56 100644 --- a/todolists/views.py +++ b/todolists/views.py @@ -23,8 +23,8 @@ def clean_packages(self): package_names = [s.strip() for s in self.cleaned_data['packages'].split("\n")] package_names = set(package_names) - packages = Package.objects.filter(pkgname__in=package_names).exclude( - repo__testing=True).select_related( + packages = Package.objects.filter(pkgname__in=package_names).filter( + repo__testing=False, repo__staging=False).select_related( 'arch', 'repo').order_by('arch') return packages -- cgit v1.2.3-54-g00ecf From c584ffc9b6bc9bdab1616b096ccaa1c1cd762f69 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 23 Jun 2011 20:30:31 -0500 Subject: Fix syntax errors, == not = Signed-off-by: Dan McGee --- main/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/models.py b/main/models.py index b52c45fb..9010817b 100644 --- a/main/models.py +++ b/main/models.py @@ -243,7 +243,7 @@ def get_requiredby(self): if len(dep_pkgs) > 1: dep_pkgs = [d for d in dep_pkgs if d.pkg.repo.testing == self.repo.testing and - d.pkg.repo.staging = self.repo.staging] + d.pkg.repo.staging == self.repo.staging] if len(dep_pkgs) > 0: dep = dep_pkgs[0] trimmed.append(dep) @@ -276,7 +276,7 @@ def get_depends(self): pkg = pkgs[0] # prevents yet more DB queries, these lists should be short pkgs = [p for p in pkgs if p.repo.testing == self.repo.testing - and p.repo.staging = self.repo.staging] + and p.repo.staging == self.repo.staging] if len(pkgs) > 0: pkg = pkgs[0] deps.append({'dep': dep, 'pkg': pkg}) -- cgit v1.2.3-54-g00ecf From 8734d6ccdf10fd03ed3dfea5fb1028812d0ba8cd Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 27 Jun 2011 16:22:05 -0500 Subject: Adjust CSS to make front page news look better A code element inside a pre block looked like ass, which is how markdown generates indented code blocks. Drop the bright yellow from these code elements, and while we are at it, also lighten up the yellow a bit as it stands out way too much. Signed-off-by: Dan McGee --- media/archweb.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/media/archweb.css b/media/archweb.css index fce88e21..85fdb610 100644 --- a/media/archweb.css +++ b/media/archweb.css @@ -20,8 +20,9 @@ body { min-width: 650px; background: #f6f9fc; color: #222; font: normal 100% san p { margin: .33em 0 1em; } ol, ul { margin-bottom: 1em; padding-left: 2em; } ul { list-style: square; } -code { font: 1.2em monospace; background: #ffa; padding: 0.15em 0.25em; } +code { font: 1.2em monospace; background: #ffd; padding: 0.15em 0.25em; } pre { font: 1.2em monospace; border: 1px solid #bdb; background: #dfd; padding: 0.5em; margin: 0.25em 2em; } +pre code { display: block; background: none; } blockquote { margin: 1.5em 2em; } input { vertical-align: middle; } select[multiple] { padding-top: 1px; padding-bottom: 1px; } -- cgit v1.2.3-54-g00ecf From f913bbcab4dc458d1566778a094bdd337cb91841 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 28 Jun 2011 00:11:49 -0500 Subject: Add order_by to packager count query No real idea why SQLite is returning wrong results without out this, but it is likely a bug in the ORM layer I'm not interested in digging into. Signed-off-by: Dan McGee --- devel/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devel/utils.py b/devel/utils.py index 6bc52c89..d7a154a8 100644 --- a/devel/utils.py +++ b/devel/utils.py @@ -32,8 +32,8 @@ def get_annotated_maintainers(): pkg_count[k] = total flag_count[k] = flagged - update_count = Package.objects.values_list('packager').annotate( - Count('packager')) + update_count = Package.objects.values_list('packager').order_by( + 'packager').annotate(Count('packager')) update_count = dict(update_count) for m in maintainers: -- cgit v1.2.3-54-g00ecf From d3f1763efefef9ff5095a49b075b27b38df83d16 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 28 Jun 2011 00:12:45 -0500 Subject: Add a bad compression ratio report Signed-off-by: Dan McGee --- devel/views.py | 19 ++++++++++++++++++- templates/devel/index.html | 3 +++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/devel/views.py b/devel/views.py index 4399b73f..79eef318 100644 --- a/devel/views.py +++ b/devel/views.py @@ -6,7 +6,7 @@ from django.contrib.sites.models import Site from django.core.mail import send_mail from django.db import transaction -from django.db.models import Q +from django.db.models import F, Q from django.http import Http404 from django.shortcuts import get_object_or_404 from django.template import loader, Context @@ -163,6 +163,23 @@ def report(request, report, username=None): package.compressed_size) package.installed_size_pretty = filesizeformat( package.installed_size) + elif report == '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 = '%.2f' % ratio + package.compress_type = package.filename.split('.')[-1] elif report == 'uncompressed-man': title = 'Packages with uncompressed manpages' # magic going on here! Checking for all '.1'...'.9' extensions diff --git a/templates/devel/index.html b/templates/devel/index.html index f9ca2135..1689319b 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -115,6 +115,9 @@

Developer Reports

  • Uncompressed Info Pages: Self-explanatory (yours only)
  • +
  • Bad Compression: + Packages with a compression ratio of less than 10% + (yours only)
  • Unneeded Orphans: Packages that have no maintainer and are not required by any other package in any repository
  • -- cgit v1.2.3-54-g00ecf From 33b9bf44aac4b70fa4cc6e9d1e82fb556b836801 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 29 Jun 2011 09:17:00 -0500 Subject: Use normal date format on signoffs page Fixes FS#24949. Signed-off-by: Dan McGee --- templates/packages/signoffs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index b7184039..93be4979 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -27,7 +27,7 @@

    Package Signoffs

    {{ pkg.pkgname }} {{ pkg.full_version }} - {{ pkg.last_update }} + {{ pkg.last_update|date }} {{ target }} {{ pkg.approved_for_signoff|yesno:"Yes,No" }} -- cgit v1.2.3-54-g00ecf From 5e85c5ac9ed09551d65ec07767094770d248f3b1 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 29 Jun 2011 10:52:30 -0500 Subject: Move set_created_field() to shared utils class Signed-off-by: Dan McGee --- main/utils.py | 10 ++++++++++ packages/models.py | 10 ++-------- releng/models.py | 13 +++---------- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/main/utils.py b/main/utils.py index 12d12503..2ca69bcb 100644 --- a/main/utils.py +++ b/main/utils.py @@ -2,6 +2,9 @@ import cPickle as pickle except ImportError: import pickle + +from datetime import datetime + from django.core.cache import cache from django.utils.hashcompat import md5_constructor @@ -81,4 +84,11 @@ def retrieve_latest(sender): pass return None +def set_created_field(sender, **kwargs): + '''This will set the 'created' field on any object to datetime.utcnow() if + it is unset. For use as a pre_save signal handler.''' + obj = kwargs['instance'] + if hasattr(obj, 'created') and not obj.created: + obj.created = datetime.utcnow() + # vim: set ts=4 sw=4 et: diff --git a/packages/models.py b/packages/models.py index a950bddb..0983c642 100644 --- a/packages/models.py +++ b/packages/models.py @@ -1,9 +1,9 @@ -from datetime import datetime - from django.db import models from django.db.models.signals import pre_save, post_save from django.contrib.auth.models import User +from main.utils import set_created_field + class PackageRelation(models.Model): ''' Represents maintainership (or interest) in a package by a given developer. @@ -112,12 +112,6 @@ def remove_inactive_maintainers(sender, instance, created, **kwargs): type=PackageRelation.MAINTAINER) maint_relations.delete() -def set_created_field(sender, **kwargs): - # We use this same callback for both Isos and Tests - obj = kwargs['instance'] - if not obj.created: - obj.created = datetime.utcnow() - post_save.connect(remove_inactive_maintainers, sender=User, dispatch_uid="packages.models") pre_save.connect(set_created_field, sender=PackageRelation, diff --git a/releng/models.py b/releng/models.py index 07ede1c5..5510db6a 100644 --- a/releng/models.py +++ b/releng/models.py @@ -1,6 +1,7 @@ -from datetime import datetime - from django.db import models +from django.db.models.signals import pre_save + +from main.utils import set_created_field class IsoOption(models.Model): name = models.CharField(max_length=200) @@ -104,14 +105,6 @@ class Test(models.Model): success = models.BooleanField() comments = models.TextField(null=True, blank=True) -def set_created_field(sender, **kwargs): - # We use this same callback for both Isos and Tests - obj = kwargs['instance'] - if not obj.created: - obj.created = datetime.utcnow() - -from django.db.models.signals import pre_save - pre_save.connect(set_created_field, sender=Iso, dispatch_uid="releng.models") pre_save.connect(set_created_field, sender=Test, -- cgit v1.2.3-54-g00ecf From 65e274dcb6dc58990e60af892be941c6b53f195a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 5 Jul 2011 09:43:36 -0500 Subject: Simplify package differences code Signed-off-by: Dan McGee --- packages/utils.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/utils.py b/packages/utils.py index af4675bb..37f23244 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -92,21 +92,16 @@ def get_differences_info(arch_a, arch_b): WHERE p.arch_id IN (%s, %s) AND ( q.id IS NULL - OR - p.pkgver != q.pkgver - OR - p.pkgrel != q.pkgrel - OR - p.epoch != q.epoch + OR p.pkgver != q.pkgver + OR p.pkgrel != q.pkgrel + OR p.epoch != q.epoch ) """ cursor = connection.cursor() cursor.execute(sql, [arch_a.id, arch_b.id]) results = cursor.fetchall() - to_fetch = [] - for row in results: - # column A will always have a value, column B might be NULL - to_fetch.append(row[0]) + # column A will always have a value, column B might be NULL + to_fetch = [row[0] for row in results] # fetch all of the necessary packages pkgs = Package.objects.normal().in_bulk(to_fetch) # now build a list of tuples containing differences -- cgit v1.2.3-54-g00ecf From fa65115afccc612dd902b6324069a0b312f6a0f6 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 3 Jul 2011 18:56:29 -0500 Subject: Add rel="nofollow" to download package links Signed-off-by: Dan McGee --- templates/packages/details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/packages/details.html b/templates/packages/details.html index 1926abc2..68016501 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -38,7 +38,7 @@

    Package Actions

    onclick="return !window.open('/packages/flaghelp/','FlagHelp', 'height=350,width=450,location=no,scrollbars=yes,menubars=no,toolbars=no,resizable=no');">(?) {% endif %} -
  • Download From Mirror
  • +
  • Download From Mirror
  • {% if perms.main.change_package %} -- cgit v1.2.3-54-g00ecf From 1bcb2b7ed0bdb11ea3b22cdbc93192b4259303d5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 3 Jul 2011 18:56:05 -0500 Subject: Add a default datetime format Signed-off-by: Dan McGee --- settings.py | 1 + templates/packages/details.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/settings.py b/settings.py index e0d1b1a2..5cca6047 100644 --- a/settings.py +++ b/settings.py @@ -32,6 +32,7 @@ # Default date format in templates for 'date' filter DATE_FORMAT = 'Y-m-d' +DATETIME_FORMAT = 'Y-m-d H:i' # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # trailing slash. diff --git a/templates/packages/details.html b/templates/packages/details.html index 68016501..7972b9ab 100644 --- a/templates/packages/details.html +++ b/templates/packages/details.html @@ -144,7 +144,7 @@

    Versions Elsewhere

    {% with pkg.packager as pkgr %}{% if pkgr %}{% userpkgs pkgr %}{% else %}{{ pkg.packager_str }}{% endif %}{% endwith %} Build Date: - {{ pkg.build_date }} UTC + {{ pkg.build_date|date:"DATETIME_FORMAT" }} UTC Last Updated: {{ pkg.last_update|date }} -- cgit v1.2.3-54-g00ecf From e37d465c4eb03259b3ce0c89e43c2c21badb0063 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 1 Jul 2011 18:59:28 -0500 Subject: Allow more lenient data entry for PGP key field Don't allow the max_length parameter to make it through to the HTML form, silently cutting off HTML cut and pastes. Trim out spaces automatically, as well as '0x' and '2048R/' type prefixes. Signed-off-by: Dan McGee --- .../0051_auto__chg_field_userprofile_pgp_key.py | 160 +++++++++++++++++++++ main/models.py | 21 ++- 2 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 main/migrations/0051_auto__chg_field_userprofile_pgp_key.py diff --git a/main/migrations/0051_auto__chg_field_userprofile_pgp_key.py b/main/migrations/0051_auto__chg_field_userprofile_pgp_key.py new file mode 100644 index 00000000..6b8abf6e --- /dev/null +++ b/main/migrations/0051_auto__chg_field_userprofile_pgp_key.py @@ -0,0 +1,160 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.alter_column('user_profiles', 'pgp_key', self.gf('main.models.PGPKeyField')(max_length=40, null=True)) + + + def backwards(self, orm): + db.alter_column('user_profiles', 'pgp_key', self.gf('django.db.models.fields.CharField')(max_length=40, null=True)) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "['name']", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.donor': { + 'Meta': {'ordering': "['name']", 'object_name': 'Donor', 'db_table': "'donors'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.packagedepend': { + 'Meta': {'object_name': 'PackageDepend', 'db_table': "'package_depends'"}, + 'depname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'depvcmp': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.packagefile': { + 'Meta': {'object_name': 'PackageFile', 'db_table': "'package_files'"}, + 'directory': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_directory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.repo': { + 'Meta': {'ordering': "['name']", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'main.signoff': { + 'Meta': {'object_name': 'Signoff'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'main.todolist': { + 'Meta': {'object_name': 'Todolist', 'db_table': "'todolists'"}, + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'main.todolistpkg': { + 'Meta': {'unique_together': "(('list', 'pkg'),)", 'object_name': 'TodolistPkg', 'db_table': "'todolist_pkgs'"}, + 'complete': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Todolist']"}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'user_profiles'"}, + 'alias': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'allowed_repos': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Repo']", 'symmetrical': 'False', 'blank': 'True'}), + 'favorite_distros': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'interests': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'languages': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'other_contact': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'pgp_key': ('main.models.PGPKeyField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'picture': ('django.db.models.fields.files.FileField', [], {'default': "'devs/silhouette.png'", 'max_length': '100'}), + 'public_email': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'roles': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'time_zone': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'userprofile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'website': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'yob': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['main'] diff --git a/main/models.py b/main/models.py index 9010817b..2f549081 100644 --- a/main/models.py +++ b/main/models.py @@ -22,6 +22,25 @@ def formfield(self, **kwargs): defaults.update(kwargs) return super(PositiveBigIntegerField, self).formfield(**defaults) +class PGPKeyField(models.CharField): + _south_introspects = True + + def to_python(self, value): + if value == '': + return None + value = super(PGPKeyField, self).to_python(value) + # remove all spaces + value = value.replace(' ', '') + # prune prefixes, either 0x or 2048R/ type + if value.startswith('0x'): + value = value[2:] + value = value.split('/')[-1] + return value + + def formfield(self, **kwargs): + # override so we don't set max_length form field attribute + return models.Field.formfield(self, **kwargs) + def validate_pgp_key_length(value): if len(value) not in (8, 16, 40): raise ValidationError( @@ -45,7 +64,7 @@ class UserProfile(models.Model): max_length=50, help_text="Required field") other_contact = models.CharField(max_length=100, null=True, blank=True) - pgp_key = models.CharField(max_length=40, null=True, blank=True, + pgp_key = PGPKeyField(max_length=40, null=True, blank=True, verbose_name="PGP key", validators=[RegexValidator(r'^[0-9A-F]+$', "Ensure this value consists of only hex characters.", 'hex_char'), validate_pgp_key_length], -- cgit v1.2.3-54-g00ecf From f3262790b37da4883d74095ce34a84951432399b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 29 Jun 2011 11:14:56 -0500 Subject: Convert tabs to spaces in devel packages template Signed-off-by: Dan McGee --- templates/devel/packages.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/devel/packages.html b/templates/devel/packages.html index 051ee4a3..a9553d70 100644 --- a/templates/devel/packages.html +++ b/templates/devel/packages.html @@ -5,12 +5,12 @@ {% block content %}
    -

    {{ title }}{% if maintainer %}, - maintained by {{ maintainer.get_full_name }}{% endif%}

    -

    {{ packages|length }} package{{ packages|pluralize }} found. - {% if maintainer %}This report only includes packages maintained by - {{ maintainer.get_full_name }} ({{ maintainer.username }}).{% endif %} -

    +

    {{ title }}{% if maintainer %}, + maintained by {{ maintainer.get_full_name }}{% endif%}

    +

    {{ packages|length }} package{{ packages|pluralize }} found. + {% if maintainer %}This report only includes packages maintained by + {{ maintainer.get_full_name }} ({{ maintainer.username }}).{% endif %} +

    -- cgit v1.2.3-54-g00ecf From c5308b75837dfcbb851bcf9e93553c72e4b8996e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 29 Jun 2011 12:05:07 -0500 Subject: Recent updates refactor Pull out a few helpful objects and functions for use later elsewhere. Signed-off-by: Dan McGee --- main/utils.py | 35 +++++++++++++++++++++++++++++++++++ public/utils.py | 38 +++++++++++++------------------------- 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/main/utils.py b/main/utils.py index 2ca69bcb..81f689e7 100644 --- a/main/utils.py +++ b/main/utils.py @@ -91,4 +91,39 @@ def set_created_field(sender, **kwargs): if hasattr(obj, 'created') and not obj.created: obj.created = datetime.utcnow() +def groupby_preserve_order(iterable, keyfunc): + '''Take an iterable and regroup using keyfunc to determine whether items + belong to the same group. The order of the iterable is preserved and + similar keys do not have to be consecutive. This means the earliest + occurrence of a given key will determine the order of the lists in the + returned list.''' + seen_keys = {} + result = [] + for item in iterable: + key = keyfunc(item) + + group = seen_keys.get(key, None) + if group is None: + group = [] + seen_keys[key] = group + result.append(group) + + group.append(item) + + return result + +class PackageStandin(object): + '''Resembles a Package object, and has a few of the same fields, but is + really a link to a pkgbase that has no package with matching pkgname.''' + def __init__(self, package): + self.package = package + self.pkgname = package.pkgbase + + def __getattr__(self, name): + return getattr(self.package, name) + + def get_absolute_url(self): + return '/packages/%s/%s/%s/' % ( + self.repo.name.lower(), self.arch.name, self.pkgbase) + # vim: set ts=4 sw=4 et: diff --git a/public/utils.py b/public/utils.py index 0be3ebaa..5900c674 100644 --- a/public/utils.py +++ b/public/utils.py @@ -1,7 +1,7 @@ from operator import attrgetter from main.models import Arch, Package, Repo -from main.utils import cache_function +from main.utils import cache_function, groupby_preserve_order, PackageStandin class RecentUpdate(object): def __init__(self, packages): @@ -35,18 +35,12 @@ def package_links(self): for package in self.packages: yield package else: - # time to fake out the template, this is a tad dirty - arches = set(pkg.arch for pkg in self.others) - for arch in arches: - url = '/packages/%s/%s/%s/' % ( - self.repo.name.lower(), arch.name, self.pkgbase) - package_stub = { - 'pkgname': self.pkgbase, - 'arch': arch, - 'repo': self.repo, - 'get_absolute_url': url - } - yield package_stub + # fake out the template- this is slightly hacky but yields one + # 'package-like' object per arch which is what the normal loop does + arches = set() + for package in self.others: + if package.arch not in arches and not arches.add(package.arch): + yield PackageStandin(package) @cache_function(300) def get_recent_updates(number=15): @@ -59,20 +53,14 @@ def get_recent_updates(number=15): for arch in Arch.objects.all(): pkgs += list(Package.objects.normal().filter( arch=arch).order_by('-last_update')[:fetch]) - pkgs.sort(key=attrgetter('last_update')) + pkgs.sort(key=attrgetter('last_update'), reverse=True) - updates = [] - while len(pkgs) > 0: - pkg = pkgs.pop() - - in_group = lambda x: pkg.repo == x.repo and pkg.pkgbase == x.pkgbase - samepkgs = [other for other in pkgs if in_group(other)] - samepkgs.append(pkg) + same_pkgbase_key = lambda x: (x.repo.name, x.pkgbase) + grouped = groupby_preserve_order(pkgs, same_pkgbase_key) - # now remove all the packages we just pulled out - pkgs = [other for other in pkgs if other not in samepkgs] - - update = RecentUpdate(samepkgs) + updates = [] + for group in grouped: + update = RecentUpdate(group) updates.append(update) return updates[:number] -- cgit v1.2.3-54-g00ecf From 52363933c0b04848a26c8ed65e7f975ccfc5b846 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 6 Jul 2011 11:00:58 -0500 Subject: Initial signoff template changes Signed-off-by: Dan McGee --- templates/packages/signoffs.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index 93be4979..ab00b645 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -3,11 +3,12 @@ {% block navbarclass %}anb-packages{% endblock %} {% block content %} -{% if packages %}

    Package Signoffs

    +

    {{ packages|length }} package{{ packages|pluralize }} found.

    +
    @@ -43,7 +44,7 @@

    Package Signoffs

    - {% endfor %} + {% endfor %}
    @@ -57,5 +58,4 @@

    Package Signoffs

    headers: { 6: { sorter: false } } }); }); -{% endif %} {% endblock %} -- cgit v1.2.3-54-g00ecf From 0f9a1da2cb9dd2a20a5e12bb346ec460b4335f9f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 6 Jul 2011 11:08:22 -0500 Subject: Move package signoff URL to more logical location Signed-off-by: Dan McGee --- packages/models.py | 3 +-- packages/urls.py | 3 +-- packages/views.py | 6 ++---- templates/packages/signoffs.html | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/models.py b/packages/models.py index 0983c642..faf5f398 100644 --- a/packages/models.py +++ b/packages/models.py @@ -25,8 +25,7 @@ class PackageRelation(models.Model): def get_associated_packages(self): # TODO: delayed import to avoid circular reference from main.models import Package - return Package.objects.filter(pkgbase=self.pkgbase).select_related( - 'arch', 'repo') + return Package.objects.normal().filter(pkgbase=self.pkgbase) def repositories(self): packages = self.get_associated_packages() diff --git a/packages/urls.py b/packages/urls.py index d408e6cf..d7d01170 100644 --- a/packages/urls.py +++ b/packages/urls.py @@ -9,14 +9,13 @@ (r'^flag/done/$', 'flag_confirmed', {}, 'package-flag-confirmed'), (r'^unflag/$', 'unflag'), (r'^unflag/all/$', 'unflag_all'), + (r'^signoff/$', 'signoff_package'), (r'^download/$', 'download'), ) urlpatterns = patterns('packages.views', (r'^flaghelp/$', 'flaghelp'), (r'^signoffs/$', 'signoffs', {}, 'package-signoffs'), - (r'^signoff_package/(?P[A-z0-9]+)/(?P[A-z0-9\-+.]+)/$', - 'signoff_package'), (r'^update/$', 'update'), (r'^$', 'search', {}, 'packages-search'), diff --git a/packages/views.py b/packages/views.py index 01d01e20..d12583f0 100644 --- a/packages/views.py +++ b/packages/views.py @@ -372,11 +372,9 @@ def signoffs(request): @permission_required('main.change_package') @never_cache -def signoff_package(request, arch, pkgname): +def signoff_package(request, name, repo, arch): pkg = get_object_or_404(Package, - arch__name=arch, - pkgname=pkgname, - repo__testing=True) + pkgname=name, repo__name__iexact=repo, arch__name=arch) signoff, created = Signoff.objects.get_or_create( pkg=pkg, diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index ab00b645..b1153d7c 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -34,7 +34,7 @@

    Package Signoffs

    {{ pkg.approved_for_signoff|yesno:"Yes,No" }}
      -
    • Signoff
    • {% for signoff in pkg.signoffs %} -- cgit v1.2.3-54-g00ecf From ba975112cbc36f7515293543baaacfbcb46a96c2 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 6 Jul 2011 11:20:42 -0500 Subject: Add new packages signoff model This one is centered around pkgbase, much as our PackageRelation object is. However, it also tracks all of the versioning fields we have in order to making joining against the current package testing list possible. Finally, additional metadata including a created date, an (optional) revoke date, and a comments field are added. Signed-off-by: Dan McGee --- packages/migrations/0008_add_signoff_model.py | 166 ++++++++++++++++++++++++++ packages/models.py | 39 +++++- 2 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 packages/migrations/0008_add_signoff_model.py diff --git a/packages/migrations/0008_add_signoff_model.py b/packages/migrations/0008_add_signoff_model.py new file mode 100644 index 00000000..5feed909 --- /dev/null +++ b/packages/migrations/0008_add_signoff_model.py @@ -0,0 +1,166 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'Signoff' + db.create_table('packages_signoff', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('pkgbase', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('pkgver', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('pkgrel', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('epoch', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)), + ('arch', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Arch'])), + ('repo', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Repo'])), + ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='package_signoffs', to=orm['auth.User'])), + ('created', self.gf('django.db.models.fields.DateTimeField')()), + ('revoked', self.gf('django.db.models.fields.DateTimeField')(null=True)), + ('comments', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + )) + db.send_create_signal('packages', ['Signoff']) + + + def backwards(self, orm): + # Deleting model 'Signoff' + db.delete_table('packages_signoff') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "['name']", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "['name']", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'packages.conflict': { + 'Meta': {'ordering': "['name']", 'object_name': 'Conflict'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conflicts'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.license': { + 'Meta': {'ordering': "['name']", 'object_name': 'License'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'licenses'", 'to': "orm['main.Package']"}) + }, + 'packages.packagegroup': { + 'Meta': {'object_name': 'PackageGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Package']"}) + }, + 'packages.packagerelation': { + 'Meta': {'unique_together': "(('pkgbase', 'user', 'type'),)", 'object_name': 'PackageRelation'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_relations'", 'to': "orm['auth.User']"}) + }, + 'packages.provision': { + 'Meta': {'ordering': "['name']", 'object_name': 'Provision'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provides'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.replacement': { + 'Meta': {'ordering': "['name']", 'object_name': 'Replacement'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'replaces'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.signoff': { + 'Meta': {'object_name': 'Signoff'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'revoked': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_signoffs'", 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index faf5f398..55725e8e 100644 --- a/packages/models.py +++ b/packages/models.py @@ -32,12 +32,47 @@ def repositories(self): return sorted(set([p.repo for p in packages])) def __unicode__(self): - return "%s: %s (%s)" % ( + return u'%s: %s (%s)' % ( self.pkgbase, self.user, self.get_type_display()) class Meta: unique_together = (('pkgbase', 'user', 'type'),) +class Signoff(models.Model): + ''' + A signoff for a package (by pkgbase) at a given point in time. These are + not keyed directly to a Package object so they don't ever get deleted when + Packages come and go from testing repositories. + ''' + pkgbase = models.CharField(max_length=255, db_index=True) + pkgver = models.CharField(max_length=255) + pkgrel = models.CharField(max_length=255) + epoch = models.PositiveIntegerField(default=0) + arch = models.ForeignKey('main.Arch') + repo = models.ForeignKey('main.Repo') + user = models.ForeignKey(User, related_name="package_signoffs") + created = models.DateTimeField(editable=False) + revoked = models.DateTimeField(null=True) + comments = models.TextField(null=True, blank=True) + + @property + def packages(self): + # TODO: delayed import to avoid circular reference + from main.models import Package + return Package.objects.normal().filter(pkgbase=self.pkgbase, + pkgver=self.pkgver, pkgrel=self.pkgrel, epoch=pkg.epoch, + arch=self.arch, repo=self.repo) + + @property + def full_version(self): + if self.epoch > 0: + return u'%d:%s-%s' % (self.epoch, self.pkgver, self.pkgrel) + return u'%s-%s' % (self.pkgver, self.pkgrel) + + def __unicode__(self): + return u'%s-%s: %s' % ( + self.pkgbase, self.full_version, self.user) + class PackageGroup(models.Model): ''' Represents a group a package is in. There is no actual group entity, @@ -115,5 +150,7 @@ def remove_inactive_maintainers(sender, instance, created, **kwargs): dispatch_uid="packages.models") pre_save.connect(set_created_field, sender=PackageRelation, dispatch_uid="packages.models") +pre_save.connect(set_created_field, sender=Signoff, + dispatch_uid="packages.models") # vim: set ts=4 sw=4 et: -- cgit v1.2.3-54-g00ecf From 4876a1258042a85d9c0a1d57a1a185f48850c929 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 6 Jul 2011 11:37:35 -0500 Subject: Add get_current_signoffs utility method This is another SQL-based utility method that dramatically cuts back on how many queries we run and gets around the shortcoming of arbitrary joins in Django. It will be used by the new signoff page logic. Signed-off-by: Dan McGee --- packages/utils.py | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/utils.py b/packages/utils.py index 37f23244..c8c1f8a6 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -1,11 +1,12 @@ +from collections import defaultdict +from operator import itemgetter + from django.db import connection from django.db.models import Count, Max -from operator import itemgetter - from main.models import Package from main.utils import cache_function -from .models import PackageGroup, PackageRelation +from .models import PackageGroup, PackageRelation, Signoff @cache_function(300) def get_group_info(include_arches=None): @@ -147,4 +148,28 @@ def get_wrong_permissions(): id__in=to_fetch) return relations +def get_current_signoffs(): + '''Returns a mapping of pkgbase -> signoff objects.''' + sql = """ +SELECT DISTINCT s.id + FROM packages_signoff s + JOIN packages p ON ( + s.pkgbase = p.pkgbase + AND s.pkgver = p.pkgver + AND s.pkgrel = p.pkgrel + AND s.epoch = p.epoch + AND s.arch_id = p.arch_id + AND s.repo_id = p.repo_id + ) + JOIN repos r ON p.repo_id = r.id + WHERE r.testing = %s +""" + cursor = connection.cursor() + cursor.execute(sql, [True]) + results = cursor.fetchall() + # fetch all of the returned signoffs by ID + to_fetch = [row[0] for row in results] + signoffs = Signoff.objects.select_related('user').in_bulk(to_fetch) + return signoffs.values() + # vim: set ts=4 sw=4 et: -- cgit v1.2.3-54-g00ecf From f95abca269aec1409ec1e57de4c6cb5ba1da6369 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 6 Jul 2011 12:05:19 -0500 Subject: Refactor code to use new signoff model This moves signoff creation and display to the new packages.Signoff model. Signed-off-by: Dan McGee --- main/models.py | 14 ++--- packages/models.py | 2 + packages/views.py | 120 ++++++++++++++++++++++++++++++--------- templates/packages/signoffs.html | 25 ++++---- 4 files changed, 115 insertions(+), 46 deletions(-) diff --git a/main/models.py b/main/models.py index 2f549081..473144da 100644 --- a/main/models.py +++ b/main/models.py @@ -6,6 +6,7 @@ from main.utils import cache_function, make_choice from packages.models import PackageRelation +from packages.models import Signoff as PackageSignoff from datetime import datetime from itertools import groupby @@ -206,16 +207,13 @@ def maintainers(self): @property def signoffs(self): - if 'signoffs_cache' in dir(self): - return self.signoffs_cache - self.signoffs_cache = list(Signoff.objects.filter( - pkg=self, - pkgver=self.pkgver, - pkgrel=self.pkgrel)) - return self.signoffs_cache + return PackageSignoff.objects.select_related('user').filter( + pkgbase=self.pkgbase, pkgver=self.pkgver, pkgrel=self.pkgrel, + epoch=self.epoch, arch=self.arch, repo=self.repo) def approved_for_signoff(self): - return len(self.signoffs) >= 2 + count = self.signoffs.filter(revoked__isnull=True).count() + return count >= PackageSignoff.REQUIRED @cache_function(300) def applicable_arches(self): diff --git a/packages/models.py b/packages/models.py index 55725e8e..7ae2d57a 100644 --- a/packages/models.py +++ b/packages/models.py @@ -55,6 +55,8 @@ class Signoff(models.Model): revoked = models.DateTimeField(null=True) comments = models.TextField(null=True, blank=True) + REQUIRED = 2 + @property def packages(self): # TODO: delayed import to avoid circular reference diff --git a/packages/views.py b/packages/views.py index d12583f0..3d9032e6 100644 --- a/packages/views.py +++ b/packages/views.py @@ -8,7 +8,7 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db.models import Q from django.http import HttpResponse, Http404 -from django.shortcuts import get_object_or_404, redirect +from django.shortcuts import get_object_or_404, get_list_or_404, redirect from django.template import loader, Context from django.utils import simplejson from django.views.decorators.cache import never_cache @@ -18,15 +18,16 @@ from django.views.generic.simple import direct_to_template from datetime import datetime +from operator import attrgetter import string from urllib import urlencode -from main.models import Package, PackageFile -from main.models import Arch, Repo, Signoff -from main.utils import make_choice +from main.models import Package, PackageFile, Arch, Repo +from main.utils import make_choice, groupby_preserve_order, PackageStandin from mirrors.models import MirrorUrl -from .models import PackageRelation, PackageGroup -from .utils import get_group_info, get_differences_info, get_wrong_permissions +from .models import PackageRelation, PackageGroup, Signoff +from .utils import (get_group_info, get_differences_info, + get_wrong_permissions, get_current_signoffs) class PackageJSONEncoder(DjangoJSONEncoder): pkg_attributes = [ 'pkgname', 'pkgbase', 'repo', 'arch', 'pkgver', @@ -349,38 +350,101 @@ def unflag_all(request, name, repo, arch): pkgs.update(flag_date=None) return redirect(pkg) +class PackageSignoffGroup(object): + '''Encompasses all packages in testing with the same pkgbase.''' + def __init__(self, packages, target_repo=None, signoffs=None): + if len(packages) == 0: + raise Exception + self.packages = packages + self.target_repo = target_repo + self.signoffs = signoffs + + first = packages[0] + self.pkgbase = first.pkgbase + self.arch = first.arch + self.repo = first.repo + self.version = '' + + version = first.full_version + if all(version == pkg.full_version for pkg in packages): + self.version = version + + @property + def package(self): + '''Try and return a relevant single package object representing this + group. Start by seeing if there is only one package, then look for the + matching package by name, finally falling back to a standin package + object.''' + if len(self.packages) == 1: + return self.packages[0] + + same_pkgs = [p for p in self.packages if p.pkgname == p.pkgbase] + if same_pkgs: + return same_pkgs[0] + + return PackageStandin(self.packages[0]) + + def find_signoffs(self, all_signoffs): + '''Look through a list of Signoff objects for ones matching this + particular group and store them on the object.''' + if self.signoffs is None: + self.signoffs = [] + for s in all_signoffs: + if s.pkgbase != self.pkgbase: + continue + if self.version and not s.full_version == self.version: + continue + if s.arch_id == self.arch.id and s.repo_id == self.repo.id: + self.signoffs.append(s) + + def approved(self): + if self.signoffs: + good_signoffs = [s for s in self.signoffs if not s.revoked] + return len(good_signoffs) >= Signoff.REQUIRED + return False + @permission_required('main.change_package') @never_cache def signoffs(request): - packages = Package.objects.select_related('arch', 'repo', 'signoffs').filter(repo__testing=True).order_by("pkgname") - package_list = [] - - q_pkgname = Package.objects.filter(repo__testing=True).values('pkgname').distinct().query - package_repos = Package.objects.values('pkgname', 'repo__name').exclude(repo__testing=True).filter(pkgname__in=q_pkgname) - pkgtorepo = dict() - for pr in package_repos: - pkgtorepo[pr['pkgname']] = pr['repo__name'] - - for package in packages: - if package.pkgname in pkgtorepo: - repo = pkgtorepo[package.pkgname] - else: - repo = "Unknown" - package_list.append((package, repo)) + test_pkgs = Package.objects.normal().filter(repo__testing=True) + packages = test_pkgs.order_by('pkgname') + + # Collect all pkgbase values in testing repos + q_pkgbase = test_pkgs.values('pkgbase') + package_repos = Package.objects.order_by().values_list( + 'pkgbase', 'repo__name').filter( + repo__testing=False, repo__staging=False, + pkgbase__in=q_pkgbase).distinct() + pkgtorepo = dict(package_repos) + + # Collect all existing signoffs for these packages + signoffs = get_current_signoffs() + + same_pkgbase_key = lambda x: (x.repo.name, x.arch.name, x.pkgbase) + grouped = groupby_preserve_order(packages, same_pkgbase_key) + signoff_groups = [] + for group in grouped: + signoff_group = PackageSignoffGroup(group) + signoff_group.target_repo = pkgtorepo.get(signoff_group.pkgbase, + "Unknown") + signoff_group.find_signoffs(signoffs) + signoff_groups.append(signoff_group) + + signoff_groups.sort(key=attrgetter('pkgbase')) + return direct_to_template(request, 'packages/signoffs.html', - {'packages': package_list}) + {'signoff_groups': signoff_groups}) @permission_required('main.change_package') @never_cache def signoff_package(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) + packages = get_list_or_404(Package, pkgbase=name, + arch__name=arch, repo__name__iexact=repo, repo__testing=True) + pkg = packages[0] signoff, created = Signoff.objects.get_or_create( - pkg=pkg, - pkgver=pkg.pkgver, - pkgrel=pkg.pkgrel, - packager=request.user) + pkgbase=pkg.pkgbase, pkgver=pkg.pkgver, pkgrel=pkg.pkgrel, + epoch=pkg.epoch, arch=pkg.arch, repo=pkg.repo, user=request.user) if request.is_ajax(): data = { diff --git a/templates/packages/signoffs.html b/templates/packages/signoffs.html index b1153d7c..6014396c 100644 --- a/templates/packages/signoffs.html +++ b/templates/packages/signoffs.html @@ -7,13 +7,15 @@

      Package Signoffs

      -

      {{ packages|length }} package{{ packages|pluralize }} found.

      +

      {{ signoff_groups|length }} signoff group{{ signoff_groups|pluralize }} found. + A "signoff group" consists of packages grouped by pkgbase, architecture, and repository.

      - + + @@ -22,28 +24,31 @@

      Package Signoffs

      - {% for pkg,target in packages %} + {% for group in signoff_groups %} + {% with group.package as pkg %} + - - + + + {% endwith %} {% endfor %}
      ArchPackagePackage Base# of Packages Version Last Updated Target Repo
      {{ pkg.arch.name }} {{ pkg.pkgname }}{{ group.packages|length }} {{ pkg.full_version }} {{ pkg.last_update|date }}{{ target }} - {{ pkg.approved_for_signoff|yesno:"Yes,No" }}{{ group.target_repo }} + {{ group.approved|yesno:"Yes,No" }}
      • Signoff + title="Signoff {{ pkg.pkgname }} for {{ pkg.arch }}">Signoff
      • - {% for signoff in pkg.signoffs %} -
      • - {{signoff.packager}}
      • + {% for signoff in group.signoffs %} +
      • + {{ signoff.user }}{% if signoff.revoked %} (revoked){% endif %}
      • {% endfor %}
      -- cgit v1.2.3-54-g00ecf From d8e089fd30ac2dacac54c444cc522ee44ee332ed Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 6 Jul 2011 12:08:42 -0500 Subject: Delete old signoff model Signed-off-by: Dan McGee --- main/migrations/0052_auto__del_signoff.py | 166 ++++++++++++++++++++++++++++++ main/models.py | 6 -- 2 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 main/migrations/0052_auto__del_signoff.py diff --git a/main/migrations/0052_auto__del_signoff.py b/main/migrations/0052_auto__del_signoff.py new file mode 100644 index 00000000..8da6becc --- /dev/null +++ b/main/migrations/0052_auto__del_signoff.py @@ -0,0 +1,166 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + depends_on = ( + ('packages', '0008_add_signoff_model'), + ) + + def forwards(self, orm): + # Deleting model 'Signoff' + db.delete_table('main_signoff') + + + def backwards(self, orm): + # Adding model 'Signoff' + db.create_table('main_signoff', ( + ('pkgrel', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('packager', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('pkg', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Package'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('pkgver', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('main', ['Signoff']) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "['name']", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.donor': { + 'Meta': {'ordering': "['name']", 'object_name': 'Donor', 'db_table': "'donors'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'visible': ('django.db.models.fields.BooleanField', [], {'default': 'True'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.packagedepend': { + 'Meta': {'object_name': 'PackageDepend', 'db_table': "'package_depends'"}, + 'depname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'depvcmp': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.packagefile': { + 'Meta': {'object_name': 'PackageFile', 'db_table': "'package_files'"}, + 'directory': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_directory': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.repo': { + 'Meta': {'ordering': "['name']", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'main.todolist': { + 'Meta': {'object_name': 'Todolist', 'db_table': "'todolists'"}, + 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'main.todolistpkg': { + 'Meta': {'unique_together': "(('list', 'pkg'),)", 'object_name': 'TodolistPkg', 'db_table': "'todolist_pkgs'"}, + 'complete': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Todolist']"}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}) + }, + 'main.userprofile': { + 'Meta': {'object_name': 'UserProfile', 'db_table': "'user_profiles'"}, + 'alias': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'allowed_repos': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Repo']", 'symmetrical': 'False', 'blank': 'True'}), + 'favorite_distros': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'interests': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'languages': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'other_contact': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'pgp_key': ('main.models.PGPKeyField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'picture': ('django.db.models.fields.files.FileField', [], {'default': "'devs/silhouette.png'", 'max_length': '100'}), + 'public_email': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'roles': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'time_zone': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '100'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'userprofile'", 'unique': 'True', 'to': "orm['auth.User']"}), + 'website': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'yob': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['main'] diff --git a/main/models.py b/main/models.py index 473144da..6e134810 100644 --- a/main/models.py +++ b/main/models.py @@ -367,12 +367,6 @@ def elsewhere(self): pkgname=self.pkgname).exclude(id=self.id).order_by( 'arch__name', 'repo__name') -class Signoff(models.Model): - pkg = models.ForeignKey(Package) - pkgver = models.CharField(max_length=255) - pkgrel = models.CharField(max_length=255) - packager = models.ForeignKey(User) - class PackageFile(models.Model): pkg = models.ForeignKey(Package) is_directory = models.BooleanField(default=False) -- cgit v1.2.3-54-g00ecf From 86c48cc4314b593ee6c9136490a72433a5dca1e2 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 6 Jul 2011 13:17:29 -0500 Subject: Add a few more valid package sort fields Just for fun and for people that know what they are doing. Signed-off-by: Dan McGee --- packages/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/views.py b/packages/views.py index 3d9032e6..1f2a38d3 100644 --- a/packages/views.py +++ b/packages/views.py @@ -279,7 +279,9 @@ def search(request, page=None): 'search_form': form, 'current_query': current_query } - allowed_sort = ["arch", "repo", "pkgname", "last_update", "flag_date"] + allowed_sort = ["arch", "repo", "pkgname", "pkgname", + "compressed_size", "installed_size", + "build_date", "last_update", "flag_date"] allowed_sort += ["-" + s for s in allowed_sort] sort = request.GET.get('sort', None) # TODO: sorting by multiple fields makes using a DB index much harder -- cgit v1.2.3-54-g00ecf From 59badd066238c946cbe64d7002a260b9de606938 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 15 Jul 2011 10:10:11 -0500 Subject: Add pacakge count to group/split package details page Signed-off-by: Dan McGee --- templates/packages/packages_list.html | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/packages/packages_list.html b/templates/packages/packages_list.html index c897aac5..ccc091d8 100644 --- a/templates/packages/packages_list.html +++ b/templates/packages/packages_list.html @@ -5,6 +5,7 @@ {% block content %}

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

      +

      {{ packages|length }} package{{ packages|pluralize }} found.

      -- cgit v1.2.3-54-g00ecf From ae12aa58fd6c6965b3eadcf3843ea68602069c60 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 25 Jul 2011 12:27:46 -0500 Subject: Small template updates * Show full version in developer dashboard out of date pane * Link packages on flag confirmation screen Signed-off-by: Dan McGee --- templates/devel/index.html | 2 +- templates/packages/flag_confirmed.html | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/templates/devel/index.html b/templates/devel/index.html index 1689319b..ad101145 100644 --- a/templates/devel/index.html +++ b/templates/devel/index.html @@ -27,7 +27,7 @@

      My Flagged Packages

      - + diff --git a/templates/packages/flag_confirmed.html b/templates/packages/flag_confirmed.html index 02c24f72..9ef316cb 100644 --- a/templates/packages/flag_confirmed.html +++ b/templates/packages/flag_confirmed.html @@ -4,12 +4,13 @@ {% block content %}
      -

      Package Flagged - {{ package.pkgname }}

      +

      Package Flagged - {{ package.pkgname }}

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

        {% for pkg in packages %} -
      • {{ pkg.pkgname }} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})
      • +
      • {{ pkg.pkgname }} {{ pkg.full_version }} [{{ pkg.repo.name|lower }}] ({{ pkg.arch.name }})
      • {% endfor %}
      -- cgit v1.2.3-54-g00ecf From ad65f4c98e6ea3e8078b4a482fa88fb4595ccedd Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 29 Jul 2011 12:23:03 -0500 Subject: Fix package sort fields Don't list pkgname twice, include pkgbase instead. Signed-off-by: Dan McGee --- packages/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/views.py b/packages/views.py index 1f2a38d3..8c9c1b18 100644 --- a/packages/views.py +++ b/packages/views.py @@ -279,7 +279,7 @@ def search(request, page=None): 'search_form': form, 'current_query': current_query } - allowed_sort = ["arch", "repo", "pkgname", "pkgname", + allowed_sort = ["arch", "repo", "pkgname", "pkgbase", "compressed_size", "installed_size", "build_date", "last_update", "flag_date"] allowed_sort += ["-" + s for s in allowed_sort] -- cgit v1.2.3-54-g00ecf From 82cb7c3586154da7196b513e349ea9c91efbc9ac Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 29 Jul 2011 12:26:27 -0500 Subject: Add index to package groups name field Signed-off-by: Dan McGee --- .../migrations/0009_add_packagegroup_name_index.py | 149 +++++++++++++++++++++ packages/models.py | 2 +- 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 packages/migrations/0009_add_packagegroup_name_index.py diff --git a/packages/migrations/0009_add_packagegroup_name_index.py b/packages/migrations/0009_add_packagegroup_name_index.py new file mode 100644 index 00000000..f81e77fc --- /dev/null +++ b/packages/migrations/0009_add_packagegroup_name_index.py @@ -0,0 +1,149 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.create_index('packages_packagegroup', ['name']) + + def backwards(self, orm): + db.delete_index('packages_packagegroup', ['name']) + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'main.arch': { + 'Meta': {'ordering': "['name']", 'object_name': 'Arch', 'db_table': "'arches'"}, + 'agnostic': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'main.package': { + 'Meta': {'ordering': "('pkgname',)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'flag_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'installed_size': ('main.models.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}) + }, + 'main.repo': { + 'Meta': {'ordering': "['name']", 'object_name': 'Repo', 'db_table': "'repos'"}, + 'bugs_category': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'bugs_project': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'staging': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'svn_root': ('django.db.models.fields.CharField', [], {'max_length': '64'}), + 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'packages.conflict': { + 'Meta': {'ordering': "['name']", 'object_name': 'Conflict'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'conflicts'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.license': { + 'Meta': {'ordering': "['name']", 'object_name': 'License'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'licenses'", 'to': "orm['main.Package']"}) + }, + 'packages.packagegroup': { + 'Meta': {'object_name': 'PackageGroup'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Package']"}) + }, + 'packages.packagerelation': { + 'Meta': {'unique_together': "(('pkgbase', 'user', 'type'),)", 'object_name': 'PackageRelation'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'type': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_relations'", 'to': "orm['auth.User']"}) + }, + 'packages.provision': { + 'Meta': {'ordering': "['name']", 'object_name': 'Provision'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'provides'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.replacement': { + 'Meta': {'ordering': "['name']", 'object_name': 'Replacement'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'replaces'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.signoff': { + 'Meta': {'object_name': 'Signoff'}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Arch']"}), + 'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'revoked': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'package_signoffs'", 'to': "orm['auth.User']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 7ae2d57a..d2fe1878 100644 --- a/packages/models.py +++ b/packages/models.py @@ -81,7 +81,7 @@ class PackageGroup(models.Model): only names that link to given packages. ''' pkg = models.ForeignKey('main.Package', related_name='groups') - name = models.CharField(max_length=255) + name = models.CharField(max_length=255, db_index=True) def __unicode__(self): return "%s: %s" % (self.name, self.pkg) -- cgit v1.2.3-54-g00ecf From 2a73675f0ed894d57ed13c5799e988f8d10ccfe2 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 29 Jul 2011 23:08:33 -0500 Subject: Select arch/repo for split package related fields Signed-off-by: Dan McGee --- main/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/models.py b/main/models.py index 6e134810..70372823 100644 --- a/main/models.py +++ b/main/models.py @@ -307,12 +307,12 @@ def base_package(self): """ try: # start by looking for something in this repo - return Package.objects.get(arch=self.arch, + return Package.objects.normal().get(arch=self.arch, repo=self.repo, pkgname=self.pkgbase) except Package.DoesNotExist: # this package might be split across repos? just find one # that matches the correct [testing] repo flag - pkglist = Package.objects.filter(arch=self.arch, + pkglist = Package.objects.normal().filter(arch=self.arch, repo__testing=self.repo.testing, repo__staging=self.repo.staging, pkgname=self.pkgbase) if len(pkglist) > 0: @@ -327,7 +327,7 @@ def split_packages(self): repo.testing and repo.staging flags. For any non-split packages, the return value will be an empty list. """ - return Package.objects.filter(arch__in=self.applicable_arches(), + return Package.objects.normal().filter(arch__in=self.applicable_arches(), repo__testing=self.repo.testing, repo__staging=self.repo.staging, pkgbase=self.pkgbase).exclude(id=self.id) -- cgit v1.2.3-54-g00ecf From 0f6c80e9a36bc5770e95543b4374c5ace4383cf5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 29 Jul 2011 23:34:24 -0500 Subject: mirrors: do protocol formatting in template Signed-off-by: Dan McGee --- mirrors/models.py | 2 +- templates/mirrors/mirrors.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mirrors/models.py b/mirrors/models.py index 4f70e5a9..a8217844 100644 --- a/mirrors/models.py +++ b/mirrors/models.py @@ -42,7 +42,7 @@ def __unicode__(self): def supported_protocols(self): protocols = MirrorProtocol.objects.filter( urls__mirror=self).order_by('protocol').distinct() - return ", ".join([p.protocol for p in protocols]) + return sorted(protocols) def downstream(self): return Mirror.objects.filter(upstream=self).order_by('name') diff --git a/templates/mirrors/mirrors.html b/templates/mirrors/mirrors.html index 67a678d9..ee76acbe 100644 --- a/templates/mirrors/mirrors.html +++ b/templates/mirrors/mirrors.html @@ -28,7 +28,7 @@

      Mirror Overview

      - + {% if user.is_authenticated %} -- cgit v1.2.3-54-g00ecf
      {{ pkg.pkgname }} {{ pkg.repo.name }}{{ pkg.pkgver }}{{ pkg.full_version }} {{ pkg.arch.name }} {{ pkg.flag_date|date }} {{ pkg.last_update|date }}{{mirror.get_tier_display}} {{mirror.country}} {{mirror.isos|yesno}}{{mirror.supported_protocols}}{{mirror.supported_protocols|join:", "}}{{mirror.public|yesno}} {{mirror.active|yesno}}