summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan McGee <dan@archlinux.org>2010-03-27 16:15:20 -0500
committerDan McGee <dan@archlinux.org>2010-03-27 16:15:20 -0500
commitfe832ea845f07a79b4580f7bca1dcf44b2f215ee (patch)
treecbe8554621f84d4f40b4991b883571ad5d419888
parentf3b3117d1f0ee8862a0b47d6dfe9b20960dbb13e (diff)
downloadarchweb-fe832ea845f07a79b4580f7bca1dcf44b2f215ee.tar.gz
archweb-fe832ea845f07a79b4580f7bca1dcf44b2f215ee.zip
Move package maintainer off of package model
This is an attempt to fix our long-standing problems dealing with maintainer information. Move the actual maintainer information off of the package model into a PackageRelation object, which has some flexibility to later represent more than just maintainership. This solves multiple problems: * If a package gets accidentally deleted, so did the maintainer info * Testing packages have always shown up as orphans * With split packages, it was easy to miss some of the sub-packages This commit does not include the deletion of the original maintainer column; that will come at a later time when I feel more confident that the data was migrated correctly. Signed-off-by: Dan McGee <dan@archlinux.org>
-rw-r--r--devel/views.py13
-rw-r--r--main/admin.py4
-rw-r--r--main/models.py21
-rw-r--r--packages/migrations/0001_initial.py72
-rw-r--r--packages/migrations/0002_populate_package_relation.py233
-rw-r--r--packages/migrations/__init__.py0
-rw-r--r--packages/models.py23
-rw-r--r--packages/views.py65
-rw-r--r--templates/devel/index.html7
-rw-r--r--templates/packages/details.html9
-rw-r--r--templates/packages/flagged.html2
-rw-r--r--templates/packages/search.html6
12 files changed, 410 insertions, 45 deletions
diff --git a/devel/views.py b/devel/views.py
index 4b278d16..045e60f7 100644
--- a/devel/views.py
+++ b/devel/views.py
@@ -5,10 +5,14 @@ from django.contrib.auth.models import User
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.core.mail import send_mail
+from django.db.models import Q
+
from main.models import Package, Todolist
from main.models import Arch, Repo
from main.models import UserProfile, News
from main.models import Mirror
+from packages.models import PackageRelation
+
import random
from string import ascii_letters, digits
pwletters = ascii_letters + digits
@@ -17,12 +21,15 @@ pwletters = ascii_letters + digits
@login_required
def index(request):
'''the Developer dashboard'''
+ inner_q = PackageRelation.objects.filter(user=request.user).values('pkgbase')
+ packages = Package.objects.select_related('arch', 'repo').filter(needupdate=True)
+ packages = packages.filter(Q(pkgname__in=inner_q) | Q(pkgbase__in=inner_q))
+
page_dict = {
'todos': Todolist.objects.incomplete(),
'repos': Repo.objects.all(), 'arches': Arch.objects.all(),
- 'maintainers': [
- User(id=None, username="orphan", first_name="Orphans")
- ] + list(User.objects.filter(is_active=True).order_by('last_name'))
+ 'maintainers': User.objects.filter(is_active=True).order_by('last_name'),
+ 'flagged' : packages,
}
return render_to_response('devel/index.html',
diff --git a/main/admin.py b/main/admin.py
index 3ab6d5d4..d4a78068 100644
--- a/main/admin.py
+++ b/main/admin.py
@@ -74,8 +74,8 @@ class RepoAdmin(admin.ModelAdmin):
search_fields = ('name',)
class PackageAdmin(admin.ModelAdmin):
- list_display = ('pkgname', 'repo', 'arch', 'maintainer')
- list_filter = ('repo', 'arch', 'maintainer')
+ list_display = ('pkgname', 'repo', 'arch', 'last_update')
+ list_filter = ('repo', 'arch')
ordering = ['pkgname']
search_fields = ('pkgname',)
diff --git a/main/models.py b/main/models.py
index 0954f79d..b49acd2c 100644
--- a/main/models.py
+++ b/main/models.py
@@ -1,7 +1,9 @@
from django.db import models
from django.db.models import Q
from django.contrib.auth.models import User
+
from main.middleware import get_user
+from packages.models import PackageRelation
###########################
### User Profile Class ####
@@ -203,6 +205,18 @@ class Package(models.Model):
self.arch.name, self.pkgname)
@property
+ def pkgbase_safe(self):
+ if self.pkgbase:
+ return self.pkgbase
+ return self.pkgname
+
+ @property
+ def maintainers(self):
+ return User.objects.filter(
+ package_relations__pkgbase=self.pkgbase_safe,
+ package_relations__type=PackageRelation.MAINTAINER)
+
+ @property
def signoffs(self):
if 'signoffs_cache' in dir(self):
if len(self.signoffs_cache) > 0:
@@ -265,16 +279,12 @@ class Package(models.Model):
def get_svn_link(self, svnpath):
linkbase = "http://repos.archlinux.org/wsvn/%s/%s/%s/"
- if self.pkgbase:
- dirname = self.pkgbase
- else:
- dirname = self.pkgname
repo = self.repo.name.lower()
if repo.startswith('community'):
root = 'community'
else:
root = 'packages'
- return linkbase % (root, dirname, svnpath)
+ return linkbase % (root, self.pkgbase_safe, svnpath)
def get_arch_svn_link(self):
repo = self.repo.name.lower()
@@ -362,4 +372,3 @@ class ExternalProject(models.Model):
return self.name
# vim: set ts=4 sw=4 et:
-
diff --git a/packages/migrations/0001_initial.py b/packages/migrations/0001_initial.py
new file mode 100644
index 00000000..76e97340
--- /dev/null
+++ b/packages/migrations/0001_initial.py
@@ -0,0 +1,72 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+ def forwards(self, orm):
+ # Adding model 'PackageRelation'
+ db.create_table('packages_packagerelation', (
+ ('pkgbase', self.gf('django.db.models.fields.CharField')(max_length=255)),
+ ('type', self.gf('django.db.models.fields.PositiveIntegerField')(default=1)),
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='package_relations', to=orm['auth.User'])),
+ ))
+ db.send_create_signal('packages', ['PackageRelation'])
+ # Adding unique constraint on 'PackageRelation', fields ['pkgbase', 'user', 'type']
+ db.create_unique('packages_packagerelation', ['pkgbase', 'user_id', 'type'])
+
+ def backwards(self, orm):
+ # Deleting model 'PackageRelation'
+ db.delete_table('packages_packagerelation')
+ # Removing unique constraint on 'PackageRelation', fields ['pkgbase', 'user', 'type']
+ db.delete_unique('packages_packagerelation', ['pkgbase', 'user_id', 'type'])
+
+ 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']", 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'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']", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ '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']", 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'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'})
+ },
+ 'packages.packagerelation': {
+ 'Meta': {'unique_together': "(('pkgbase', 'user', 'type'),)", 'object_name': 'PackageRelation'},
+ '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']"})
+ }
+ }
+
+ complete_apps = ['packages']
diff --git a/packages/migrations/0002_populate_package_relation.py b/packages/migrations/0002_populate_package_relation.py
new file mode 100644
index 00000000..7f903503
--- /dev/null
+++ b/packages/migrations/0002_populate_package_relation.py
@@ -0,0 +1,233 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+
+ no_dry_run = True
+
+ 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')
+ for row in qs:
+ pr, created = orm.PackageRelation.objects.get_or_create(
+ pkgbase=row['pkgbase'], user__id=row['maintainer_id'],
+ defaults={'user_id': row['maintainer_id']})
+
+ # next search by pkgname first and insert those records
+ qs = orm['main.Package'].objects.exclude(maintainer=None).filter(
+ pkgbase=None).distinct().values('pkgname', 'maintainer_id')
+ for row in qs:
+ pr, created = orm.PackageRelation.objects.get_or_create(
+ pkgbase=row['pkgname'], user__id=row['maintainer_id'],
+ 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
+
+ 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']", 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'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']", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ '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']", 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'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.altforum': {
+ 'Meta': {'object_name': 'AltForum', 'db_table': "'alt_forums'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'language': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'main.arch': {
+ 'Meta': {'object_name': 'Arch', 'db_table': "'arches'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'main.donor': {
+ 'Meta': {'object_name': 'Donor', 'db_table': "'donors'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+ },
+ 'main.externalproject': {
+ 'Meta': {'object_name': 'ExternalProject'},
+ 'description': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
+ },
+ 'main.mirror': {
+ 'Meta': {'object_name': 'Mirror'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'admin_email': ('django.db.models.fields.EmailField', [], {'max_length': '255', 'blank': 'True'}),
+ 'country': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'isos': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'notes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'public': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'rsync_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
+ 'rsync_user': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'blank': 'True'}),
+ 'tier': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}),
+ 'upstream': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Mirror']", 'null': 'True'})
+ },
+ 'main.mirrorprotocol': {
+ 'Meta': {'object_name': 'MirrorProtocol'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'protocol': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '10'})
+ },
+ 'main.mirrorrsync': {
+ 'Meta': {'object_name': 'MirrorRsync'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'ip': ('django.db.models.fields.CharField', [], {'max_length': '24'}),
+ 'mirror': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rsync_ips'", 'to': "orm['main.Mirror']"})
+ },
+ 'main.mirrorurl': {
+ 'Meta': {'object_name': 'MirrorUrl'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mirror': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'urls'", 'to': "orm['main.Mirror']"}),
+ 'protocol': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'urls'", 'to': "orm['main.MirrorProtocol']"}),
+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'main.news': {
+ 'Meta': {'object_name': 'News', 'db_table': "'news'"},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'news_author'", 'to': "orm['auth.User']"}),
+ 'content': ('django.db.models.fields.TextField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'postdate': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'main.package': {
+ 'Meta': {'object_name': 'Package', 'db_table': "'packages'"},
+ 'arch': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Arch']"}),
+ 'build_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+ 'compressed_size': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+ 'files_last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'installed_size': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+ 'last_update': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'license': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'maintainer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'maintained_packages'", 'null': 'True', 'to': "orm['auth.User']"}),
+ 'needupdate': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'pkgbase': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'pkgdesc': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'pkgname': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'repo': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'packages'", 'to': "orm['main.Repo']"}),
+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ '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', [], {'max_length': '255'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"})
+ },
+ 'main.packagefile': {
+ 'Meta': {'object_name': 'PackageFile', 'db_table': "'package_files'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"})
+ },
+ 'main.press': {
+ 'Meta': {'object_name': 'Press', 'db_table': "'press'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'main.repo': {
+ 'Meta': {'object_name': 'Repo', 'db_table': "'repos'"},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}),
+ 'testing': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'})
+ },
+ 'main.signoff': {
+ 'Meta': {'object_name': 'Signoff'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'packager': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"}),
+ 'pkgrel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'pkgver': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'main.todolist': {
+ 'Meta': {'object_name': 'Todolist', 'db_table': "'todolists'"},
+ 'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}),
+ 'date_added': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': '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', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Todolist']"}),
+ 'pkg': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Package']"})
+ },
+ 'main.userprofile': {
+ 'Meta': {'object_name': 'UserProfile', 'db_table': "'user_profiles'"},
+ 'alias': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'allowed_repos': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Repo']", 'blank': 'True'}),
+ 'favorite_distros': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'interests': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'languages': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'location': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'notify': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'occupation': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'other_contact': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}),
+ 'picture': ('django.db.models.fields.files.FileField', [], {'default': "'devs/silhouette.png'", 'max_length': '100'}),
+ 'public_email': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'roles': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'userprofile_user'", 'unique': 'True', 'to': "orm['auth.User']"}),
+ 'website': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'yob': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'packages.packagerelation': {
+ 'Meta': {'unique_together': "(('pkgbase', 'user', 'type'),)", 'object_name': 'PackageRelation'},
+ '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']"})
+ }
+ }
+
+ complete_apps = ['main', 'packages']
diff --git a/packages/migrations/__init__.py b/packages/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/packages/migrations/__init__.py
diff --git a/packages/models.py b/packages/models.py
new file mode 100644
index 00000000..9eff5177
--- /dev/null
+++ b/packages/models.py
@@ -0,0 +1,23 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+class PackageRelation(models.Model):
+ '''
+ Represents maintainership (or interest) in a package by a given developer.
+ It is not a true foreign key to packages as we want to key off
+ pkgbase/pkgname instead, as well as preserve this information across
+ package deletes, adds, and in all repositories.
+ '''
+ MAINTAINER = 1
+ WATCHER = 2
+ TYPE_CHOICES = (
+ (MAINTAINER, 'Maintainer'),
+ (WATCHER, 'Watcher'),
+ )
+ pkgbase = models.CharField(max_length=255)
+ user = models.ForeignKey(User, related_name="package_relations")
+ type = models.PositiveIntegerField(choices=TYPE_CHOICES, default=MAINTAINER)
+ class Meta:
+ unique_together = (('pkgbase', 'user', 'type'),)
+
+# vim: set ts=4 sw=4 et:
diff --git a/packages/views.py b/packages/views.py
index 6bd54a66..47ad1d6c 100644
--- a/packages/views.py
+++ b/packages/views.py
@@ -16,6 +16,7 @@ import datetime
from main.models import Package, PackageFile
from main.models import Arch, Repo, Signoff
from main.utils import make_choice
+from packages.models import PackageRelation
def opensearch(request):
if request.is_secure():
@@ -36,23 +37,31 @@ def update(request):
mode = None
if request.POST.has_key('adopt'):
mode = 'adopt'
- maint = request.user
if request.POST.has_key('disown'):
mode = 'disown'
- maint = None
if mode:
repos = request.user.userprofile_user.all()[0].allowed_repos.all()
pkgs = Package.objects.filter(id__in=ids, repo__in=repos)
disallowed_pkgs = Package.objects.filter(id__in=ids).exclude(
repo__in=repos)
- pkgs.update(maintainer=maint)
+ for pkg in pkgs:
+ maints = pkg.maintainers
+ if mode == 'adopt' and request.user not in maints:
+ pr = PackageRelation(pkgbase=pkg.pkgbase_safe,
+ user=request.user,
+ type=PackageRelation.MAINTAINER)
+ pr.save()
+ elif mode == 'disown' and request.user in maints:
+ rels = PackageRelation.objects.filter(pkgbase=pkg.pkgbase_safe,
+ user=request.user)
+ rels.delete()
request.user.message_set.create(message="%d packages %sed" % (
len(pkgs), mode))
if disallowed_pkgs:
request.user.message_set.create(
- message="You do not have permmission to adopt: %s" % (
+ message="You do not have permission to adopt: %s" % (
' '.join([p.pkgname for p in disallowed_pkgs])
))
else:
@@ -70,12 +79,13 @@ def details(request, name='', repo='', arch=''):
arch.lower(), repo.title(), name))
def getmaintainer(request, name, repo, arch):
- "Returns the maintainer as a plaintext."
+ "Returns the maintainers as plaintext."
- pkg= get_object_or_404(Package,
+ pkg = get_object_or_404(Package,
pkgname=name, repo__name__iexact=repo, arch__name=arch)
+ names = [m.username for m in pkg.maintainers]
- return HttpResponse(str(pkg.maintainer))
+ return HttpResponse(str('\n'.join(names)), mimetype='text/plain')
class PackageSearchForm(forms.Form):
repo = forms.ChoiceField(required=False)
@@ -122,7 +132,7 @@ class PackageSearchForm(forms.Form):
def search(request, page=None):
current_query = '?'
limit=50
- packages = Package.objects.select_related('arch', 'repo', 'maintainer')
+ packages = Package.objects.select_related('arch', 'repo')
if request.GET:
current_query += request.GET.urlencode()
@@ -131,18 +141,23 @@ def search(request, page=None):
if form.cleaned_data['repo']:
packages = packages.filter(
repo__name=form.cleaned_data['repo'])
+
if form.cleaned_data['arch']:
packages = packages.filter(
arch__name=form.cleaned_data['arch'])
+
if form.cleaned_data['maintainer'] == 'orphan':
- packages=packages.filter(maintainer=None)
+ inner_q = PackageRelation.objects.all().values('pkgbase')
+ packages = packages.exclude(Q(pkgname__in=inner_q) | Q(pkgbase__in=inner_q))
elif form.cleaned_data['maintainer']:
- packages = packages.filter(
- maintainer__username=form.cleaned_data['maintainer'])
+ inner_q = PackageRelation.objects.filter(user__username=form.cleaned_data['maintainer']).values('pkgbase')
+ packages = packages.filter(Q(pkgname__in=inner_q) | Q(pkgbase__in=inner_q))
+
if form.cleaned_data['flagged'] == 'Flagged':
packages=packages.filter(needupdate=True)
elif form.cleaned_data['flagged'] == 'Not Flagged':
packages = packages.filter(needupdate=False)
+
if form.cleaned_data['q']:
query = form.cleaned_data['q']
q = Q(pkgname__icontains=query) | Q(pkgdesc__icontains=query)
@@ -161,7 +176,7 @@ def search(request, page=None):
if packages.count() == 1:
return HttpResponseRedirect(packages[0].get_absolute_url())
- allowed_sort = ["arch", "repo", "pkgname", "maintainer", "last_update"]
+ allowed_sort = ["arch", "repo", "pkgname", "last_update"]
allowed_sort += ["-" + s for s in allowed_sort]
sort = request.GET.get('sort', None)
# TODO: sorting by multiple fields makes using a DB index much harder
@@ -258,23 +273,25 @@ def flag(request, pkgid):
if request.POST:
form = FlagForm(request.POST)
if form.is_valid() and form.cleaned_data['website'] == '':
- send_email = True
# flag all architectures
pkgs = Package.objects.filter(
pkgname=pkg.pkgname, repo=pkg.repo)
pkgs.update(needupdate=True)
- if not pkg.maintainer:
- toemail = 'arch-notifications@archlinux.org'
- subject = 'Orphan %s package [%s] marked out-of-date' % (pkg.repo.name, pkg.pkgname)
- elif pkg.maintainer.get_profile().notify == True:
- toemail = pkg.maintainer.email
- subject = '%s package [%s] marked out-of-date' % (pkg.repo.name, pkg.pkgname)
+ maints = pkg.maintainers
+ if not maints:
+ toemail = ['arch-notifications@archlinux.org']
+ subject = 'Orphan %s package [%s] marked out-of-date' % \
+ (pkg.repo.name, pkg.pkgname)
else:
- # no need to send any email, packager didn't want notification
- send_email = False
-
- if send_email:
+ toemail = []
+ subject = '%s package [%s] marked out-of-date' % \
+ (pkg.repo.name, pkg.pkgname)
+ for maint in maints:
+ if maint.get_profile().notify == True:
+ toemail.append(maint.email)
+
+ if toemail:
# send notification email to the maintainer
t = loader.get_template('packages/outofdate.txt')
c = Context({
@@ -286,7 +303,7 @@ def flag(request, pkgid):
send_mail(subject,
t.render(c),
'Arch Website Notification <nobody@archlinux.org>',
- [toemail],
+ toemail,
fail_silently=True)
context['confirmed'] = True
diff --git a/templates/devel/index.html b/templates/devel/index.html
index 12c0791e..acbe90bf 100644
--- a/templates/devel/index.html
+++ b/templates/devel/index.html
@@ -65,18 +65,17 @@
<br /><br />
<div class="greybox">
+ <div>Counts are by 'pkgbase' and not raw number of packages.</div>
<h3 class="title" style="cursor: pointer" onclick="$(this).next().toggle();">Stats by Maintainer</h3>
<table class="results" width="100%" style="display: none">
<tr>
<th width="50%">Maintainer</th>
<th># Packages</th>
- <th># Flagged</th>
</tr>
{% for maint in maintainers %}
<tr class="{% cycle pkgr2,pkgr1 %}">
<td><strong>{{ maint.get_full_name }}</strong></td>
- <td><a href="/packages/?maintainer={{ maint.username }}"><strong>{{ maint.maintained_packages.count }}</strong> packages</a></td>
- <td><a href="/packages/?maintainer={{ maint.username }}&flagged=Flagged"><strong>{{ maint.maintained_packages.flagged.count }}</strong> packages</a></td>
+ <td><a href="/packages/?maintainer={{ maint.username }}"><strong>{{ maint.package_relations.count }}</strong> packages</a></td>
</tr>
{% endfor %}
</table>
@@ -99,7 +98,7 @@
<th>Version</th>
<th>Arch</th>
</tr>
- {% for pkg in user.maintained_packages.flagged %}
+ {% for pkg in flagged %}
<tr class="{% cycle pkgr2,pkgr1 %}">
<td>
<a href="{{ pkg.get_absolute_url }}">{{ pkg.pkgname }}</a>
diff --git a/templates/packages/details.html b/templates/packages/details.html
index def07501..648b6483 100644
--- a/templates/packages/details.html
+++ b/templates/packages/details.html
@@ -49,7 +49,14 @@
<td>{{ pkg.license }}</td>
</tr><tr>
<th>Maintainer:</th>
- <td>{% if pkg.maintainer %}{{ pkg.maintainer.get_full_name }}{% else %}None{% endif %}</td>
+ {% with pkg.maintainers as maints %}
+ <td>{% if maints %}
+ {% for m in maints %}
+ {{ m.get_full_name }}<br/>
+ {% endfor %}
+ {% else %}Orphan{% endif %}
+ </td>
+ {% endwith %}
</tr><tr>
<th>Package Size:</th>
<td>{{ pkg.compressed_size|filesizeformat }}</td>
diff --git a/templates/packages/flagged.html b/templates/packages/flagged.html
index 64cb2452..3461bbda 100644
--- a/templates/packages/flagged.html
+++ b/templates/packages/flagged.html
@@ -3,6 +3,6 @@
{% block content %}
<p>
- {{pkg.pkgname}} on {{pkg.arch}} has already been flagged out of date.
+ {{pkg.pkgname}} has already been flagged out of date.
</p>
{% endblock %}
diff --git a/templates/packages/search.html b/templates/packages/search.html
index e760788e..4f7bc776 100644
--- a/templates/packages/search.html
+++ b/templates/packages/search.html
@@ -44,7 +44,7 @@
<table class="results" width="100%">
{% if paginator %}
<tr>
- <td colspan="{% if user.is_authenticated %}6{% else %}5{% endif %}">
+ <td colspan="{% if user.is_authenticated %}5{% else %}4{% endif %}">
{{paginator.count}} packages found.
Page {{page_obj.number}} of {{paginator.num_pages}}.
</td>
@@ -77,7 +77,6 @@
<th><a href="/packages/{% buildsortqs "pkgname" %}">Name</a></th>
<th>Version</th>
<th>Description</th>
- <th><a href="/packages/{% buildsortqs "maintainer" %}">Maintainer</a></th>
<th><a href="/packages/{% buildsortqs "-last_update" %}">Last Updated</a></th>
</tr>
@@ -95,13 +94,12 @@
<td>{{ pkg.pkgver }}-{{ pkg.pkgrel }}</td>
{% endif %}
<td>{{ pkg.pkgdesc }}</td>
- <td>{{ pkg.maintainer|default:"Orphan" }}</td>
<td>{{ pkg.last_update|date:"Y-m-d" }}</td>
</tr>
{% endfor %}
{% if paginator %}
<tr>
- <td colspan="{% if user.is_authenticated %}6{% else %}5{% endif %}">
+ <td colspan="{% if user.is_authenticated %}5{% else %}4{% endif %}">
{{paginator.count}} packages found.
Page {{page_obj.number}} of {{paginator.num_pages}}.
</td>