From 4e1e28729f97eb694cdcae2f3fe51b5178963069 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 27 Apr 2012 09:07:26 -0500 Subject: Use GenericIPAddressField in flag request ip_address New (and slightly odd with regards to verbose_name) in Django 1.4. This simply ensures a deployment in an IPv6 environment actually works as expected. If you were using PostgreSQL as a database backend, you won't be affected by this as the 'inet' type was already used, but at least now you can edit the values in the admin without getting an error. Signed-off-by: Dan McGee --- .../0014_auto__chg_field_flagrequest_ip_address.py | 181 +++++++++++++++++++++ packages/models.py | 4 +- 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 packages/migrations/0014_auto__chg_field_flagrequest_ip_address.py (limited to 'packages') diff --git a/packages/migrations/0014_auto__chg_field_flagrequest_ip_address.py b/packages/migrations/0014_auto__chg_field_flagrequest_ip_address.py new file mode 100644 index 00000000..351b9985 --- /dev/null +++ b/packages/migrations/0014_auto__chg_field_flagrequest_ip_address.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.alter_column('packages_flagrequest', 'ip_address', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39)) + + def backwards(self, orm): + db.alter_column('packages_flagrequest', 'ip_address', self.gf('django.db.models.fields.IPAddressField')(max_length=15)) + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 820e61ba..7a7a81cd 100644 --- a/packages/models.py +++ b/packages/models.py @@ -173,7 +173,9 @@ class FlagRequest(models.Model): user = models.ForeignKey(User, blank=True, null=True) user_email = models.EmailField('email address') created = models.DateTimeField(editable=False) - ip_address = models.IPAddressField('IP address') + # Great work, Django... https://code.djangoproject.com/ticket/18212 + ip_address = models.GenericIPAddressField(verbose_name='IP address', + unpack_ipv4=True) pkgbase = models.CharField(max_length=255, db_index=True) version = models.CharField(max_length=255, default='') repo = models.ForeignKey(Repo) -- cgit v1.2.3-2-g168b From b59e79f3878d59b83c6867eb5c6196f8f003dcd9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 1 May 2012 17:13:33 -0500 Subject: Opensearch enhancements * Add a 64x64 icon as indicated in the Opensearch specification. * Add suggestions capability and a new view providing suggestions based on package name starting with the typed value. Signed-off-by: Dan McGee --- packages/views/__init__.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 6a9c5275..21d17470 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -7,7 +7,7 @@ from django.contrib.auth.models import User from django.http import HttpResponse, Http404 from django.shortcuts import get_object_or_404, redirect from django.utils import simplejson -from django.views.decorators.http import require_POST +from django.views.decorators.http import require_GET, require_POST from django.views.decorators.vary import vary_on_headers from django.views.generic.simple import direct_to_template @@ -24,6 +24,7 @@ from .search import search, search_json from .signoff import signoffs, signoff_package, signoff_options, signoffs_json +@require_GET def opensearch(request): if request.is_secure(): domain = "https://%s" % request.META['HTTP_HOST'] @@ -34,6 +35,21 @@ def opensearch(request): {'domain': domain}, mimetype='application/opensearchdescription+xml') + +@require_GET +def opensearch_suggest(request): + search_term = request.GET.get('q', '') + if search_term == '': + return HttpResponse('', mimetype='application/x-suggestions+json') + + names = Package.objects.filter( + pkgname__startswith=search_term).values_list( + 'pkgname', flat=True).order_by('pkgname').distinct()[:10] + results = [search_term, list(names)] + to_json = simplejson.dumps(results, ensure_ascii=False) + return HttpResponse(to_json, mimetype='application/x-suggestions+json') + + @permission_required('main.change_package') @require_POST def update(request): -- cgit v1.2.3-2-g168b From f3e0adcb2fc9a26e2ad9337a47550a37590074d9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 1 May 2012 18:12:50 -0500 Subject: Add some caching to the Opensearch-related views Both some simple cache headers as well as low-level results caching on search terms suggestions. Signed-off-by: Dan McGee --- packages/views/__init__.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 21d17470..0f1dc799 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -4,9 +4,11 @@ from urllib import urlencode from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import User +from django.core.cache import cache from django.http import HttpResponse, Http404 from django.shortcuts import get_object_or_404, redirect from django.utils import simplejson +from django.views.decorators.cache import cache_control from django.views.decorators.http import require_GET, require_POST from django.views.decorators.vary import vary_on_headers from django.views.generic.simple import direct_to_template @@ -25,6 +27,7 @@ from .signoff import signoffs, signoff_package, signoff_options, signoffs_json @require_GET +@cache_control(public=True, max_age=86400) def opensearch(request): if request.is_secure(): domain = "https://%s" % request.META['HTTP_HOST'] @@ -37,16 +40,21 @@ def opensearch(request): @require_GET +@cache_control(public=True, max_age=300) def opensearch_suggest(request): search_term = request.GET.get('q', '') if search_term == '': return HttpResponse('', mimetype='application/x-suggestions+json') - names = Package.objects.filter( - pkgname__startswith=search_term).values_list( - 'pkgname', flat=True).order_by('pkgname').distinct()[:10] - results = [search_term, list(names)] - to_json = simplejson.dumps(results, ensure_ascii=False) + cache_key = 'opensearch:packages:' + search_term + to_json = cache.get(cache_key, None) + if to_json is None: + names = Package.objects.filter( + pkgname__startswith=search_term).values_list( + 'pkgname', flat=True).order_by('pkgname').distinct()[:10] + results = [search_term, list(names)] + to_json = simplejson.dumps(results, ensure_ascii=False) + cache.set('opensearch:packages:%s' % search_term, to_json, 300) return HttpResponse(to_json, mimetype='application/x-suggestions+json') -- cgit v1.2.3-2-g168b From badc535aeb1d310a9b8aa59aade07045e6eae653 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 18 Apr 2012 15:05:43 -0500 Subject: Ensure order_by default value is cleared when using distinct() Otherwise the queryset returns nonsensical results. I find the design of this less than obvious but so be it; we can ensure the results work regardless of a default ordering on the model. Signed-off-by: Dan McGee --- packages/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index a3c13b17..8d00bd68 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -218,7 +218,8 @@ def attach_maintainers(packages): packages = list(packages) pkgbases = set(p.pkgbase for p in packages) rels = PackageRelation.objects.filter(type=PackageRelation.MAINTAINER, - pkgbase__in=pkgbases).values_list('pkgbase', 'user_id').distinct() + pkgbase__in=pkgbases).values_list( + 'pkgbase', 'user_id').order_by().distinct() # get all the user objects we will need user_ids = set(rel[1] for rel in rels) -- cgit v1.2.3-2-g168b From 768bc688aab844cf9fdf9809b9381aaf0042f2fc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 2 May 2012 10:21:30 -0500 Subject: Flagging related cleanups and improvements Touch up the style slightly on the flag help popup to match the main site style more closely. When a logged-in user is flagging a package out of date, we have no need for them to fill in the email field since we already have an email address on file. Signed-off-by: Dan McGee --- packages/views/flag.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/views/flag.py b/packages/views/flag.py index 0d2f9009..7fa2d508 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -6,16 +6,13 @@ from django.db import transaction from django.shortcuts import get_object_or_404, redirect from django.template import loader, Context from django.views.generic.simple import direct_to_template -from django.views.decorators.cache import never_cache +from django.views.decorators.cache import cache_page, never_cache from ..models import FlagRequest from main.models import Package from main.utils import utc_now -def flaghelp(request): - return direct_to_template(request, 'packages/flaghelp.html') - class FlagForm(forms.Form): email = forms.EmailField(label='E-mail Address') message = forms.CharField(label='Message To Developer', @@ -26,6 +23,20 @@ class FlagForm(forms.Form): widget=forms.TextInput(attrs={'style': 'display:none;'}), required=False) + def __init__(self, *args, **kwargs): + # we remove the 'email' field if this form is being shown to a + # logged-in user, e.g., a developer. + auth = kwargs.pop('authenticated', False) + super(FlagForm, self).__init__(*args, **kwargs) + if auth: + del self.fields['email'] + + +@cache_page(3600) +def flaghelp(request): + return direct_to_template(request, 'packages/flaghelp.html') + + @never_cache def flag(request, name, repo, arch): pkg = get_object_or_404(Package, @@ -41,8 +52,10 @@ def flag(request, name, repo, arch): repo__staging=pkg.repo.staging).order_by( 'pkgname', 'repo__name', 'arch__name') + authenticated = request.user.is_authenticated() + if request.POST: - form = FlagForm(request.POST) + form = FlagForm(request.POST, authenticated=authenticated) if form.is_valid() and form.cleaned_data['website'] == '': # save the package list for later use flagged_pkgs = list(pkgs) @@ -54,9 +67,12 @@ def flag(request, name, repo, arch): else: version = '' - email = form.cleaned_data['email'] message = form.cleaned_data['message'] ip_addr = request.META.get('REMOTE_ADDR') + if authenticated: + email = request.user.email + else: + email = form.cleaned_data['email'] @transaction.commit_on_success def perform_updates(): @@ -68,7 +84,7 @@ def flag(request, name, repo, arch): ip_address=ip_addr, pkgbase=pkg.pkgbase, version=version, repo=pkg.repo, num_packages=len(flagged_pkgs)) - if request.user.is_authenticated(): + if authenticated: flag_request.user = request.user flag_request.save() @@ -106,9 +122,7 @@ def flag(request, name, repo, arch): arch=arch) else: initial = {} - if request.user.is_authenticated(): - initial['email'] = request.user.email - form = FlagForm(initial=initial) + form = FlagForm(authenticated=authenticated) context = { 'package': pkg, -- cgit v1.2.3-2-g168b From 86f8efaeb1f67138c194d0c373f9d91e2999c5dd Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 4 May 2012 10:57:59 -0500 Subject: Fix search suggestions for invalid cache keys Unfortunately, "invalid" in this case includes spaces, which is a bit crazy. MD5 the provided search term before using it as a cache key to be safe. Signed-off-by: Dan McGee --- packages/views/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 0f1dc799..559368b9 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -1,3 +1,4 @@ +import hashlib from string import Template from urllib import urlencode @@ -46,7 +47,7 @@ def opensearch_suggest(request): if search_term == '': return HttpResponse('', mimetype='application/x-suggestions+json') - cache_key = 'opensearch:packages:' + search_term + cache_key = 'opensearch:packages:' + hashlib.md5(search_term).hexdigest() to_json = cache.get(cache_key, None) if to_json is None: names = Package.objects.filter( @@ -54,7 +55,7 @@ def opensearch_suggest(request): 'pkgname', flat=True).order_by('pkgname').distinct()[:10] results = [search_term, list(names)] to_json = simplejson.dumps(results, ensure_ascii=False) - cache.set('opensearch:packages:%s' % search_term, to_json, 300) + cache.set(cache_key, to_json, 300) return HttpResponse(to_json, mimetype='application/x-suggestions+json') -- cgit v1.2.3-2-g168b From d4c7a48623f90cdc508d1824bf47ce3e398dd820 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 5 May 2012 10:19:49 -0500 Subject: Fix suggestion caching again for non-ASCII characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is why you should test this stuff with random input before rolling it out. Whoops. URL that caught this problem: /opensearch/packages/suggest?q=%D7%A0%D7%9F%D7%92%D7%9F aka /opensearch/packages/suggest?q=נןגן Signed-off-by: Dan McGee --- packages/views/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 559368b9..60c3a46b 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -47,7 +47,8 @@ def opensearch_suggest(request): if search_term == '': return HttpResponse('', mimetype='application/x-suggestions+json') - cache_key = 'opensearch:packages:' + hashlib.md5(search_term).hexdigest() + cache_key = 'opensearch:packages:' + \ + hashlib.md5(search_term.encode('utf-8')).hexdigest() to_json = cache.get(cache_key, None) if to_json is None: names = Package.objects.filter( -- cgit v1.2.3-2-g168b From a5f5557493446bede78adb0584c88208234f874e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 12 May 2012 09:32:30 -0500 Subject: Use python json module directly in place of simplejson As of Python 2.6, this is a builtin module that has all the same functions and capabilities of the Django simplejson module. Additionally simplejson is deprecated in the upcoming Django 1.5 release. Signed-off-by: Dan McGee --- packages/views/__init__.py | 10 ++++------ packages/views/search.py | 5 ++--- packages/views/signoff.py | 7 +++---- 3 files changed, 9 insertions(+), 13 deletions(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 60c3a46b..c1a035d0 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -1,4 +1,5 @@ import hashlib +import json from string import Template from urllib import urlencode @@ -8,7 +9,6 @@ from django.contrib.auth.models import User from django.core.cache import cache from django.http import HttpResponse, Http404 from django.shortcuts import get_object_or_404, redirect -from django.utils import simplejson from django.views.decorators.cache import cache_control from django.views.decorators.http import require_GET, require_POST from django.views.decorators.vary import vary_on_headers @@ -55,7 +55,7 @@ def opensearch_suggest(request): pkgname__startswith=search_term).values_list( 'pkgname', flat=True).order_by('pkgname').distinct()[:10] results = [search_term, list(names)] - to_json = simplejson.dumps(results, ensure_ascii=False) + to_json = json.dumps(results, ensure_ascii=False) cache.set(cache_key, to_json, 300) return HttpResponse(to_json, mimetype='application/x-suggestions+json') @@ -197,8 +197,7 @@ def files(request, name, repo, arch): def details_json(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) - to_json = simplejson.dumps(pkg, ensure_ascii=False, - cls=PackageJSONEncoder) + to_json = json.dumps(pkg, ensure_ascii=False, cls=PackageJSONEncoder) return HttpResponse(to_json, mimetype='application/json') def files_json(request, name, repo, arch): @@ -212,8 +211,7 @@ def files_json(request, name, repo, arch): 'arch': pkg.arch.name.lower(), 'files': fileslist, } - to_json = simplejson.dumps(data, ensure_ascii=False, - cls=PackageJSONEncoder) + to_json = json.dumps(data, ensure_ascii=False, cls=PackageJSONEncoder) return HttpResponse(to_json, mimetype='application/json') def download(request, name, repo, arch): diff --git a/packages/views/search.py b/packages/views/search.py index a09de0a7..a89822be 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -1,4 +1,5 @@ from datetime import datetime +import json from django import forms from django.contrib.admin.widgets import AdminDateWidget @@ -6,7 +7,6 @@ from django.contrib.auth.models import User from django.db.models import Q from django.http import HttpResponse from django.views.generic import list_detail -from django.utils import simplejson from main.models import Package, Arch, Repo from main.utils import make_choice @@ -179,8 +179,7 @@ def search_json(request): container['results'] = packages container['valid'] = True - to_json = simplejson.dumps(container, ensure_ascii=False, - cls=PackageJSONEncoder) + to_json = json.dumps(container, ensure_ascii=False, cls=PackageJSONEncoder) return HttpResponse(to_json, mimetype='application/json') # vim: set ts=4 sw=4 et: diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 63341a1d..61d949fc 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -1,3 +1,4 @@ +import json from operator import attrgetter from django import forms @@ -7,7 +8,6 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db import transaction from django.http import HttpResponse, Http404 from django.shortcuts import get_list_or_404, redirect, render -from django.utils import simplejson from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template @@ -67,7 +67,7 @@ def signoff_package(request, name, repo, arch, revoke=False): 'known_bad': spec.known_bad, 'user': str(request.user), } - return HttpResponse(simplejson.dumps(data, ensure_ascii=False), + return HttpResponse(json.dumps(data, ensure_ascii=False), mimetype='application/json') return redirect('package-signoffs') @@ -183,8 +183,7 @@ def signoffs_json(request): 'version': 2, 'signoff_groups': signoff_groups, } - to_json = simplejson.dumps(data, ensure_ascii=False, - cls=SignoffJSONEncoder) + to_json = json.dumps(data, ensure_ascii=False, cls=SignoffJSONEncoder) response = HttpResponse(to_json, mimetype='application/json') return response -- cgit v1.2.3-2-g168b From 17e33f9118e9749b1e3fdfd76686e85dbcecfb00 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 18 May 2012 19:17:30 -0500 Subject: Refactor an abstract base class out of conflicts/provides/replaces Signed-off-by: Dan McGee --- packages/models.py | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 7a7a81cd..f57c9f3c 100644 --- a/packages/models.py +++ b/packages/models.py @@ -218,10 +218,10 @@ class License(models.Model): class Meta: ordering = ['name'] -class Conflict(models.Model): - pkg = models.ForeignKey('main.Package', related_name='conflicts') + +class RelatedToBase(models.Model): + '''A base class for conflicts/provides/replaces/etc.''' name = models.CharField(max_length=255, db_index=True) - comparison = models.CharField(max_length=255, default='') version = models.CharField(max_length=255, default='') def __unicode__(self): @@ -230,36 +230,29 @@ class Conflict(models.Model): return self.name class Meta: + abstract = True ordering = ['name'] -class Provision(models.Model): + +class Conflict(RelatedToBase): + pkg = models.ForeignKey('main.Package', related_name='conflicts') + comparison = models.CharField(max_length=255, default='') + + +class Provision(RelatedToBase): pkg = models.ForeignKey('main.Package', related_name='provides') - name = models.CharField(max_length=255, db_index=True) # comparison must be '=' for provides - comparison = '=' - version = models.CharField(max_length=255, default='') - def __unicode__(self): - if self.version: - return u'%s=%s' % (self.name, self.version) - return self.name + @property + def comparison(self): + if self.version is not None and self.version != '': + return '=' + return None - class Meta: - ordering = ['name'] -class Replacement(models.Model): +class Replacement(RelatedToBase): pkg = models.ForeignKey('main.Package', related_name='replaces') - name = models.CharField(max_length=255, db_index=True) comparison = models.CharField(max_length=255, default='') - version = models.CharField(max_length=255, default='') - - def __unicode__(self): - if self.version: - return u'%s%s%s' % (self.name, self.comparison, self.version) - return self.name - - class Meta: - ordering = ['name'] # hook up some signals -- cgit v1.2.3-2-g168b From 158be107e4ad682de0c9360658dfa5a72c21ee58 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 18 May 2012 19:57:12 -0500 Subject: Add a get_best_satisfier method to RelatedToBase This is basically what we do in PackageDepend already. Signed-off-by: Dan McGee --- packages/models.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index f57c9f3c..a4d2a556 100644 --- a/packages/models.py +++ b/packages/models.py @@ -4,7 +4,7 @@ from django.db import models from django.db.models.signals import pre_save from django.contrib.auth.models import User -from main.models import Arch, Repo +from main.models import Arch, Repo, Package from main.utils import set_created_field class PackageRelation(models.Model): @@ -224,6 +224,40 @@ class RelatedToBase(models.Model): name = models.CharField(max_length=255, db_index=True) version = models.CharField(max_length=255, default='') + def get_best_satisfier(self): + '''Find a satisfier for this related package that best matches the + given criteria. It will not search provisions, but will find packages + named and matching repo characteristics if possible.''' + # NOTE: this is cribbed directly from the PackageDepend method of the + # same name. Really, all of these things could use the same method if + # the PackageDepend class was moved here and field names were changed + # to match the layout we use here. + pkgs = Package.objects.normal().filter(pkgname=self.name) + if not self.pkg.arch.agnostic: + # make sure we match architectures if possible + arches = self.pkg.applicable_arches() + pkgs = pkgs.filter(arch__in=arches) + if len(pkgs) == 0: + # couldn't find a package in the DB + # it should be a virtual depend (or a removed package) + return None + if len(pkgs) == 1: + return pkgs[0] + # more than one package, see if we can't shrink it down + # grab the first though in case we fail + pkg = pkgs[0] + # prevents yet more DB queries, these lists should be short; + # after each grab the best available in case we remove all entries + pkgs = [p for p in pkgs if p.repo.staging == self.pkg.repo.staging] + if len(pkgs) > 0: + pkg = pkgs[0] + + pkgs = [p for p in pkgs if p.repo.testing == self.pkg.repo.testing] + if len(pkgs) > 0: + pkg = pkgs[0] + + return pkg + def __unicode__(self): if self.version: return u'%s%s%s' % (self.name, self.comparison, self.version) -- cgit v1.2.3-2-g168b From cf67e7952396121d3f7190195d812ea3f5fc7dcf Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 18 May 2012 20:26:16 -0500 Subject: Issue redirects from non-agnostic to agnostic URLs if unambiguous For something like "/extra/i686/apache-ant/", we can redirect to "/extra/any/apache-ant/" without ambiguity. Previously this redirected to the split packages listing with a single package, which was neither correct nor really expected. Signed-off-by: Dan McGee --- packages/views/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index c1a035d0..3e574c26 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -136,6 +136,16 @@ def details(request, name='', repo='', arch=''): return direct_to_template(request, 'packages/details.html', {'pkg': pkg, }) except Package.DoesNotExist: + arch_obj = get_object_or_404(Arch, name=arch) + # for arch='any' packages, we can issue a redirect to them if we + # have a single non-ambiguous option by changing the arch to match + # any arch-agnostic package + if not arch_obj.agnostic: + pkgs = Package.objects.select_related( + 'arch', 'repo', 'packager').filter(pkgname=name, + repo__name__iexact=repo, arch__agnostic=True) + if len(pkgs) == 1: + return redirect(pkgs[0], permanent=True) return split_package_details(request, name, repo, arch) else: pkg_data = [ -- cgit v1.2.3-2-g168b From ef561bcd705e1047b1a81364ac143ce52c60defa Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 18 May 2012 20:54:39 -0500 Subject: Add new depends model Signed-off-by: Dan McGee --- packages/migrations/0015_auto__add_depend.py | 199 +++++++++++++++++++++++++++ packages/models.py | 7 + 2 files changed, 206 insertions(+) create mode 100644 packages/migrations/0015_auto__add_depend.py (limited to 'packages') diff --git a/packages/migrations/0015_auto__add_depend.py b/packages/migrations/0015_auto__add_depend.py new file mode 100644 index 00000000..c9685ecb --- /dev/null +++ b/packages/migrations/0015_auto__add_depend.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + def forwards(self, orm): + db.create_table('packages_depend', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, db_index=True)), + ('version', self.gf('django.db.models.fields.CharField')(default='', max_length=255)), + ('pkg', self.gf('django.db.models.fields.related.ForeignKey')(related_name='depends_new', to=orm['main.Package'])), + ('comparison', self.gf('django.db.models.fields.CharField')(default='', max_length=255)), + ('optional', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('description', self.gf('django.db.models.fields.TextField')(null=True, blank=True)), + )) + db.send_create_signal('packages', ['Depend']) + + def backwards(self, orm): + db.delete_table('packages_depend') + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('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'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends_new'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index a4d2a556..c7b1cab4 100644 --- a/packages/models.py +++ b/packages/models.py @@ -268,6 +268,13 @@ class RelatedToBase(models.Model): ordering = ['name'] +class Depend(RelatedToBase): + pkg = models.ForeignKey('main.Package', related_name='depends_new') + comparison = models.CharField(max_length=255, default='') + optional = models.BooleanField(default=False) + description = models.TextField(null=True, blank=True) + + class Conflict(RelatedToBase): pkg = models.ForeignKey('main.Package', related_name='conflicts') comparison = models.CharField(max_length=255, default='') -- cgit v1.2.3-2-g168b From cc44fdbea59596daf106e48acdb3f4137988d0d9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 18 May 2012 21:10:14 -0500 Subject: Migrate package depends data into new model Signed-off-by: Dan McGee --- packages/migrations/0016_copy_depends_data.py | 246 ++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 packages/migrations/0016_copy_depends_data.py (limited to 'packages') diff --git a/packages/migrations/0016_copy_depends_data.py b/packages/migrations/0016_copy_depends_data.py new file mode 100644 index 00000000..a4b55d4e --- /dev/null +++ b/packages/migrations/0016_copy_depends_data.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +import re +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + depends_on = ( + ('main', '0038_add_depends_optional_description.py'), + ) + + def forwards(self, orm): + Depend = orm['packages.Depend'] + vcmp_re = re.compile(r"^(>=|<=|=|>|<)(.*)$") + for old in orm['main.PackageDepend'].objects.all(): + comp = ver = '' + m = vcmp_re.match(old.depvcmp) + if m: + comp = m.group(1) + ver = m.group(2) + new_dep = Depend(pkg_id=old.pkg_id, name=old.depname, + comparison=comp, version=ver, optional=old.optional, + description=old.description) + new_dep.save(force_insert=True) + + def backwards(self, orm): + orm['packages.Depend'].objects.all().delete() + + 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'"}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + '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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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', [], {'related_name': "'depends'", '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': '2'}), + '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']", 'on_delete': 'models.PROTECT'}), + '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']"}) + }, + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('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'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends_new'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + } + } + + complete_apps = ['main', 'packages'] + symmetrical = True -- cgit v1.2.3-2-g168b From 72a92102df4999dbcc370064707c9026d51c4fe7 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 18 May 2012 21:29:03 -0500 Subject: Switch to usage of new Depend object Signed-off-by: Dan McGee --- packages/models.py | 34 ++++++++++++++++++++++++++++++---- packages/utils.py | 6 +++--- 2 files changed, 33 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index c7b1cab4..cb65f1f1 100644 --- a/packages/models.py +++ b/packages/models.py @@ -228,10 +228,6 @@ class RelatedToBase(models.Model): '''Find a satisfier for this related package that best matches the given criteria. It will not search provisions, but will find packages named and matching repo characteristics if possible.''' - # NOTE: this is cribbed directly from the PackageDepend method of the - # same name. Really, all of these things could use the same method if - # the PackageDepend class was moved here and field names were changed - # to match the layout we use here. pkgs = Package.objects.normal().filter(pkgname=self.name) if not self.pkg.arch.agnostic: # make sure we match architectures if possible @@ -258,6 +254,36 @@ class RelatedToBase(models.Model): return pkg + def get_providers(self): + '''Return providers of this related package. Does *not* include exact + matches as it checks the Provision names only, use get_best_satisfier() + instead for exact matches.''' + pkgs = Package.objects.normal().filter( + provides__name=self.name).order_by().distinct() + if not self.pkg.arch.agnostic: + # make sure we match architectures if possible + arches = self.pkg.applicable_arches() + pkgs = pkgs.filter(arch__in=arches) + + # Logic here is to filter out packages that are in multiple repos if + # they are not requested. For example, if testing is False, only show a + # testing package if it doesn't exist in a non-testing repo. + filtered = {} + for package in pkgs: + if package.pkgname not in filtered or \ + package.repo.staging == self.pkg.repo.staging: + filtered[package.pkgname] = package + pkgs = filtered.values() + + filtered = {} + for package in pkgs: + if package.pkgname not in filtered or \ + package.repo.testing == self.pkg.repo.testing: + filtered[package.pkgname] = package + pkgs = filtered.values() + + return pkgs + def __unicode__(self): if self.version: return u'%s%s%s' % (self.name, self.comparison, self.version) diff --git a/packages/utils.py b/packages/utils.py index 8d00bd68..82313472 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -7,10 +7,10 @@ from django.db import connection from django.db.models import Count, Max, F from django.contrib.auth.models import User -from main.models import Package, PackageDepend, PackageFile, Arch, Repo +from main.models import Package, PackageFile, Arch, Repo from main.utils import cache_function, groupby_preserve_order, PackageStandin from .models import (PackageGroup, PackageRelation, - License, Conflict, Provision, Replacement, + License, Depend, Conflict, Provision, Replacement, SignoffSpecification, Signoff, DEFAULT_SIGNOFF_SPEC) @cache_function(127) @@ -451,7 +451,7 @@ class PackageJSONEncoder(DjangoJSONEncoder): return obj.name.lower() if isinstance(obj, (PackageGroup, License)): return obj.name - if isinstance(obj, (Conflict, Provision, Replacement, PackageDepend)): + if isinstance(obj, (Depend, Conflict, Provision, Replacement)): return unicode(obj) elif isinstance(obj, User): return obj.username -- cgit v1.2.3-2-g168b From e1f9a3c44a1449a36f3981b868814f3d1f65d70d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 18 May 2012 21:35:28 -0500 Subject: Drop old PackageDepend model Signed-off-by: Dan McGee --- packages/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index cb65f1f1..c3a16fc5 100644 --- a/packages/models.py +++ b/packages/models.py @@ -295,7 +295,7 @@ class RelatedToBase(models.Model): class Depend(RelatedToBase): - pkg = models.ForeignKey('main.Package', related_name='depends_new') + pkg = models.ForeignKey('main.Package', related_name='depends') comparison = models.CharField(max_length=255, default='') optional = models.BooleanField(default=False) description = models.TextField(null=True, blank=True) -- cgit v1.2.3-2-g168b From b547d41dbf338fb75eb2c6ae05da143a5cd32c74 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 1 Jul 2012 19:40:42 -0500 Subject: Remove no-longer necessary delayed imports of Package Since commit 158be107e4ad6, we have been importing the Package model at the top-level in this file, so we can kill this code that was never updated. This should also give us back any performance hit we were seeing from the delayed imports. Signed-off-by: Dan McGee --- packages/models.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index c3a16fc5..2c6ad43c 100644 --- a/packages/models.py +++ b/packages/models.py @@ -26,8 +26,6 @@ class PackageRelation(models.Model): created = models.DateTimeField(editable=False) def get_associated_packages(self): - # TODO: delayed import to avoid circular reference - from main.models import Package return Package.objects.normal().filter(pkgbase=self.pkgbase) def repositories(self): @@ -146,8 +144,6 @@ class Signoff(models.Model): @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=self.epoch, arch=self.arch, repo=self.repo) @@ -202,14 +198,15 @@ class PackageGroup(models.Model): Represents a group a package is in. There is no actual group entity, only names that link to given packages. ''' - pkg = models.ForeignKey('main.Package', related_name='groups') + pkg = models.ForeignKey(Package, related_name='groups') name = models.CharField(max_length=255, db_index=True) def __unicode__(self): return "%s: %s" % (self.name, self.pkg) + class License(models.Model): - pkg = models.ForeignKey('main.Package', related_name='licenses') + pkg = models.ForeignKey(Package, related_name='licenses') name = models.CharField(max_length=255) def __unicode__(self): @@ -295,19 +292,19 @@ class RelatedToBase(models.Model): class Depend(RelatedToBase): - pkg = models.ForeignKey('main.Package', related_name='depends') + pkg = models.ForeignKey(Package, related_name='depends') comparison = models.CharField(max_length=255, default='') optional = models.BooleanField(default=False) description = models.TextField(null=True, blank=True) class Conflict(RelatedToBase): - pkg = models.ForeignKey('main.Package', related_name='conflicts') + pkg = models.ForeignKey(Package, related_name='conflicts') comparison = models.CharField(max_length=255, default='') class Provision(RelatedToBase): - pkg = models.ForeignKey('main.Package', related_name='provides') + pkg = models.ForeignKey(Package, related_name='provides') # comparison must be '=' for provides @property @@ -318,7 +315,7 @@ class Provision(RelatedToBase): class Replacement(RelatedToBase): - pkg = models.ForeignKey('main.Package', related_name='replaces') + pkg = models.ForeignKey(Package, related_name='replaces') comparison = models.CharField(max_length=255, default='') -- cgit v1.2.3-2-g168b From 43b5c29b3d89cc2e7e7109bb3c7717a87cfc67b5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 1 Jul 2012 19:57:16 -0500 Subject: Add new package Update model This will be used to track updates to package as we do them during reporead. By storing enough relevant fields from the package object, we should be able to produce a useful report on a regular basis of what has been happening in the repositories. Signed-off-by: Dan McGee --- packages/admin.py | 14 +- packages/migrations/0017_auto__add_update.py | 226 +++++++++++++++++++++++++++ packages/models.py | 64 +++++++- 3 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 packages/migrations/0017_auto__add_update.py (limited to 'packages') diff --git a/packages/admin.py b/packages/admin.py index 0589209f..d43cecce 100644 --- a/packages/admin.py +++ b/packages/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin -from .models import PackageRelation, FlagRequest, Signoff, SignoffSpecification +from .models import (PackageRelation, FlagRequest, + Signoff, SignoffSpecification, Update) class PackageRelationAdmin(admin.ModelAdmin): list_display = ('pkgbase', 'user', 'type', 'created') @@ -9,6 +10,7 @@ class PackageRelationAdmin(admin.ModelAdmin): ordering = ('pkgbase', 'user') date_hierarchy = 'created' + class FlagRequestAdmin(admin.ModelAdmin): list_display = ('pkgbase', 'version', 'repo', 'created', 'who', 'is_spam', 'is_legitimate', 'message') @@ -35,9 +37,19 @@ class SignoffSpecificationAdmin(admin.ModelAdmin): date_hierarchy = 'created' +class UpdateAdmin(admin.ModelAdmin): + list_display = ('pkgname', 'repo', 'arch', 'action_flag', + 'old_version', 'new_version', 'created') + list_filter = ('action_flag', 'repo', 'arch') + search_fields = ('pkgname',) + ordering = ('-created',) + date_hierarchy = 'created' + + admin.site.register(PackageRelation, PackageRelationAdmin) admin.site.register(FlagRequest, FlagRequestAdmin) admin.site.register(Signoff, SignoffAdmin) admin.site.register(SignoffSpecification, SignoffSpecificationAdmin) +admin.site.register(Update, UpdateAdmin) # vim: set ts=4 sw=4 et: diff --git a/packages/migrations/0017_auto__add_update.py b/packages/migrations/0017_auto__add_update.py new file mode 100644 index 00000000..c7c16d4e --- /dev/null +++ b/packages/migrations/0017_auto__add_update.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.create_table('packages_update', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('package', self.gf('django.db.models.fields.related.ForeignKey')(related_name='updates', null=True, on_delete=models.SET_NULL, to=orm['main.Package'])), + ('repo', self.gf('django.db.models.fields.related.ForeignKey')(related_name='updates', to=orm['main.Repo'])), + ('arch', self.gf('django.db.models.fields.related.ForeignKey')(related_name='updates', to=orm['main.Arch'])), + ('pkgname', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('pkgbase', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('action_flag', self.gf('django.db.models.fields.PositiveSmallIntegerField')()), + ('created', self.gf('django.db.models.fields.DateTimeField')()), + ('old_pkgver', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)), + ('old_pkgrel', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)), + ('old_epoch', self.gf('django.db.models.fields.PositiveIntegerField')(null=True)), + ('new_pkgver', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)), + ('new_pkgrel', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)), + ('new_epoch', self.gf('django.db.models.fields.PositiveIntegerField')(null=True)), + )) + db.send_create_signal('packages', ['Update']) + + + def backwards(self, orm): + db.delete_table('packages_update') + + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('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'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 2c6ad43c..5ee06575 100644 --- a/packages/models.py +++ b/packages/models.py @@ -2,11 +2,13 @@ from collections import namedtuple from django.db import models from django.db.models.signals import pre_save +from django.contrib.admin.models import ADDITION, CHANGE, DELETION from django.contrib.auth.models import User from main.models import Arch, Repo, Package from main.utils import set_created_field + class PackageRelation(models.Model): ''' Represents maintainership (or interest) in a package by a given developer. @@ -193,6 +195,66 @@ class FlagRequest(models.Model): def __unicode__(self): return u'%s from %s on %s' % (self.pkgbase, self.who(), self.created) + +UPDATE_ACTION_CHOICES = ( + (ADDITION, 'Addition'), + (CHANGE, 'Change'), + (DELETION, 'Deletion'), +) + + +class Update(models.Model): + package = models.ForeignKey(Package, related_name="updates", + null=True, on_delete=models.SET_NULL) + repo = models.ForeignKey(Repo, related_name="updates") + arch = models.ForeignKey(Arch, related_name="updates") + pkgname = models.CharField(max_length=255) + pkgbase = models.CharField(max_length=255) + action_flag = models.PositiveSmallIntegerField('action flag', + choices=UPDATE_ACTION_CHOICES) + created = models.DateTimeField(editable=False) + + old_pkgver = models.CharField(max_length=255, null=True) + old_pkgrel = models.CharField(max_length=255, null=True) + old_epoch = models.PositiveIntegerField(null=True) + + new_pkgver = models.CharField(max_length=255, null=True) + new_pkgrel = models.CharField(max_length=255, null=True) + new_epoch = models.PositiveIntegerField(null=True) + + class Meta: + get_latest_by = 'created' + + def is_addition(self): + return self.action_flag == ADDITION + + def is_change(self): + return self.action_flag == CHANGE + + def is_deletion(self): + return self.action_flag == DELETION + + @property + def old_version(self): + if self.action_flag == ADDITION: + return None + if self.old_epoch > 0: + return u'%d:%s-%s' % (self.old_epoch, self.old_pkgver, self.old_pkgrel) + return u'%s-%s' % (self.old_pkgver, self.old_pkgrel) + + @property + def new_version(self): + if self.action_flag == DELETION: + return None + if self.new_epoch > 0: + return u'%d:%s-%s' % (self.new_epoch, self.new_pkgver, self.new_pkgrel) + return u'%s-%s' % (self.new_pkgver, self.new_pkgrel) + + def __unicode__(self): + return u'%s of %s on %s' % (self.get_action_flag_display(), + self.pkgname, self.created) + + class PackageGroup(models.Model): ''' Represents a group a package is in. There is no actual group entity, @@ -320,7 +382,7 @@ class Replacement(RelatedToBase): # hook up some signals -for sender in (PackageRelation, SignoffSpecification, Signoff): +for sender in (PackageRelation, SignoffSpecification, Signoff, Update): pre_save.connect(set_created_field, sender=sender, dispatch_uid="packages.models") -- cgit v1.2.3-2-g168b From a87fe016d1a1bf7fdcd2b19f515aa72a5b93db2b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 1 Jul 2012 20:21:34 -0500 Subject: Log package updates during reporead invocation This adds a Manager and log_update method to help log all updates made to the packages table during reporead runs. Signed-off-by: Dan McGee --- packages/models.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 5ee06575..04f35f9d 100644 --- a/packages/models.py +++ b/packages/models.py @@ -203,6 +203,40 @@ UPDATE_ACTION_CHOICES = ( ) +class UpdateManager(models.Manager): + def log_update(self, old_pkg, new_pkg): + '''Utility method to help log an update. This will determine the type + based on how many packages are passed in, and will pull the relevant + necesary fields off the given packages.''' + update = Update() + if new_pkg: + update.action_flag = ADDITION + update.package = new_pkg + update.arch = new_pkg.arch + update.repo = new_pkg.repo + update.pkgname = new_pkg.pkgname + update.pkgbase = new_pkg.pkgbase + update.new_pkgver = new_pkg.pkgver + update.new_pkgrel = new_pkg.pkgrel + update.new_epoch = new_pkg.epoch + if old_pkg: + if new_pkg: + update.action_flag = CHANGE + else: + update.action_flag = DELETION + update.arch = old_pkg.arch + update.repo = old_pkg.repo + update.pkgname = old_pkg.pkgname + update.pkgbase = old_pkg.pkgbase + + update.old_pkgver = old_pkg.pkgver + update.old_pkgrel = old_pkg.pkgrel + update.old_epoch = old_pkg.epoch + + update.save(force_insert=True) + return update + + class Update(models.Model): package = models.ForeignKey(Package, related_name="updates", null=True, on_delete=models.SET_NULL) @@ -222,6 +256,8 @@ class Update(models.Model): new_pkgrel = models.CharField(max_length=255, null=True) new_epoch = models.PositiveIntegerField(null=True) + objects = UpdateManager() + class Meta: get_latest_by = 'created' -- cgit v1.2.3-2-g168b From 34e877d3328edd93b24bc82e63a89781d4350658 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 1 Jul 2012 20:35:34 -0500 Subject: Add indexes on 'created' field to several package-related models These models regularly sort by or limit by the created field, so adding a index on the created database column makes sense. Signed-off-by: Dan McGee --- packages/migrations/0018_create_created_indexes.py | 214 +++++++++++++++++++++ packages/models.py | 6 +- 2 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 packages/migrations/0018_create_created_indexes.py (limited to 'packages') diff --git a/packages/migrations/0018_create_created_indexes.py b/packages/migrations/0018_create_created_indexes.py new file mode 100644 index 00000000..678a04d4 --- /dev/null +++ b/packages/migrations/0018_create_created_indexes.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.create_index('packages_flagrequest', ['created']) + db.create_index('packages_update', ['created']) + db.create_index('packages_signoff', ['created']) + + + def backwards(self, orm): + db.delete_index('packages_signoff', ['created']) + db.delete_index('packages_update', ['created']) + db.delete_index('packages_flagrequest', ['created']) + + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('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'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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', [], {'db_index': 'True'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 04f35f9d..2f03a28b 100644 --- a/packages/models.py +++ b/packages/models.py @@ -138,7 +138,7 @@ class Signoff(models.Model): arch = models.ForeignKey(Arch) repo = models.ForeignKey(Repo) user = models.ForeignKey(User, related_name="package_signoffs") - created = models.DateTimeField(editable=False) + created = models.DateTimeField(editable=False, db_index=True) revoked = models.DateTimeField(null=True) comments = models.TextField(null=True, blank=True) @@ -170,7 +170,7 @@ class FlagRequest(models.Model): ''' user = models.ForeignKey(User, blank=True, null=True) user_email = models.EmailField('email address') - created = models.DateTimeField(editable=False) + created = models.DateTimeField(editable=False, db_index=True) # Great work, Django... https://code.djangoproject.com/ticket/18212 ip_address = models.GenericIPAddressField(verbose_name='IP address', unpack_ipv4=True) @@ -246,7 +246,7 @@ class Update(models.Model): pkgbase = models.CharField(max_length=255) action_flag = models.PositiveSmallIntegerField('action flag', choices=UPDATE_ACTION_CHOICES) - created = models.DateTimeField(editable=False) + created = models.DateTimeField(editable=False, db_index=True) old_pkgver = models.CharField(max_length=255, null=True) old_pkgrel = models.CharField(max_length=255, null=True) -- cgit v1.2.3-2-g168b From b95b0cd4197d70831754a7e81b40388c37ab1a3d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 8 Jul 2012 20:51:23 -0500 Subject: Use a set instead of list when gathering package IDs to fetch If we have duplicates in this list, it makes no sense to include them in the list we send to the database. Signed-off-by: Dan McGee --- packages/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index 82313472..b86b6eba 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -123,7 +123,7 @@ SELECT p.id, q.id cursor.execute(sql, [arch_a.id, arch_b.id]) results = cursor.fetchall() # column A will always have a value, column B might be NULL - to_fetch = [row[0] for row in results] + to_fetch = set(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-2-g168b From 4cd588ae898c2abc8035cf0165ccdd038f2321bd Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 7 Jul 2012 16:50:06 -0500 Subject: Add triggers for adding package update rows This will be done instead of doing this logic at the application level, which has some subtle race conditions. When two simultaneous threads attempt to delete the same package, two update rows for the delete action are inserted. When done at the database level, we can ensure a one-to-one mapping between row operations and entries in this table. Signed-off-by: Dan McGee --- packages/sql/update.postgresql_psycopg2.sql | 45 +++++++++++++++++++++++++++++ packages/sql/update.sqlite3.sql | 30 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 packages/sql/update.postgresql_psycopg2.sql create mode 100644 packages/sql/update.sqlite3.sql (limited to 'packages') diff --git a/packages/sql/update.postgresql_psycopg2.sql b/packages/sql/update.postgresql_psycopg2.sql new file mode 100644 index 00000000..6d678387 --- /dev/null +++ b/packages/sql/update.postgresql_psycopg2.sql @@ -0,0 +1,45 @@ +CREATE OR REPLACE FUNCTION packages_on_insert() RETURNS trigger AS $body$ +BEGIN + INSERT INTO packages_update + (action_flag, created, package_id, arch_id, repo_id, pkgname, pkgbase, new_pkgver, new_pkgrel, new_epoch) + VALUES (1, now(), NEW.id, NEW.arch_id, NEW.repo_id, NEW.pkgname, NEW.pkgbase, NEW.pkgver, NEW.pkgrel, NEW.epoch); + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION packages_on_update() RETURNS trigger AS $body$ +BEGIN + INSERT INTO packages_update + (action_flag, created, package_id, arch_id, repo_id, pkgname, pkgbase, old_pkgver, old_pkgrel, old_epoch, new_pkgver, new_pkgrel, new_epoch) + VALUES (2, now(), NEW.id, NEW.arch_id, NEW.repo_id, NEW.pkgname, NEW.pkgbase, OLD.pkgver, OLD.pkgrel, OLD.epoch, NEW.pkgver, NEW.pkgrel, NEW.epoch); + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION packages_on_delete() RETURNS trigger AS $body$ +BEGIN + INSERT INTO packages_update + (action_flag, created, arch_id, repo_id, pkgname, pkgbase, old_pkgver, old_pkgrel, old_epoch) + VALUES (3, now(), OLD.arch_id, OLD.repo_id, OLD.pkgname, OLD.pkgbase, OLD.pkgver, OLD.pkgrel, OLD.epoch); + RETURN NULL; +END; +$body$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS packages_insert ON packages; +CREATE TRIGGER packages_insert + AFTER INSERT ON packages + FOR EACH ROW + EXECUTE PROCEDURE packages_on_insert(); + +DROP TRIGGER IF EXISTS packages_update ON packages; +CREATE TRIGGER packages_update + AFTER UPDATE ON packages + FOR EACH ROW + WHEN (OLD.pkgver != NEW.pkgver OR OLD.pkgrel != NEW.pkgrel OR OLD.epoch != NEW.epoch) + EXECUTE PROCEDURE packages_on_update(); + +DROP TRIGGER IF EXISTS packages_delete ON packages; +CREATE TRIGGER packages_delete + AFTER DELETE ON packages + FOR EACH ROW + EXECUTE PROCEDURE packages_on_delete(); diff --git a/packages/sql/update.sqlite3.sql b/packages/sql/update.sqlite3.sql new file mode 100644 index 00000000..6f151bdd --- /dev/null +++ b/packages/sql/update.sqlite3.sql @@ -0,0 +1,30 @@ +DROP TRIGGER IF EXISTS packages_insert; +CREATE TRIGGER packages_insert + AFTER INSERT ON packages + FOR EACH ROW + BEGIN + INSERT INTO packages_update + (action_flag, created, package_id, arch_id, repo_id, pkgname, pkgbase, new_pkgver, new_pkgrel, new_epoch) + VALUES (1, strftime('%Y-%m-%d %H:%M:%f', 'now'), NEW.id, NEW.arch_id, NEW.repo_id, NEW.pkgname, NEW.pkgbase, NEW.pkgver, NEW.pkgrel, NEW.epoch); + END; + +DROP TRIGGER IF EXISTS packages_update; +CREATE TRIGGER packages_update + AFTER UPDATE ON packages + FOR EACH ROW + WHEN (OLD.pkgver != NEW.pkgver OR OLD.pkgrel != NEW.pkgrel OR OLD.epoch != NEW.epoch) + BEGIN + INSERT INTO packages_update + (action_flag, created, package_id, arch_id, repo_id, pkgname, pkgbase, old_pkgver, old_pkgrel, old_epoch, new_pkgver, new_pkgrel, new_epoch) + VALUES (2, strftime('%Y-%m-%d %H:%M:%f', 'now'), NEW.id, NEW.arch_id, NEW.repo_id, NEW.pkgname, NEW.pkgbase, OLD.pkgver, OLD.pkgrel, OLD.epoch, NEW.pkgver, NEW.pkgrel, NEW.epoch); + END; + +DROP TRIGGER IF EXISTS packages_delete; +CREATE TRIGGER packages_delete + AFTER DELETE ON packages + FOR EACH ROW + BEGIN + INSERT INTO packages_update + (action_flag, created, arch_id, repo_id, pkgname, pkgbase, old_pkgver, old_pkgrel, old_epoch) + VALUES (3, strftime('%Y-%m-%d %H:%M:%f', 'now'), OLD.arch_id, OLD.repo_id, OLD.pkgname, OLD.pkgbase, OLD.pkgver, OLD.pkgrel, OLD.epoch); + END; -- cgit v1.2.3-2-g168b From 26a00cadcebc0b37775954d261ec73f927ceca12 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 7 Jul 2012 17:28:02 -0500 Subject: Don't log package updates in Python when we have DB trigger support This adds a helper method to find the database engine in use, and then skips code we shouldn't execute if we are doing this another way. Note that this helper method could be useful for backend-specific code paths elsewhere, such as custom SQL being called or lack of StdDev() in sqlite3 out of the box. Signed-off-by: Dan McGee --- packages/models.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 2f03a28b..45ff3c08 100644 --- a/packages/models.py +++ b/packages/models.py @@ -6,7 +6,7 @@ from django.contrib.admin.models import ADDITION, CHANGE, DELETION from django.contrib.auth.models import User from main.models import Arch, Repo, Package -from main.utils import set_created_field +from main.utils import set_created_field, database_vendor class PackageRelation(models.Model): @@ -207,7 +207,12 @@ class UpdateManager(models.Manager): def log_update(self, old_pkg, new_pkg): '''Utility method to help log an update. This will determine the type based on how many packages are passed in, and will pull the relevant - necesary fields off the given packages.''' + necesary fields off the given packages. + Note that in some cases, this is a no-op if we know this database type + supports triggers to add these rows instead.''' + if database_vendor(Package, 'write') in ('sqlite', 'postgresql'): + # we log updates using database triggers for these backends + return update = Update() if new_pkg: update.action_flag = ADDITION @@ -222,6 +227,12 @@ class UpdateManager(models.Manager): if old_pkg: if new_pkg: update.action_flag = CHANGE + # ensure we should even be logging this + if (old_pkg.pkgver == new_pkg.pkgver and + old_pkg.pkgrel == new_pkg.pkgrel and + old_pkg.epoch == new_pkg.epoch): + # all relevant fields were the same; e.g. a force update + return else: update.action_flag = DELETION update.arch = old_pkg.arch -- cgit v1.2.3-2-g168b From 3c906888e2ba9e55cef00dfc61667fb383c9754d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 8 Jul 2012 20:44:07 -0500 Subject: Get multilib package differences query working on sqlite3 Thank you database engines for all implementing such simple operations as substring() and length() in different ways. Signed-off-by: Dan McGee --- packages/utils.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index b86b6eba..6d54d71a 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -8,7 +8,8 @@ from django.db.models import Count, Max, F from django.contrib.auth.models import User from main.models import Package, PackageFile, Arch, Repo -from main.utils import cache_function, groupby_preserve_order, PackageStandin +from main.utils import (cache_function, database_vendor, + groupby_preserve_order, PackageStandin) from .models import (PackageGroup, PackageRelation, License, Depend, Conflict, Provision, Replacement, SignoffSpecification, Signoff, DEFAULT_SIGNOFF_SPEC) @@ -150,12 +151,18 @@ SELECT p.id, q.id def multilib_differences(): # Query for checking multilib out of date-ness - sql = """ -SELECT ml.id, reg.id - FROM packages ml - JOIN packages reg - ON ( - reg.pkgname = ( + if database_vendor(Package) == 'sqlite': + pkgname_sql = """ + CASE WHEN ml.pkgname LIKE %s + THEN SUBSTR(ml.pkgname, 7) + WHEN ml.pkgname LIKE %s + THEN SUBSTR(ml.pkgname, 1, LENGTH(ml.pkgname) - 9) + ELSE + ml.pkgname + END + """ + else: + pkgname_sql = """ CASE WHEN ml.pkgname LIKE %s THEN SUBSTRING(ml.pkgname, 7) WHEN ml.pkgname LIKE %s @@ -163,7 +170,13 @@ SELECT ml.id, reg.id ELSE ml.pkgname END - ) + """ + sql = """ +SELECT ml.id, reg.id + FROM packages ml + JOIN packages reg + ON ( + reg.pkgname = (""" + pkgname_sql + """) AND reg.pkgver != ml.pkgver ) JOIN repos r ON reg.repo_id = r.id @@ -172,7 +185,7 @@ SELECT ml.id, reg.id AND r.staging = %s AND reg.arch_id = %s ORDER BY ml.last_update -""" + """ multilib = Repo.objects.get(name__iexact='multilib') i686 = Arch.objects.get(name='i686') params = ['lib32-%', '%-multilib', multilib.id, False, False, i686.id] -- cgit v1.2.3-2-g168b From 872d4bcaa2ce85d2d319a1146e0fc05ab6808eb9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 21 Jul 2012 11:26:34 -0500 Subject: Split details/display package views into new module This moves a lot of the package and group display logic into a new view module, similar to what we already did earlier with a bunch of other views. Signed-off-by: Dan McGee --- packages/views/__init__.py | 154 ++----------------------------------------- packages/views/display.py | 160 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 147 deletions(-) create mode 100644 packages/views/display.py (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 3e574c26..fa67daa8 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -1,27 +1,24 @@ import hashlib import json -from string import Template -from urllib import urlencode from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import User from django.core.cache import cache -from django.http import HttpResponse, Http404 -from django.shortcuts import get_object_or_404, redirect +from django.http import HttpResponse +from django.shortcuts import redirect from django.views.decorators.cache import cache_control from django.views.decorators.http import require_GET, require_POST -from django.views.decorators.vary import vary_on_headers from django.views.generic.simple import direct_to_template -from main.models import Package, PackageFile, Arch, Repo -from mirrors.models import MirrorUrl -from mirrors.utils import get_mirror_url_for_download +from main.models import Package, Arch from ..models import PackageRelation -from ..utils import (get_group_info, get_differences_info, - multilib_differences, get_wrong_permissions, PackageJSONEncoder) +from ..utils import (get_differences_info, + multilib_differences, get_wrong_permissions) # make other views available from this same package +from .display import (details, groups, group_details, files, details_json, + files_json, download) from .flag import flaghelp, flag, flag_confirmed, unflag, unflag_all from .search import search, search_json from .signoff import signoffs, signoff_package, signoff_options, signoffs_json @@ -105,143 +102,6 @@ def update(request): messages.error(request, "Are you trying to adopt or disown?") return redirect('/packages/') -def split_package_details(request, name='', repo='', arch=''): - arch = get_object_or_404(Arch, name=arch) - arches = [ 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, repo__staging=repo.staging, - arch__in=arches).order_by('pkgname') - if len(pkgs) == 0: - raise Http404 - # we have packages, but ensure at least one is in the given repo - if not any(True for pkg in pkgs if pkg.repo == repo): - raise Http404 - context = { - 'list_title': 'Split Package Details', - 'name': name, - 'arch': arch, - 'packages': pkgs, - } - return direct_to_template(request, 'packages/packages_list.html', - context) - -def details(request, name='', repo='', arch=''): - if all([name, repo, arch]): - try: - pkg = Package.objects.select_related( - 'arch', 'repo', 'packager').get(pkgname=name, - repo__name__iexact=repo, arch__name=arch) - return direct_to_template(request, 'packages/details.html', - {'pkg': pkg, }) - except Package.DoesNotExist: - arch_obj = get_object_or_404(Arch, name=arch) - # for arch='any' packages, we can issue a redirect to them if we - # have a single non-ambiguous option by changing the arch to match - # any arch-agnostic package - if not arch_obj.agnostic: - pkgs = Package.objects.select_related( - 'arch', 'repo', 'packager').filter(pkgname=name, - repo__name__iexact=repo, arch__agnostic=True) - if len(pkgs) == 1: - return redirect(pkgs[0], permanent=True) - return split_package_details(request, name, repo, arch) - else: - pkg_data = [ - ('arch', arch.lower()), - ('repo', repo.lower()), - ('q', name), - ] - # only include non-blank values in the query we generate - pkg_data = [(x, y.encode('utf-8')) for x, y in pkg_data if y] - return redirect("/packages/?%s" % urlencode(pkg_data)) - -def groups(request, arch=None): - arches = [] - if arch: - get_object_or_404(Arch, name=arch, agnostic=False) - arches.append(arch) - grps = get_group_info(arches) - context = { - 'groups': grps, - 'arch': arch, - } - return direct_to_template(request, 'packages/groups.html', context) - -def group_details(request, arch, name): - arch = get_object_or_404(Arch, name=arch) - arches = [ arch ] - arches.extend(Arch.objects.filter(agnostic=True)) - pkgs = Package.objects.normal().filter( - groups__name=name, arch__in=arches).order_by('pkgname') - if len(pkgs) == 0: - raise Http404 - context = { - 'list_title': 'Group Details', - 'name': name, - 'arch': arch, - 'packages': pkgs, - } - return direct_to_template(request, 'packages/packages_list.html', context) - -@vary_on_headers('X-Requested-With') -def files(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - # files are inserted in sorted order, so preserve that - fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') - dir_count = sum(1 for f in fileslist if f.is_directory) - files_count = len(fileslist) - dir_count - context = { - 'pkg': pkg, - 'files': fileslist, - 'files_count': files_count, - 'dir_count': dir_count, - } - template = 'packages/files.html' - if request.is_ajax(): - template = 'packages/files_list.html' - return direct_to_template(request, template, context) - -def details_json(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - to_json = json.dumps(pkg, ensure_ascii=False, cls=PackageJSONEncoder) - return HttpResponse(to_json, mimetype='application/json') - -def files_json(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - # files are inserted in sorted order, so preserve that - fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') - data = { - 'pkgname': pkg.pkgname, - 'repo': pkg.repo.name.lower(), - 'arch': pkg.arch.name.lower(), - 'files': fileslist, - } - to_json = json.dumps(data, ensure_ascii=False, cls=PackageJSONEncoder) - return HttpResponse(to_json, mimetype='application/json') - -def download(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) - url = get_mirror_url_for_download() - if not url: - raise Http404 - arch = pkg.arch.name - if pkg.arch.agnostic: - # grab the first non-any arch to fake the download path - arch = Arch.objects.exclude(agnostic=True)[0].name - values = { - 'host': url.url, - 'arch': arch, - 'repo': pkg.repo.name.lower(), - 'file': pkg.filename, - } - url = Template('${host}${repo}/os/${arch}/${file}').substitute(values) - return redirect(url) def arch_differences(request): # TODO: we have some hardcoded magic here with respect to the arches. diff --git a/packages/views/display.py b/packages/views/display.py new file mode 100644 index 00000000..dd57571a --- /dev/null +++ b/packages/views/display.py @@ -0,0 +1,160 @@ +import json +from string import Template +from urllib import urlencode + +from django.http import HttpResponse, Http404 +from django.shortcuts import get_object_or_404, redirect +from django.views.generic.simple import direct_to_template +from django.views.decorators.vary import vary_on_headers + +from main.models import Package, PackageFile, Arch, Repo +from mirrors.utils import get_mirror_url_for_download +from ..utils import get_group_info, PackageJSONEncoder + + +def split_package_details(request, name='', repo='', arch=''): + arch = get_object_or_404(Arch, name=arch) + arches = [ 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, repo__staging=repo.staging, + arch__in=arches).order_by('pkgname') + if len(pkgs) == 0: + raise Http404 + # we have packages, but ensure at least one is in the given repo + if not any(True for pkg in pkgs if pkg.repo == repo): + raise Http404 + context = { + 'list_title': 'Split Package Details', + 'name': name, + 'arch': arch, + 'packages': pkgs, + } + return direct_to_template(request, 'packages/packages_list.html', + context) + + +def details(request, name='', repo='', arch=''): + if all([name, repo, arch]): + try: + pkg = Package.objects.select_related( + 'arch', 'repo', 'packager').get(pkgname=name, + repo__name__iexact=repo, arch__name=arch) + return direct_to_template(request, 'packages/details.html', + {'pkg': pkg, }) + except Package.DoesNotExist: + arch_obj = get_object_or_404(Arch, name=arch) + # for arch='any' packages, we can issue a redirect to them if we + # have a single non-ambiguous option by changing the arch to match + # any arch-agnostic package + if not arch_obj.agnostic: + pkgs = Package.objects.select_related( + 'arch', 'repo', 'packager').filter(pkgname=name, + repo__name__iexact=repo, arch__agnostic=True) + if len(pkgs) == 1: + return redirect(pkgs[0], permanent=True) + return split_package_details(request, name, repo, arch) + else: + pkg_data = [ + ('arch', arch.lower()), + ('repo', repo.lower()), + ('q', name), + ] + # only include non-blank values in the query we generate + pkg_data = [(x, y.encode('utf-8')) for x, y in pkg_data if y] + return redirect("/packages/?%s" % urlencode(pkg_data)) + + +def groups(request, arch=None): + arches = [] + if arch: + get_object_or_404(Arch, name=arch, agnostic=False) + arches.append(arch) + grps = get_group_info(arches) + context = { + 'groups': grps, + 'arch': arch, + } + return direct_to_template(request, 'packages/groups.html', context) + + +def group_details(request, arch, name): + arch = get_object_or_404(Arch, name=arch) + arches = [ arch ] + arches.extend(Arch.objects.filter(agnostic=True)) + pkgs = Package.objects.normal().filter( + groups__name=name, arch__in=arches).order_by('pkgname') + if len(pkgs) == 0: + raise Http404 + context = { + 'list_title': 'Group Details', + 'name': name, + 'arch': arch, + 'packages': pkgs, + } + return direct_to_template(request, 'packages/packages_list.html', context) + + +@vary_on_headers('X-Requested-With') +def files(request, name, repo, arch): + pkg = get_object_or_404(Package, + pkgname=name, repo__name__iexact=repo, arch__name=arch) + # files are inserted in sorted order, so preserve that + fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') + dir_count = sum(1 for f in fileslist if f.is_directory) + files_count = len(fileslist) - dir_count + context = { + 'pkg': pkg, + 'files': fileslist, + 'files_count': files_count, + 'dir_count': dir_count, + } + template = 'packages/files.html' + if request.is_ajax(): + template = 'packages/files_list.html' + return direct_to_template(request, template, context) + + +def details_json(request, name, repo, arch): + pkg = get_object_or_404(Package, + pkgname=name, repo__name__iexact=repo, arch__name=arch) + to_json = json.dumps(pkg, ensure_ascii=False, cls=PackageJSONEncoder) + return HttpResponse(to_json, mimetype='application/json') + + +def files_json(request, name, repo, arch): + pkg = get_object_or_404(Package, + pkgname=name, repo__name__iexact=repo, arch__name=arch) + # files are inserted in sorted order, so preserve that + fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') + data = { + 'pkgname': pkg.pkgname, + 'repo': pkg.repo.name.lower(), + 'arch': pkg.arch.name.lower(), + 'files': fileslist, + } + to_json = json.dumps(data, ensure_ascii=False, cls=PackageJSONEncoder) + return HttpResponse(to_json, mimetype='application/json') + + +def download(request, name, repo, arch): + pkg = get_object_or_404(Package, + pkgname=name, repo__name__iexact=repo, arch__name=arch) + url = get_mirror_url_for_download() + if not url: + raise Http404 + arch = pkg.arch.name + if pkg.arch.agnostic: + # grab the first non-any arch to fake the download path + arch = Arch.objects.exclude(agnostic=True)[0].name + values = { + 'host': url.url, + 'arch': arch, + 'repo': pkg.repo.name.lower(), + 'file': pkg.filename, + } + url = Template('${host}${repo}/os/${arch}/${file}').substitute(values) + return redirect(url) + +# vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From 946d90d08f29094153142056a1778cd595e568a3 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 23 Jul 2012 09:45:44 -0500 Subject: Add '410 Gone' support for packages moved out of repositories This allows us to do better than a generic 404 handler when we know a package previously existed in a given repository, and should also make things a bit nicer when getting sent in from a search engine to a page that no longer exists. Signed-off-by: Dan McGee --- packages/models.py | 3 +++ packages/views/display.py | 46 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 45ff3c08..18a62a3c 100644 --- a/packages/models.py +++ b/packages/models.py @@ -297,6 +297,9 @@ class Update(models.Model): return u'%d:%s-%s' % (self.new_epoch, self.new_pkgver, self.new_pkgrel) return u'%s-%s' % (self.new_pkgver, self.new_pkgrel) + def elsewhere(self): + return Package.objects.filter(pkgname=self.pkgname, arch=self.arch) + def __unicode__(self): return u'%s of %s on %s' % (self.get_action_flag_display(), self.pkgname, self.created) diff --git a/packages/views/display.py b/packages/views/display.py index dd57571a..5332dc13 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -1,18 +1,21 @@ +import datetime import json from string import Template from urllib import urlencode from django.http import HttpResponse, Http404 -from django.shortcuts import get_object_or_404, redirect +from django.shortcuts import get_object_or_404, redirect, render +from django.utils.timezone import now from django.views.generic.simple import direct_to_template from django.views.decorators.vary import vary_on_headers from main.models import Package, PackageFile, Arch, Repo from mirrors.utils import get_mirror_url_for_download +from ..models import Update from ..utils import get_group_info, PackageJSONEncoder -def split_package_details(request, name='', repo='', arch=''): +def split_package_details(request, name, repo, arch): arch = get_object_or_404(Arch, name=arch) arches = [ arch ] arches.extend(Arch.objects.filter(agnostic=True)) @@ -21,10 +24,10 @@ def split_package_details(request, name='', repo='', arch=''): repo__testing=repo.testing, repo__staging=repo.staging, arch__in=arches).order_by('pkgname') if len(pkgs) == 0: - raise Http404 + return None # we have packages, but ensure at least one is in the given repo if not any(True for pkg in pkgs if pkg.repo == repo): - raise Http404 + return None context = { 'list_title': 'Split Package Details', 'name': name, @@ -35,6 +38,30 @@ def split_package_details(request, name='', repo='', arch=''): context) +CUTOFF = datetime.timedelta(days=60) + + +def recently_removed_package(request, name, repo, arch, cutoff=CUTOFF): + '''We're just steps away from raising a 404, but check our packages update + table first to see if this package has existed in this repo before. If so, + we can show a 410 Gone page and point the requester in the right + direction.''' + arch = get_object_or_404(Arch, name=arch) + arches = [ arch ] + arches.extend(Arch.objects.filter(agnostic=True)) + match = Update.objects.select_related('arch', 'repo').filter( + pkgname=name, repo__name__iexact=repo, arch__in=arches) + if cutoff is not None: + when = now() - cutoff + match = match.filter(created__gte=when) + try: + match = match.latest() + return render(request, 'packages/removed.html', + {'update': match, }, status=410) + except Update.DoesNotExist: + return None + + def details(request, name='', repo='', arch=''): if all([name, repo, arch]): try: @@ -54,7 +81,16 @@ def details(request, name='', repo='', arch=''): repo__name__iexact=repo, arch__agnostic=True) if len(pkgs) == 1: return redirect(pkgs[0], permanent=True) - return split_package_details(request, name, repo, arch) + # do we have a split package matching this criteria? + ret = split_package_details(request, name, repo, arch) + if ret is None: + # maybe we have a recently-removed package? + ret = recently_removed_package(request, name, repo, arch) + if ret is not None: + return ret + else: + # we've tried everything at this point, nothing to see + raise Http404 else: pkg_data = [ ('arch', arch.lower()), -- cgit v1.2.3-2-g168b From 374bf53505480eebd675e95f975dc908c485a6fa Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 23 Jul 2012 21:17:02 -0500 Subject: Remove files list AJAX conditionals Now that we just generate this list in JS, we don't need this separate code. Signed-off-by: Dan McGee --- packages/views/display.py | 4 ---- 1 file changed, 4 deletions(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index 5332dc13..d5aa2c20 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -7,7 +7,6 @@ from django.http import HttpResponse, Http404 from django.shortcuts import get_object_or_404, redirect, render from django.utils.timezone import now from django.views.generic.simple import direct_to_template -from django.views.decorators.vary import vary_on_headers from main.models import Package, PackageFile, Arch, Repo from mirrors.utils import get_mirror_url_for_download @@ -132,7 +131,6 @@ def group_details(request, arch, name): return direct_to_template(request, 'packages/packages_list.html', context) -@vary_on_headers('X-Requested-With') def files(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) @@ -147,8 +145,6 @@ def files(request, name, repo, arch): 'dir_count': dir_count, } template = 'packages/files.html' - if request.is_ajax(): - template = 'packages/files_list.html' return direct_to_template(request, template, context) -- cgit v1.2.3-2-g168b From 211340c8bd6ccd6b16f3115a71fce4abedcc4c06 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 23 Jul 2012 21:31:17 -0500 Subject: Ensure package files JS can support corner cases We should handle the cases dealing with no filelist available, outdated filelist, or a package without files, just as the HTML server-side page does. Add a bit more info to the JSON returned so we can do so. Signed-off-by: Dan McGee --- packages/views/display.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index d5aa2c20..02f5a5b2 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -160,10 +160,16 @@ def files_json(request, name, repo, arch): pkgname=name, repo__name__iexact=repo, arch__name=arch) # files are inserted in sorted order, so preserve that fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') + dir_count = sum(1 for f in fileslist if f.is_directory) + files_count = len(fileslist) - dir_count data = { 'pkgname': pkg.pkgname, 'repo': pkg.repo.name.lower(), 'arch': pkg.arch.name.lower(), + 'pkg_last_update': pkg.last_update, + 'files_last_update': pkg.files_last_update, + 'files_count': files_count, + 'dir_count': dir_count, 'files': fileslist, } to_json = json.dumps(data, ensure_ascii=False, cls=PackageJSONEncoder) -- cgit v1.2.3-2-g168b From 9ab460c53a1ac4c79da6f05f2850ee21beedbab2 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 23 Jul 2012 21:47:43 -0500 Subject: Fall back to 410 Gone for package files view as well This is another thing that Google and other search engines try to crawl that no longer exists at times, so we should handle it gracefully. Signed-off-by: Dan McGee --- packages/views/display.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index 02f5a5b2..31f18c79 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -132,8 +132,16 @@ def group_details(request, arch, name): def files(request, name, repo, arch): - pkg = get_object_or_404(Package, - pkgname=name, repo__name__iexact=repo, arch__name=arch) + try: + pkg = Package.objects.get(pkgname=name, + repo__name__iexact=repo, arch__name=arch) + except Package.DoesNotExist: + # this may have been deleted recently, so follow the same logic as we + # do on the package details page if possible + ret = recently_removed_package(request, name, repo, arch) + if ret is not None: + return ret + raise Http404 # files are inserted in sorted order, so preserve that fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') dir_count = sum(1 for f in fileslist if f.is_directory) -- cgit v1.2.3-2-g168b From 61b4098c611592d62b40a9ee941976a869dff4fc Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 24 Jul 2012 08:55:48 -0500 Subject: Add index on package updates pkgname field Signed-off-by: Dan McGee --- .../0019_package_update_pkgname_index.py | 208 +++++++++++++++++++++ packages/models.py | 2 +- 2 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 packages/migrations/0019_package_update_pkgname_index.py (limited to 'packages') diff --git a/packages/migrations/0019_package_update_pkgname_index.py b/packages/migrations/0019_package_update_pkgname_index.py new file mode 100644 index 00000000..047f11ec --- /dev/null +++ b/packages/migrations/0019_package_update_pkgname_index.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.create_index('packages_update', ['pkgname']) + + def backwards(self, orm): + db.delete_index('packages_update', ['pkgname']) + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('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'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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', [], {'db_index': 'True'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 18a62a3c..5b48b30f 100644 --- a/packages/models.py +++ b/packages/models.py @@ -253,7 +253,7 @@ class Update(models.Model): null=True, on_delete=models.SET_NULL) repo = models.ForeignKey(Repo, related_name="updates") arch = models.ForeignKey(Arch, related_name="updates") - pkgname = models.CharField(max_length=255) + pkgname = models.CharField(max_length=255, db_index=True) pkgbase = models.CharField(max_length=255) action_flag = models.PositiveSmallIntegerField('action flag', choices=UPDATE_ACTION_CHOICES) -- cgit v1.2.3-2-g168b From c0bf9e20660cfae7ea8994472555bba23398b598 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 24 Jul 2012 09:19:48 -0500 Subject: Remove custom utc_now() function, use django.utils.timezone.now() This was around from the time when we handled timezones sanely and Django did not; now that we are on 1.4 we no longer need our own code to handle this. Signed-off-by: Dan McGee --- packages/management/commands/signoff_report.py | 8 ++++---- packages/views/flag.py | 8 ++++---- packages/views/signoff.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/management/commands/signoff_report.py b/packages/management/commands/signoff_report.py index ddf930db..72fcbe1e 100644 --- a/packages/management/commands/signoff_report.py +++ b/packages/management/commands/signoff_report.py @@ -15,6 +15,7 @@ from django.contrib.auth.models import User from django.contrib.sites.models import Site from django.db.models import Count from django.template import loader, Context +from django.utils.timezone import now from collections import namedtuple from datetime import timedelta @@ -23,7 +24,6 @@ from operator import attrgetter import sys from main.models import Repo -from main.utils import utc_now from packages.models import Signoff from packages.utils import get_signoff_groups @@ -66,9 +66,9 @@ def generate_report(email, repo_name): new_hours = 24 old_days = 14 - now = utc_now() - new_cutoff = now - timedelta(hours=new_hours) - old_cutoff = now - timedelta(days=old_days) + current_time = now() + new_cutoff = current_time - timedelta(hours=new_hours) + old_cutoff = current_time - timedelta(days=old_days) if len(signoff_groups) == 0: # no need to send an email at all diff --git a/packages/views/flag.py b/packages/views/flag.py index 7fa2d508..f3db93b3 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -5,12 +5,12 @@ from django.core.mail import send_mail from django.db import transaction from django.shortcuts import get_object_or_404, redirect from django.template import loader, Context +from django.utils.timezone import now from django.views.generic.simple import direct_to_template from django.views.decorators.cache import cache_page, never_cache from ..models import FlagRequest from main.models import Package -from main.utils import utc_now class FlagForm(forms.Form): @@ -76,10 +76,10 @@ def flag(request, name, repo, arch): @transaction.commit_on_success def perform_updates(): - now = utc_now() - pkgs.update(flag_date=now) + current_time = now() + pkgs.update(flag_date=current_time) # store our flag request - flag_request = FlagRequest(created=now, + flag_request = FlagRequest(created=current_time, user_email=email, message=message, ip_address=ip_addr, pkgbase=pkg.pkgbase, version=version, repo=pkg.repo, diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 61d949fc..7aa39106 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -8,11 +8,11 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db import transaction from django.http import HttpResponse, Http404 from django.shortcuts import get_list_or_404, redirect, render +from django.utils.timezone import now from django.views.decorators.cache import never_cache from django.views.generic.simple import direct_to_template from main.models import Package, Arch, Repo -from main.utils import utc_now from ..models import SignoffSpecification, Signoff from ..utils import (get_signoff_groups, approved_by_signoffs, PackageSignoffGroup) @@ -45,7 +45,7 @@ def signoff_package(request, name, repo, arch, revoke=False): package, request.user, False) except Signoff.DoesNotExist: raise Http404 - signoff.revoked = utc_now() + signoff.revoked = now() signoff.save() created = False else: -- cgit v1.2.3-2-g168b From 76c37ce3acc7a4af0271c7535d4a33042f7749b5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 24 Jul 2012 09:35:55 -0500 Subject: Replace deprecated direct_to_template() with render() shortcut Now that Django actually provides a concise way to use a RequestContext object without instantiating it, we can use that rather than the old function-based generic view that worked well to do the same. Additionally, these function-based generic views will be gone in Django 1.5, so might as well make the move now. Signed-off-by: Dan McGee --- packages/views/__init__.py | 11 +++++------ packages/views/display.py | 13 +++++-------- packages/views/flag.py | 12 +++++------- packages/views/signoff.py | 5 ++--- 4 files changed, 17 insertions(+), 24 deletions(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index fa67daa8..19ea9103 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -6,10 +6,9 @@ from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import User from django.core.cache import cache from django.http import HttpResponse -from django.shortcuts import redirect +from django.shortcuts import redirect, render from django.views.decorators.cache import cache_control from django.views.decorators.http import require_GET, require_POST -from django.views.generic.simple import direct_to_template from main.models import Package, Arch from ..models import PackageRelation @@ -32,9 +31,9 @@ def opensearch(request): else: domain = "http://%s" % request.META['HTTP_HOST'] - return direct_to_template(request, 'packages/opensearch.xml', + return render(request, 'packages/opensearch.xml', {'domain': domain}, - mimetype='application/opensearchdescription+xml') + content_type='application/opensearchdescription+xml') @require_GET @@ -115,7 +114,7 @@ def arch_differences(request): 'differences': differences, 'multilib_differences': multilib_diffs } - return direct_to_template(request, 'packages/differences.html', context) + return render(request, 'packages/differences.html', context) @permission_required('main.change_package') def stale_relations(request): @@ -132,7 +131,7 @@ def stale_relations(request): 'missing_pkgbase': missing_pkgbase, 'wrong_permissions': wrong_permissions, } - return direct_to_template(request, 'packages/stale_relations.html', context) + return render(request, 'packages/stale_relations.html', context) @permission_required('packages.delete_packagerelation') @require_POST diff --git a/packages/views/display.py b/packages/views/display.py index 31f18c79..585e0e4e 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -6,7 +6,6 @@ from urllib import urlencode from django.http import HttpResponse, Http404 from django.shortcuts import get_object_or_404, redirect, render from django.utils.timezone import now -from django.views.generic.simple import direct_to_template from main.models import Package, PackageFile, Arch, Repo from mirrors.utils import get_mirror_url_for_download @@ -33,8 +32,7 @@ def split_package_details(request, name, repo, arch): 'arch': arch, 'packages': pkgs, } - return direct_to_template(request, 'packages/packages_list.html', - context) + return render(request, 'packages/packages_list.html', context) CUTOFF = datetime.timedelta(days=60) @@ -67,8 +65,7 @@ def details(request, name='', repo='', arch=''): pkg = Package.objects.select_related( 'arch', 'repo', 'packager').get(pkgname=name, repo__name__iexact=repo, arch__name=arch) - return direct_to_template(request, 'packages/details.html', - {'pkg': pkg, }) + return render(request, 'packages/details.html', {'pkg': pkg}) except Package.DoesNotExist: arch_obj = get_object_or_404(Arch, name=arch) # for arch='any' packages, we can issue a redirect to them if we @@ -111,7 +108,7 @@ def groups(request, arch=None): 'groups': grps, 'arch': arch, } - return direct_to_template(request, 'packages/groups.html', context) + return render(request, 'packages/groups.html', context) def group_details(request, arch, name): @@ -128,7 +125,7 @@ def group_details(request, arch, name): 'arch': arch, 'packages': pkgs, } - return direct_to_template(request, 'packages/packages_list.html', context) + return render(request, 'packages/packages_list.html', context) def files(request, name, repo, arch): @@ -153,7 +150,7 @@ def files(request, name, repo, arch): 'dir_count': dir_count, } template = 'packages/files.html' - return direct_to_template(request, template, context) + return render(request, template, context) def details_json(request, name, repo, arch): diff --git a/packages/views/flag.py b/packages/views/flag.py index f3db93b3..b9542a62 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -3,10 +3,9 @@ from django.conf import settings from django.contrib.auth.decorators import permission_required from django.core.mail import send_mail from django.db import transaction -from django.shortcuts import get_object_or_404, redirect +from django.shortcuts import get_object_or_404, redirect, render from django.template import loader, Context from django.utils.timezone import now -from django.views.generic.simple import direct_to_template from django.views.decorators.cache import cache_page, never_cache from ..models import FlagRequest @@ -34,7 +33,7 @@ class FlagForm(forms.Form): @cache_page(3600) def flaghelp(request): - return direct_to_template(request, 'packages/flaghelp.html') + return render(request, 'packages/flaghelp.html') @never_cache @@ -43,8 +42,7 @@ def flag(request, name, repo, arch): pkgname=name, repo__name__iexact=repo, arch__name=arch) if pkg.flag_date is not None: # already flagged. do nothing. - return direct_to_template(request, 'packages/flagged.html', - {'pkg': pkg}) + return render(request, 'packages/flagged.html', {'pkg': pkg}) # find all packages from (hopefully) the same PKGBUILD pkgs = Package.objects.normal().filter( pkgbase=pkg.pkgbase, flag_date__isnull=True, @@ -129,7 +127,7 @@ def flag(request, name, repo, arch): 'packages': pkgs, 'form': form } - return direct_to_template(request, 'packages/flag.html', context) + return render(request, 'packages/flag.html', context) def flag_confirmed(request, name, repo, arch): pkg = get_object_or_404(Package, @@ -142,7 +140,7 @@ def flag_confirmed(request, name, repo, arch): context = {'package': pkg, 'packages': pkgs} - return direct_to_template(request, 'packages/flag_confirmed.html', context) + return render(request, 'packages/flag_confirmed.html', context) @permission_required('main.change_package') def unflag(request, name, repo, arch): diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 7aa39106..56eb060c 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -10,7 +10,6 @@ from django.http import HttpResponse, Http404 from django.shortcuts import get_list_or_404, redirect, render from django.utils.timezone import now from django.views.decorators.cache import never_cache -from django.views.generic.simple import direct_to_template from main.models import Package, Arch, Repo from ..models import SignoffSpecification, Signoff @@ -28,7 +27,7 @@ def signoffs(request): 'arches': Arch.objects.all(), 'repo_names': sorted(set(g.target_repo for g in signoff_groups)), } - return direct_to_template(request, 'packages/signoffs.html', context) + return render(request, 'packages/signoffs.html', context) @permission_required('main.change_package') @never_cache @@ -144,7 +143,7 @@ def signoff_options(request, name, repo, arch): 'package': package, 'form': form, } - return direct_to_template(request, 'packages/signoff_options.html', context) + return render(request, 'packages/signoff_options.html', context) class SignoffJSONEncoder(DjangoJSONEncoder): '''Base JSONEncoder extended to handle all serialization of all classes -- cgit v1.2.3-2-g168b From 24b28a504cabcf077882aa95cfa0edbc6a8d4569 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 24 Jul 2012 21:01:31 -0500 Subject: Replace deprecated list_detail usage in search with class-based view We can convert the entire search view to a generic class-based ListView. This is still one of the more disgusting views in the application and has a ton of logic scattered buckshot across several methods, but this commit is not meant to address all of that in one go. This is the last of the deprecated pieces I know of we are still using in the codebase, so we should be relatively safe in the long run now for an upgrade to the eventual next major Django release. Signed-off-by: Dan McGee --- packages/urls.py | 8 +++-- packages/views/__init__.py | 2 +- packages/views/search.py | 77 +++++++++++++++++++++++----------------------- 3 files changed, 45 insertions(+), 42 deletions(-) (limited to 'packages') diff --git a/packages/urls.py b/packages/urls.py index 6eddc5fe..9a151b4b 100644 --- a/packages/urls.py +++ b/packages/urls.py @@ -1,5 +1,7 @@ from django.conf.urls import include, patterns +from .views.search import SearchListView + package_patterns = patterns('packages.views', (r'^$', 'details'), (r'^json/$', 'details_json'), @@ -21,9 +23,9 @@ urlpatterns = patterns('packages.views', (r'^signoffs/json/$', 'signoffs_json', {}, 'package-signoffs-json'), (r'^update/$', 'update'), - (r'^$', 'search', {}, 'packages-search'), - (r'^search/json/$', 'search_json'), - (r'^(?P\d+)/$', 'search'), + (r'^$', SearchListView.as_view(), {}, 'packages-search'), + (r'^(?P\d+)/$', SearchListView.as_view()), + (r'^search/json/$', 'search_json'), (r'^differences/$', 'arch_differences', {}, 'packages-differences'), (r'^stale_relations/$', 'stale_relations'), diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 19ea9103..038d40ac 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -19,7 +19,7 @@ from ..utils import (get_differences_info, from .display import (details, groups, group_details, files, details_json, files_json, download) from .flag import flaghelp, flag, flag_confirmed, unflag, unflag_all -from .search import search, search_json +from .search import search_json from .signoff import signoffs, signoff_package, signoff_options, signoffs_json diff --git a/packages/views/search.py b/packages/views/search.py index a89822be..9750894a 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -6,7 +6,7 @@ from django.contrib.admin.widgets import AdminDateWidget from django.contrib.auth.models import User from django.db.models import Q from django.http import HttpResponse -from django.views.generic import list_detail +from django.views.generic import ListView from main.models import Package, Arch, Repo from main.utils import make_choice @@ -33,6 +33,7 @@ class LimitTypedChoiceField(forms.TypedChoiceField): except (ValueError, TypeError): return False + class PackageSearchForm(forms.Form): repo = forms.MultipleChoiceField(required=False) arch = forms.MultipleChoiceField(required=False) @@ -69,6 +70,7 @@ class PackageSearchForm(forms.Form): [('', 'All'), ('unknown', 'Unknown')] + \ [(m.username, m.get_full_name()) for m in maints] + def parse_form(form, packages): if form.cleaned_data['repo']: packages = packages.filter( @@ -117,48 +119,47 @@ def parse_form(form, packages): return packages -def search(request, page=None): - limit = 50 - sort = None - packages = Package.objects.normal() - if request.GET: - form = PackageSearchForm(data=request.GET) - if form.is_valid(): - packages = parse_form(form, packages) - asked_limit = form.cleaned_data['limit'] +class SearchListView(ListView): + template_name = "packages/search.html" + + sort_fields = ("arch", "repo", "pkgname", "pkgbase", "compressed_size", + "installed_size", "build_date", "last_update", "flag_date") + allowed_sort = list(sort_fields) + ["-" + s for s in sort_fields] + + def get(self, request, *args, **kwargs): + self.form = PackageSearchForm(data=request.GET) + return super(SearchListView, self).get(request, *args, **kwargs) + + def get_queryset(self): + packages = Package.objects.normal() + if self.form.is_valid(): + packages = parse_form(self.form, packages) + sort = self.form.cleaned_data['sort'] + if sort in self.allowed_sort: + packages = packages.order_by(sort) + else: + packages = packages.order_by('pkgname') + return packages + + # Form had errors so don't return any results + return Package.objects.none() + + def get_paginate_by(self, queryset): + limit = 50 + if self.form.is_valid(): + asked_limit = self.form.cleaned_data['limit'] if asked_limit and asked_limit < 0: limit = None elif asked_limit: limit = asked_limit - sort = form.cleaned_data['sort'] - else: - # Form had errors, don't return any results, just the busted form - packages = Package.objects.none() - else: - form = PackageSearchForm() - - current_query = request.GET.urlencode() - page_dict = { - 'search_form': form, - 'current_query': current_query - } - allowed_sort = ["arch", "repo", "pkgname", "pkgbase", - "compressed_size", "installed_size", - "build_date", "last_update", "flag_date"] - allowed_sort += ["-" + s for s in allowed_sort] - if sort in allowed_sort: - packages = packages.order_by(sort) - page_dict['sort'] = sort - else: - packages = packages.order_by('pkgname') - - return list_detail.object_list(request, packages, - template_name="packages/search.html", - page=page, - paginate_by=limit, - template_object_name="package", - extra_context=page_dict) + return limit + + def get_context_data(self, **kwargs): + context = super(SearchListView, self).get_context_data(**kwargs) + context['current_query'] = self.request.GET.urlencode() + context['search_form'] = self.form + return context def search_json(request): -- cgit v1.2.3-2-g168b From 4ad43fd8165834b26914ff8ba0666ce96267205b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 00:25:19 -0500 Subject: Fix broken hidden input sort field on search form Signed-off-by: Dan McGee --- packages/views/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/search.py b/packages/views/search.py index 9750894a..497d9bca 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -40,7 +40,7 @@ class PackageSearchForm(forms.Form): name = forms.CharField(required=False) desc = forms.CharField(required=False) q = forms.CharField(required=False) - sort = forms.CharField(required=False) + sort = forms.CharField(required=False, widget=forms.HiddenInput()) maintainer = forms.ChoiceField(required=False) packager = forms.ChoiceField(required=False) last_update = forms.DateField(required=False, widget=AdminDateWidget(), -- cgit v1.2.3-2-g168b From 0b7939ae1a1e3ce55ee458d24fd5946542d9c14a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 19:01:45 -0500 Subject: Rework package details dispatch code We had a variety of fallback paths that we took if a details page didn't exist for the combination of URL parts passed in. Before I go adding a few more possibilities, rework this so it is more flexible. It is now as simple as adding a method to the dispatch options list in order to have further fallback options. Signed-off-by: Dan McGee --- packages/views/display.py | 82 ++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 37 deletions(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index 585e0e4e..1ea9ea48 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -14,10 +14,10 @@ from ..utils import get_group_info, PackageJSONEncoder def split_package_details(request, name, repo, arch): - arch = get_object_or_404(Arch, name=arch) + '''Check if we have a split package (e.g. pkgbase) value matching this + name. If so, we can show a listing page for the entire set of packages.''' arches = [ 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, repo__staging=repo.staging, arch__in=arches).order_by('pkgname') @@ -39,15 +39,13 @@ CUTOFF = datetime.timedelta(days=60) def recently_removed_package(request, name, repo, arch, cutoff=CUTOFF): - '''We're just steps away from raising a 404, but check our packages update - table first to see if this package has existed in this repo before. If so, - we can show a 410 Gone page and point the requester in the right - direction.''' - arch = get_object_or_404(Arch, name=arch) + '''Check our packages update table to see if this package has existed in + this repo before. If so, we can show a 410 Gone page and point the + requester in the right direction.''' arches = [ arch ] arches.extend(Arch.objects.filter(agnostic=True)) match = Update.objects.select_related('arch', 'repo').filter( - pkgname=name, repo__name__iexact=repo, arch__in=arches) + pkgname=name, repo=repo, arch__in=arches) if cutoff is not None: when = now() - cutoff match = match.filter(created__gte=when) @@ -59,43 +57,53 @@ def recently_removed_package(request, name, repo, arch, cutoff=CUTOFF): return None +def redirect_agnostic(request, name, repo, arch): + '''For arch='any' packages, we can issue a redirect to them if we have a + single non-ambiguous option by changing the arch to match any arch-agnostic + package.''' + if not arch.agnostic: + # limit to 2 results, we only need to know whether there is anything + # except only one matching result + pkgs = Package.objects.select_related( + 'arch', 'repo', 'packager').filter(pkgname=name, + repo=repo, arch__agnostic=True)[:2] + if len(pkgs) == 1: + return redirect(pkgs[0], permanent=True) + return None + + +def redirect_to_search(request, name, repo, arch): + pkg_data = [ + ('arch', arch.lower()), + ('repo', repo.lower()), + ('q', name), + ] + # only include non-blank values in the query we generate + pkg_data = [(x, y.encode('utf-8')) for x, y in pkg_data if y] + return redirect("/packages/?%s" % urlencode(pkg_data)) + + def details(request, name='', repo='', arch=''): if all([name, repo, arch]): + arch_obj = get_object_or_404(Arch, name=arch) + repo_obj = get_object_or_404(Repo, name__iexact=repo) try: pkg = Package.objects.select_related( 'arch', 'repo', 'packager').get(pkgname=name, - repo__name__iexact=repo, arch__name=arch) + repo=repo_obj, arch=arch_obj) return render(request, 'packages/details.html', {'pkg': pkg}) except Package.DoesNotExist: - arch_obj = get_object_or_404(Arch, name=arch) - # for arch='any' packages, we can issue a redirect to them if we - # have a single non-ambiguous option by changing the arch to match - # any arch-agnostic package - if not arch_obj.agnostic: - pkgs = Package.objects.select_related( - 'arch', 'repo', 'packager').filter(pkgname=name, - repo__name__iexact=repo, arch__agnostic=True) - if len(pkgs) == 1: - return redirect(pkgs[0], permanent=True) - # do we have a split package matching this criteria? - ret = split_package_details(request, name, repo, arch) - if ret is None: - # maybe we have a recently-removed package? - ret = recently_removed_package(request, name, repo, arch) - if ret is not None: - return ret - else: - # we've tried everything at this point, nothing to see - raise Http404 + # attempt a variety of fallback options before 404ing + options = (redirect_agnostic, split_package_details, + recently_removed_package) + for method in options: + ret = method(request, name, repo_obj, arch_obj) + if ret: + return ret + # we've tried everything at this point, nothing to see + raise Http404 else: - pkg_data = [ - ('arch', arch.lower()), - ('repo', repo.lower()), - ('q', name), - ] - # only include non-blank values in the query we generate - pkg_data = [(x, y.encode('utf-8')) for x, y in pkg_data if y] - return redirect("/packages/?%s" % urlencode(pkg_data)) + return redirect_to_search(request, name, repo, arch) def groups(request, arch=None): -- cgit v1.2.3-2-g168b From 5f410c000eaca4b5b25664f4dfb59cbe85ea034e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 19:12:27 -0500 Subject: Add package details redirect for package replacements This makes sense if there is only one available replacement. We could get more sophisticated and show the removed page if there are multiple replacements available. Additionally, automatically redirect if there was only one matching package for a given package update deletion object. Signed-off-by: Dan McGee --- packages/views/display.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index 1ea9ea48..d6922314 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -51,12 +51,24 @@ def recently_removed_package(request, name, repo, arch, cutoff=CUTOFF): match = match.filter(created__gte=when) try: match = match.latest() + elsewhere = match.elsewhere() + if len(elsewhere) == 1: + return redirect(elsewhere[0]) return render(request, 'packages/removed.html', {'update': match, }, status=410) except Update.DoesNotExist: return None +def replaced_package(request, name, repo, arch): + '''Check our package replacements to see if this is a package we used to + have but no longer do.''' + match = Package.objects.filter(replaces__name=name, repo=repo, arch=arch) + if len(match) == 1: + return redirect(match[0], permanent=True) + return None + + def redirect_agnostic(request, name, repo, arch): '''For arch='any' packages, we can issue a redirect to them if we have a single non-ambiguous option by changing the arch to match any arch-agnostic @@ -95,7 +107,7 @@ def details(request, name='', repo='', arch=''): except Package.DoesNotExist: # attempt a variety of fallback options before 404ing options = (redirect_agnostic, split_package_details, - recently_removed_package) + recently_removed_package, replaced_package) for method in options: ret = method(request, name, repo_obj, arch_obj) if ret: -- cgit v1.2.3-2-g168b From 5f85a1240da14e57760c2ba6585ae943d7a1d8c2 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 19:31:44 -0500 Subject: Reuse removed template for packages with multiple replacements For example, bitcoin-git in the Arch repos is currently marked replaced by both bitcoin-qt and bitcoin-daemon. This allows us to show a page with both options listed instead of a blank 404 page. Signed-off-by: Dan McGee --- packages/views/display.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index d6922314..8adf3bee 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -50,12 +50,19 @@ def recently_removed_package(request, name, repo, arch, cutoff=CUTOFF): when = now() - cutoff match = match.filter(created__gte=when) try: - match = match.latest() - elsewhere = match.elsewhere() + update = match.latest() + elsewhere = update.elsewhere() if len(elsewhere) == 1: return redirect(elsewhere[0]) - return render(request, 'packages/removed.html', - {'update': match, }, status=410) + context = { + 'update': update, + 'elsewhere': elsewhere, + 'name': name, + 'version': update.old_version, + 'arch': arch, + 'repo': repo, + } + return render(request, 'packages/removed.html', context, status=410) except Update.DoesNotExist: return None @@ -66,6 +73,15 @@ def replaced_package(request, name, repo, arch): match = Package.objects.filter(replaces__name=name, repo=repo, arch=arch) if len(match) == 1: return redirect(match[0], permanent=True) + elif len(match) > 1: + context = { + 'elsewhere': match, + 'name': name, + 'version': '', + 'arch': arch, + 'repo': repo, + } + return render(request, 'packages/removed.html', context, status=410) return None -- cgit v1.2.3-2-g168b From f5d3c02eb14ea8b0018e17fa9be9c511ad7ebff9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 19:49:37 -0500 Subject: Revert "Fall back to 410 Gone for package files view as well" This reverts commit 9ab460c53a1ac4c79da6f05f2850ee21beedbab2. This seemed like the right thing to do, but it doesn't really play well with our more general dispatch framework we now do on the package details pages. Just let it 404 like it always did, as these pages are less essential. We can perhaps add a full dispatcher later if we really feel the need. --- packages/views/display.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index 8adf3bee..b5cd643a 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -165,16 +165,8 @@ def group_details(request, arch, name): def files(request, name, repo, arch): - try: - pkg = Package.objects.get(pkgname=name, - repo__name__iexact=repo, arch__name=arch) - except Package.DoesNotExist: - # this may have been deleted recently, so follow the same logic as we - # do on the package details page if possible - ret = recently_removed_package(request, name, repo, arch) - if ret is not None: - return ret - raise Http404 + pkg = get_object_or_404(Package, + pkgname=name, repo__name__iexact=repo, arch__name=arch) # files are inserted in sorted order, so preserve that fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') dir_count = sum(1 for f in fileslist if f.is_directory) -- cgit v1.2.3-2-g168b From 686942b8788fa43031b3999ac00d60baadc82f53 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 19:56:49 -0500 Subject: Declare 'enums' at class scope Signed-off-by: Dan McGee --- packages/models.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 5b48b30f..1d538cce 100644 --- a/packages/models.py +++ b/packages/models.py @@ -196,13 +196,6 @@ class FlagRequest(models.Model): return u'%s from %s on %s' % (self.pkgbase, self.who(), self.created) -UPDATE_ACTION_CHOICES = ( - (ADDITION, 'Addition'), - (CHANGE, 'Change'), - (DELETION, 'Deletion'), -) - - class UpdateManager(models.Manager): def log_update(self, old_pkg, new_pkg): '''Utility method to help log an update. This will determine the type @@ -249,6 +242,12 @@ class UpdateManager(models.Manager): class Update(models.Model): + UPDATE_ACTION_CHOICES = ( + (ADDITION, 'Addition'), + (CHANGE, 'Change'), + (DELETION, 'Deletion'), + ) + package = models.ForeignKey(Package, related_name="updates", null=True, on_delete=models.SET_NULL) repo = models.ForeignKey(Repo, related_name="updates") -- cgit v1.2.3-2-g168b From 71859672267ccfc15e31398c5d86f5c0f69f0ed7 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 20:01:03 -0500 Subject: Use a raw ID field for package Update package Signed-off-by: Dan McGee --- packages/admin.py | 1 + 1 file changed, 1 insertion(+) (limited to 'packages') diff --git a/packages/admin.py b/packages/admin.py index d43cecce..51c6fb06 100644 --- a/packages/admin.py +++ b/packages/admin.py @@ -44,6 +44,7 @@ class UpdateAdmin(admin.ModelAdmin): search_fields = ('pkgname',) ordering = ('-created',) date_hierarchy = 'created' + raw_id_fields = ('package',) admin.site.register(PackageRelation, PackageRelationAdmin) -- cgit v1.2.3-2-g168b From 566a9803dd4928fa2145ef14da2d59d2631eeb05 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 20:07:15 -0500 Subject: Add new deptype column to package depends This is more flexible than our existing 'optional' boolean and will allow us to import check and make depends into the database as well as what we are already doing. Signed-off-by: Dan McGee --- .../0020_auto__add_field_depend_deptype.py | 212 +++++++++++++++++++++ packages/models.py | 9 + 2 files changed, 221 insertions(+) create mode 100644 packages/migrations/0020_auto__add_field_depend_deptype.py (limited to 'packages') diff --git a/packages/migrations/0020_auto__add_field_depend_deptype.py b/packages/migrations/0020_auto__add_field_depend_deptype.py new file mode 100644 index 00000000..4cc5bc17 --- /dev/null +++ b/packages/migrations/0020_auto__add_field_depend_deptype.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.add_column('packages_depend', 'deptype', + self.gf('django.db.models.fields.CharField')(default='D', max_length=1), + keep_default=True) + + def backwards(self, orm): + db.delete_column('packages_depend', 'deptype') + + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'deptype': ('django.db.models.fields.CharField', [], {'default': "'D'", 'max_length': '1'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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', [], {'db_index': 'True'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 1d538cce..b3752b6c 100644 --- a/packages/models.py +++ b/packages/models.py @@ -403,10 +403,19 @@ class RelatedToBase(models.Model): class Depend(RelatedToBase): + DEPTYPE_CHOICES = ( + ('D', 'Depend'), + ('O', 'Optional Depend'), + ('M', 'Make Depend'), + ('C', 'Check Depend'), + ) + pkg = models.ForeignKey(Package, related_name='depends') comparison = models.CharField(max_length=255, default='') optional = models.BooleanField(default=False) description = models.TextField(null=True, blank=True) + deptype = models.CharField(max_length=1, default='D', + choices=DEPTYPE_CHOICES) class Conflict(RelatedToBase): -- cgit v1.2.3-2-g168b From a64bbbd4139d91cbbca10d804067cbd87a95872d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 20:27:43 -0500 Subject: Make adjustments for optional -> deptype conversion Very little dealt directly with this field. Signed-off-by: Dan McGee --- packages/migrations/0021_migrate_optional_deps.py | 210 ++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 packages/migrations/0021_migrate_optional_deps.py (limited to 'packages') diff --git a/packages/migrations/0021_migrate_optional_deps.py b/packages/migrations/0021_migrate_optional_deps.py new file mode 100644 index 00000000..f6652ce1 --- /dev/null +++ b/packages/migrations/0021_migrate_optional_deps.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + orm['packages.Depend'].objects.filter(optional=False).update(deptype='D') + orm['packages.Depend'].objects.filter(optional=True).update(deptype='O') + + def backwards(self, orm): + orm['packages.Depend'].objects.all().update(deptype='D') + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'deptype': ('django.db.models.fields.CharField', [], {'default': "'D'", 'max_length': '1'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'optional': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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', [], {'db_index': 'True'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] + symmetrical = True -- cgit v1.2.3-2-g168b From 4c02b11cd6c53e122e1c919b64e28646960c5eda Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 31 Jul 2012 20:29:53 -0500 Subject: Remove optional package depends column This is now completely replaced by the deptype column. Signed-off-by: Dan McGee --- .../0022_auto__del_field_depend_optional.py | 211 +++++++++++++++++++++ packages/models.py | 1 - 2 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 packages/migrations/0022_auto__del_field_depend_optional.py (limited to 'packages') diff --git a/packages/migrations/0022_auto__del_field_depend_optional.py b/packages/migrations/0022_auto__del_field_depend_optional.py new file mode 100644 index 00000000..8e65ccb1 --- /dev/null +++ b/packages/migrations/0022_auto__del_field_depend_optional.py @@ -0,0 +1,211 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.delete_column('packages_depend', 'optional') + + def backwards(self, orm): + db.add_column('packages_depend', 'optional', + self.gf('django.db.models.fields.BooleanField')(default=False), + keep_default=False) + + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'deptype': ('django.db.models.fields.CharField', [], {'default': "'D'", 'max_length': '1'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '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': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Repo']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + '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', [], {'db_index': 'True'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index b3752b6c..65aa8f4a 100644 --- a/packages/models.py +++ b/packages/models.py @@ -412,7 +412,6 @@ class Depend(RelatedToBase): pkg = models.ForeignKey(Package, related_name='depends') comparison = models.CharField(max_length=255, default='') - optional = models.BooleanField(default=False) description = models.TextField(null=True, blank=True) deptype = models.CharField(max_length=1, default='D', choices=DEPTYPE_CHOICES) -- cgit v1.2.3-2-g168b From f61e61c8a6fc0753359963a836bf65a3a8b1981e Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 4 Aug 2012 15:07:03 -0500 Subject: Include description in Depend unicode() output This overrides the base class __unicode__ method. Signed-off-by: Dan McGee --- packages/models.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 65aa8f4a..1959183f 100644 --- a/packages/models.py +++ b/packages/models.py @@ -416,6 +416,13 @@ class Depend(RelatedToBase): deptype = models.CharField(max_length=1, default='D', choices=DEPTYPE_CHOICES) + def __unicode__(self): + '''For depends, we may also have a description and a modifier.''' + to_str = super(Depend, self).__unicode__() + if self.description: + return u'%s: %s' % (to_str, self.description) + return to_str + class Conflict(RelatedToBase): pkg = models.ForeignKey(Package, related_name='conflicts') -- cgit v1.2.3-2-g168b From 2cb15547b0d3dfee46f3d51341bebd0b1251c0f9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 7 Aug 2012 01:53:26 -0500 Subject: Add ctypes-based wrapper to ALPM vercmp API This will allow us to do some additional stuff on systems that have libalpm available; namely we can use the version comparison logic it provides to do smarter filtering etc. of fields that use comparsion operations. Signed-off-by: Dan McGee --- packages/alpm.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 packages/alpm.py (limited to 'packages') diff --git a/packages/alpm.py b/packages/alpm.py new file mode 100644 index 00000000..a7f4c3b5 --- /dev/null +++ b/packages/alpm.py @@ -0,0 +1,68 @@ +import ctypes +from ctypes.util import find_library +import operator + + +def load_alpm(name=None): + # Load the alpm library and set up some of the functions we might use + if name == None: + name = find_library('alpm') + try: + alpm = ctypes.cdll.LoadLibrary(name) + except OSError: + return None + alpm.alpm_version.argtypes = () + alpm.alpm_version.restype = ctypes.c_char_p + alpm.alpm_pkg_vercmp.argtypes = (ctypes.c_char_p, ctypes.c_char_p) + alpm.alpm_pkg_vercmp.restype = ctypes.c_int + return alpm + + +ALPM = load_alpm() + +class AlpmAPI(object): + OPERATOR_MAP = { + '=': operator.eq, + '==': operator.eq, + '!=': operator.ne, + '<': operator.lt, + '<=': operator.le, + '>': operator.gt, + '>=': operator.ge, + } + + def __init__(self): + self.alpm = ALPM + self.available = ALPM is not None + + def version(self): + if not self.available: + return None + return ALPM.alpm_version() + + def vercmp(self, ver1, ver2): + if not self.available: + return None + return ALPM.alpm_pkg_vercmp(str(ver1), str(ver2)) + + def compare_versions(self, ver1, oper, ver2): + func = self.OPERATOR_MAP.get(oper, None) + if func is None: + raise Exception("Invalid operator %s specified" % oper) + if not self.available: + return None + res = self.vercmp(ver1, ver2) + return func(res, 0) + + +def main(): + api = AlpmAPI() + print api.version() + print api.vercmp(1, 2) + print api.compare_versions(1, '<', 2) + + +if __name__ == '__main__': + main() + +# vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From f63a70f5118781fe34d82ae0d59c911f0ea28d1d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 7 Aug 2012 19:36:07 -0500 Subject: Make use of new ctypes ALPM API We can use this when filtering down lists of depends, required by, conflicts, etc. to ensure we are honoring the version specifications the same way pacman would. Signed-off-by: Dan McGee --- packages/models.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 1959183f..403dfef0 100644 --- a/packages/models.py +++ b/packages/models.py @@ -7,6 +7,7 @@ from django.contrib.auth.models import User from main.models import Arch, Repo, Package from main.utils import set_created_field, database_vendor +from packages.alpm import AlpmAPI class PackageRelation(models.Model): @@ -341,6 +342,13 @@ class RelatedToBase(models.Model): # make sure we match architectures if possible arches = self.pkg.applicable_arches() pkgs = pkgs.filter(arch__in=arches) + # if we have a comparison operation, make sure the packages we grab + # actually satisfy the requirements + if self.comparison and self.version: + alpm = AlpmAPI() + pkgs = [pkg for pkg in pkgs if not alpm.available or + alpm.compare_versions(pkg.full_version, self.comparison, + self.version)] if len(pkgs) == 0: # couldn't find a package in the DB # it should be a virtual depend (or a removed package) @@ -373,6 +381,21 @@ class RelatedToBase(models.Model): arches = self.pkg.applicable_arches() pkgs = pkgs.filter(arch__in=arches) + # If we have a comparison operation, make sure the packages we grab + # actually satisfy the requirements. + alpm = AlpmAPI() + if alpm.available and self.comparison and self.version: + pkgs = pkgs.prefetch_related('provides') + new_pkgs = [] + for package in pkgs: + for provide in package.provides.all(): + if provide.name != self.name: + continue + if alpm.compare_versions(provide.version, + self.comparison, self.version): + new_pkgs.append(package) + pkgs = new_pkgs + # Logic here is to filter out packages that are in multiple repos if # they are not requested. For example, if testing is False, only show a # testing package if it doesn't exist in a non-testing repo. -- cgit v1.2.3-2-g168b From b7a03d89d126989bf53005404759482e17163991 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 7 Aug 2012 23:22:18 -0500 Subject: Push more default values down into the database This makes it easier to do manual manipulation/insertion/etc. at the database level, as well as just making things act more sane from an overall software stack perspective. Signed-off-by: Dan McGee --- packages/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 403dfef0..11fd0a66 100644 --- a/packages/models.py +++ b/packages/models.py @@ -176,7 +176,7 @@ class FlagRequest(models.Model): ip_address = models.GenericIPAddressField(verbose_name='IP address', unpack_ipv4=True) pkgbase = models.CharField(max_length=255, db_index=True) - version = models.CharField(max_length=255, default='') + version = models.CharField(max_length=255) repo = models.ForeignKey(Repo) num_packages = models.PositiveIntegerField('number of packages', default=1) message = models.TextField('message to developer', blank=True) -- cgit v1.2.3-2-g168b From 064a1b7699ef8f65ee1c123f93cd8da02e843dea Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 8 Aug 2012 19:12:06 -0500 Subject: Fix alpm ctypes interface on systems not having alpm Signed-off-by: Dan McGee --- packages/alpm.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/alpm.py b/packages/alpm.py index a7f4c3b5..3762ea68 100644 --- a/packages/alpm.py +++ b/packages/alpm.py @@ -5,16 +5,23 @@ import operator def load_alpm(name=None): # Load the alpm library and set up some of the functions we might use - if name == None: + if name is None: name = find_library('alpm') + if name is None: + # couldn't locate the correct library + return None try: alpm = ctypes.cdll.LoadLibrary(name) except OSError: return None - alpm.alpm_version.argtypes = () - alpm.alpm_version.restype = ctypes.c_char_p - alpm.alpm_pkg_vercmp.argtypes = (ctypes.c_char_p, ctypes.c_char_p) - alpm.alpm_pkg_vercmp.restype = ctypes.c_int + try: + alpm.alpm_version.argtypes = () + alpm.alpm_version.restype = ctypes.c_char_p + alpm.alpm_pkg_vercmp.argtypes = (ctypes.c_char_p, ctypes.c_char_p) + alpm.alpm_pkg_vercmp.restype = ctypes.c_int + except AttributeError: + return None + return alpm -- cgit v1.2.3-2-g168b From 411ccfb3c74c521969ca6b68459289134976547d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 8 Aug 2012 22:04:07 -0500 Subject: Begin split of flag request version column into parts Not sure why on only this one I decided to put all three parts in the same column. We don't do this anywhere else. Signed-off-by: Dan McGee --- .../0023_split_flag_req_version_field.py | 222 +++++++++++++++++++++ packages/models.py | 14 ++ 2 files changed, 236 insertions(+) create mode 100644 packages/migrations/0023_split_flag_req_version_field.py (limited to 'packages') diff --git a/packages/migrations/0023_split_flag_req_version_field.py b/packages/migrations/0023_split_flag_req_version_field.py new file mode 100644 index 00000000..b3d6c05c --- /dev/null +++ b/packages/migrations/0023_split_flag_req_version_field.py @@ -0,0 +1,222 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.add_column('packages_flagrequest', 'pkgver', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + db.add_column('packages_flagrequest', 'pkgrel', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + db.add_column('packages_flagrequest', 'epoch', + self.gf('django.db.models.fields.PositiveIntegerField')(default=0), + keep_default=True) + + def backwards(self, orm): + db.delete_column('packages_flagrequest', 'pkgver') + db.delete_column('packages_flagrequest', 'pkgrel') + db.delete_column('packages_flagrequest', 'epoch') + + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'deptype': ('django.db.models.fields.CharField', [], {'default': "'D'", 'max_length': '1'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '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': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + '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']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'version': ('django.db.models.fields.CharField', [], {'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', [], {'db_index': 'True'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 11fd0a66..03da27ec 100644 --- a/packages/models.py +++ b/packages/models.py @@ -177,6 +177,9 @@ class FlagRequest(models.Model): unpack_ipv4=True) pkgbase = models.CharField(max_length=255, db_index=True) version = models.CharField(max_length=255) + pkgver = models.CharField(max_length=255) + pkgrel = models.CharField(max_length=255) + epoch = models.PositiveIntegerField(default=0) repo = models.ForeignKey(Repo) num_packages = models.PositiveIntegerField('number of packages', default=1) message = models.TextField('message to developer', blank=True) @@ -193,6 +196,17 @@ class FlagRequest(models.Model): return self.user.get_full_name() return self.user_email + @property + def full_version(self): + # Difference here from other implementations at the moment: we need to + # handle the case of pkgver and pkgrel being null as this table didn't + # originally have version columns. + if self.pkgver == '' and self.pkgrel == '': + return u'' + 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 from %s on %s' % (self.pkgbase, self.who(), self.created) -- cgit v1.2.3-2-g168b From 241ff8fbd79f9f17cd326a34eb39096851f630ba Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 8 Aug 2012 22:07:06 -0500 Subject: Extract parse_version function from reporead logic Signed-off-by: Dan McGee --- packages/utils.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index 6d54d71a..d4b4e611 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -1,6 +1,7 @@ from collections import defaultdict from itertools import chain from operator import itemgetter +import re from django.core.serializers.json import DjangoJSONEncoder from django.db import connection @@ -14,6 +15,23 @@ from .models import (PackageGroup, PackageRelation, License, Depend, Conflict, Provision, Replacement, SignoffSpecification, Signoff, DEFAULT_SIGNOFF_SPEC) + +VERSION_RE = re.compile(r'^((\d+):)?(.+)-([^-]+)$') + + +def parse_version(version): + match = VERSION_RE.match(version) + if not match: + return None, None, 0 + ver = match.group(3) + rel = match.group(4) + if match.group(2): + epoch = int(match.group(2)) + else: + epoch = 0 + return ver, rel, epoch + + @cache_function(127) def get_group_info(include_arches=None): raw_groups = PackageGroup.objects.values_list( -- cgit v1.2.3-2-g168b From ad05f3eb2c8511c63dbdc9378bf3561ab949e940 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 8 Aug 2012 22:07:38 -0500 Subject: PEP8 cleanups in package utils Signed-off-by: Dan McGee --- packages/utils.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index d4b4e611..d95c015f 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -36,7 +36,7 @@ def parse_version(version): def get_group_info(include_arches=None): raw_groups = PackageGroup.objects.values_list( 'name', 'pkg__arch__name').order_by('name').annotate( - cnt=Count('pkg'), last_update=Max('pkg__last_update')) + cnt=Count('pkg'), last_update=Max('pkg__last_update')) # now for post_processing. we need to seperate things out and add # the count in for 'any' to all of the other architectures. group_mapping = {} @@ -71,6 +71,7 @@ def get_group_info(include_arches=None): groups.extend(val.itervalues()) return sorted(groups, key=itemgetter('name', 'arch')) + def get_split_packages_info(): '''Return info on split packages that do not have an actual package name matching the split pkgbase.''' @@ -276,6 +277,7 @@ def approved_by_signoffs(signoffs, spec): return good_signoffs >= spec.required return False + class PackageSignoffGroup(object): '''Encompasses all packages in testing with the same pkgbase.''' def __init__(self, packages): @@ -375,6 +377,7 @@ SELECT DISTINCT s.id AND p.repo_id IN (%s) """ + def get_current_signoffs(repos): '''Returns a mapping of pkgbase -> signoff objects for the given repos.''' cursor = connection.cursor() @@ -389,6 +392,7 @@ def get_current_signoffs(repos): signoffs = Signoff.objects.select_related('user').in_bulk(to_fetch) return signoffs.values() + def get_current_specifications(repos): '''Returns a mapping of pkgbase -> signoff specification objects for the given repos.''' @@ -401,6 +405,7 @@ def get_current_specifications(repos): to_fetch = [row[0] for row in results] return SignoffSpecification.objects.in_bulk(to_fetch).values() + def get_target_repo_map(repos): sql = """ SELECT DISTINCT p1.pkgbase, r.name @@ -421,6 +426,7 @@ SELECT DISTINCT p1.pkgbase, r.name cursor.execute(sql, params) return dict(cursor.fetchall()) + def get_signoff_groups(repos=None, user=None): if repos is None: repos = Repo.objects.filter(testing=True) @@ -458,12 +464,12 @@ def get_signoff_groups(repos=None, user=None): class PackageJSONEncoder(DjangoJSONEncoder): - pkg_attributes = [ 'pkgname', 'pkgbase', 'repo', 'arch', 'pkgver', + pkg_attributes = ['pkgname', 'pkgbase', 'repo', 'arch', 'pkgver', 'pkgrel', 'epoch', 'pkgdesc', 'url', 'filename', 'compressed_size', 'installed_size', 'build_date', 'last_update', 'flag_date', - 'maintainers', 'packager' ] - pkg_list_attributes = [ 'groups', 'licenses', 'conflicts', - 'provides', 'replaces', 'depends' ] + 'maintainers', 'packager'] + pkg_list_attributes = ['groups', 'licenses', 'conflicts', + 'provides', 'replaces', 'depends'] def default(self, obj): if hasattr(obj, '__iter__'): @@ -488,5 +494,4 @@ class PackageJSONEncoder(DjangoJSONEncoder): return obj.username return super(PackageJSONEncoder, self).default(obj) - # vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From b425b192e12afd0584bbffc9ff1d997a330bcd5a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 8 Aug 2012 22:21:05 -0500 Subject: Migrate flag request version info to new format Signed-off-by: Dan McGee --- packages/admin.py | 4 +- .../migrations/0024_move_flag_req_version_info.py | 218 +++++++++++++++++++++ packages/views/flag.py | 10 +- 3 files changed, 226 insertions(+), 6 deletions(-) create mode 100644 packages/migrations/0024_move_flag_req_version_info.py (limited to 'packages') diff --git a/packages/admin.py b/packages/admin.py index 51c6fb06..5e32dbb4 100644 --- a/packages/admin.py +++ b/packages/admin.py @@ -12,8 +12,8 @@ class PackageRelationAdmin(admin.ModelAdmin): class FlagRequestAdmin(admin.ModelAdmin): - list_display = ('pkgbase', 'version', 'repo', 'created', 'who', 'is_spam', - 'is_legitimate', 'message') + list_display = ('pkgbase', 'full_version', 'repo', 'created', 'who', + 'is_spam', 'is_legitimate', 'message') list_filter = ('is_spam', 'is_legitimate', 'repo') search_fields = ('pkgbase', 'user_email', 'message') ordering = ('-created',) diff --git a/packages/migrations/0024_move_flag_req_version_info.py b/packages/migrations/0024_move_flag_req_version_info.py new file mode 100644 index 00000000..3be4654a --- /dev/null +++ b/packages/migrations/0024_move_flag_req_version_info.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import DataMigration +from django.db import models + +from packages.utils import parse_version + + +class Migration(DataMigration): + + def forwards(self, orm): + for pk, version in orm.FlagRequest.objects.exclude( + version='').values_list('pk', 'version'): + ver, rel, epoch = parse_version(version) + orm.FlagRequest.objects.filter(pk=pk).update( + pkgver=ver, pkgrel=rel, epoch=epoch) + + def backwards(self, orm): + orm.FlagRequest.objects.all().update(pkgver='', pkgrel='', epoch=0) + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'deptype': ('django.db.models.fields.CharField', [], {'default': "'D'", 'max_length': '1'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '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': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + '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']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), + 'version': ('django.db.models.fields.CharField', [], {'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', [], {'db_index': 'True'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] + symmetrical = True diff --git a/packages/views/flag.py b/packages/views/flag.py index b9542a62..16f5f202 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -59,11 +59,12 @@ def flag(request, name, repo, arch): flagged_pkgs = list(pkgs) # find a common version if there is one available to store - versions = set(pkg.full_version for pkg in flagged_pkgs) + versions = set((pkg.pkgver, pkg.pkgrel, pkg.epoch) + for pkg in flagged_pkgs) if len(versions) == 1: version = versions.pop() else: - version = '' + version = ('', '', 0) message = form.cleaned_data['message'] ip_addr = request.META.get('REMOTE_ADDR') @@ -77,11 +78,12 @@ def flag(request, name, repo, arch): current_time = now() pkgs.update(flag_date=current_time) # store our flag request + # TODO flag_request = FlagRequest(created=current_time, user_email=email, message=message, ip_address=ip_addr, pkgbase=pkg.pkgbase, - version=version, repo=pkg.repo, - num_packages=len(flagged_pkgs)) + repo=pkg.repo, pkgver=version[0], pkgrel=version[1], + epoch=version[2], num_packages=len(flagged_pkgs)) if authenticated: flag_request.user = request.user flag_request.save() -- cgit v1.2.3-2-g168b From 32e3e24bbc26a44cbf6fce06cf802ee26f81aa48 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 8 Aug 2012 22:22:24 -0500 Subject: Drop old flag request version column Signed-off-by: Dan McGee --- .../0025_auto__del_field_flagrequest_version.py | 213 +++++++++++++++++++++ packages/models.py | 1 - 2 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 packages/migrations/0025_auto__del_field_flagrequest_version.py (limited to 'packages') diff --git a/packages/migrations/0025_auto__del_field_flagrequest_version.py b/packages/migrations/0025_auto__del_field_flagrequest_version.py new file mode 100644 index 00000000..963b0b12 --- /dev/null +++ b/packages/migrations/0025_auto__del_field_flagrequest_version.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.delete_column('packages_flagrequest', 'version') + + def backwards(self, orm): + db.add_column('packages_flagrequest', 'version', + self.gf('django.db.models.fields.CharField')(default='', max_length=255), + keep_default=False) + + + 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',)", 'unique_together': "(('pkgname', 'repo', 'arch'),)", 'object_name': 'Package', 'db_table': "'packages'"}, + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "orm['main.Arch']"}), + 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'compressed_size': ('main.fields.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.fields.PositiveBigIntegerField', [], {}), + 'last_update': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}), + 'packager_str': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pgp_signature': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'pkgdesc': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'on_delete': 'models.PROTECT', 'to': "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': '2'}), + '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.depend': { + 'Meta': {'ordering': "['name']", 'object_name': 'Depend'}, + 'comparison': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}), + 'deptype': ('django.db.models.fields.CharField', [], {'default': "'D'", 'max_length': '1'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + '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': "'depends'", 'to': "orm['main.Package']"}), + 'version': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}) + }, + 'packages.flagrequest': { + 'Meta': {'object_name': 'FlagRequest'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'is_legitimate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_spam': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'message': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'num_packages': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + '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']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}) + }, + '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', [], {'db_index': 'True'}), + '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']"}) + }, + 'packages.signoffspecification': { + 'Meta': {'object_name': 'SignoffSpecification'}, + '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', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'epoch': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'known_bad': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + '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']"}), + 'required': ('django.db.models.fields.PositiveIntegerField', [], {'default': '2'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}) + }, + 'packages.update': { + 'Meta': {'object_name': 'Update'}, + 'action_flag': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), + 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Arch']"}), + 'created': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'new_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'new_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'new_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_epoch': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}), + 'old_pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'old_pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Package']"}), + 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'updates'", 'to': "orm['main.Repo']"}) + } + } + + complete_apps = ['packages'] diff --git a/packages/models.py b/packages/models.py index 03da27ec..af428a77 100644 --- a/packages/models.py +++ b/packages/models.py @@ -176,7 +176,6 @@ class FlagRequest(models.Model): ip_address = models.GenericIPAddressField(verbose_name='IP address', unpack_ipv4=True) pkgbase = models.CharField(max_length=255, db_index=True) - version = models.CharField(max_length=255) pkgver = models.CharField(max_length=255) pkgrel = models.CharField(max_length=255) epoch = models.PositiveIntegerField(default=0) -- cgit v1.2.3-2-g168b From 3cb16e4784f492c50555e879ea6b07fd898b1c3d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 13 Aug 2012 09:34:11 -0500 Subject: Attempt to screen for useless out-of-date messages Things like ' ', '-', '.', etc. will no longer be accepted in this field. Signed-off-by: Dan McGee --- packages/views/flag.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/flag.py b/packages/views/flag.py index 16f5f202..33cec006 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -1,3 +1,5 @@ +import re + from django import forms from django.conf import settings from django.contrib.auth.decorators import permission_required @@ -30,6 +32,15 @@ class FlagForm(forms.Form): if auth: del self.fields['email'] + def clean_message(self): + data = self.cleaned_data['message'] + # make sure the message isn't garbage (only punctuation or whitespace) + # and ensure a certain minimum length + if re.match(r'^[^0-9A-Za-z]+$', data) or len(data) < 3: + raise forms.ValidationError( + "Enter a valid and useful out-of-date message.") + return data + @cache_page(3600) def flaghelp(request): @@ -78,7 +89,6 @@ def flag(request, name, repo, arch): current_time = now() pkgs.update(flag_date=current_time) # store our flag request - # TODO flag_request = FlagRequest(created=current_time, user_email=email, message=message, ip_address=ip_addr, pkgbase=pkg.pkgbase, -- cgit v1.2.3-2-g168b From a071d800c6a26d3efcdc0d32fe1adb1cde7e6f31 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 15 Aug 2012 08:22:01 -0500 Subject: Fix signoffs SQL query Although the old query returned the same results, the repos IN clause should really be a part of the WHERE, not the JOIN condition. Signed-off-by: Dan McGee --- packages/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index d95c015f..ee1b56b3 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -363,6 +363,7 @@ class PackageSignoffGroup(object): return u'%s-%s (%s): %d' % ( self.pkgbase, self.version, self.arch, len(self.signoffs)) + _SQL_SPEC_OR_SIGNOFF = """ SELECT DISTINCT s.id FROM %s s @@ -374,7 +375,7 @@ SELECT DISTINCT s.id AND s.arch_id = p.arch_id AND s.repo_id = p.repo_id ) - AND p.repo_id IN (%s) + WHERE p.repo_id IN (%s) """ -- cgit v1.2.3-2-g168b From 9189665c2c64c13a59b25b981633ae9e12cfcc92 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 15 Aug 2012 08:24:10 -0500 Subject: Ensure created is set when creating flag request via admin Signed-off-by: Dan McGee --- packages/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index af428a77..0bea21b1 100644 --- a/packages/models.py +++ b/packages/models.py @@ -482,7 +482,8 @@ class Replacement(RelatedToBase): # hook up some signals -for sender in (PackageRelation, SignoffSpecification, Signoff, Update): +for sender in (FlagRequest, PackageRelation, + SignoffSpecification, Signoff, Update): pre_save.connect(set_created_field, sender=sender, dispatch_uid="packages.models") -- cgit v1.2.3-2-g168b From f7289625000d0f83675fc5a70650b49707338dca Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 20 Aug 2012 21:19:02 -0500 Subject: Use case-insensitive search in opensearch suggestions There is no real good reason not to do this, since our packages are lowercased by convention. Signed-off-by: Dan McGee --- packages/views/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 038d40ac..f7952255 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -5,6 +5,7 @@ from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.models import User from django.core.cache import cache +from django.db.models import Q from django.http import HttpResponse from django.shortcuts import redirect, render from django.views.decorators.cache import cache_control @@ -47,8 +48,13 @@ def opensearch_suggest(request): hashlib.md5(search_term.encode('utf-8')).hexdigest() to_json = cache.get(cache_key, None) if to_json is None: - names = Package.objects.filter( - pkgname__startswith=search_term).values_list( + q = Q(pkgname__startswith=search_term) + lookup = search_term.lower() + if search_term != lookup: + # package names are lowercase by convention, so include that in + # search if original wasn't lowercase already + q |= Q(pkgname__startswith=lookup) + names = Package.objects.filter(q).values_list( 'pkgname', flat=True).order_by('pkgname').distinct()[:10] results = [search_term, list(names)] to_json = json.dumps(results, ensure_ascii=False) -- cgit v1.2.3-2-g168b From cd51842ce86c44eef4e3c3d5334aca13e234151a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 20 Aug 2012 23:50:21 -0500 Subject: Fix scm_link for Unicode characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This blew up with non-ASCII due to us trying to stuff 8-bit characters into the URL parameters where they aren't allowed. Tested with a package entered via the admin with pkgname and pkgbase set to 'αναζήτη'. Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index 9daecd96..a0ee8bee 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -91,11 +91,12 @@ def packager_link(user): @register.simple_tag def scm_link(package, operation): - parts = (package.repo.svn_root, operation, package.pkgbase) - linkbase = ( - "https://projects.archlinux.org/svntogit/%s.git/%s/trunk?" - "h=packages/%s") - return linkbase % tuple(urlquote(part) for part in parts) + parts = (package.repo.svn_root, operation) + url = "https://projects.archlinux.org/svntogit/%s.git/%s/trunk" % parts + data = { + 'h': 'packages/%s' % package.pkgbase + } + return link_encode(url, data) @register.simple_tag def get_wiki_link(package): -- cgit v1.2.3-2-g168b From 1c416b97e6a9f3d6bf5f8747bd10ed1ca9cad68c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 21 Aug 2012 00:02:57 -0500 Subject: Revert "Fix scm_link for Unicode characters" This reverts commit cd51842ce86c44eef4e3c3d5334aca13e234151a. It turns out cgit doesn't like it if you escape the '/' in the URL parameter when viewing the log (a clear upstream bug), but we need to be able to work around this. Example: 'extra%2Fkdelibs' and 'extra/kdelibs' are handled differently. --- packages/templatetags/package_extras.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index a0ee8bee..9daecd96 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -91,12 +91,11 @@ def packager_link(user): @register.simple_tag def scm_link(package, operation): - parts = (package.repo.svn_root, operation) - url = "https://projects.archlinux.org/svntogit/%s.git/%s/trunk" % parts - data = { - 'h': 'packages/%s' % package.pkgbase - } - return link_encode(url, data) + parts = (package.repo.svn_root, operation, package.pkgbase) + linkbase = ( + "https://projects.archlinux.org/svntogit/%s.git/%s/trunk?" + "h=packages/%s") + return linkbase % tuple(urlquote(part) for part in parts) @register.simple_tag def get_wiki_link(package): -- cgit v1.2.3-2-g168b From 90001d9ac781d07feba6120bfe64112481b9a5fb Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 21 Aug 2012 00:10:12 -0500 Subject: Style cleanups in package_extras Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index 9daecd96..1aedf677 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -9,6 +9,7 @@ from django.utils.html import escape register = template.Library() + def link_encode(url, query): # massage the data into all utf-8 encoded strings first, so urlencode # doesn't barf at the data we pass it @@ -16,6 +17,7 @@ def link_encode(url, query): data = urlencode(query).replace('&', '&') return "%s?%s" % (url, data) + @register.filter def url_unquote(original_url): try: @@ -27,6 +29,7 @@ def url_unquote(original_url): except UnicodeError: return original_url + class BuildQueryStringNode(template.Node): def __init__(self, sortfield): self.sortfield = sortfield @@ -34,7 +37,7 @@ class BuildQueryStringNode(template.Node): def render(self, context): qs = parse_qs(context['current_query']) - if qs.has_key('sort') and self.sortfield in qs['sort']: + if 'sort' in qs and self.sortfield in qs['sort']: if self.sortfield.startswith('-'): qs['sort'] = [self.sortfield[1:]] else: @@ -43,6 +46,7 @@ class BuildQueryStringNode(template.Node): qs['sort'] = [self.sortfield] return urlencode(qs, True).replace('&', '&') + @register.tag(name='buildsortqs') def do_buildsortqs(parser, token): try: @@ -55,15 +59,18 @@ def do_buildsortqs(parser, token): "%r tag's argument should be in quotes" % tagname) return BuildQueryStringNode(sortfield[1:-1]) + @register.simple_tag def pkg_details_link(pkg): link = '%s' return link % (pkg.get_absolute_url(), pkg.pkgname, pkg.pkgname) + @register.simple_tag def multi_pkg_details(pkgs): return ', '.join([pkg_details_link(pkg) for pkg in pkgs]) + @register.simple_tag def maintainer_link(user): if user: @@ -76,6 +83,7 @@ def maintainer_link(user): ) return '' + @register.simple_tag def packager_link(user): if user: @@ -97,6 +105,7 @@ def scm_link(package, operation): "h=packages/%s") return linkbase % tuple(urlquote(part) for part in parts) + @register.simple_tag def get_wiki_link(package): url = "https://wiki.archlinux.org/index.php/Special:Search" @@ -105,6 +114,7 @@ def get_wiki_link(package): } return link_encode(url, data) + @register.simple_tag def bugs_list(package): url = "https://bugs.archlinux.org/" @@ -115,6 +125,7 @@ def bugs_list(package): } return link_encode(url, data) + @register.simple_tag def bug_report(package): url = "https://bugs.archlinux.org/newtask" -- cgit v1.2.3-2-g168b From eb44404a17ae9feacdf2948a5cda958510ac2fed Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 21 Aug 2012 00:12:38 -0500 Subject: Fix scm_link in a way that doesn't make cgit barf This does what commit cd51842ce86 set out to do in a way that doesn't break cgit's not-so-hit query string parsing. Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index 1aedf677..d87a74a5 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -103,7 +103,7 @@ def scm_link(package, operation): linkbase = ( "https://projects.archlinux.org/svntogit/%s.git/%s/trunk?" "h=packages/%s") - return linkbase % tuple(urlquote(part) for part in parts) + return linkbase % tuple(urlquote(part.encode('utf-8')) for part in parts) @register.simple_tag -- cgit v1.2.3-2-g168b From f2f00b3c0474c4776e7a7f0e58162dc67ce2ca18 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Sat, 8 Sep 2012 17:08:21 +0200 Subject: p/v/flag: Add reply-to to out-of-date notifications Signed-off-by: Florian Pritz Signed-off-by: Dan McGee --- packages/views/flag.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/views/flag.py b/packages/views/flag.py index 33cec006..d7302f72 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -3,7 +3,7 @@ import re from django import forms from django.conf import settings from django.contrib.auth.decorators import permission_required -from django.core.mail import send_mail +from django.core.mail import send_mail, EmailMessage from django.db import transaction from django.shortcuts import get_object_or_404, redirect, render from django.template import loader, Context @@ -122,11 +122,13 @@ def flag(request, name, repo, arch): 'pkg': pkg, 'packages': flagged_pkgs, }) - send_mail(subject, + msg = EmailMessage(subject, tmpl.render(ctx), 'Arch Website Notification ', toemail, - fail_silently=True) + headers={"Reply-To": email } + ) + msg.send(fail_silently=True) return redirect('package-flag-confirmed', name=name, repo=repo, arch=arch) -- cgit v1.2.3-2-g168b From 10f5c27bf8124a3ccffb94930283b5062ad96cce Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 10 Sep 2012 08:27:23 -0500 Subject: Remove now unnecessary import Signed-off-by: Dan McGee --- packages/views/flag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/flag.py b/packages/views/flag.py index d7302f72..dadadd19 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -3,7 +3,7 @@ import re from django import forms from django.conf import settings from django.contrib.auth.decorators import permission_required -from django.core.mail import send_mail, EmailMessage +from django.core.mail import EmailMessage from django.db import transaction from django.shortcuts import get_object_or_404, redirect, render from django.template import loader, Context -- cgit v1.2.3-2-g168b From 7a0e6620c9c8782fbef37db15afc3ccebc642d19 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 25 Sep 2012 18:22:16 -0500 Subject: Don't show staging in package search repo listing This is temporary or at least a quick way to ensure regular users aren't confused by staging packages; later updates should re-enable display of this for logged in developers and trusted users. Signed-off-by: Dan McGee --- packages/views/search.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/search.py b/packages/views/search.py index 497d9bca..1fbe5694 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -56,8 +56,9 @@ class PackageSearchForm(forms.Form): def __init__(self, *args, **kwargs): super(PackageSearchForm, self).__init__(*args, **kwargs) + repos = Repo.objects.filter(staging=False) self.fields['repo'].choices = make_choice( - [repo.name for repo in Repo.objects.all()]) + [repo.name for repo in repos]) self.fields['arch'].choices = make_choice( [arch.name for arch in Arch.objects.all()]) self.fields['q'].widget.attrs.update({"size": "30"}) -- cgit v1.2.3-2-g168b From 44dc458a2b8ca500acda72b437a720dd4f57bedf Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 30 Sep 2012 01:40:25 -0500 Subject: Hide staging packages in search results This is for users that aren't logged in; developers will still see them. Signed-off-by: Dan McGee --- packages/views/search.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/views/search.py b/packages/views/search.py index 1fbe5694..99bf703a 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -55,8 +55,11 @@ class PackageSearchForm(forms.Form): initial=50) def __init__(self, *args, **kwargs): + show_staging = kwargs.pop('show_staging', False) super(PackageSearchForm, self).__init__(*args, **kwargs) - repos = Repo.objects.filter(staging=False) + repos = Repo.objects.all() + if not show_staging: + repos = repos.filter(staging=False) self.fields['repo'].choices = make_choice( [repo.name for repo in repos]) self.fields['arch'].choices = make_choice( @@ -129,11 +132,14 @@ class SearchListView(ListView): allowed_sort = list(sort_fields) + ["-" + s for s in sort_fields] def get(self, request, *args, **kwargs): - self.form = PackageSearchForm(data=request.GET) + self.form = PackageSearchForm(data=request.GET, + show_staging=self.request.user.is_authenticated()) return super(SearchListView, self).get(request, *args, **kwargs) def get_queryset(self): packages = Package.objects.normal() + if not self.request.user.is_authenticated(): + packages = packages.filter(repo__staging=False) if self.form.is_valid(): packages = parse_form(self.form, packages) sort = self.form.cleaned_data['sort'] @@ -174,9 +180,12 @@ def search_json(request): } if request.GET: - form = PackageSearchForm(data=request.GET) + form = PackageSearchForm(data=request.GET, + show_staging=request.user.is_authenticated()) if form.is_valid(): packages = Package.objects.normal() + if not request.user.is_authenticated(): + packages = packages.filter(repo__staging=False) packages = parse_form(form, packages)[:limit] container['results'] = packages container['valid'] = True -- cgit v1.2.3-2-g168b From feabc12d384a448614dbc8a9a51cd39ee63b4a83 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 30 Sep 2012 10:11:19 -0500 Subject: =?UTF-8?q?Fix=20usage=20of=20na=C3=AFve=20datetime=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dan McGee --- packages/views/search.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/views/search.py b/packages/views/search.py index 99bf703a..f7b8ed1d 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -1,5 +1,6 @@ from datetime import datetime import json +from pytz import utc from django import forms from django.contrib.admin.widgets import AdminDateWidget @@ -105,8 +106,9 @@ def parse_form(form, packages): if form.cleaned_data['last_update']: lu = form.cleaned_data['last_update'] - packages = packages.filter(last_update__gte= - datetime(lu.year, lu.month, lu.day, 0, 0)) + cutoff = datetime(lu.year, lu.month, lu.day, 0, 0) + cutoff = cutoff.replace(tzinfo=utc) + packages = packages.filter(last_update__gte=cutoff) if form.cleaned_data['name']: name = form.cleaned_data['name'] -- cgit v1.2.3-2-g168b From 50daf33d080c9fa85bf10e98b975cc44f6f72314 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 30 Sep 2012 13:00:41 -0500 Subject: pkg_details_link template tag enhancements * Output the blank string when passed None for pkg argument * Allow override of the link text if optional argument passed Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index d87a74a5..994265d8 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -61,9 +61,13 @@ def do_buildsortqs(parser, token): @register.simple_tag -def pkg_details_link(pkg): +def pkg_details_link(pkg, link_title=None): + if not pkg: + return '' + if link_title is None: + link_title = pkg.pkgname link = '%s' - return link % (pkg.get_absolute_url(), pkg.pkgname, pkg.pkgname) + return link % (pkg.get_absolute_url(), pkg.pkgname, link_title) @register.simple_tag -- cgit v1.2.3-2-g168b From a71aa2e354599950f4bd464f0f19215f1c581141 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 12 Oct 2012 11:34:49 -0500 Subject: Make wrong permissions query more efficient This removes the subplan and per-row query in favor of a LEFT JOIN where we look for non-matching rows. Tested in sqlite3 and PostgreSQL. Signed-off-by: Dan McGee --- packages/utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index ee1b56b3..c29e2297 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -228,12 +228,13 @@ SELECT DISTINCT id FROM packages p JOIN packages_packagerelation pr ON p.pkgbase = pr.pkgbase WHERE pr.type = %s - ) pkgs - WHERE pkgs.repo_id NOT IN ( - SELECT repo_id FROM user_profiles_allowed_repos ar + ) mp + LEFT JOIN ( + SELECT user_id, repo_id FROM user_profiles_allowed_repos ar INNER JOIN user_profiles up ON ar.userprofile_id = up.id - WHERE up.user_id = pkgs.user_id - ) + ) ur + ON mp.user_id = ur.user_id AND mp.repo_id = ur.repo_id + WHERE ur.user_id IS NULL; """ cursor = connection.cursor() cursor.execute(sql, [PackageRelation.MAINTAINER]) -- cgit v1.2.3-2-g168b From 0b3aa29cb63c6ca07f066a4a68fa3df9b92f6216 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 14 Oct 2012 15:42:15 -0500 Subject: Refactor signoff-grabbing queries Make them a bit more efficient by adding an explicit condition on both the packages and signoff table for the repo ID, and move the common code into a shared function both can use. Signed-off-by: Dan McGee --- packages/utils.py | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index c29e2297..051fed8e 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -365,7 +365,8 @@ class PackageSignoffGroup(object): self.pkgbase, self.version, self.arch, len(self.signoffs)) -_SQL_SPEC_OR_SIGNOFF = """ +def signoffs_id_query(model, repos): + sql = """ SELECT DISTINCT s.id FROM %s s JOIN packages p ON ( @@ -377,34 +378,29 @@ SELECT DISTINCT s.id AND s.repo_id = p.repo_id ) WHERE p.repo_id IN (%s) -""" - - -def get_current_signoffs(repos): - '''Returns a mapping of pkgbase -> signoff objects for the given repos.''' + AND s.repo_id IN (%s) + """ cursor = connection.cursor() # query pre-process- fill in table name and placeholders for IN - sql = _SQL_SPEC_OR_SIGNOFF % ('packages_signoff', - ','.join(['%s' for r in repos])) - cursor.execute(sql, [r.pk for r in repos]) + repo_sql = ','.join(['%s' for r in repos]) + sql = sql % (model._meta.db_table, repo_sql, repo_sql) + repo_ids = [r.pk for r in repos] + # repo_ids are needed twice, so double the array + cursor.execute(sql, repo_ids * 2) 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() + return [row[0] for row in results] -def get_current_specifications(repos): - '''Returns a mapping of pkgbase -> signoff specification objects for the - given repos.''' - cursor = connection.cursor() - sql = _SQL_SPEC_OR_SIGNOFF % ('packages_signoffspecification', - ','.join(['%s' for r in repos])) - cursor.execute(sql, [r.pk for r in repos]) +def get_current_signoffs(repos): + '''Returns a list of signoff objects for the given repos.''' + to_fetch = signoffs_id_query(Signoff, repos) + return Signoff.objects.select_related('user').in_bulk(to_fetch).values() - results = cursor.fetchall() - to_fetch = [row[0] for row in results] + +def get_current_specifications(repos): + '''Returns a list of signoff specification objects for the given repos.''' + to_fetch = signoffs_id_query(SignoffSpecification, repos) return SignoffSpecification.objects.in_bulk(to_fetch).values() -- cgit v1.2.3-2-g168b From 2ee662c77cf559d6ea82c9096533abfcd38f4801 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 26 Oct 2012 17:05:39 -0500 Subject: Extract some common architecture grabbing logic Signed-off-by: Dan McGee --- packages/views/display.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index b5cd643a..efedf6ff 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -13,11 +13,16 @@ from ..models import Update from ..utils import get_group_info, PackageJSONEncoder +def arch_plus_agnostic(arch): + arches = [ arch ] + arches.extend(Arch.objects.filter(agnostic=True).order_by()) + return arches + + def split_package_details(request, name, repo, arch): '''Check if we have a split package (e.g. pkgbase) value matching this name. If so, we can show a listing page for the entire set of packages.''' - arches = [ arch ] - arches.extend(Arch.objects.filter(agnostic=True)) + arches = arch_plus_agnostic(arch) pkgs = Package.objects.normal().filter(pkgbase=name, repo__testing=repo.testing, repo__staging=repo.staging, arch__in=arches).order_by('pkgname') @@ -42,8 +47,7 @@ def recently_removed_package(request, name, repo, arch, cutoff=CUTOFF): '''Check our packages update table to see if this package has existed in this repo before. If so, we can show a 410 Gone page and point the requester in the right direction.''' - arches = [ arch ] - arches.extend(Arch.objects.filter(agnostic=True)) + arches = arch_plus_agnostic(arch) match = Update.objects.select_related('arch', 'repo').filter( pkgname=name, repo=repo, arch__in=arches) if cutoff is not None: @@ -149,8 +153,7 @@ def groups(request, arch=None): def group_details(request, arch, name): arch = get_object_or_404(Arch, name=arch) - arches = [ arch ] - arches.extend(Arch.objects.filter(agnostic=True)) + arches = arch_plus_agnostic(arch) pkgs = Package.objects.normal().filter( groups__name=name, arch__in=arches).order_by('pkgname') if len(pkgs) == 0: -- cgit v1.2.3-2-g168b From bdee24b9d1279de67dd238e3644c2efff314bd7b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 26 Oct 2012 17:11:11 -0500 Subject: Cleanup meta model attributes Signed-off-by: Dan McGee --- packages/models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 0bea21b1..0d0fbdf2 100644 --- a/packages/models.py +++ b/packages/models.py @@ -329,6 +329,9 @@ class PackageGroup(models.Model): def __unicode__(self): return "%s: %s" % (self.name, self.pkg) + class Meta: + ordering = ('name',) + class License(models.Model): pkg = models.ForeignKey(Package, related_name='licenses') @@ -338,7 +341,7 @@ class License(models.Model): return self.name class Meta: - ordering = ['name'] + ordering = ('name',) class RelatedToBase(models.Model): @@ -435,7 +438,7 @@ class RelatedToBase(models.Model): class Meta: abstract = True - ordering = ['name'] + ordering = ('name',) class Depend(RelatedToBase): -- cgit v1.2.3-2-g168b From 6dd4d54bb0adbbb0f8c2b1beaa92b7a58971cf88 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 16 Nov 2012 16:20:11 -0600 Subject: Use Python 2.7 dictionary comprehension syntax Rather than the old idiom of dict((k, v) for <> in <>). Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 2 +- packages/utils.py | 5 ++--- packages/views/signoff.py | 8 +++----- 3 files changed, 6 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index 994265d8..f3613e69 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -13,7 +13,7 @@ register = template.Library() def link_encode(url, query): # massage the data into all utf-8 encoded strings first, so urlencode # doesn't barf at the data we pass it - query = dict((k, unicode(v).encode('utf-8')) for k, v in query.items()) + query = {k: unicode(v).encode('utf-8') for k, v in query.items()} data = urlencode(query).replace('&', '&') return "%s?%s" % (url, data) diff --git a/packages/utils.py b/packages/utils.py index 051fed8e..199e141d 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -385,7 +385,7 @@ SELECT DISTINCT s.id repo_sql = ','.join(['%s' for r in repos]) sql = sql % (model._meta.db_table, repo_sql, repo_sql) repo_ids = [r.pk for r in repos] - # repo_ids are needed twice, so double the array + # repo_ids are needed twice, so double the array cursor.execute(sql, repo_ids * 2) results = cursor.fetchall() @@ -474,8 +474,7 @@ class PackageJSONEncoder(DjangoJSONEncoder): # mainly for queryset serialization return list(obj) if isinstance(obj, Package): - data = dict((attr, getattr(obj, attr)) - for attr in self.pkg_attributes) + data = {attr: getattr(obj, attr) for attr in self.pkg_attributes} for attr in self.pkg_list_attributes: data[attr] = getattr(obj, attr).all() return data diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 56eb060c..824a9922 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -155,8 +155,8 @@ class SignoffJSONEncoder(DjangoJSONEncoder): def default(self, obj): if isinstance(obj, PackageSignoffGroup): - data = dict((attr, getattr(obj, attr)) - for attr in self.signoff_group_attrs) + data = {attr: getattr(obj, attr) + for attr in self.signoff_group_attrs} data['pkgnames'] = [p.pkgname for p in obj.packages] data['package_count'] = len(obj.packages) data['approved'] = obj.approved() @@ -164,9 +164,7 @@ class SignoffJSONEncoder(DjangoJSONEncoder): for attr in self.signoff_spec_attrs) return data elif isinstance(obj, Signoff): - data = dict((attr, getattr(obj, attr)) - for attr in self.signoff_attrs) - return data + return {attr: getattr(obj, attr) for attr in self.signoff_attrs} elif isinstance(obj, Arch) or isinstance(obj, Repo): return unicode(obj) elif isinstance(obj, User): -- cgit v1.2.3-2-g168b From 9e9157d0a8cbf9ea076231e438fb30f58bff8e29 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 16 Nov 2012 16:37:31 -0600 Subject: Use python set comprehension syntax supported in 2.7 Signed-off-by: Dan McGee --- packages/models.py | 2 +- packages/utils.py | 10 +++++----- packages/views/signoff.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 0d0fbdf2..ede8c275 100644 --- a/packages/models.py +++ b/packages/models.py @@ -33,7 +33,7 @@ class PackageRelation(models.Model): def repositories(self): packages = self.get_associated_packages() - return sorted(set([p.repo for p in packages])) + return sorted({p.repo for p in packages}) def __unicode__(self): return u'%s: %s (%s)' % ( diff --git a/packages/utils.py b/packages/utils.py index 199e141d..5adc8637 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -79,8 +79,8 @@ def get_split_packages_info(): split_pkgs = Package.objects.exclude(pkgname=F('pkgbase')).exclude( pkgbase__in=pkgnames).values('pkgbase', 'repo', 'arch').annotate( last_update=Max('last_update')) - all_arches = Arch.objects.in_bulk(set(s['arch'] for s in split_pkgs)) - all_repos = Repo.objects.in_bulk(set(s['repo'] for s in split_pkgs)) + all_arches = Arch.objects.in_bulk({s['arch'] for s in split_pkgs}) + all_repos = Repo.objects.in_bulk({s['repo'] for s in split_pkgs}) for split in split_pkgs: split['arch'] = all_arches[split['arch']] split['repo'] = all_repos[split['repo']] @@ -143,7 +143,7 @@ SELECT p.id, q.id cursor.execute(sql, [arch_a.id, arch_b.id]) results = cursor.fetchall() # column A will always have a value, column B might be NULL - to_fetch = set(row[0] for row in results) + 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 @@ -249,13 +249,13 @@ def attach_maintainers(packages): the maintainers and attach them to the packages to prevent N+1 query cascading.''' packages = list(packages) - pkgbases = set(p.pkgbase for p in packages) + pkgbases = {p.pkgbase for p in packages} rels = PackageRelation.objects.filter(type=PackageRelation.MAINTAINER, pkgbase__in=pkgbases).values_list( 'pkgbase', 'user_id').order_by().distinct() # get all the user objects we will need - user_ids = set(rel[1] for rel in rels) + user_ids = {rel[1] for rel in rels} users = User.objects.in_bulk(user_ids) # now build a pkgbase -> [maintainers...] map diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 824a9922..340b2311 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -25,7 +25,7 @@ def signoffs(request): context = { 'signoff_groups': signoff_groups, 'arches': Arch.objects.all(), - 'repo_names': sorted(set(g.target_repo for g in signoff_groups)), + 'repo_names': sorted({g.target_repo for g in signoff_groups}), } return render(request, 'packages/signoffs.html', context) -- cgit v1.2.3-2-g168b From e08096ee214b1fd60d093e2902c6acec9cf4ae5f Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 11 Dec 2012 21:19:16 -0600 Subject: Fix FS#32018, provides links always go to [testing] packages Remove some of the smarts and do less, but be better about properly sorting the items as one might expect. Signed-off-by: Dan McGee --- packages/models.py | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index ede8c275..a4095f53 100644 --- a/packages/models.py +++ b/packages/models.py @@ -412,24 +412,12 @@ class RelatedToBase(models.Model): new_pkgs.append(package) pkgs = new_pkgs - # Logic here is to filter out packages that are in multiple repos if - # they are not requested. For example, if testing is False, only show a - # testing package if it doesn't exist in a non-testing repo. - filtered = {} - for package in pkgs: - if package.pkgname not in filtered or \ - package.repo.staging == self.pkg.repo.staging: - filtered[package.pkgname] = package - pkgs = filtered.values() - - filtered = {} - for package in pkgs: - if package.pkgname not in filtered or \ - package.repo.testing == self.pkg.repo.testing: - filtered[package.pkgname] = package - pkgs = filtered.values() - - return pkgs + # Sort providers by preference. We sort those in same staging/testing + # combination first, followed by others. We sort by a (staging, + # testing) match tuple that will be (True, True) in the best case. + key_func = lambda x: (x.repo.staging == self.pkg.repo.staging, + x.repo.testing == self.pkg.repo.testing) + return sorted(pkgs, key=key_func, reverse=True) def __unicode__(self): if self.version: -- cgit v1.2.3-2-g168b From a993295002ad36b40c49619e892de6e78aa00c06 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 27 Dec 2012 17:04:14 -0600 Subject: Add some more tests for ALPM API stuff These are super-simple, but it is also trivial to test. Signed-off-by: Dan McGee --- packages/tests.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 packages/tests.py (limited to 'packages') diff --git a/packages/tests.py b/packages/tests.py new file mode 100644 index 00000000..3a460865 --- /dev/null +++ b/packages/tests.py @@ -0,0 +1,27 @@ +import unittest + +from .alpm import AlpmAPI + +class AlpmTestCase(unittest.TestCase): + def test_version(self): + alpm = AlpmAPI() + version = alpm.version() + self.assertIsNotNone(version) + version = version.split('.') + # version is a 3-tuple, e.g., '7.0.2' + self.assertEqual(3, len(version)) + + def test_compare_versions(self): + alpm = AlpmAPI() + self.assertTrue(alpm.compare_versions("1.0", "<=", "2.0")) + self.assertTrue(alpm.compare_versions("1.0", "<", "2.0")) + self.assertFalse(alpm.compare_versions("1.0", ">=", "2.0")) + self.assertFalse(alpm.compare_versions("1.0", ">", "2.0")) + self.assertTrue(alpm.compare_versions("1:1.0", ">", "2.0")) + self.assertFalse(alpm.compare_versions("1.0.2", ">=", "2.1.0")) + + self.assertTrue(alpm.compare_versions("1.0", "=", "1.0")) + self.assertTrue(alpm.compare_versions("1.0", "=", "1.0-1")) + self.assertFalse(alpm.compare_versions("1.0", "!=", "1.0")) + +# vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From b801818eeed1068595cea863e9ae427f3931f925 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 27 Dec 2012 23:25:51 -0600 Subject: Make attach_maintainers null-safe Signed-off-by: Dan McGee --- packages/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index 5adc8637..5f0c111e 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -249,7 +249,7 @@ def attach_maintainers(packages): the maintainers and attach them to the packages to prevent N+1 query cascading.''' packages = list(packages) - pkgbases = {p.pkgbase for p in packages} + pkgbases = {p.pkgbase for p in packages if p is not None} rels = PackageRelation.objects.filter(type=PackageRelation.MAINTAINER, pkgbase__in=pkgbases).values_list( 'pkgbase', 'user_id').order_by().distinct() @@ -266,6 +266,8 @@ def attach_maintainers(packages): annotated = [] # and finally, attach the maintainer lists on the original packages for package in packages: + if package is None: + continue package.maintainers = maintainers[package.pkgbase] annotated.append(package) -- cgit v1.2.3-2-g168b From 6667b017669ffbae98ce70f5472f107b91da36d5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Thu, 27 Dec 2012 23:56:34 -0600 Subject: Allow pkg_details_link to return link title if package is missing For todolist packages that have had their package removed, this will allow the package name to continue to appear even after the linked package has been deleted. Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index f3613e69..f14fab1e 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -63,7 +63,7 @@ def do_buildsortqs(parser, token): @register.simple_tag def pkg_details_link(pkg, link_title=None): if not pkg: - return '' + return link_title or '' if link_title is None: link_title = pkg.pkgname link = '%s' -- cgit v1.2.3-2-g168b From 20b64e42672d185821cc584dfa4b133ee259a144 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 28 Dec 2012 14:41:18 -0600 Subject: Reduce query count when retrieving satisfiers and providers Django doesn't attach the parent object to the child objects, even when queried through the related manager. This causes up to 3 extra queries: one to retrieve the package again, and one each for arch and repo retrieval. For a package like archboot, this drops the number of necessary queries for the package page from 805 to 222 (yes, this is still too high) and cuts the time spent waiting on the database from 505ms to 262ms. Signed-off-by: Dan McGee --- packages/models.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index a4095f53..18d57c58 100644 --- a/packages/models.py +++ b/packages/models.py @@ -349,14 +349,16 @@ class RelatedToBase(models.Model): name = models.CharField(max_length=255, db_index=True) version = models.CharField(max_length=255, default='') - def get_best_satisfier(self): + def get_best_satisfier(self, main_pkg=None): '''Find a satisfier for this related package that best matches the given criteria. It will not search provisions, but will find packages named and matching repo characteristics if possible.''' + if main_pkg is None: + main_pkg = self.pkg pkgs = Package.objects.normal().filter(pkgname=self.name) - if not self.pkg.arch.agnostic: + if not main_pkg.arch.agnostic: # make sure we match architectures if possible - arches = self.pkg.applicable_arches() + arches = main_pkg.applicable_arches() pkgs = pkgs.filter(arch__in=arches) # if we have a comparison operation, make sure the packages we grab # actually satisfy the requirements @@ -376,25 +378,27 @@ class RelatedToBase(models.Model): pkg = pkgs[0] # prevents yet more DB queries, these lists should be short; # after each grab the best available in case we remove all entries - pkgs = [p for p in pkgs if p.repo.staging == self.pkg.repo.staging] + pkgs = [p for p in pkgs if p.repo.staging == main_pkg.repo.staging] if len(pkgs) > 0: pkg = pkgs[0] - pkgs = [p for p in pkgs if p.repo.testing == self.pkg.repo.testing] + pkgs = [p for p in pkgs if p.repo.testing == main_pkg.repo.testing] if len(pkgs) > 0: pkg = pkgs[0] return pkg - def get_providers(self): + def get_providers(self, main_pkg=None): '''Return providers of this related package. Does *not* include exact matches as it checks the Provision names only, use get_best_satisfier() instead for exact matches.''' + if main_pkg is None: + main_pkg = self.pkg pkgs = Package.objects.normal().filter( provides__name=self.name).order_by().distinct() - if not self.pkg.arch.agnostic: + if not main_pkg.arch.agnostic: # make sure we match architectures if possible - arches = self.pkg.applicable_arches() + arches = main_pkg.applicable_arches() pkgs = pkgs.filter(arch__in=arches) # If we have a comparison operation, make sure the packages we grab -- cgit v1.2.3-2-g168b From 255a992c151b1c9386c3450083144447be2ce27a Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 9 Jan 2013 08:02:19 -0500 Subject: Add get_associated_packages method to FlagRequest Signed-off-by: Dan McGee --- packages/models.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 18d57c58..ef86d8e9 100644 --- a/packages/models.py +++ b/packages/models.py @@ -206,6 +206,13 @@ class FlagRequest(models.Model): return u'%d:%s-%s' % (self.epoch, self.pkgver, self.pkgrel) return u'%s-%s' % (self.pkgver, self.pkgrel) + def get_associated_packages(self): + return Package.objects.normal().filter( + pkgbase=self.pkgbase, + repo__testing=self.repo.testing, + repo__staging=self.repo.staging).order_by( + 'pkgname', 'repo__name', 'arch__name') + def __unicode__(self): return u'%s from %s on %s' % (self.pkgbase, self.who(), self.created) -- cgit v1.2.3-2-g168b From 2bfdcec869ed4fceb11b9e0a2777fa53d46fb336 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 12 Jan 2013 16:47:20 -0600 Subject: Make packages JSON search more performant We were peppering the database with a bunch of queries here; using prefetch_related and attach_maintainers can cut down the count significantly. Signed-off-by: Dan McGee --- packages/views/search.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/views/search.py b/packages/views/search.py index f7b8ed1d..f6e670df 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -12,7 +12,7 @@ from django.views.generic import ListView from main.models import Package, Arch, Repo from main.utils import make_choice from ..models import PackageRelation -from ..utils import PackageJSONEncoder +from ..utils import attach_maintainers, PackageJSONEncoder def coerce_limit_value(value): @@ -185,10 +185,14 @@ def search_json(request): form = PackageSearchForm(data=request.GET, show_staging=request.user.is_authenticated()) if form.is_valid(): - packages = Package.objects.normal() + packages = Package.objects.select_related('arch', 'repo', + 'packager') if not request.user.is_authenticated(): packages = packages.filter(repo__staging=False) packages = parse_form(form, packages)[:limit] + packages = packages.prefetch_related('groups', 'licenses', + 'conflicts', 'provides', 'replaces', 'depends') + attach_maintainers(packages) container['results'] = packages container['valid'] = True -- cgit v1.2.3-2-g168b From 66850026ca934e5a09238e9033c541cdc5085a42 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 13 Jan 2013 22:34:33 -0600 Subject: Use content_type and not mimetype on HttpResponse() Bug #16519 in Django deprecates mimetype, so update our code accordingly. Signed-off-by: Dan McGee --- packages/views/__init__.py | 4 ++-- packages/views/display.py | 4 ++-- packages/views/search.py | 2 +- packages/views/signoff.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index f7952255..4c195385 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -42,7 +42,7 @@ def opensearch(request): def opensearch_suggest(request): search_term = request.GET.get('q', '') if search_term == '': - return HttpResponse('', mimetype='application/x-suggestions+json') + return HttpResponse('', content_type='application/x-suggestions+json') cache_key = 'opensearch:packages:' + \ hashlib.md5(search_term.encode('utf-8')).hexdigest() @@ -59,7 +59,7 @@ def opensearch_suggest(request): results = [search_term, list(names)] to_json = json.dumps(results, ensure_ascii=False) cache.set(cache_key, to_json, 300) - return HttpResponse(to_json, mimetype='application/x-suggestions+json') + return HttpResponse(to_json, content_type='application/x-suggestions+json') @permission_required('main.change_package') diff --git a/packages/views/display.py b/packages/views/display.py index efedf6ff..445c1abe 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -188,7 +188,7 @@ def details_json(request, name, repo, arch): pkg = get_object_or_404(Package, pkgname=name, repo__name__iexact=repo, arch__name=arch) to_json = json.dumps(pkg, ensure_ascii=False, cls=PackageJSONEncoder) - return HttpResponse(to_json, mimetype='application/json') + return HttpResponse(to_json, content_type='application/json') def files_json(request, name, repo, arch): @@ -209,7 +209,7 @@ def files_json(request, name, repo, arch): 'files': fileslist, } to_json = json.dumps(data, ensure_ascii=False, cls=PackageJSONEncoder) - return HttpResponse(to_json, mimetype='application/json') + return HttpResponse(to_json, content_type='application/json') def download(request, name, repo, arch): diff --git a/packages/views/search.py b/packages/views/search.py index f6e670df..0f313ccb 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -197,6 +197,6 @@ def search_json(request): container['valid'] = True to_json = json.dumps(container, ensure_ascii=False, cls=PackageJSONEncoder) - return HttpResponse(to_json, mimetype='application/json') + return HttpResponse(to_json, content_type='application/json') # vim: set ts=4 sw=4 et: diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 340b2311..17f3095c 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -67,7 +67,7 @@ def signoff_package(request, name, repo, arch, revoke=False): 'user': str(request.user), } return HttpResponse(json.dumps(data, ensure_ascii=False), - mimetype='application/json') + content_type='application/json') return redirect('package-signoffs') @@ -181,7 +181,7 @@ def signoffs_json(request): 'signoff_groups': signoff_groups, } to_json = json.dumps(data, ensure_ascii=False, cls=SignoffJSONEncoder) - response = HttpResponse(to_json, mimetype='application/json') + response = HttpResponse(to_json, content_type='application/json') return response # vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From 375684ed91dd5499e7a4ea7787e45803e8467e16 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 18 Jan 2013 20:52:20 -0600 Subject: Use a set instead of list when gathering package differences If we implement the __eq__ and __hash__ methods, we can use a set to gather package difference objects and make deduplication of objects a lot more efficient. Signed-off-by: Dan McGee --- packages/utils.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index 5f0c111e..a72404f4 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -1,6 +1,6 @@ from collections import defaultdict from itertools import chain -from operator import itemgetter +from operator import attrgetter, itemgetter import re from django.core.serializers.json import DjangoJSONEncoder @@ -108,10 +108,15 @@ class Difference(object): css_classes.append(self.pkg_b.arch.name) return ' '.join(css_classes) - def __cmp__(self, other): - if isinstance(other, Difference): - return cmp(self.__dict__, other.__dict__) - return False + def __key(self): + return (self.pkgname, hash(self.repo), + hash(self.pkg_a), hash(self.pkg_b)) + + def __eq__(self, other): + return self.__key() == other.__key() + + def __hash__(self): + return hash(self.__key()) @cache_function(127) @@ -146,8 +151,8 @@ SELECT p.id, q.id 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 - differences = [] + # now build a set containing differences + differences = set() for row in results: pkg_a = pkgs.get(row[0]) pkg_b = pkgs.get(row[1]) @@ -160,11 +165,11 @@ SELECT p.id, q.id name = pkg_a.pkgname if pkg_a else pkg_b.pkgname repo = pkg_a.repo if pkg_a else pkg_b.repo item = Difference(name, repo, pkg_b, pkg_a) - if item not in differences: - differences.append(item) + differences.add(item) # now sort our list by repository, package name - differences.sort(key=lambda a: (a.repo.name, a.pkgname)) + key_func = attrgetter('repo.name', 'pkgname') + differences = sorted(differences, key=key_func) return differences -- cgit v1.2.3-2-g168b From 2c958511c41f53fb7de49ed4662eec966e0b76a5 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 22 Jan 2013 16:48:49 -0600 Subject: Use a subquery rather than two queries in attach_maintainers Now that we are using a database that doesn't stink, it makes more sense to do all of the stuff we need to do down at the database level. This helps a lot when 500+ packages are in play at a given time, such as some of our larger rebuild todo lists. Signed-off-by: Dan McGee --- packages/utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index a72404f4..49aeb8ce 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -6,6 +6,7 @@ import re from django.core.serializers.json import DjangoJSONEncoder from django.db import connection from django.db.models import Count, Max, F +from django.db.models.query import QuerySet from django.contrib.auth.models import User from main.models import Package, PackageFile, Arch, Repo @@ -253,8 +254,11 @@ def attach_maintainers(packages): '''Given a queryset or something resembling it of package objects, find all the maintainers and attach them to the packages to prevent N+1 query cascading.''' - packages = list(packages) - pkgbases = {p.pkgbase for p in packages if p is not None} + if isinstance(packages, QuerySet): + pkgbases = packages.values('pkgbase') + else: + packages = list(packages) + pkgbases = {p.pkgbase for p in packages if p is not None} rels = PackageRelation.objects.filter(type=PackageRelation.MAINTAINER, pkgbase__in=pkgbases).values_list( 'pkgbase', 'user_id').order_by().distinct() -- cgit v1.2.3-2-g168b From 85dc9d6df9f9038be6049aefbf8eb9ea94f9f23c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 22 Jan 2013 17:20:41 -0700 Subject: Override the default admin queryset for some models Because some attributes are optional or otherwise not auto-magically picked up by Django, we can help the performance of loading these pages a lot by forcing a select_related() on the queryset used by the admin. For something like signoff_specifications, this drops the query count from ~107 to 9 queries. Signed-off-by: Dan McGee --- packages/admin.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'packages') diff --git a/packages/admin.py b/packages/admin.py index 5e32dbb4..820bbb29 100644 --- a/packages/admin.py +++ b/packages/admin.py @@ -3,6 +3,7 @@ from django.contrib import admin from .models import (PackageRelation, FlagRequest, Signoff, SignoffSpecification, Update) + class PackageRelationAdmin(admin.ModelAdmin): list_display = ('pkgbase', 'user', 'type', 'created') list_filter = ('type', 'user') @@ -19,6 +20,10 @@ class FlagRequestAdmin(admin.ModelAdmin): ordering = ('-created',) date_hierarchy = 'created' + def queryset(self, request): + qs = super(FlagRequestAdmin, self).queryset(request) + return qs.select_related('repo', 'user') + class SignoffAdmin(admin.ModelAdmin): list_display = ('pkgbase', 'full_version', 'arch', 'repo', @@ -28,6 +33,7 @@ class SignoffAdmin(admin.ModelAdmin): ordering = ('-created',) date_hierarchy = 'created' + class SignoffSpecificationAdmin(admin.ModelAdmin): list_display = ('pkgbase', 'full_version', 'arch', 'repo', 'user', 'created', 'comments') @@ -36,6 +42,10 @@ class SignoffSpecificationAdmin(admin.ModelAdmin): ordering = ('-created',) date_hierarchy = 'created' + def queryset(self, request): + qs = super(SignoffSpecificationAdmin, self).queryset(request) + return qs.select_related('arch', 'repo', 'user') + class UpdateAdmin(admin.ModelAdmin): list_display = ('pkgname', 'repo', 'arch', 'action_flag', -- cgit v1.2.3-2-g168b From be49f26a815cca589c625ff8dd99c85a80262281 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 22 Jan 2013 20:58:50 -0700 Subject: Slight optimization when searching for removed package Signed-off-by: Dan McGee --- packages/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index ef86d8e9..ff677883 100644 --- a/packages/models.py +++ b/packages/models.py @@ -318,7 +318,8 @@ class Update(models.Model): return u'%s-%s' % (self.new_pkgver, self.new_pkgrel) def elsewhere(self): - return Package.objects.filter(pkgname=self.pkgname, arch=self.arch) + return Package.objects.normal().filter( + pkgname=self.pkgname, arch=self.arch) def __unicode__(self): return u'%s of %s on %s' % (self.get_action_flag_display(), -- cgit v1.2.3-2-g168b From a10798b756bbfc5d8dbad76546ca670efca75e56 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Jan 2013 09:18:59 -0700 Subject: Use querysets for calls to get_object_or_404(Package) This works better in most cases since we need the architecture and repository objects at some point during the view process. Signed-off-by: Dan McGee --- packages/views/display.py | 8 ++++---- packages/views/flag.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index 445c1abe..c2369aba 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -168,7 +168,7 @@ def group_details(request, arch, name): def files(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) # files are inserted in sorted order, so preserve that fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') @@ -185,14 +185,14 @@ def files(request, name, repo, arch): def details_json(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) to_json = json.dumps(pkg, ensure_ascii=False, cls=PackageJSONEncoder) return HttpResponse(to_json, content_type='application/json') def files_json(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) # files are inserted in sorted order, so preserve that fileslist = PackageFile.objects.filter(pkg=pkg).order_by('id') @@ -213,7 +213,7 @@ def files_json(request, name, repo, arch): def download(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) url = get_mirror_url_for_download() if not url: diff --git a/packages/views/flag.py b/packages/views/flag.py index dadadd19..edb3f092 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -49,7 +49,7 @@ def flaghelp(request): @never_cache def flag(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) if pkg.flag_date is not None: # already flagged. do nothing. @@ -158,7 +158,7 @@ def flag_confirmed(request, name, repo, arch): @permission_required('main.change_package') def unflag(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) pkg.flag_date = None pkg.save() @@ -166,7 +166,7 @@ def unflag(request, name, repo, arch): @permission_required('main.change_package') def unflag_all(request, name, repo, arch): - pkg = get_object_or_404(Package, + pkg = get_object_or_404(Package.objects.normal(), pkgname=name, repo__name__iexact=repo, arch__name=arch) # find all packages from (hopefully) the same PKGBUILD pkgs = Package.objects.filter(pkgbase=pkg.pkgbase, -- cgit v1.2.3-2-g168b From dc6cc49f6f876983f76f5f8c05a2285801f27ea0 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 23 Jan 2013 09:20:19 -0700 Subject: Use more modern verison of string template formatting Signed-off-by: Dan McGee --- packages/views/display.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index c2369aba..497c8d48 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -1,6 +1,5 @@ import datetime import json -from string import Template from urllib import urlencode from django.http import HttpResponse, Http404 @@ -223,12 +222,9 @@ def download(request, name, repo, arch): # grab the first non-any arch to fake the download path arch = Arch.objects.exclude(agnostic=True)[0].name values = { - 'host': url.url, - 'arch': arch, - 'repo': pkg.repo.name.lower(), - 'file': pkg.filename, } - url = Template('${host}${repo}/os/${arch}/${file}').substitute(values) + url = '{host}{repo}/os/{arch}/{filename}'.format(host=url.url, + repo=pkg.repo.name.lower(), arch=arch, filename=pkg.filename) return redirect(url) # vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From e65c7805547484cad1be55dfa20355ef18b857be Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Fri, 8 Feb 2013 21:09:47 -0600 Subject: Remove package seach by 'Last Updated After' It is a lot easier to just sort the list rather than mess with this particular field, which didn't even allow you to specify a range or direction to search in. Signed-off-by: Dan McGee --- packages/views/search.py | 9 --------- 1 file changed, 9 deletions(-) (limited to 'packages') diff --git a/packages/views/search.py b/packages/views/search.py index 0f313ccb..9cb5f38d 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -3,7 +3,6 @@ import json from pytz import utc from django import forms -from django.contrib.admin.widgets import AdminDateWidget from django.contrib.auth.models import User from django.db.models import Q from django.http import HttpResponse @@ -44,8 +43,6 @@ class PackageSearchForm(forms.Form): sort = forms.CharField(required=False, widget=forms.HiddenInput()) 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( choices=[('', 'All')] + make_choice(['Flagged', 'Not Flagged']), required=False) @@ -104,12 +101,6 @@ def parse_form(form, packages): elif form.cleaned_data['flagged'] == 'Not Flagged': packages = packages.filter(flag_date__isnull=True) - if form.cleaned_data['last_update']: - lu = form.cleaned_data['last_update'] - cutoff = datetime(lu.year, lu.month, lu.day, 0, 0) - cutoff = cutoff.replace(tzinfo=utc) - packages = packages.filter(last_update__gte=cutoff) - if form.cleaned_data['name']: name = form.cleaned_data['name'] packages = packages.filter(pkgname=name) -- cgit v1.2.3-2-g168b From 271d1babbf8038e17d9dc5cfc3cd659463848400 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 20 Jan 2013 12:43:33 -0600 Subject: Revert "Reduce query count when retrieving satisfiers and providers" This reverts commit 20b64e42672d185821cc584dfa4b133ee259a144. Django 1.5 fixed this issue and now parent objects are automatically attached to their children when queries go through the related manager. See "Caching of related model instances" in the release notes. Signed-off-by: Dan McGee --- packages/models.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index ff677883..7bcdc000 100644 --- a/packages/models.py +++ b/packages/models.py @@ -357,16 +357,14 @@ class RelatedToBase(models.Model): name = models.CharField(max_length=255, db_index=True) version = models.CharField(max_length=255, default='') - def get_best_satisfier(self, main_pkg=None): + def get_best_satisfier(self): '''Find a satisfier for this related package that best matches the given criteria. It will not search provisions, but will find packages named and matching repo characteristics if possible.''' - if main_pkg is None: - main_pkg = self.pkg pkgs = Package.objects.normal().filter(pkgname=self.name) - if not main_pkg.arch.agnostic: + if not self.pkg.arch.agnostic: # make sure we match architectures if possible - arches = main_pkg.applicable_arches() + arches = self.pkg.applicable_arches() pkgs = pkgs.filter(arch__in=arches) # if we have a comparison operation, make sure the packages we grab # actually satisfy the requirements @@ -386,27 +384,25 @@ class RelatedToBase(models.Model): pkg = pkgs[0] # prevents yet more DB queries, these lists should be short; # after each grab the best available in case we remove all entries - pkgs = [p for p in pkgs if p.repo.staging == main_pkg.repo.staging] + pkgs = [p for p in pkgs if p.repo.staging == self.pkg.repo.staging] if len(pkgs) > 0: pkg = pkgs[0] - pkgs = [p for p in pkgs if p.repo.testing == main_pkg.repo.testing] + pkgs = [p for p in pkgs if p.repo.testing == self.pkg.repo.testing] if len(pkgs) > 0: pkg = pkgs[0] return pkg - def get_providers(self, main_pkg=None): + def get_providers(self): '''Return providers of this related package. Does *not* include exact matches as it checks the Provision names only, use get_best_satisfier() instead for exact matches.''' - if main_pkg is None: - main_pkg = self.pkg pkgs = Package.objects.normal().filter( provides__name=self.name).order_by().distinct() - if not main_pkg.arch.agnostic: + if not self.pkg.arch.agnostic: # make sure we match architectures if possible - arches = main_pkg.applicable_arches() + arches = self.pkg.applicable_arches() pkgs = pkgs.filter(arch__in=arches) # If we have a comparison operation, make sure the packages we grab -- cgit v1.2.3-2-g168b From 5566d43a7734f6bb2f48d5d511351da12ddc5cc1 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 9 Feb 2013 16:43:40 -0600 Subject: Use 'update_fields' model.save() kwarg This was added in Django 1.5 and allows saving only a subset of a model's fields. It makes sense in a few cases to utilize it. Signed-off-by: Dan McGee --- packages/views/signoff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/signoff.py b/packages/views/signoff.py index 17f3095c..c37aa0fc 100644 --- a/packages/views/signoff.py +++ b/packages/views/signoff.py @@ -45,7 +45,7 @@ def signoff_package(request, name, repo, arch, revoke=False): except Signoff.DoesNotExist: raise Http404 signoff.revoked = now() - signoff.save() + signoff.save(update_fields=('revoked',)) created = False else: # ensure we should even be accepting signoffs -- cgit v1.2.3-2-g168b From 5bc85244281efc916132c86046018d0ebe70b5e9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 10 Feb 2013 12:45:24 -0600 Subject: Fix split packages sitemap We had a ton of duplicate entries included due to the query implicitly including a 'GROUP BY' clause on the default sorting by pkgname. Fix it and cut the sitemap down to the correct size without duplicate entries. Signed-off-by: Dan McGee --- packages/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index 49aeb8ce..ef6311eb 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -79,7 +79,7 @@ def get_split_packages_info(): pkgnames = Package.objects.values('pkgname') split_pkgs = Package.objects.exclude(pkgname=F('pkgbase')).exclude( pkgbase__in=pkgnames).values('pkgbase', 'repo', 'arch').annotate( - last_update=Max('last_update')) + last_update=Max('last_update')).order_by().distinct() all_arches = Arch.objects.in_bulk({s['arch'] for s in split_pkgs}) all_repos = Repo.objects.in_bulk({s['repo'] for s in split_pkgs}) for split in split_pkgs: -- cgit v1.2.3-2-g168b From e3837b5a872b6203b7ae338bc8075b339e031627 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 16 Feb 2013 15:23:49 -0600 Subject: Remove configurable pagination for package search Switch it to a hardcoded value of 100 for all searches instead. It didn't make much sense having a page number be part of the URL and a limit value being part of the query string. Signed-off-by: Dan McGee --- packages/views/search.py | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) (limited to 'packages') diff --git a/packages/views/search.py b/packages/views/search.py index 9cb5f38d..9ca299f6 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -1,6 +1,4 @@ -from datetime import datetime import json -from pytz import utc from django import forms from django.contrib.auth.models import User @@ -14,26 +12,6 @@ from ..models import PackageRelation from ..utils import attach_maintainers, PackageJSONEncoder -def coerce_limit_value(value): - if not value: - return None - if value == 'all': - # negative value indicates show all results - return -1 - value = int(value) - if value < 0: - raise ValueError - return value - -class LimitTypedChoiceField(forms.TypedChoiceField): - def valid_value(self, value): - try: - coerce_limit_value(value) - return True - except (ValueError, TypeError): - return False - - class PackageSearchForm(forms.Form): repo = forms.MultipleChoiceField(required=False) arch = forms.MultipleChoiceField(required=False) @@ -46,11 +24,6 @@ class PackageSearchForm(forms.Form): flagged = forms.ChoiceField( choices=[('', 'All')] + make_choice(['Flagged', 'Not Flagged']), required=False) - limit = LimitTypedChoiceField( - choices=make_choice([50, 100, 250]) + [('all', 'All')], - coerce=coerce_limit_value, - required=False, - initial=50) def __init__(self, *args, **kwargs): show_staging = kwargs.pop('show_staging', False) @@ -119,6 +92,7 @@ def parse_form(form, packages): class SearchListView(ListView): template_name = "packages/search.html" + paginate_by = 100 sort_fields = ("arch", "repo", "pkgname", "pkgbase", "compressed_size", "installed_size", "build_date", "last_update", "flag_date") @@ -145,16 +119,6 @@ class SearchListView(ListView): # Form had errors so don't return any results return Package.objects.none() - def get_paginate_by(self, queryset): - limit = 50 - if self.form.is_valid(): - asked_limit = self.form.cleaned_data['limit'] - if asked_limit and asked_limit < 0: - limit = None - elif asked_limit: - limit = asked_limit - return limit - def get_context_data(self, **kwargs): context = super(SearchListView, self).get_context_data(**kwargs) context['current_query'] = self.request.GET.urlencode() -- cgit v1.2.3-2-g168b From 746023d529489b68f1a2494ff7572734b3b368ce Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 16 Feb 2013 15:38:29 -0600 Subject: Make page a query string parameter on package search This is a bit silly to encode in the URL, or at least makes it much harder to screen out via robots.txt and other such things. Signed-off-by: Dan McGee --- packages/urls.py | 1 - packages/views/display.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/urls.py b/packages/urls.py index 9a151b4b..4e2e2638 100644 --- a/packages/urls.py +++ b/packages/urls.py @@ -24,7 +24,6 @@ urlpatterns = patterns('packages.views', (r'^update/$', 'update'), (r'^$', SearchListView.as_view(), {}, 'packages-search'), - (r'^(?P\d+)/$', SearchListView.as_view()), (r'^search/json/$', 'search_json'), (r'^differences/$', 'arch_differences', {}, 'packages-differences'), diff --git a/packages/views/display.py b/packages/views/display.py index 497c8d48..fcf8fdea 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -104,6 +104,8 @@ def redirect_agnostic(request, name, repo, arch): def redirect_to_search(request, name, repo, arch): + if request.GET.get('q'): + name = request.GET.get('q') pkg_data = [ ('arch', arch.lower()), ('repo', repo.lower()), -- cgit v1.2.3-2-g168b From 0491bdb2452e496b0a243e7abb3d15ef3fd71743 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Wed, 20 Feb 2013 00:19:12 -0600 Subject: Fix some fallout with moving page to query params Signed-off-by: Dan McGee --- packages/views/search.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/search.py b/packages/views/search.py index 9ca299f6..0362602e 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -121,7 +121,9 @@ class SearchListView(ListView): def get_context_data(self, **kwargs): context = super(SearchListView, self).get_context_data(**kwargs) - context['current_query'] = self.request.GET.urlencode() + query_params = self.request.GET.copy() + query_params.pop('page', None) + context['current_query'] = query_params.urlencode() context['search_form'] = self.form return context -- cgit v1.2.3-2-g168b From ddcba1117722095bfb5f4f1c98273c54e747eea9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 26 Feb 2013 19:06:27 -0600 Subject: Fix ALPM unittest to run when ALPM isn't present This gets some proper unittest skip decorator action now in addition to adding more testing around everything. Signed-off-by: Dan McGee --- packages/tests.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'packages') diff --git a/packages/tests.py b/packages/tests.py index 3a460865..bbe9f00e 100644 --- a/packages/tests.py +++ b/packages/tests.py @@ -2,17 +2,27 @@ import unittest from .alpm import AlpmAPI + +alpm = AlpmAPI() + + class AlpmTestCase(unittest.TestCase): + + @unittest.skipUnless(alpm.available, "ALPM is unavailable") def test_version(self): - alpm = AlpmAPI() version = alpm.version() self.assertIsNotNone(version) version = version.split('.') # version is a 3-tuple, e.g., '7.0.2' self.assertEqual(3, len(version)) + @unittest.skipUnless(alpm.available, "ALPM is unavailable") + def test_vercmp(self): + self.assertEqual(0, alpm.vercmp("1.0", "1.0")) + self.assertEqual(1, alpm.vercmp("1.1", "1.0")) + + @unittest.skipUnless(alpm.available, "ALPM is unavailable") def test_compare_versions(self): - alpm = AlpmAPI() self.assertTrue(alpm.compare_versions("1.0", "<=", "2.0")) self.assertTrue(alpm.compare_versions("1.0", "<", "2.0")) self.assertFalse(alpm.compare_versions("1.0", ">=", "2.0")) @@ -24,4 +34,13 @@ class AlpmTestCase(unittest.TestCase): self.assertTrue(alpm.compare_versions("1.0", "=", "1.0-1")) self.assertFalse(alpm.compare_versions("1.0", "!=", "1.0")) + def test_behavior_when_unavailable(self): + mock_alpm = AlpmAPI() + mock_alpm.available = False + + self.assertIsNone(mock_alpm.version()) + self.assertIsNone(mock_alpm.vercmp("1.0", "1.0")) + self.assertIsNone(mock_alpm.compare_versions("1.0", "=", "1.0")) + + # vim: set ts=4 sw=4 et: -- cgit v1.2.3-2-g168b From dd0ecfaeaceb1e1b8a185800de35f0f6e741feac Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 26 Feb 2013 19:51:40 -0600 Subject: Use user.userprofile rather than user.get_profile() The get_profile() function is deprecated as of Django 1.5. Signed-off-by: Dan McGee --- packages/views/flag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/flag.py b/packages/views/flag.py index edb3f092..5c76e1d5 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -110,7 +110,7 @@ def flag(request, name, repo, arch): subject = '%s package [%s] marked out-of-date' % \ (pkg.repo.name, pkg.pkgname) for maint in maints: - if maint.get_profile().notify == True: + if maint.userprofile.notify == True: toemail.append(maint.email) if toemail: -- cgit v1.2.3-2-g168b From 1f2a6384f332e75e9befc13b5a4b7b2906db6c50 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Mon, 11 Mar 2013 21:25:27 -0500 Subject: Remove function caching in packages/utils We don't see these called enough to make caching the data worth it. Signed-off-by: Dan McGee --- packages/utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/utils.py b/packages/utils.py index ef6311eb..a4217fbd 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -10,7 +10,7 @@ from django.db.models.query import QuerySet from django.contrib.auth.models import User from main.models import Package, PackageFile, Arch, Repo -from main.utils import (cache_function, database_vendor, +from main.utils import (database_vendor, groupby_preserve_order, PackageStandin) from .models import (PackageGroup, PackageRelation, License, Depend, Conflict, Provision, Replacement, @@ -33,7 +33,6 @@ def parse_version(version): return ver, rel, epoch -@cache_function(127) def get_group_info(include_arches=None): raw_groups = PackageGroup.objects.values_list( 'name', 'pkg__arch__name').order_by('name').annotate( @@ -120,7 +119,6 @@ class Difference(object): return hash(self.__key()) -@cache_function(127) def get_differences_info(arch_a, arch_b): # This is a monster. Join packages against itself, looking for packages in # our non-'any' architectures only, and not having a corresponding package -- cgit v1.2.3-2-g168b From f2a6316be0b025a9ee22f22d34df1c00f60a8bdf Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 13 Apr 2013 11:56:26 -0500 Subject: Add additional pg_trgm indexes for quicker searches This allows our normal keyword-based search to be index-optimized rather than always doing full table scans. It requires the pg_trgm extension which is shipped out of the box with any sane install of PostgreSQL. Signed-off-by: Dan McGee --- packages/sql/search_indexes.postgresql_psycopg2.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/sql/search_indexes.postgresql_psycopg2.sql (limited to 'packages') diff --git a/packages/sql/search_indexes.postgresql_psycopg2.sql b/packages/sql/search_indexes.postgresql_psycopg2.sql new file mode 100644 index 00000000..a7eaf998 --- /dev/null +++ b/packages/sql/search_indexes.postgresql_psycopg2.sql @@ -0,0 +1,3 @@ +CREATE EXTENSION IF NOT EXISTS pg_trgm; +CREATE INDEX packages_pkgname_trgm_gist ON packages USING gist (UPPER(pkgname) gist_trgm_ops); +CREATE INDEX packages_pkgdesc_trgm_gist ON packages USING gist (UPPER(pkgdesc) gist_trgm_ops); -- cgit v1.2.3-2-g168b From 7fc8da7d959556b1204b7864959e73e7f5f5ec59 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 13 Apr 2013 13:05:02 -0500 Subject: Show replacments for package if it has been removed This covers the case where we can't find the package in any other repositories, but it was removed recently enough that we have a found package update object. Signed-off-by: Dan McGee --- packages/models.py | 9 +++++++++ packages/views/display.py | 2 ++ 2 files changed, 11 insertions(+) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 7bcdc000..92566a56 100644 --- a/packages/models.py +++ b/packages/models.py @@ -321,6 +321,15 @@ class Update(models.Model): return Package.objects.normal().filter( pkgname=self.pkgname, arch=self.arch) + def replacements(self): + pkgs = Package.objects.normal().filter( + replaces__name=self.pkgname) + if not self.arch.agnostic: + # make sure we match architectures if possible + arches = self.pkg.applicable_arches() + pkgs = pkgs.filter(arch__in=arches) + return pkgs + def __unicode__(self): return u'%s of %s on %s' % (self.get_action_flag_display(), self.pkgname, self.created) diff --git a/packages/views/display.py b/packages/views/display.py index fcf8fdea..50783835 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -55,6 +55,8 @@ def recently_removed_package(request, name, repo, arch, cutoff=CUTOFF): try: update = match.latest() elsewhere = update.elsewhere() + if len(elsewhere) == 0: + elsewhere = update.replacements() if len(elsewhere) == 1: return redirect(elsewhere[0]) context = { -- cgit v1.2.3-2-g168b From 4d7d08f93de9e6af9e664a00e090158e738a890c Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sun, 14 Apr 2013 13:45:00 -0500 Subject: Fix missing attribute error in replacment find code Whoops. Just introduced this when ensuring we look for both the packgae in other repositories as well as any replacments. Signed-off-by: Dan McGee --- packages/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/models.py b/packages/models.py index 92566a56..f830aade 100644 --- a/packages/models.py +++ b/packages/models.py @@ -326,7 +326,8 @@ class Update(models.Model): replaces__name=self.pkgname) if not self.arch.agnostic: # make sure we match architectures if possible - arches = self.pkg.applicable_arches() + arches = set(Arch.objects.filter(agnostic=True)) + arches.add(self.arch) pkgs = pkgs.filter(arch__in=arches) return pkgs -- cgit v1.2.3-2-g168b From 283cd944beefce8e364f238f25133e2d65b7702b Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 16 Apr 2013 20:16:06 -0500 Subject: Use require_safe decorator rather than require_GET This was added in Django 1.4, and ensures both GET and HEAD requests, but not POST requests, are allowed through. Signed-off-by: Dan McGee --- packages/views/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'packages') diff --git a/packages/views/__init__.py b/packages/views/__init__.py index 4c195385..c1f0f492 100644 --- a/packages/views/__init__.py +++ b/packages/views/__init__.py @@ -9,7 +9,7 @@ from django.db.models import Q from django.http import HttpResponse from django.shortcuts import redirect, render from django.views.decorators.cache import cache_control -from django.views.decorators.http import require_GET, require_POST +from django.views.decorators.http import require_safe, require_POST from main.models import Package, Arch from ..models import PackageRelation @@ -24,7 +24,7 @@ from .search import search_json from .signoff import signoffs, signoff_package, signoff_options, signoffs_json -@require_GET +@require_safe @cache_control(public=True, max_age=86400) def opensearch(request): if request.is_secure(): @@ -37,7 +37,7 @@ def opensearch(request): content_type='application/opensearchdescription+xml') -@require_GET +@require_safe @cache_control(public=True, max_age=300) def opensearch_suggest(request): search_term = request.GET.get('q', '') -- cgit v1.2.3-2-g168b From 31d39e75eea7fb6cdf3bb8bfd8b490d45de04ee9 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 16 Apr 2013 21:59:32 -0500 Subject: Add shortcut for HEAD requests on slower views We sometimes see some web bots and crawlers make HEAD requests to verify existence of certain pages in the application. However, they are less than kind as 20-50 requests might arrive at the same time, and package search and details pages are some of the slowest rendering pages we have due to the Django template engine. Rather than waste time generating the content only to throw it away, response as soon as we can with either a 404 or 200 response as appropriate, omitting the 'Content-Length' header completely, which seems to be acceptable by the HTTP spec. Signed-off-by: Dan McGee --- packages/views/display.py | 3 +++ packages/views/search.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'packages') diff --git a/packages/views/display.py b/packages/views/display.py index 50783835..87424483 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -7,6 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render from django.utils.timezone import now from main.models import Package, PackageFile, Arch, Repo +from main.utils import empty_response from mirrors.utils import get_mirror_url_for_download from ..models import Update from ..utils import get_group_info, PackageJSONEncoder @@ -126,6 +127,8 @@ def details(request, name='', repo='', arch=''): pkg = Package.objects.select_related( 'arch', 'repo', 'packager').get(pkgname=name, repo=repo_obj, arch=arch_obj) + if request.method == 'HEAD': + return empty_response() return render(request, 'packages/details.html', {'pkg': pkg}) except Package.DoesNotExist: # attempt a variety of fallback options before 404ing diff --git a/packages/views/search.py b/packages/views/search.py index 0362602e..b3778172 100644 --- a/packages/views/search.py +++ b/packages/views/search.py @@ -7,7 +7,7 @@ from django.http import HttpResponse from django.views.generic import ListView from main.models import Package, Arch, Repo -from main.utils import make_choice +from main.utils import empty_response, make_choice from ..models import PackageRelation from ..utils import attach_maintainers, PackageJSONEncoder @@ -99,6 +99,8 @@ class SearchListView(ListView): allowed_sort = list(sort_fields) + ["-" + s for s in sort_fields] def get(self, request, *args, **kwargs): + if request.method == 'HEAD': + return empty_response() self.form = PackageSearchForm(data=request.GET, show_staging=self.request.user.is_authenticated()) return super(SearchListView, self).get(request, *args, **kwargs) -- cgit v1.2.3-2-g168b From b7b24740640e24883cd17fd683e1d465fbb343f8 Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Tue, 16 Apr 2013 22:12:01 -0500 Subject: Various minor code cleanups and fixes Most of these were suggested by PyCharm, and include everything from little syntax issues and other bad smells to dead or bad code. Signed-off-by: Dan McGee --- packages/migrations/0002_populate_package_relation.py | 2 -- packages/templatetags/package_extras.py | 4 ++-- packages/utils.py | 2 +- packages/views/display.py | 2 -- packages/views/flag.py | 3 +-- 5 files changed, 4 insertions(+), 9 deletions(-) (limited to 'packages') diff --git a/packages/migrations/0002_populate_package_relation.py b/packages/migrations/0002_populate_package_relation.py index 738e068f..b0d32c7a 100644 --- a/packages/migrations/0002_populate_package_relation.py +++ b/packages/migrations/0002_populate_package_relation.py @@ -11,7 +11,6 @@ class Migration(DataMigration): ) def forwards(self, orm): - "Write your forwards methods here." # search by pkgbase first and insert those records qs = orm['main.Package'].objects.exclude(maintainer=None).exclude( pkgbase=None).distinct().values('pkgbase', 'maintainer_id') @@ -29,7 +28,6 @@ class Migration(DataMigration): defaults={'user_id': row['maintainer_id']}) def backwards(self, orm): - "Write your backwards methods here." if not db.dry_run: orm.PackageRelation.objects.all().delete() pass diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index f14fab1e..ef0e1aea 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -53,10 +53,10 @@ def do_buildsortqs(parser, token): tagname, sortfield = token.split_contents() except ValueError: raise template.TemplateSyntaxError( - "%r tag requires a single argument" % tagname) + "%r tag requires a single argument" % token) if not (sortfield[0] == sortfield[-1] and sortfield[0] in ('"', "'")): raise template.TemplateSyntaxError( - "%r tag's argument should be in quotes" % tagname) + "%r tag's argument should be in quotes" % token) return BuildQueryStringNode(sortfield[1:-1]) diff --git a/packages/utils.py b/packages/utils.py index a4217fbd..4f3b8665 100644 --- a/packages/utils.py +++ b/packages/utils.py @@ -391,7 +391,7 @@ SELECT DISTINCT s.id """ cursor = connection.cursor() # query pre-process- fill in table name and placeholders for IN - repo_sql = ','.join(['%s' for r in repos]) + repo_sql = ','.join(['%s' for _ in repos]) sql = sql % (model._meta.db_table, repo_sql, repo_sql) repo_ids = [r.pk for r in repos] # repo_ids are needed twice, so double the array diff --git a/packages/views/display.py b/packages/views/display.py index 87424483..021c7ed8 100644 --- a/packages/views/display.py +++ b/packages/views/display.py @@ -228,8 +228,6 @@ def download(request, name, repo, arch): if pkg.arch.agnostic: # grab the first non-any arch to fake the download path arch = Arch.objects.exclude(agnostic=True)[0].name - values = { - } url = '{host}{repo}/os/{arch}/{filename}'.format(host=url.url, repo=pkg.repo.name.lower(), arch=arch, filename=pkg.filename) return redirect(url) diff --git a/packages/views/flag.py b/packages/views/flag.py index 5c76e1d5..39cdcef8 100644 --- a/packages/views/flag.py +++ b/packages/views/flag.py @@ -110,7 +110,7 @@ def flag(request, name, repo, arch): subject = '%s package [%s] marked out-of-date' % \ (pkg.repo.name, pkg.pkgname) for maint in maints: - if maint.userprofile.notify == True: + if maint.userprofile.notify is True: toemail.append(maint.email) if toemail: @@ -133,7 +133,6 @@ def flag(request, name, repo, arch): return redirect('package-flag-confirmed', name=name, repo=repo, arch=arch) else: - initial = {} form = FlagForm(authenticated=authenticated) context = { -- cgit v1.2.3-2-g168b From bb18fa3323a0494a2774ea61911572b089d04b6d Mon Sep 17 00:00:00 2001 From: Dan McGee Date: Sat, 20 Apr 2013 13:32:09 -0500 Subject: Fix parsing issues when query string keys contain unicode This is dirty, but it works. There is probably a better and cleaner way to do all of this, but for now just fix it quickly. Signed-off-by: Dan McGee --- packages/templatetags/package_extras.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'packages') diff --git a/packages/templatetags/package_extras.py b/packages/templatetags/package_extras.py index ef0e1aea..f7392a96 100644 --- a/packages/templatetags/package_extras.py +++ b/packages/templatetags/package_extras.py @@ -37,6 +37,12 @@ class BuildQueryStringNode(template.Node): def render(self, context): qs = parse_qs(context['current_query']) + # This is really dirty. The crazy round trips we do on our query string + # mean we get things like u'\xe2\x98\x83' in our views, when we should + # have simply u'\u2603' or a byte string of the UTF-8 value. Force the + # keys and list of values to be byte strings only. + qs = {k.encode('latin-1'): [v.encode('latin-1') for v in vals] + for k, vals in qs.items()} if 'sort' in qs and self.sortfield in qs['sort']: if self.sortfield.startswith('-'): qs['sort'] = [self.sortfield[1:]] -- cgit v1.2.3-2-g168b