From dbe77eff4172412613af9a8f3e692bea8ad0ea6a Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Sun, 21 Mar 2010 17:13:14 -0500 Subject: [PATCH 001/146] Switched to TemporaryUploadFile because the magic foo in SimpleUploadFile is broken on some systems --- djangopypi/http.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/djangopypi/http.py b/djangopypi/http.py index 7be5959..40d84de 100644 --- a/djangopypi/http.py +++ b/djangopypi/http.py @@ -1,5 +1,5 @@ from django.http import HttpResponse -from django.core.files.uploadedfile import SimpleUploadedFile +from django.core.files.uploadedfile import TemporaryUploadedFile from django.utils.datastructures import MultiValueDict from django.contrib.auth import authenticate @@ -38,8 +38,11 @@ def parse_distutils_request(request): continue content = part[len("\n".join(item[0:2]))+2:len(part)-1] if "filename" in headers: - file = SimpleUploadedFile(headers["filename"], content, - content_type="application/gzip") + file = TemporaryUploadedFile(name=headers["filename"], + size=len(content), + content_type="application/gzip") + file.write(content) + file.seek(0) files["distribution"] = [file] elif headers["name"] in post_data: post_data[headers["name"]].append(content) From bdd9c998730f9ddd21960c23dd7e805192079685 Mon Sep 17 00:00:00 2001 From: Bo Shi Date: Wed, 9 Dec 2009 03:11:26 +0800 Subject: [PATCH 002/146] Fix error in README (register without -r attempts pypi.python.org). --- README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index ad431a6..ca3b5e1 100644 --- a/README +++ b/README @@ -60,7 +60,7 @@ Uploading a package: Python >=2.6 To push the package to the local pypi:: - $ python setup.py register sdist upload -r local + $ python setup.py register -r local sdist upload -r local Uploading a package: Python <2.6 @@ -74,6 +74,6 @@ instead of using register and dist command, you can use "mregister" and "mupload To push the package to the local pypi:: - $ python setup.py mregister sdist mupload -r local + $ python setup.py mregister -r local sdist mupload -r local .. # vim: syntax=rst expandtab tabstop=4 shiftwidth=4 shiftround From 3def7822bed443c48b27f41f52f7af1244eec180 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Fri, 19 Mar 2010 03:44:20 +0800 Subject: [PATCH 003/146] Switched download_url to be a CharField instead of a URLField. download_url can be an FTP URL (for instance, python-memcached does this) and since Django doesn't validate FTP URLs on the URLField in models, it's a 400. --- djangopypi/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangopypi/models.py b/djangopypi/models.py index 195c491..28c7bc8 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -52,7 +52,7 @@ class Project(models.Model): metadata_version = models.CharField(max_length=64, default=1.0) author = models.CharField(max_length=128, blank=True) home_page = models.URLField(verify_exists=False, blank=True, null=True) - download_url = models.URLField(verify_exists=False, blank=True, null=True) + download_url = models.CharField(max_length=200, blank=True, null=True) summary = models.TextField(blank=True) description = models.TextField(blank=True) author_email = models.CharField(max_length=255, blank=True) From d06c58f694cb15a59b06c6f419768d9e2505df63 Mon Sep 17 00:00:00 2001 From: Ask Solem Date: Mon, 22 Mar 2010 17:42:20 +0800 Subject: [PATCH 004/146] Addded Michael Richardson to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 7c61fd8..dea9f74 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,3 +9,4 @@ Carl Meyer Vinícius das Chagas Silva Vanderson Mota dos Santos Stefan Foulis +Michael Richardson From 9c4795498052bdc627f403ca9c975de5e1c7aeec Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Tue, 27 Apr 2010 21:32:51 -0500 Subject: [PATCH 005/146] Began adding South migration support, refactoring models to more closely match those on pypi.python.org --- .gitignore | 2 + buildout.cfg | 1 + chishop/conf/default.py | 1 + djangopypi/migrations/0001_initial.py | 153 ++++++++++++++++++++++++++ djangopypi/migrations/__init__.py | 0 djangopypi/models.py | 97 ++++++++++++---- 6 files changed, 230 insertions(+), 24 deletions(-) create mode 100644 djangopypi/migrations/0001_initial.py create mode 100644 djangopypi/migrations/__init__.py diff --git a/.gitignore b/.gitignore index b0a673f..17b4857 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,6 @@ parts eggs bin developer-eggs +develop-eggs downloads +.installed.cfg diff --git a/buildout.cfg b/buildout.cfg index 160db4c..9ec9559 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -4,6 +4,7 @@ find-links = http://bitbucket.org/ubernostrum/django-registration/downloads/djan unzip = true eggs = pkginfo django-registration==0.8-alpha-1 + South [django] recipe = djangorecipe diff --git a/chishop/conf/default.py b/chishop/conf/default.py index 97002b6..4828a00 100644 --- a/chishop/conf/default.py +++ b/chishop/conf/default.py @@ -108,4 +108,5 @@ 'django.contrib.admindocs', 'registration', 'djangopypi', + 'south', ) diff --git a/djangopypi/migrations/0001_initial.py b/djangopypi/migrations/0001_initial.py new file mode 100644 index 0000000..2180a43 --- /dev/null +++ b/djangopypi/migrations/0001_initial.py @@ -0,0 +1,153 @@ +# 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 'Classifier' + db.create_table('djangopypi_classifier', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), + )) + db.send_create_signal('djangopypi', ['Classifier']) + + # Adding model 'Project' + db.create_table('djangopypi_project', ( + ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + ('license', self.gf('django.db.models.fields.TextField')(blank=True)), + ('metadata_version', self.gf('django.db.models.fields.CharField')(default=1.0, max_length=64)), + ('author', self.gf('django.db.models.fields.CharField')(max_length=128, blank=True)), + ('home_page', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + ('download_url', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True)), + ('summary', self.gf('django.db.models.fields.TextField')(blank=True)), + ('author_email', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('owner', self.gf('django.db.models.fields.related.ForeignKey')(related_name='projects', to=orm['auth.User'])), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), + )) + db.send_create_signal('djangopypi', ['Project']) + + # Adding M2M table for field classifiers on 'Project' + db.create_table('djangopypi_project_classifiers', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), + ('classifier', models.ForeignKey(orm['djangopypi.classifier'], null=False)) + )) + db.create_unique('djangopypi_project_classifiers', ['project_id', 'classifier_id']) + + # Adding model 'Release' + db.create_table('djangopypi_release', ( + ('upload_time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + ('md5_digest', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('filetype', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('pyversion', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='releases', to=orm['djangopypi.Project'])), + ('platform', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('version', self.gf('django.db.models.fields.CharField')(max_length=128)), + ('signature', self.gf('django.db.models.fields.CharField')(max_length=128, blank=True)), + ('distribution', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + )) + db.send_create_signal('djangopypi', ['Release']) + + # Adding unique constraint on 'Release', fields ['project', 'version', 'platform', 'distribution', 'pyversion'] + db.create_unique('djangopypi_release', ['project_id', 'version', 'platform', 'distribution', 'pyversion']) + + + def backwards(self, orm): + + # Deleting model 'Classifier' + db.delete_table('djangopypi_classifier') + + # Deleting model 'Project' + db.delete_table('djangopypi_project') + + # Removing M2M table for field classifiers on 'Project' + db.delete_table('djangopypi_project_classifiers') + + # Deleting model 'Release' + db.delete_table('djangopypi_release') + + # Removing unique constraint on 'Release', fields ['project', 'version', 'platform', 'distribution', 'pyversion'] + db.delete_unique('djangopypi_release', ['project_id', 'version', 'platform', 'distribution', 'pyversion']) + + + 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'}) + }, + 'djangopypi.classifier': { + 'Meta': {'object_name': 'Classifier'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'djangopypi.project': { + 'Meta': {'object_name': 'Project'}, + 'author': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'classifiers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djangopypi.Classifier']"}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'download_url': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'home_page': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'license': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'metadata_version': ('django.db.models.fields.CharField', [], {'default': '1.0', 'max_length': '64'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}), + 'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) + }, + 'djangopypi.release': { + 'Meta': {'unique_together': "(('project', 'version', 'platform', 'distribution', 'pyversion'),)", 'object_name': 'Release'}, + 'distribution': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Project']"}), + 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'signature': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), + 'upload_time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + } + } + + complete_apps = ['djangopypi'] diff --git a/djangopypi/migrations/__init__.py b/djangopypi/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djangopypi/models.py b/djangopypi/models.py index 28c7bc8..5d3b6e6 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -32,6 +32,29 @@ ("ultrasparc", "UltraSparc"), ) +DIST_FILE_TYPES = ( + ('sdist','Source'), + ('bdist_dumb','"dumb" binary'), + ('bdist_rpm','RPM'), + ('bdist_wininst','MS Windows installer'), + ('bdist_egg','Python Egg'), + ('bdist_dmg','OS X Disk Image'), +) + +PYTHON_VERSIONS = ( + ('any','Any i.e. pure python'), + '2.1', + '2.2', + '2.3', + '2.4', + '2.5', + '2.6', + '2.7', + '3.0', + '3.1', + '3.2', +) + UPLOAD_TO = getattr(settings, "DJANGOPYPI_RELEASE_UPLOAD_TO", 'dist') @@ -47,19 +70,12 @@ def __unicode__(self): class Project(models.Model): - name = models.CharField(max_length=255, unique=True) - license = models.TextField(blank=True) - metadata_version = models.CharField(max_length=64, default=1.0) - author = models.CharField(max_length=128, blank=True) - home_page = models.URLField(verify_exists=False, blank=True, null=True) - download_url = models.CharField(max_length=200, blank=True, null=True) - summary = models.TextField(blank=True) - description = models.TextField(blank=True) - author_email = models.CharField(max_length=255, blank=True) - classifiers = models.ManyToManyField(Classifier) - owner = models.ForeignKey(User, related_name="projects") - updated = models.DateTimeField(auto_now=True) - + name = models.CharField(max_length=255, unique=True, primary_key=True) + auto_hide = models.BooleanField(default=True, blank=False) + allow_comments = models.BooleanField(default=True, blank=False) + owners = models.ManyToManyField(User, related_name="projects_owned") + maintainers = models.ManyToManyField(User, related_name="projects_maintained") + class Meta: verbose_name = _(u"project") verbose_name_plural = _(u"projects") @@ -83,20 +99,34 @@ def get_release(self, version): return None class Release(models.Model): - version = models.CharField(max_length=128) - distribution = models.FileField(upload_to=UPLOAD_TO) - md5_digest = models.CharField(max_length=255, blank=True) - platform = models.CharField(max_length=255, blank=True) - signature = models.CharField(max_length=128, blank=True) - filetype = models.CharField(max_length=255, blank=True) - pyversion = models.CharField(max_length=255, blank=True) - project = models.ForeignKey(Project, related_name="releases") - upload_time = models.DateTimeField(auto_now=True) - + project = models.ForeignKey(Project, related_name="releases", primary_key=True) + version = models.CharField(max_length=128, primary_key=True) + metadata_version = models.CharField(max_length=64, default=1.0) + + author = models.CharField(max_length=128, blank=True) + author_email = models.CharField(max_length=255, blank=True) + maintainer = models.CharField(max_length=128, blank=True) + maintainer_email = models.CharField(max_length=255, blank=True) + + home_page = models.URLField(verify_exists=False, blank=True, null=True) + license = models.TextField(blank=True) + summary = models.CharField(max_length=255, blank=True) + description = models.TextField(blank=True) + keywords = models.CharField(max_length=255, blank=True) + platform = models.TextField(blank=True) + download_url = models.CharField(max_length=200, blank=True, null=True) + hidden = models.BooleanField(default=False, blank=False) + requires = models.TextField(blank=True) + provides = models.TextField(blank=True) + obsoletes = models.TextField(blank=True) + classifiers = models.ManyToManyField(Classifier) + + created = models.DateTimeField(auto_now_add=True, editable=False) + class Meta: verbose_name = _(u"release") verbose_name_plural = _(u"releases") - unique_together = ("project", "version", "platform", "distribution", "pyversion") + unique_together = ("project", "version") def __unicode__(self): return u"%s (%s)" % (self.release_name, self.platform) @@ -130,3 +160,22 @@ def get_absolute_url(self): def get_dl_url(self): return "%s#md5=%s" % (self.distribution.url, self.md5_digest) + +class File(models.Model): + release = models.ForeignKey(Release, related_name="files") + distribution = models.FileField(upload_to=UPLOAD_TO) + md5_digest = models.CharField(max_length=32, blank=True) + filetype = models.CharField(max_length=255, blank=False, + choices=DIST_FILE_TYPES) + pyversion = models.CharField(max_length=255, blank=True, + choices=PYTHON_VERSIONS) + comment = models.CharField(max_length=255, blank=True) + signature = models.TextField(blank=True) + + class Meta: + verbose_name = _(u"file") + verbose_name_plural = _(u"files") + unique_together = ("release", "filetype", "pyversion") + + def __unicode__(self): + return self.distribution.name \ No newline at end of file From b195ce1a08de0aa7603676c893ac6c1fffdf9a2b Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Fri, 30 Apr 2010 23:42:27 -0500 Subject: [PATCH 006/146] More model refactorization and second South migration --- djangopypi/migrations/0002_refactoring.py | 291 ++++++++++++++++++++++ djangopypi/models.py | 101 ++++---- 2 files changed, 334 insertions(+), 58 deletions(-) create mode 100644 djangopypi/migrations/0002_refactoring.py diff --git a/djangopypi/migrations/0002_refactoring.py b/djangopypi/migrations/0002_refactoring.py new file mode 100644 index 0000000..382653d --- /dev/null +++ b/djangopypi/migrations/0002_refactoring.py @@ -0,0 +1,291 @@ +# 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 'File' + db.create_table('djangopypi_file', ( + ('comment', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('md5_digest', self.gf('django.db.models.fields.CharField')(max_length=32, blank=True)), + ('filetype', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('pyversion', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('uploader', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('signature', self.gf('django.db.models.fields.TextField')(blank=True)), + ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='files', to=orm['djangopypi.Release'])), + ('distribution', self.gf('django.db.models.fields.files.FileField')(max_length=100)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + )) + db.send_create_signal('djangopypi', ['File']) + + # Adding model 'Review' + db.create_table('djangopypi_review', ( + ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reviews', to=orm['djangopypi.Release'])), + ('rating', self.gf('django.db.models.fields.PositiveSmallIntegerField')(blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('comment', self.gf('django.db.models.fields.TextField')(blank=True)), + )) + db.send_create_signal('djangopypi', ['Review']) + + db.delete_column('djangopypi_project', 'license') + db.delete_column('djangopypi_project', 'updated') + db.delete_column('djangopypi_project', 'metadata_version') + db.delete_column('djangopypi_project', 'author') + db.delete_column('djangopypi_project', 'home_page') + db.delete_column('djangopypi_project', 'download_url') + db.delete_column('djangopypi_project', 'summary') + db.delete_column('djangopypi_project', 'author_email') + db.delete_column('djangopypi_project', 'owner_id') + db.delete_column('djangopypi_project', 'id') + db.delete_column('djangopypi_project', 'description') + + db.add_column('djangopypi_project', 'auto_hide', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True), keep_default=False) + db.add_column('djangopypi_project', 'allow_comments', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True), keep_default=False) + + db.alter_column('djangopypi_project', 'name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, primary_key=True)) + + db.delete_table('djangopypi_project_classifiers') + + # Adding M2M table for field owners on 'Project' + db.create_table('djangopypi_project_owners', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('djangopypi_project_owners', ['project_id', 'user_id']) + + # Adding M2M table for field maintainers on 'Project' + db.create_table('djangopypi_project_maintainers', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('djangopypi_project_maintainers', ['project_id', 'user_id']) + + + + # Deleting field 'Release.upload_time' + db.delete_column('djangopypi_release', 'upload_time') + + # Deleting field 'Release.md5_digest' + db.delete_column('djangopypi_release', 'md5_digest') + + # Deleting field 'Release.filetype' + db.delete_column('djangopypi_release', 'filetype') + + # Deleting field 'Release.pyversion' + db.delete_column('djangopypi_release', 'pyversion') + + # Deleting field 'Release.platform' + db.delete_column('djangopypi_release', 'platform') + + # Deleting field 'Release.signature' + db.delete_column('djangopypi_release', 'signature') + + # Deleting field 'Release.distribution' + db.delete_column('djangopypi_release', 'distribution') + + # Adding field 'Release.metadata_version' + db.add_column('djangopypi_release', 'metadata_version', self.gf('django.db.models.fields.CharField')(default='1.0', max_length=64), keep_default=False) + + # Adding field 'Release.created' + db.add_column('djangopypi_release', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default='', blank=True), keep_default=False) + + # Adding field 'Release.package_info' + db.add_column('djangopypi_release', 'package_info', self.gf('django.db.models.fields.TextField')(default=''), keep_default=False) + + # Removing unique constraint on 'Release', fields ['project', 'platform', 'distribution', 'version', 'pyversion'] + db.delete_unique('djangopypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) + + # Adding unique constraint on 'Release', fields ['project', 'version'] + db.create_unique('djangopypi_release', ['project_id', 'version']) + + + def backwards(self, orm): + + # Deleting model 'File' + db.delete_table('djangopypi_file') + + # Deleting model 'Review' + db.delete_table('djangopypi_review') + + # Adding field 'Project.license' + db.add_column('djangopypi_project', 'license', self.gf('django.db.models.fields.TextField')(default='', blank=True), keep_default=False) + + # Adding field 'Project.updated' + db.add_column('djangopypi_project', 'updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default='', blank=True), keep_default=False) + + # Adding field 'Project.metadata_version' + db.add_column('djangopypi_project', 'metadata_version', self.gf('django.db.models.fields.CharField')(default=1.0, max_length=64), keep_default=False) + + # Adding field 'Project.author' + db.add_column('djangopypi_project', 'author', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), keep_default=False) + + # Adding field 'Project.home_page' + db.add_column('djangopypi_project', 'home_page', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True), keep_default=False) + + # Adding field 'Project.download_url' + db.add_column('djangopypi_project', 'download_url', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True), keep_default=False) + + # Adding field 'Project.summary' + db.add_column('djangopypi_project', 'summary', self.gf('django.db.models.fields.TextField')(default='', blank=True), keep_default=False) + + # Adding field 'Project.author_email' + db.add_column('djangopypi_project', 'author_email', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + + # Adding field 'Project.owner' + db.add_column('djangopypi_project', 'owner', self.gf('django.db.models.fields.related.ForeignKey')(default='', related_name='projects', to=orm['auth.User']), keep_default=False) + + # Adding field 'Project.id' + db.add_column('djangopypi_project', 'id', self.gf('django.db.models.fields.AutoField')(default='', primary_key=True), keep_default=False) + + # Adding field 'Project.description' + db.add_column('djangopypi_project', 'description', self.gf('django.db.models.fields.TextField')(default='', blank=True), keep_default=False) + + # Deleting field 'Project.auto_hide' + db.delete_column('djangopypi_project', 'auto_hide') + + # Deleting field 'Project.allow_comments' + db.delete_column('djangopypi_project', 'allow_comments') + + # Adding M2M table for field classifiers on 'Project' + db.create_table('djangopypi_project_classifiers', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), + ('classifier', models.ForeignKey(orm['djangopypi.classifier'], null=False)) + )) + db.create_unique('djangopypi_project_classifiers', ['project_id', 'classifier_id']) + + # Removing M2M table for field owners on 'Project' + db.delete_table('djangopypi_project_owners') + + # Removing M2M table for field maintainers on 'Project' + db.delete_table('djangopypi_project_maintainers') + + # Changing field 'Project.name' + db.alter_column('djangopypi_project', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True)) + + # Adding field 'Release.upload_time' + db.add_column('djangopypi_release', 'upload_time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default='', blank=True), keep_default=False) + + # Adding field 'Release.md5_digest' + db.add_column('djangopypi_release', 'md5_digest', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + + # Adding field 'Release.filetype' + db.add_column('djangopypi_release', 'filetype', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + + # Adding field 'Release.pyversion' + db.add_column('djangopypi_release', 'pyversion', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + + # Adding field 'Release.platform' + db.add_column('djangopypi_release', 'platform', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + + # Adding field 'Release.signature' + db.add_column('djangopypi_release', 'signature', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), keep_default=False) + + # Adding field 'Release.distribution' + db.add_column('djangopypi_release', 'distribution', self.gf('django.db.models.fields.files.FileField')(default='', max_length=100), keep_default=False) + + # Deleting field 'Release.metadata_version' + db.delete_column('djangopypi_release', 'metadata_version') + + # Deleting field 'Release.created' + db.delete_column('djangopypi_release', 'created') + + # Deleting field 'Release.package_info' + db.delete_column('djangopypi_release', 'package_info') + + # Adding unique constraint on 'Release', fields ['project', 'platform', 'distribution', 'version', 'pyversion'] + db.create_unique('djangopypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) + + # Removing unique constraint on 'Release', fields ['project', 'version'] + db.delete_unique('djangopypi_release', ['project_id', 'version']) + + + 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'}) + }, + 'djangopypi.classifier': { + 'Meta': {'object_name': 'Classifier'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'djangopypi.file': { + 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'File'}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'distribution': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'files'", 'to': "orm['djangopypi.Release']"}), + 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'djangopypi.project': { + 'Meta': {'object_name': 'Project'}, + 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects_maintained'", 'to': "orm['auth.User']"}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), + 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects_owned'", 'to': "orm['auth.User']"}) + }, + 'djangopypi.release': { + 'Meta': {'unique_together': "(('project', 'version'),)", 'object_name': 'Release'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), + 'package_info': ('django.db.models.fields.TextField', [], {}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Project']"}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djangopypi.review': { + 'Meta': {'object_name': 'Review'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + } + } + + complete_apps = ['djangopypi'] diff --git a/djangopypi/models.py b/djangopypi/models.py index 5d3b6e6..ddd2a5e 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -43,16 +43,16 @@ PYTHON_VERSIONS = ( ('any','Any i.e. pure python'), - '2.1', - '2.2', - '2.3', - '2.4', - '2.5', - '2.6', - '2.7', - '3.0', - '3.1', - '3.2', + ('2.1','2.1'), + ('2.2','2.2'), + ('2.3','2.3'), + ('2.4','2.4'), + ('2.5','2.5'), + ('2.6','2.6'), + ('2.7','2.7'), + ('3.0','3.0'), + ('3.1','3.1'), + ('3.2','3.2'), ) UPLOAD_TO = getattr(settings, @@ -99,28 +99,10 @@ def get_release(self, version): return None class Release(models.Model): - project = models.ForeignKey(Project, related_name="releases", primary_key=True) - version = models.CharField(max_length=128, primary_key=True) - metadata_version = models.CharField(max_length=64, default=1.0) - - author = models.CharField(max_length=128, blank=True) - author_email = models.CharField(max_length=255, blank=True) - maintainer = models.CharField(max_length=128, blank=True) - maintainer_email = models.CharField(max_length=255, blank=True) - - home_page = models.URLField(verify_exists=False, blank=True, null=True) - license = models.TextField(blank=True) - summary = models.CharField(max_length=255, blank=True) - description = models.TextField(blank=True) - keywords = models.CharField(max_length=255, blank=True) - platform = models.TextField(blank=True) - download_url = models.CharField(max_length=200, blank=True, null=True) - hidden = models.BooleanField(default=False, blank=False) - requires = models.TextField(blank=True) - provides = models.TextField(blank=True) - obsoletes = models.TextField(blank=True) - classifiers = models.ManyToManyField(Classifier) - + project = models.ForeignKey(Project, related_name="releases") + version = models.CharField(max_length=128) + metadata_version = models.CharField(max_length=64, default='1.0') + package_info = models.TextField(blank=False) created = models.DateTimeField(auto_now_add=True, editable=False) class Meta: @@ -129,37 +111,17 @@ class Meta: unique_together = ("project", "version") def __unicode__(self): - return u"%s (%s)" % (self.release_name, self.platform) - - @property - def type(self): - dist_file_types = { - 'sdist':'Source', - 'bdist_dumb':'"dumb" binary', - 'bdist_rpm':'RPM', - 'bdist_wininst':'MS Windows installer', - 'bdist_egg':'Python Egg', - 'bdist_dmg':'OS X Disk Image'} - return dist_file_types.get(self.filetype, self.filetype) - - @property - def filename(self): - return os.path.basename(self.distribution.name) - + return self.release_name + @property def release_name(self): return u"%s-%s" % (self.project.name, self.version) - - @property - def path(self): - return self.distribution.name - + @models.permalink def get_absolute_url(self): - return ('djangopypi-show_version', (), {'dist_name': self.project, 'version': self.version}) + return ('djangopypi-show_version', (), {'project': self.project.name, + 'version': self.version}) - def get_dl_url(self): - return "%s#md5=%s" % (self.distribution.url, self.md5_digest) class File(models.Model): release = models.ForeignKey(Release, related_name="files") @@ -171,6 +133,20 @@ class File(models.Model): choices=PYTHON_VERSIONS) comment = models.CharField(max_length=255, blank=True) signature = models.TextField(blank=True) + created = models.DateTimeField(auto_now_add=True, editable=False) + uploader = models.ForeignKey(User) + + @property + def filename(self): + return os.path.basename(self.distribution.name) + + @property + def path(self): + return self.distribution.name + + def get_absolute_url(self): + return "%s#md5=%s" % (self.distribution.url, self.md5_digest) + class Meta: verbose_name = _(u"file") @@ -178,4 +154,13 @@ class Meta: unique_together = ("release", "filetype", "pyversion") def __unicode__(self): - return self.distribution.name \ No newline at end of file + return self.distribution.name + +class Review(models.Model): + release = models.ForeignKey(Release, related_name="reviews") + rating = models.PositiveSmallIntegerField(blank=True) + comment = models.TextField(blank=True) + + class Meta: + verbose_name = _(u'release review') + verbose_name_plural = _(u'release reviews') From 4a397369a79a0bb46a29498ed083673f183b8bae Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Sun, 23 May 2010 12:04:17 -0500 Subject: [PATCH 007/146] Moved djangopypi module into a src directory as it's own package, moved docs there as well. Separating the djangopypi module from the django site chishop code more to make a better distinction between the two --- MANIFEST.in | 8 -- buildout.cfg | 9 +- setup.py | 88 ------------------- .../djangopypi/djangopypi}/__init__.py | 0 .../djangopypi/djangopypi}/admin.py | 0 .../djangopypi/djangopypi}/forms.py | 0 .../djangopypi/djangopypi}/http.py | 3 + .../djangopypi}/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/loadclassifiers.py | 0 .../djangopypi}/management/commands/ppadd.py | 0 .../djangopypi}/migrations/0001_initial.py | 0 .../migrations/0002_refactoring.py | 0 .../djangopypi}/migrations/__init__.py | 0 .../djangopypi/djangopypi}/models.py | 0 .../djangopypi}/templatetags/__init__.py | 0 .../djangopypi}/templatetags/safemarkup.py | 0 .../djangopypi/djangopypi}/tests.py | 0 .../djangopypi/djangopypi}/urls.py | 0 .../djangopypi/djangopypi}/utils.py | 0 .../djangopypi/djangopypi}/views/__init__.py | 1 + .../djangopypi/djangopypi}/views/dists.py | 0 .../djangopypi/djangopypi}/views/search.py | 0 .../djangopypi/djangopypi}/views/users.py | 0 AUTHORS => src/djangopypi/docs/AUTHORS | 1 + Changelog => src/djangopypi/docs/Changelog | 0 LICENSE => src/djangopypi/docs/LICENSE | 0 README => src/djangopypi/docs/README | 0 TODO => src/djangopypi/docs/TODO | 0 src/djangopypi/setup.py | 52 +++++++++++ 30 files changed, 61 insertions(+), 101 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 setup.py rename {djangopypi => src/djangopypi/djangopypi}/__init__.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/admin.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/forms.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/http.py (96%) rename {djangopypi => src/djangopypi/djangopypi}/management/__init__.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/management/commands/__init__.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/management/commands/loadclassifiers.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/management/commands/ppadd.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/migrations/0001_initial.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/migrations/0002_refactoring.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/migrations/__init__.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/models.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/templatetags/__init__.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/templatetags/safemarkup.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/tests.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/urls.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/utils.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/views/__init__.py (98%) rename {djangopypi => src/djangopypi/djangopypi}/views/dists.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/views/search.py (100%) rename {djangopypi => src/djangopypi/djangopypi}/views/users.py (100%) rename AUTHORS => src/djangopypi/docs/AUTHORS (92%) rename Changelog => src/djangopypi/docs/Changelog (100%) rename LICENSE => src/djangopypi/docs/LICENSE (100%) rename README => src/djangopypi/docs/README (100%) rename TODO => src/djangopypi/docs/TODO (100%) create mode 100644 src/djangopypi/setup.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index b6c623c..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,8 +0,0 @@ -include AUTHORS -include README -include TODO -include MANIFEST.in -include LICENSE -include Changelog -recursive-include djangopypi * -recursive-include chishop * diff --git a/buildout.cfg b/buildout.cfg index 9ec9559..bb2b163 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,14 +1,13 @@ [buildout] +extensions = buildout.eggtractor parts = django -find-links = http://bitbucket.org/ubernostrum/django-registration/downloads/django-registration-0.8-alpha-1.tar.gz unzip = true -eggs = pkginfo - django-registration==0.8-alpha-1 - South +eggs = South +develop= [django] recipe = djangorecipe -version = 1.1.1 +version = 1.2 settings = settings eggs = ${buildout:eggs} test = djangopypi diff --git a/setup.py b/setup.py deleted file mode 100644 index c83c08c..0000000 --- a/setup.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import os -import codecs - -try: - from setuptools import setup, find_packages -except ImportError: - from ez_setup import use_setuptools - use_setuptools() - from setuptools import setup, find_packages - -from distutils.command.install_data import install_data -from distutils.command.install import INSTALL_SCHEMES -import sys - -djangopypi = __import__('djangopypi', {}, {}, ['']) - -packages, data_files = [], [] -root_dir = os.path.dirname(__file__) -if root_dir != '': - os.chdir(root_dir) -djangopypi_dir = "djangopypi" - -def osx_install_data(install_data): - def finalize_options(self): - self.set_undefined_options("install", ("install_lib", "install_dir")) - install_data.finalize_options(self) - -#if sys.platform == "darwin": -# cmdclasses = {'install_data': osx_install_data} -#else: -# cmdclasses = {'install_data': install_data} - - -def fullsplit(path, result=None): - if result is None: - result = [] - head, tail = os.path.split(path) - if head == '': - return [tail] + result - if head == path: - return result - return fullsplit(head, [tail] + result) - - -for scheme in INSTALL_SCHEMES.values(): - scheme['data'] = scheme['purelib'] - - -for dirpath, dirnames, filenames in os.walk(djangopypi_dir): - # Ignore dirnames that start with '.' - for i, dirname in enumerate(dirnames): - if dirname.startswith("."): del dirnames[i] - for filename in filenames: - if filename.endswith(".py"): - packages.append('.'.join(fullsplit(dirpath))) - else: - data_files.append([dirpath, [os.path.join(dirpath, f) for f in - filenames]]) -setup( - name='chishop', - version=djangopypi.__version__, - description='Simple PyPI server written in Django.', - author='Ask Solem', - author_email='askh@opera.com', - packages=packages, - url="http://ask.github.com/chishop", - zip_safe=False, - data_files=data_files, - install_requires=[ - 'django>=1.0', - 'docutils', - 'django-registration>0.7', - ], - classifiers=[ - "Development Status :: 3 - Alpha", - "Environment :: Web Environment", - "Framework :: Django", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - "Intended Audience :: System Administrators", - "License :: OSI Approved :: BSD License", - "Topic :: System :: Software Distribution", - "Programming Language :: Python", - ], - long_description=codecs.open('README', "r", "utf-8").read(), -) diff --git a/djangopypi/__init__.py b/src/djangopypi/djangopypi/__init__.py similarity index 100% rename from djangopypi/__init__.py rename to src/djangopypi/djangopypi/__init__.py diff --git a/djangopypi/admin.py b/src/djangopypi/djangopypi/admin.py similarity index 100% rename from djangopypi/admin.py rename to src/djangopypi/djangopypi/admin.py diff --git a/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py similarity index 100% rename from djangopypi/forms.py rename to src/djangopypi/djangopypi/forms.py diff --git a/djangopypi/http.py b/src/djangopypi/djangopypi/http.py similarity index 96% rename from djangopypi/http.py rename to src/djangopypi/djangopypi/http.py index 40d84de..9580585 100644 --- a/djangopypi/http.py +++ b/src/djangopypi/djangopypi/http.py @@ -18,6 +18,9 @@ def __init__(self, realm): def parse_distutils_request(request): raw_post_data = request.raw_post_data + print str(raw_post_data) + print str(request.POST) + print str(request.FILES) sep = raw_post_data.splitlines()[1] items = raw_post_data.split(sep) post_data = {} diff --git a/djangopypi/management/__init__.py b/src/djangopypi/djangopypi/management/__init__.py similarity index 100% rename from djangopypi/management/__init__.py rename to src/djangopypi/djangopypi/management/__init__.py diff --git a/djangopypi/management/commands/__init__.py b/src/djangopypi/djangopypi/management/commands/__init__.py similarity index 100% rename from djangopypi/management/commands/__init__.py rename to src/djangopypi/djangopypi/management/commands/__init__.py diff --git a/djangopypi/management/commands/loadclassifiers.py b/src/djangopypi/djangopypi/management/commands/loadclassifiers.py similarity index 100% rename from djangopypi/management/commands/loadclassifiers.py rename to src/djangopypi/djangopypi/management/commands/loadclassifiers.py diff --git a/djangopypi/management/commands/ppadd.py b/src/djangopypi/djangopypi/management/commands/ppadd.py similarity index 100% rename from djangopypi/management/commands/ppadd.py rename to src/djangopypi/djangopypi/management/commands/ppadd.py diff --git a/djangopypi/migrations/0001_initial.py b/src/djangopypi/djangopypi/migrations/0001_initial.py similarity index 100% rename from djangopypi/migrations/0001_initial.py rename to src/djangopypi/djangopypi/migrations/0001_initial.py diff --git a/djangopypi/migrations/0002_refactoring.py b/src/djangopypi/djangopypi/migrations/0002_refactoring.py similarity index 100% rename from djangopypi/migrations/0002_refactoring.py rename to src/djangopypi/djangopypi/migrations/0002_refactoring.py diff --git a/djangopypi/migrations/__init__.py b/src/djangopypi/djangopypi/migrations/__init__.py similarity index 100% rename from djangopypi/migrations/__init__.py rename to src/djangopypi/djangopypi/migrations/__init__.py diff --git a/djangopypi/models.py b/src/djangopypi/djangopypi/models.py similarity index 100% rename from djangopypi/models.py rename to src/djangopypi/djangopypi/models.py diff --git a/djangopypi/templatetags/__init__.py b/src/djangopypi/djangopypi/templatetags/__init__.py similarity index 100% rename from djangopypi/templatetags/__init__.py rename to src/djangopypi/djangopypi/templatetags/__init__.py diff --git a/djangopypi/templatetags/safemarkup.py b/src/djangopypi/djangopypi/templatetags/safemarkup.py similarity index 100% rename from djangopypi/templatetags/safemarkup.py rename to src/djangopypi/djangopypi/templatetags/safemarkup.py diff --git a/djangopypi/tests.py b/src/djangopypi/djangopypi/tests.py similarity index 100% rename from djangopypi/tests.py rename to src/djangopypi/djangopypi/tests.py diff --git a/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py similarity index 100% rename from djangopypi/urls.py rename to src/djangopypi/djangopypi/urls.py diff --git a/djangopypi/utils.py b/src/djangopypi/djangopypi/utils.py similarity index 100% rename from djangopypi/utils.py rename to src/djangopypi/djangopypi/utils.py diff --git a/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py similarity index 98% rename from djangopypi/views/__init__.py rename to src/djangopypi/djangopypi/views/__init__.py index 1438c54..a290618 100644 --- a/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -24,6 +24,7 @@ def simple(request, template_name="djangopypi/simple.html"): if request.method == "POST": + print str(request.POST) post_data, files = parse_distutils_request(request) action_name = post_data.get(":action") if action_name not in ACTIONS: diff --git a/djangopypi/views/dists.py b/src/djangopypi/djangopypi/views/dists.py similarity index 100% rename from djangopypi/views/dists.py rename to src/djangopypi/djangopypi/views/dists.py diff --git a/djangopypi/views/search.py b/src/djangopypi/djangopypi/views/search.py similarity index 100% rename from djangopypi/views/search.py rename to src/djangopypi/djangopypi/views/search.py diff --git a/djangopypi/views/users.py b/src/djangopypi/djangopypi/views/users.py similarity index 100% rename from djangopypi/views/users.py rename to src/djangopypi/djangopypi/views/users.py diff --git a/AUTHORS b/src/djangopypi/docs/AUTHORS similarity index 92% rename from AUTHORS rename to src/djangopypi/docs/AUTHORS index dea9f74..510b338 100644 --- a/AUTHORS +++ b/src/djangopypi/docs/AUTHORS @@ -10,3 +10,4 @@ Vinícius das Chagas Silva Vanderson Mota dos Santos Stefan Foulis Michael Richardson +Benjamin Liles \ No newline at end of file diff --git a/Changelog b/src/djangopypi/docs/Changelog similarity index 100% rename from Changelog rename to src/djangopypi/docs/Changelog diff --git a/LICENSE b/src/djangopypi/docs/LICENSE similarity index 100% rename from LICENSE rename to src/djangopypi/docs/LICENSE diff --git a/README b/src/djangopypi/docs/README similarity index 100% rename from README rename to src/djangopypi/docs/README diff --git a/TODO b/src/djangopypi/docs/TODO similarity index 100% rename from TODO rename to src/djangopypi/docs/TODO diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py new file mode 100644 index 0000000..c44ae23 --- /dev/null +++ b/src/djangopypi/setup.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os + +try: + from setuptools import setup, find_packages +except ImportError: + from ez_setup import use_setuptools + use_setuptools() + from setuptools import setup, find_packages + +import os + +version = '0.3' + +setup(name='djangopypi', + version=version, + description="A Django application that emulates the Python Package Index.", + long_description=open(os.path.join("docs", "README")).read() + "\n" + + open(os.path.join("docs", "Changelog")).read() + "\n\n" + + open(os.path.join("docs", "AUTHORS")).read(), + classifiers=[ + "Framework :: Django", + "Development Status :: 3 - Alpha", + #"Development Status :: 4 - Beta", + #"Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Environment :: Web Environment", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: BSD License", + "Topic :: System :: Software Distribution", + "Programming Language :: Python", + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords='django pypi packaging index', + author='Ask Solem', + author_email='askh@opera.com', + maintainer='Benjamin Liles', + maintainer_email='benliles@gmail.com', + url='http://github.com/benliles/chishop', + license=open(os.path.join("docs", "LICENSE")).read(), + packages=find_packages(), + include_package_data=True, + zip_safe=False, + install_requires=[ + 'setuptools', + 'django>=1.0', + 'docutils', + ], + ) From 1d1cc6a64d191f8fc0d862d8cb16f0f166c67fc8 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 24 May 2010 00:09:28 -0500 Subject: [PATCH 008/146] Removed application settings for djangopypi from chishop project, tweaked some fields in the models to save db space, worked on cleaning up the root view and request parsing --- buildout.cfg | 10 +- chishop/conf/__init__.py | 0 chishop/conf/default.py | 112 ------------------ chishop/settings.py | 106 ++++++++++++++++- src/djangopypi/djangopypi/__init__.py | 3 +- src/djangopypi/djangopypi/http.py | 79 +++++++----- .../djangopypi/migrations/0002_refactoring.py | 4 +- src/djangopypi/djangopypi/models.py | 64 +--------- src/djangopypi/djangopypi/settings.py | 80 +++++++++++++ .../{tests.py => tests/__init__.py} | 0 src/djangopypi/djangopypi/urls.py | 2 +- src/djangopypi/djangopypi/views/__init__.py | 39 +++--- src/djangopypi/djangopypi/views/dists.py | 2 +- src/djangopypi/djangopypi/views/users.py | 48 ++++---- src/djangopypi/setup.py | 8 +- 15 files changed, 296 insertions(+), 261 deletions(-) delete mode 100644 chishop/conf/__init__.py delete mode 100644 chishop/conf/default.py create mode 100644 src/djangopypi/djangopypi/settings.py rename src/djangopypi/djangopypi/{tests.py => tests/__init__.py} (100%) diff --git a/buildout.cfg b/buildout.cfg index bb2b163..b85efc0 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -1,9 +1,13 @@ [buildout] -extensions = buildout.eggtractor parts = django unzip = true -eggs = South -develop= +find-links = http://bitbucket.org/ubernostrum/django-registration/downloads/django-registration-0.8-alpha-1.tar.gz +eggs = + djangopypi + South + django-registration==0.8-alpha-1 +develop = + src/djangopypi [django] recipe = djangorecipe diff --git a/chishop/conf/__init__.py b/chishop/conf/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chishop/conf/default.py b/chishop/conf/default.py deleted file mode 100644 index 4828a00..0000000 --- a/chishop/conf/default.py +++ /dev/null @@ -1,112 +0,0 @@ -# Django settings for djangopypi project. -import os - -ADMINS = ( - # ('Your Name', 'your_email@domain.com'), -) - -# Allow uploading a new distribution file for a project version -# if a file of that type already exists. -# -# The default on PyPI is to not allow this, but it can be real handy -# if you're sloppy. -DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False -DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' - -# change to False if you do not want Django's default server to serve static pages -LOCAL_DEVELOPMENT = True - -REGISTRATION_OPEN = True -ACCOUNT_ACTIVATION_DAYS = 7 -LOGIN_REDIRECT_URL = "/" - -EMAIL_HOST = '' -DEFAULT_FROM_EMAIL = '' -SERVER_EMAIL = DEFAULT_FROM_EMAIL - -MANAGERS = ADMINS - -DATABASE_ENGINE = '' -DATABASE_NAME = '' -DATABASE_USER = '' -DATABASE_PASSWORD = '' -DATABASE_HOST = '' -DATABASE_PORT = '' - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'America/Chicago' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/" -here = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -MEDIA_ROOT = os.path.join(here, 'media') - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash if there is a path component (optional in other cases). -# Examples: "http://media.lawrence.com", "http://example.com/media/" -MEDIA_URL = '/media/' - -# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a -# trailing slash. -# Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = '/admin-media/' - -# Make this unique, and don't share it with anybody. -SECRET_KEY = 'w_#0r2hh)=!zbynb*gg&969@)sy#^-^ia3m*+sd4@lst$zyaxu' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.load_template_source', - 'django.template.loaders.app_directories.load_template_source', -# 'django.template.loaders.eggs.load_template_source', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', -) - -ROOT_URLCONF = 'urls' - -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.core.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.core.context_processors.request", -) - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - os.path.join(os.path.dirname(os.path.dirname(__file__)), "templates"), -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.admin', - 'django.contrib.markup', - 'django.contrib.admindocs', - 'registration', - 'djangopypi', - 'south', -) diff --git a/chishop/settings.py b/chishop/settings.py index e68a4e5..82eb3cb 100644 --- a/chishop/settings.py +++ b/chishop/settings.py @@ -1,6 +1,110 @@ -from conf.default import * import os +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False +DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' + +# change to False if you do not want Django's default server to serve static pages +LOCAL_DEVELOPMENT = True + +REGISTRATION_OPEN = True +ACCOUNT_ACTIVATION_DAYS = 7 +LOGIN_REDIRECT_URL = "/" + +EMAIL_HOST = '' +DEFAULT_FROM_EMAIL = '' +SERVER_EMAIL = DEFAULT_FROM_EMAIL + +MANAGERS = ADMINS + +DATABASE_ENGINE = '' +DATABASE_NAME = '' +DATABASE_USER = '' +DATABASE_PASSWORD = '' +DATABASE_HOST = '' +DATABASE_PORT = '' + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +here = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) +MEDIA_ROOT = os.path.join(here, 'media') + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '/media/' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/admin-media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = 'w_#0r2hh)=!zbynb*gg&969@)sy#^-^ia3m*+sd4@lst$zyaxu' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', + 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +) + +ROOT_URLCONF = 'urls' + +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.core.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + "django.core.context_processors.request", +) + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + os.path.join(os.path.dirname(__file__), "templates"), +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'django.contrib.markup', + 'django.contrib.admindocs', + 'djangopypi', + 'south', +) + + DEBUG = True TEMPLATE_DEBUG = DEBUG LOCAL_DEVELOPMENT = True diff --git a/src/djangopypi/djangopypi/__init__.py b/src/djangopypi/djangopypi/__init__.py index b32a761..f831b93 100644 --- a/src/djangopypi/djangopypi/__init__.py +++ b/src/djangopypi/djangopypi/__init__.py @@ -1,2 +1 @@ -VERSION = (0, 2, 0) -__version__ = ".".join(map(str, VERSION)) +from djangopypi import settings diff --git a/src/djangopypi/djangopypi/http.py b/src/djangopypi/djangopypi/http.py index 9580585..e3a414d 100644 --- a/src/djangopypi/djangopypi/http.py +++ b/src/djangopypi/djangopypi/http.py @@ -1,4 +1,4 @@ -from django.http import HttpResponse +from django.http import HttpResponse, QueryDict from django.core.files.uploadedfile import TemporaryUploadedFile from django.utils.datastructures import MultiValueDict from django.contrib.auth import authenticate @@ -17,48 +17,63 @@ def __init__(self, realm): def parse_distutils_request(request): - raw_post_data = request.raw_post_data - print str(raw_post_data) - print str(request.POST) - print str(request.FILES) - sep = raw_post_data.splitlines()[1] - items = raw_post_data.split(sep) - post_data = {} - files = {} - for part in filter(lambda e: not e.isspace(), items): - item = part.splitlines() - if len(item) < 2: + """ This is being used because the built in request parser that Django uses, + django.http.multipartparser.MultiPartParser is interperting the POST data + incorrectly and/or the post data coming from distutils is invalid. + + One portion of this is the end marker: \r\n\r\n (what Django expects) + versus \n\n (what distutils is sending). + """ + + try: + sep = request.raw_post_data.splitlines()[1] + except: + raise ValueError('Invalid post data') + + + request.POST = QueryDict('',mutable=True) + for part in filter(lambda e: e.strip(), request.raw_post_data.split(sep)): + try: + header, content = part.lstrip().split('\n',1) + except Exception, e: continue - header = item[1].replace("Content-Disposition: form-data; ", "") - kvpairs = header.split(";") - headers = {} - for kvpair in kvpairs: - if not kvpair: - continue - key, value = kvpair.split("=") - headers[key] = value.strip('"') + + if content.startswith('\n'): + content = content[1:] + + if content.endswith('\n'): + content = content[:-1] + + headers = parse_header(header) + if "name" not in headers: continue - content = part[len("\n".join(item[0:2]))+2:len(part)-1] + if "filename" in headers: file = TemporaryUploadedFile(name=headers["filename"], size=len(content), content_type="application/gzip") file.write(content) file.seek(0) - files["distribution"] = [file] - elif headers["name"] in post_data: - post_data[headers["name"]].append(content) + request.FILES.appendlist('distribution', file) else: - # Distutils sends UNKNOWN for empty fields (e.g platform) - # [russell.sim@gmail.com] - if content == 'UNKNOWN': - post_data[headers["name"]] = [None] - else: - post_data[headers["name"]] = [content] - - return MultiValueDict(post_data), MultiValueDict(files) + request.POST.appendlist(headers["name"],content) + return +def parse_header(header): + headers = {} + print 'parsing %s' % (header,) + for kvpair in filter(lambda p: p, + map(lambda p: p.strip(), + header.split(';'))): + try: + key, value = kvpair.split("=",1) + except ValueError: + continue + headers[key.strip()] = value.strip('"') + + return headers + def login_basic_auth(request): authentication = request.META.get("HTTP_AUTHORIZATION") diff --git a/src/djangopypi/djangopypi/migrations/0002_refactoring.py b/src/djangopypi/djangopypi/migrations/0002_refactoring.py index 382653d..c009ff1 100644 --- a/src/djangopypi/djangopypi/migrations/0002_refactoring.py +++ b/src/djangopypi/djangopypi/migrations/0002_refactoring.py @@ -13,8 +13,8 @@ def forwards(self, orm): ('comment', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), ('md5_digest', self.gf('django.db.models.fields.CharField')(max_length=32, blank=True)), - ('filetype', self.gf('django.db.models.fields.CharField')(max_length=255)), - ('pyversion', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + ('filetype', self.gf('django.db.models.fields.CharField')(max_length=32)), + ('pyversion', self.gf('django.db.models.fields.CharField')(max_length=16, blank=True)), ('uploader', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), ('signature', self.gf('django.db.models.fields.TextField')(blank=True)), ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='files', to=orm['djangopypi.Release'])), diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index ddd2a5e..6c9c602 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -4,59 +4,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User -OS_NAMES = ( - ("aix", "AIX"), - ("beos", "BeOS"), - ("debian", "Debian Linux"), - ("dos", "DOS"), - ("freebsd", "FreeBSD"), - ("hpux", "HP/UX"), - ("mac", "Mac System x."), - ("macos", "MacOS X"), - ("mandrake", "Mandrake Linux"), - ("netbsd", "NetBSD"), - ("openbsd", "OpenBSD"), - ("qnx", "QNX"), - ("redhat", "RedHat Linux"), - ("solaris", "SUN Solaris"), - ("suse", "SuSE Linux"), - ("yellowdog", "Yellow Dog Linux"), -) - -ARCHITECTURES = ( - ("alpha", "Alpha"), - ("hppa", "HPPA"), - ("ix86", "Intel"), - ("powerpc", "PowerPC"), - ("sparc", "Sparc"), - ("ultrasparc", "UltraSparc"), -) - -DIST_FILE_TYPES = ( - ('sdist','Source'), - ('bdist_dumb','"dumb" binary'), - ('bdist_rpm','RPM'), - ('bdist_wininst','MS Windows installer'), - ('bdist_egg','Python Egg'), - ('bdist_dmg','OS X Disk Image'), -) - -PYTHON_VERSIONS = ( - ('any','Any i.e. pure python'), - ('2.1','2.1'), - ('2.2','2.2'), - ('2.3','2.3'), - ('2.4','2.4'), - ('2.5','2.5'), - ('2.6','2.6'), - ('2.7','2.7'), - ('3.0','3.0'), - ('3.1','3.1'), - ('3.2','3.2'), -) - -UPLOAD_TO = getattr(settings, - "DJANGOPYPI_RELEASE_UPLOAD_TO", 'dist') + class Classifier(models.Model): name = models.CharField(max_length=255, unique=True) @@ -125,12 +73,12 @@ def get_absolute_url(self): class File(models.Model): release = models.ForeignKey(Release, related_name="files") - distribution = models.FileField(upload_to=UPLOAD_TO) + distribution = models.FileField(upload_to=settings.DJANGOPYPI_RELEASE_UPLOAD_TO) md5_digest = models.CharField(max_length=32, blank=True) - filetype = models.CharField(max_length=255, blank=False, - choices=DIST_FILE_TYPES) - pyversion = models.CharField(max_length=255, blank=True, - choices=PYTHON_VERSIONS) + filetype = models.CharField(max_length=32, blank=False, + choices=settings.DJANGOPYPI_DIST_FILE_TYPES) + pyversion = models.CharField(max_length=16, blank=True, + choices=settings.DJANGOPYPI_PYTHON_VERSIONS) comment = models.CharField(max_length=255, blank=True) signature = models.TextField(blank=True) created = models.DateTimeField(auto_now_add=True, editable=False) diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py new file mode 100644 index 0000000..23d5a26 --- /dev/null +++ b/src/djangopypi/djangopypi/settings.py @@ -0,0 +1,80 @@ +from django.conf import settings + +# This is disabled on pypi.python.org, can be useful if you make mistakes +if not hasattr(settings,'DJANGOPYPI_ALLOW_VERSION_OVERWRITE'): + settings.DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False + +""" The upload_to argument for the file field in releases. This can either be +a string for a path relative to your media folder or a callable. For more +information, see http://docs.djangoproject.com/ """ +if not hasattr(settings,'DJANGOPYPI_RELEASE_UPLOAD_TO'): + settings.DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' + +if not hasattr(settings,'DJANGOPYPI_OS_NAMES'): + settings.DJANGOPYPI_OS_NAMES = ( + ("aix", "AIX"), + ("beos", "BeOS"), + ("debian", "Debian Linux"), + ("dos", "DOS"), + ("freebsd", "FreeBSD"), + ("hpux", "HP/UX"), + ("mac", "Mac System x."), + ("macos", "MacOS X"), + ("mandrake", "Mandrake Linux"), + ("netbsd", "NetBSD"), + ("openbsd", "OpenBSD"), + ("qnx", "QNX"), + ("redhat", "RedHat Linux"), + ("solaris", "SUN Solaris"), + ("suse", "SuSE Linux"), + ("yellowdog", "Yellow Dog Linux"), + ) + +if not hasattr(settings,'DJANGOPYPI_ARCHITECTURES'): + settings.DJANGOPYPI_ARCHITECTURES = ( + ("alpha", "Alpha"), + ("hppa", "HPPA"), + ("ix86", "Intel"), + ("powerpc", "PowerPC"), + ("sparc", "Sparc"), + ("ultrasparc", "UltraSparc"), + ) + +if not hasattr(settings,'DJANGOPYPI_DIST_FILE_TYPES'): + settings.DJANGOPYPI_DIST_FILE_TYPES = ( + ('sdist','Source'), + ('bdist_dumb','"dumb" binary'), + ('bdist_rpm','RPM'), + ('bdist_wininst','MS Windows installer'), + ('bdist_egg','Python Egg'), + ('bdist_dmg','OS X Disk Image'), + ) + +if not hasattr(settings,'DJANGOPYPI_PYTHON_VERSIONS'): + settings.DJANGOPYPI_PYTHON_VERSIONS = ( + ('any','Any i.e. pure python'), + ('2.1','2.1'), + ('2.2','2.2'), + ('2.3','2.3'), + ('2.4','2.4'), + ('2.5','2.5'), + ('2.6','2.6'), + ('2.7','2.7'), + ('3.0','3.0'), + ('3.1','3.1'), + ('3.2','3.2'), + ) + +if not hasattr(settings, 'DJANGOPYPI_SIMPLE_VIEW'): + settings.DJANGOPYPI_SIMPLE_VIEW = 'djangopypi.views.simple' + +if not hasattr(settings,'DJANGOPYPI_ACTION_VIEWS'): + from djangopypi.views.dists import register_or_upload + from djangopypi.views.users import create_user + + settings.DJANGOPYPI_ACTION_VIEWS = { + "file_upload": register_or_upload, #``sdist`` command + "submit": register_or_upload, #``register`` command + "user": create_user, # registering a user + } + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/tests.py b/src/djangopypi/djangopypi/tests/__init__.py similarity index 100% rename from src/djangopypi/djangopypi/tests.py rename to src/djangopypi/djangopypi/tests/__init__.py diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 79b16be..9623178 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -13,7 +13,7 @@ url(r'^simple/(?P[\w\d_\.\-]+)/$', "show_links", name="djangopypi-show_links"), - url(r'^$', "simple", {'template_name': 'djangopypi/pypi.html'}, + url(r'^$', "root", {'template_name': 'djangopypi/pypi.html'}, name="djangopypi-pypi"), url(r'^(?P[\w\d_\.\-]+)/$', "show_links", diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index a290618..02557cb 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -1,4 +1,6 @@ -from django.http import Http404 +from django.conf import settings +from django.core.urlresolvers import reverse +from django.http import Http404, HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext @@ -10,31 +12,26 @@ from djangopypi.views.search import search -ACTIONS = { - # file_upload is the action used with distutils ``sdist`` command. - "file_upload": register_or_upload, - # submit is the :action used with distutils ``register`` command. - "submit": register_or_upload, - - # user is the action used when registering a new user - "user": create_user, -} +def root(request, root_redirect=None, **kwargs): + if request.method != 'POST': + if root_redirect is None: + root_redirect = reverse(settings.DJANGOPYPI_SIMPLE_VIEW) + return HttpResponseRedirect(root_redirect) + + parse_distutils_request(request) + + action = request.POST.get(':action','') + + if not action in settings.DJANGOPYPI_ACTION_VIEWS: + return HttpResponseNotImplemented("The action %s is not implemented" % (action,)) + + return settings.DJANGOPYPI_ACTION_VIEWS(request, **kwargs) def simple(request, template_name="djangopypi/simple.html"): - if request.method == "POST": - print str(request.POST) - post_data, files = parse_distutils_request(request) - action_name = post_data.get(":action") - if action_name not in ACTIONS: - return HttpResponseNotImplemented( - "The action %s is not implemented" % action_name) - return ACTIONS[action_name](request, post_data, files) - - dists = Project.objects.all().order_by("name") context = RequestContext(request, { - "dists": dists, + "dists": Project.objects.all().order_by("name"), "title": 'Package Index', }) diff --git a/src/djangopypi/djangopypi/views/dists.py b/src/djangopypi/djangopypi/views/dists.py index 9e4a146..8034ccb 100644 --- a/src/djangopypi/djangopypi/views/dists.py +++ b/src/djangopypi/djangopypi/views/dists.py @@ -8,7 +8,7 @@ from djangopypi.http import login_basic_auth, HttpResponseUnauthorized from djangopypi.forms import ProjectForm, ReleaseForm -from djangopypi.models import Project, Release, Classifier, UPLOAD_TO +from djangopypi.models import Project, Release, Classifier ALREADY_EXISTS_FMT = _( "A file named '%s' already exists for %s. Please create a new release.") diff --git a/src/djangopypi/djangopypi/views/users.py b/src/djangopypi/djangopypi/views/users.py index a58ac3e..6e6c988 100644 --- a/src/djangopypi/djangopypi/views/users.py +++ b/src/djangopypi/djangopypi/views/users.py @@ -1,24 +1,28 @@ from django.http import HttpResponse, HttpResponseBadRequest -from registration.forms import RegistrationForm -from registration.backends import get_backend - -DEFAULT_BACKEND = "registration.backends.default.DefaultBackend" - - -def create_user(request, post_data, files, backend_name=DEFAULT_BACKEND): - """Create new user from a distutil client request""" - form = RegistrationForm({"username": post_data["name"], - "email": post_data["email"], - "password1": post_data["password"], - "password2": post_data["password"]}) - if not form.is_valid(): - # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg" - # Which is HTTP/WSGI incompatible, so we're just returning a empty 400. - return HttpResponseBadRequest() - - backend = get_backend(backend_name) - if not backend.registration_allowed(request): - return HttpResponseBadRequest() - new_user = backend.register(request, **form.cleaned_data) - return HttpResponse("OK\n", status=200, mimetype='text/plain') +try: + from registration.forms import RegistrationForm + from registration.backends import get_backend + + DEFAULT_BACKEND = "registration.backends.default.DefaultBackend" + + + def create_user(request, post_data, files, backend_name=DEFAULT_BACKEND): + """Create new user from a distutil client request""" + form = RegistrationForm({"username": post_data["name"], + "email": post_data["email"], + "password1": post_data["password"], + "password2": post_data["password"]}) + if not form.is_valid(): + # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg" + # Which is HTTP/WSGI incompatible, so we're just returning a empty 400. + return HttpResponseBadRequest() + + backend = get_backend(backend_name) + if not backend.registration_allowed(request): + return HttpResponseBadRequest() + new_user = backend.register(request, **form.cleaned_data) + return HttpResponse("OK\n", status=200, mimetype='text/plain') +except ImportError: + def create_user(request, *args, **kwargs): + return HttpResponseBadRequest('no registration') \ No newline at end of file diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index c44ae23..6c64039 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- import os try: @@ -17,8 +15,7 @@ version=version, description="A Django application that emulates the Python Package Index.", long_description=open(os.path.join("docs", "README")).read() + "\n" + - open(os.path.join("docs", "Changelog")).read() + "\n\n" + - open(os.path.join("docs", "AUTHORS")).read(), + open(os.path.join("docs", "Changelog")).read(), classifiers=[ "Framework :: Django", "Development Status :: 3 - Alpha", @@ -47,6 +44,5 @@ install_requires=[ 'setuptools', 'django>=1.0', - 'docutils', - ], + 'docutils',], ) From f96238acd966e12a971ab16ee7b4e25fbcd36f53 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 24 May 2010 00:11:32 -0500 Subject: [PATCH 009/146] Added a symlink for readme --- README | 1 + 1 file changed, 1 insertion(+) create mode 120000 README diff --git a/README b/README new file mode 120000 index 0000000..63fdedb --- /dev/null +++ b/README @@ -0,0 +1 @@ +src/djangopypi/docs/README \ No newline at end of file From a84ed8138922686dd1d2dc04427f4cdca92c9338 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 24 May 2010 00:12:48 -0500 Subject: [PATCH 010/146] Nevermind on the readme --- README | 1 - 1 file changed, 1 deletion(-) delete mode 120000 README diff --git a/README b/README deleted file mode 120000 index 63fdedb..0000000 --- a/README +++ /dev/null @@ -1 +0,0 @@ -src/djangopypi/docs/README \ No newline at end of file From 07c5ffccc661f72fbc3a2bd7c6b5fe8e1c562d64 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 24 May 2010 22:39:26 -0500 Subject: [PATCH 011/146] Moving views to django generics when possible --- src/djangopypi/djangopypi/models.py | 48 +++++++++++++++++-- src/djangopypi/djangopypi/settings.py | 7 ++- .../templates/djangopypi/project_list.html | 13 +++++ src/djangopypi/djangopypi/urls.py | 7 +-- src/djangopypi/djangopypi/views/__init__.py | 28 +++++------ .../views/{dists.py => distutils.py} | 0 src/djangopypi/djangopypi/views/packages.py | 21 ++++++++ 7 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/project_list.html rename src/djangopypi/djangopypi/views/{dists.py => distutils.py} (100%) create mode 100644 src/djangopypi/djangopypi/views/packages.py diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index 6c9c602..1fbe772 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -2,6 +2,7 @@ from django.conf import settings from django.db import models from django.utils.translation import ugettext_lazy as _ +from django.utils import simplejson as json from django.contrib.auth.models import User @@ -21,8 +22,10 @@ class Project(models.Model): name = models.CharField(max_length=255, unique=True, primary_key=True) auto_hide = models.BooleanField(default=True, blank=False) allow_comments = models.BooleanField(default=True, blank=False) - owners = models.ManyToManyField(User, related_name="projects_owned") - maintainers = models.ManyToManyField(User, related_name="projects_maintained") + owners = models.ManyToManyField(User, blank=True, + related_name="projects_owned") + maintainers = models.ManyToManyField(User, blank=True, + related_name="projects_maintained") class Meta: verbose_name = _(u"project") @@ -38,7 +41,21 @@ def get_absolute_url(self): @models.permalink def get_pypi_absolute_url(self): return ('djangopypi-pypi_show_links', (), {'dist_name': self.name}) - + + @property + def description(self): + latest = self.latest + if latest: + return latest.description + return u'' + + @property + def latest(self): + try: + return self.releases.latest() + except Release.DoesNotExist: + return None + def get_release(self, version): """Return the release object for version, or None""" try: @@ -57,6 +74,8 @@ class Meta: verbose_name = _(u"release") verbose_name_plural = _(u"releases") unique_together = ("project", "version") + get_latest_by = 'created' + ordering = ['-created'] def __unicode__(self): return self.release_name @@ -65,6 +84,29 @@ def __unicode__(self): def release_name(self): return u"%s-%s" % (self.project.name, self.version) + @property + def parsed_package_info(self): + if not hasattr(self,'_parsed_package_info'): + try: + self._parsed_package_info = json.loads(self.package_info) + except Exception, e: + print str(e) + self._parsed_package_info = {} + return self._parsed_package_info + + @property + def description(self): + return self.parsed_package_info.get('description',u'') + + def save(self, *args, **kwargs): + if hasattr(self,'_parsed_package_info'): + try: + self.package_info = json.dumps(self._parsed_package_info) + delattr(self,'_parsed_package_info') + except Exception, e: + print str(e) + return super(Release, self).save(*args, **kwargs) + @models.permalink def get_absolute_url(self): return ('djangopypi-show_version', (), {'project': self.project.name, diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index 23d5a26..771f151 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -65,8 +65,9 @@ ('3.2','3.2'), ) -if not hasattr(settings, 'DJANGOPYPI_SIMPLE_VIEW'): - settings.DJANGOPYPI_SIMPLE_VIEW = 'djangopypi.views.simple' +if not hasattr(settings, 'DJANGOPYPI_FALLBACK_VIEW'): + from djangopypi import views + settings.DJANGOPYPI_FALLBACK_VIEW = views.index if not hasattr(settings,'DJANGOPYPI_ACTION_VIEWS'): from djangopypi.views.dists import register_or_upload @@ -76,5 +77,7 @@ "file_upload": register_or_upload, #``sdist`` command "submit": register_or_upload, #``register`` command "user": create_user, # registering a user + + } \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/project_list.html b/src/djangopypi/djangopypi/templates/djangopypi/project_list.html new file mode 100644 index 0000000..1c5e3bc --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/project_list.html @@ -0,0 +1,13 @@ + + + Project List + + +

Project List

+
    + {% for project in project_list %} +
  • {{ project.name }}{% if project.description %}: {{ project.description }}{% endif %}
  • + {% endfor %} +
+ + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 9623178..53f2032 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -2,9 +2,7 @@ from django.conf.urls.defaults import patterns, url, include urlpatterns = patterns("djangopypi.views", - # Simple PyPI - url(r'^simple/$', "simple", - name="djangopypi-simple"), + url(r'^pypi/$', "index", name="djangopypi-index"), url(r'^simple/(?P[\w\d_\.\-]+)/(?P[\w\.\d\-_]+)/$', "show_version", @@ -13,8 +11,7 @@ url(r'^simple/(?P[\w\d_\.\-]+)/$', "show_links", name="djangopypi-show_links"), - url(r'^$', "root", {'template_name': 'djangopypi/pypi.html'}, - name="djangopypi-pypi"), + url(r'^$', "root", name="djangopypi-root"), url(r'^(?P[\w\d_\.\-]+)/$', "show_links", {'template_name': 'djangopypi/pypi_show_links.html'}, diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index 02557cb..6871ac7 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -3,6 +3,9 @@ from django.http import Http404, HttpResponseRedirect from django.shortcuts import render_to_response from django.template import RequestContext +from django.views.generic import list_detail + + from djangopypi.models import Project, Release from djangopypi.http import HttpResponseNotImplemented @@ -13,29 +16,26 @@ -def root(request, root_redirect=None, **kwargs): +def root(request, fallback_view=None, **kwargs): + """ Root view of the package index, handle incoming actions from distutils + or redirect to a more user friendly view """ + if request.method != 'POST': - if root_redirect is None: - root_redirect = reverse(settings.DJANGOPYPI_SIMPLE_VIEW) - return HttpResponseRedirect(root_redirect) - - parse_distutils_request(request) + if fallback_view is None: + fallback_view = settings.DJANGOPYPI_FALLBACK_VIEW + return fallback_view(request, **kwargs) + parse_distutils_request(request) + print str(request.POST) action = request.POST.get(':action','') if not action in settings.DJANGOPYPI_ACTION_VIEWS: + print 'unknown action: %s' % (action,) return HttpResponseNotImplemented("The action %s is not implemented" % (action,)) - return settings.DJANGOPYPI_ACTION_VIEWS(request, **kwargs) + return settings.DJANGOPYPI_ACTION_VIEWS[action](request, **kwargs) -def simple(request, template_name="djangopypi/simple.html"): - context = RequestContext(request, { - "dists": Project.objects.all().order_by("name"), - "title": 'Package Index', - }) - - return render_to_response(template_name, context_instance=context) def show_links(request, dist_name, diff --git a/src/djangopypi/djangopypi/views/dists.py b/src/djangopypi/djangopypi/views/distutils.py similarity index 100% rename from src/djangopypi/djangopypi/views/dists.py rename to src/djangopypi/djangopypi/views/distutils.py diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py new file mode 100644 index 0000000..2f465f7 --- /dev/null +++ b/src/djangopypi/djangopypi/views/packages.py @@ -0,0 +1,21 @@ +from django.conf import settings +from django.core.urlresolvers import reverse +from django.http import Http404, HttpResponseRedirect +from django.views.generic import list_detail +from django.shortcuts import get_object_or_404 + + +from djangopypi.models import Project, Release +from djangopypi.views import release as release_views + + + +def index(request, **kwargs): + kwargs.setdefault('template_object_name','project') + return list_detail.object_list(request, queryset=Project.objects.all(), + **kwargs) + +def details(request, project, **kwargs): + kwargs.setdefault('template_object_name','project') + return list_detail.object_detail(request, queryset=Project.objects.all(), + object_id=project, **kwargs) \ No newline at end of file From 12686ff721cf290c5468488d4667a095aa66a4ea Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Tue, 25 May 2010 22:55:18 -0500 Subject: [PATCH 012/146] Fixed a misnaming of project views file, a little bit more on using standard django views --- .../djangopypi/migrations/0002_refactoring.py | 7 +++++ src/djangopypi/djangopypi/models.py | 2 ++ src/djangopypi/djangopypi/settings.py | 6 ++-- .../templates/djangopypi/project_detail.html | 0 src/djangopypi/djangopypi/urls.py | 9 +++++- .../views/{packages.py => projects.py} | 11 ++++--- src/djangopypi/djangopypi/views/releases.py | 30 +++++++++++++++++++ 7 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/project_detail.html rename src/djangopypi/djangopypi/views/{packages.py => projects.py} (53%) create mode 100644 src/djangopypi/djangopypi/views/releases.py diff --git a/src/djangopypi/djangopypi/migrations/0002_refactoring.py b/src/djangopypi/djangopypi/migrations/0002_refactoring.py index c009ff1..7b2b264 100644 --- a/src/djangopypi/djangopypi/migrations/0002_refactoring.py +++ b/src/djangopypi/djangopypi/migrations/0002_refactoring.py @@ -98,6 +98,9 @@ def forwards(self, orm): # Adding field 'Release.package_info' db.add_column('djangopypi_release', 'package_info', self.gf('django.db.models.fields.TextField')(default=''), keep_default=False) + + # Adding field 'Release.hidden' + db.add_column('djangopypi_release', 'hidden', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False) # Removing unique constraint on 'Release', fields ['project', 'platform', 'distribution', 'version', 'pyversion'] db.delete_unique('djangopypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) @@ -199,6 +202,9 @@ def backwards(self, orm): # Deleting field 'Release.package_info' db.delete_column('djangopypi_release', 'package_info') + + # Deleting field 'Release.package_info' + db.delete_column('djangopypi_release', 'hidden') # Adding unique constraint on 'Release', fields ['project', 'platform', 'distribution', 'version', 'pyversion'] db.create_unique('djangopypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) @@ -276,6 +282,7 @@ def backwards(self, orm): 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), 'package_info': ('django.db.models.fields.TextField', [], {}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Project']"}), 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) }, diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index 1fbe772..3795d46 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -68,8 +68,10 @@ class Release(models.Model): version = models.CharField(max_length=128) metadata_version = models.CharField(max_length=64, default='1.0') package_info = models.TextField(blank=False) + hidden = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True, editable=False) + class Meta: verbose_name = _(u"release") verbose_name_plural = _(u"releases") diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index 771f151..cf13267 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -66,11 +66,11 @@ ) if not hasattr(settings, 'DJANGOPYPI_FALLBACK_VIEW'): - from djangopypi import views - settings.DJANGOPYPI_FALLBACK_VIEW = views.index + from djangopypi.views import releases + settings.DJANGOPYPI_FALLBACK_VIEW = releases.index if not hasattr(settings,'DJANGOPYPI_ACTION_VIEWS'): - from djangopypi.views.dists import register_or_upload + from djangopypi.views.distutils import register_or_upload from djangopypi.views.users import create_user settings.DJANGOPYPI_ACTION_VIEWS = { diff --git a/src/djangopypi/djangopypi/templates/djangopypi/project_detail.html b/src/djangopypi/djangopypi/templates/djangopypi/project_detail.html new file mode 100644 index 0000000..e69de29 diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 53f2032..60f40cf 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -17,5 +17,12 @@ {'template_name': 'djangopypi/pypi_show_links.html'}, name="djangopypi-pypi_show_links"), - url(r'^search','search',name='djangopypi-search') + url(r'^search','search',name='djangopypi-search'), + + url(r'^(?P[\w\d_\.\-]+)/$','projects.details', + name='djangopypi-project'), + + url(r'^(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', + 'releases.details',name='djangopypi-release'), + ) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/projects.py similarity index 53% rename from src/djangopypi/djangopypi/views/packages.py rename to src/djangopypi/djangopypi/views/projects.py index 2f465f7..019727a 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/projects.py @@ -5,17 +5,16 @@ from django.shortcuts import get_object_or_404 -from djangopypi.models import Project, Release -from djangopypi.views import release as release_views +from djangopypi.models import Project def index(request, **kwargs): kwargs.setdefault('template_object_name','project') - return list_detail.object_list(request, queryset=Project.objects.all(), - **kwargs) + kwargs.setdefault('queryset',Project.objects.all()) + return list_detail.object_list(request, **kwargs) def details(request, project, **kwargs): kwargs.setdefault('template_object_name','project') - return list_detail.object_detail(request, queryset=Project.objects.all(), - object_id=project, **kwargs) \ No newline at end of file + kwargs.setdefault('queryset',Project.objects.all()) + return list_detail.object_detail(request, object_id=project, **kwargs) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py new file mode 100644 index 0000000..d63a7ab --- /dev/null +++ b/src/djangopypi/djangopypi/views/releases.py @@ -0,0 +1,30 @@ +from django.conf import settings +from django.core.urlresolvers import reverse +from django.http import Http404, HttpResponseRedirect +from django.views.generic import list_detail +from django.shortcuts import get_object_or_404, render_to_response +from django.template import RequestContext + +from djangopypi.models import Project, Release + + + +def index(request, **kwargs): + kwargs.setdefault('template_object_name','release') + kwargs.setdefault('queryset',Release.objects.all()) + return list_detail.object_list(request, **kwargs) + +def details(request, project, version, **kwargs): + kwargs.setdefault('template_object_name','release') + kwargs.setdefault('template_name','djangopypi/release_detail.html') + kwargs.setdefault('extra_context',{}) + + release = get_object_or_404(Project, name=project).get_release(version) + + if not release: + return Http404() + + kwargs['extra_context'][kwargs['template_object_name']] = release + + return render_to_response(kwargs['template'], kwargs['extra_context'], + context_instance=RequestContext(request)) \ No newline at end of file From 6d3984e468fee3b43cb4cbc5ab5fba24e2707301 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 26 May 2010 23:33:20 -0500 Subject: [PATCH 013/146] Using __getattr__ and __setattr__ to provide access to version specific release metadata, added templates for releases (empty), added DOAP view of a release --- src/djangopypi/djangopypi/models.py | 26 ++++++++++++------- src/djangopypi/djangopypi/settings.py | 15 +++++++++-- .../templates/djangopypi/release_detail.html | 0 .../templates/djangopypi/release_doap.xml | 0 .../templates/djangopypi/release_list.html | 0 src/djangopypi/djangopypi/views/releases.py | 6 ++++- 6 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/release_detail.html create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/release_list.html diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index 3795d46..1ed9f67 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -3,6 +3,7 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils import simplejson as json +from django.utils.datastructures import MultiValueDict from django.contrib.auth.models import User @@ -90,21 +91,28 @@ def release_name(self): def parsed_package_info(self): if not hasattr(self,'_parsed_package_info'): try: - self._parsed_package_info = json.loads(self.package_info) + self._package_info = MultiValueDict() + self._package_info.update(json.loads(self.package_info)) except Exception, e: print str(e) - self._parsed_package_info = {} - return self._parsed_package_info + return self._package_info - @property - def description(self): - return self.parsed_package_info.get('description',u'') + def __getattr__(self, name): + if name in self.parsed_package_info: + return self.parsed_package_info[name] + raise AttributeError() + + def __setattr__(self, name, value): + if name in settings.DJANGOPYPI_METADATA_FIELDS.get(self.metadata_version,[]): + self.package_info[name] = value + else: + super(Release, self).__setattr__(name, value) def save(self, *args, **kwargs): - if hasattr(self,'_parsed_package_info'): + if hasattr(self,'_package_info'): try: - self.package_info = json.dumps(self._parsed_package_info) - delattr(self,'_parsed_package_info') + self.package_info = json.dumps(dict(self._package_info.iterlists())) + delattr(self,'_package_info') except Exception, e: print str(e) return super(Release, self).save(*args, **kwargs) diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index cf13267..2fda860 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -77,7 +77,18 @@ "file_upload": register_or_upload, #``sdist`` command "submit": register_or_upload, #``register`` command "user": create_user, # registering a user - - } + +if not hasattr(settings, 'DJANGOPYPI_METADATA_FIELDS'): + settings.DJANGOPYPI_METADATA_FIELDS = { + '1.0': ('platform','summary','description','keywords','home-page', + 'author','author-email', 'license'), + '1.1': ('platform','supported-platform','summary','description', + 'keywords','home-page','download-url','author','author-email', + 'license','classifier','requires','provides','obsoletes',), + '1.2': ('platform','supported-platform','summary','description', + 'keywords','home-page','download-url','author','author-email', + 'maintainer','maintainer-email','license','classifier', + 'requires-dist','provides-dist','obsoletes-dist', + 'requires-python','requires-external','project-url')} \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_detail.html b/src/djangopypi/djangopypi/templates/djangopypi/release_detail.html new file mode 100644 index 0000000..e69de29 diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml b/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml new file mode 100644 index 0000000..e69de29 diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_list.html b/src/djangopypi/djangopypi/templates/djangopypi/release_list.html new file mode 100644 index 0000000..e69de29 diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py index d63a7ab..c748555 100644 --- a/src/djangopypi/djangopypi/views/releases.py +++ b/src/djangopypi/djangopypi/views/releases.py @@ -27,4 +27,8 @@ def details(request, project, version, **kwargs): kwargs['extra_context'][kwargs['template_object_name']] = release return render_to_response(kwargs['template'], kwargs['extra_context'], - context_instance=RequestContext(request)) \ No newline at end of file + context_instance=RequestContext(request)) + +def doap(request, project, version, **kwargs): + kwargs.setdefault('template_name','djangopypi/release_doap.xml') + return details(request, project, version, **kwargs) \ No newline at end of file From dce0d10ed5ad41fc4cefea99d32372958da2f1a7 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 31 May 2010 23:40:51 -0500 Subject: [PATCH 014/146] Renamed project to package and added south migration, more work on views, removed nice attribute access on releases because it caused a recursion --- src/djangopypi/djangopypi/admin.py | 6 +- src/djangopypi/djangopypi/forms.py | 8 +- .../management/commands/loadclassifiers.py | 2 +- .../djangopypi/management/commands/ppadd.py | 42 ++-- .../0003_renamed_project_to_package.py | 199 ++++++++++++++++++ src/djangopypi/djangopypi/models.py | 50 ++--- ...roject_detail.html => package_detail.html} | 0 .../templates/djangopypi/package_list.html | 13 ++ .../templates/djangopypi/project_list.html | 13 -- src/djangopypi/djangopypi/tests/__init__.py | 4 +- src/djangopypi/djangopypi/urls.py | 26 +-- src/djangopypi/djangopypi/views/__init__.py | 50 +---- src/djangopypi/djangopypi/views/distutils.py | 40 ++-- src/djangopypi/djangopypi/views/packages.py | 24 +++ src/djangopypi/djangopypi/views/projects.py | 20 -- src/djangopypi/djangopypi/views/releases.py | 11 +- src/djangopypi/djangopypi/views/search.py | 6 +- 17 files changed, 322 insertions(+), 192 deletions(-) create mode 100644 src/djangopypi/djangopypi/migrations/0003_renamed_project_to_package.py rename src/djangopypi/djangopypi/templates/djangopypi/{project_detail.html => package_detail.html} (100%) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/package_list.html delete mode 100644 src/djangopypi/djangopypi/templates/djangopypi/project_list.html create mode 100644 src/djangopypi/djangopypi/views/packages.py delete mode 100644 src/djangopypi/djangopypi/views/projects.py diff --git a/src/djangopypi/djangopypi/admin.py b/src/djangopypi/djangopypi/admin.py index 6f994e8..2f23d28 100644 --- a/src/djangopypi/djangopypi/admin.py +++ b/src/djangopypi/djangopypi/admin.py @@ -1,6 +1,8 @@ from django.contrib import admin -from djangopypi.models import Project, Release, Classifier +from djangopypi.models import * -admin.site.register(Project) +admin.site.register(Package) admin.site.register(Release) admin.site.register(Classifier) +admin.site.register(File) +admin.site.register(Review) diff --git a/src/djangopypi/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py index 6a65d37..1519391 100644 --- a/src/djangopypi/djangopypi/forms.py +++ b/src/djangopypi/djangopypi/forms.py @@ -1,17 +1,17 @@ import os from django import forms from django.conf import settings -from djangopypi.models import Project, Classifier, Release +from djangopypi.models import Package, Classifier, Release from django.utils.translation import ugettext_lazy as _ -class ProjectForm(forms.ModelForm): +class PackageForm(forms.ModelForm): class Meta: - model = Project + model = Package exclude = ['owner', 'classifiers'] class ReleaseForm(forms.ModelForm): class Meta: model = Release - exclude = ['project'] \ No newline at end of file + exclude = ['package'] \ No newline at end of file diff --git a/src/djangopypi/djangopypi/management/commands/loadclassifiers.py b/src/djangopypi/djangopypi/management/commands/loadclassifiers.py index 49e2642..4dc90f6 100644 --- a/src/djangopypi/djangopypi/management/commands/loadclassifiers.py +++ b/src/djangopypi/djangopypi/management/commands/loadclassifiers.py @@ -3,7 +3,7 @@ pypi, or from a file/url. Note, pypi docs says to not add classifiers that are not used in submitted -projects. On the other hand it can be usefull to have a list of classifiers +packages. On the other hand it can be usefull to have a list of classifiers to choose if you have to modify package data. Use it if you need it. """ diff --git a/src/djangopypi/djangopypi/management/commands/ppadd.py b/src/djangopypi/djangopypi/management/commands/ppadd.py index aa45274..2f193b8 100644 --- a/src/djangopypi/djangopypi/management/commands/ppadd.py +++ b/src/djangopypi/djangopypi/management/commands/ppadd.py @@ -18,7 +18,7 @@ from urlparse import urlsplit from setuptools.package_index import PackageIndex from django.contrib.auth.models import User -from djangopypi.models import Project, Release, Classifier +from djangopypi.models import Package, Release, Classifier @@ -68,14 +68,14 @@ def _save_package(self, path, ownerid): try: # can't use get_or_create as that demands there be an owner - project = Project.objects.get(name=meta.name) - isnewproject = False - except Project.DoesNotExist: - project = Project(name=meta.name) - isnewproject = True - - release = project.get_release(meta.version) - if not isnewproject and release and release.version == meta.version: + package = Package.objects.get(name=meta.name) + isnewpackage = False + except Package.DoesNotExist: + package = Package(name=meta.name) + isnewpackage = True + + release = package.get_release(meta.version) + if not isnewpackage and release and release.version == meta.version: print "%s-%s already added" % (meta.name, meta.version) return @@ -105,27 +105,27 @@ def _save_package(self, path, ownerid): # at this point we have metadata and an owner, can safely add it. - project.owner = owner + package.owner = owner # Some packages don't have proper licence, seems to be a problem # with setup.py upload. Use "UNKNOWN" - project.license = meta.license or "Unknown" - project.metadata_version = meta.metadata_version - project.author = meta.author - project.home_page = meta.home_page - project.download_url = meta.download_url - project.summary = meta.summary - project.description = meta.description - project.author_email = meta.author_email + package.license = meta.license or "Unknown" + package.metadata_version = meta.metadata_version + package.author = meta.author + package.home_page = meta.home_page + package.download_url = meta.download_url + package.summary = meta.summary + package.description = meta.description + package.author_email = meta.author_email - project.save() + package.save() for classifier in meta.classifiers: - project.classifiers.add( + package.classifiers.add( Classifier.objects.get_or_create(name=classifier)[0]) release = Release() release.version = meta.version - release.project = project + release.package = package filename = os.path.basename(path) file = File(open(path, "rb")) diff --git a/src/djangopypi/djangopypi/migrations/0003_renamed_project_to_package.py b/src/djangopypi/djangopypi/migrations/0003_renamed_project_to_package.py new file mode 100644 index 0000000..1791558 --- /dev/null +++ b/src/djangopypi/djangopypi/migrations/0003_renamed_project_to_package.py @@ -0,0 +1,199 @@ +# 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): + + # Deleting model 'Project' + db.delete_table('djangopypi_project') + + # Removing M2M table for field owners on 'Project' + db.delete_table('djangopypi_project_owners') + + # Removing M2M table for field maintainers on 'Project' + db.delete_table('djangopypi_project_maintainers') + + # Adding model 'Package' + db.create_table('djangopypi_package', ( + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, primary_key=True)), + ('auto_hide', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + ('allow_comments', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + )) + db.send_create_signal('djangopypi', ['Package']) + + # Adding M2M table for field owners on 'Package' + db.create_table('djangopypi_package_owners', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('package', models.ForeignKey(orm['djangopypi.package'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('djangopypi_package_owners', ['package_id', 'user_id']) + + # Adding M2M table for field maintainers on 'Package' + db.create_table('djangopypi_package_maintainers', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('package', models.ForeignKey(orm['djangopypi.package'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('djangopypi_package_maintainers', ['package_id', 'user_id']) + + # Changing field 'File.filetype' + db.alter_column('djangopypi_file', 'filetype', self.gf('django.db.models.fields.CharField')(max_length=32)) + + # Changing field 'File.pyversion' + db.alter_column('djangopypi_file', 'pyversion', self.gf('django.db.models.fields.CharField')(max_length=16, blank=True)) + + # Deleting field 'Release.project' + db.delete_column('djangopypi_release', 'project_id') + + # Adding field 'Release.package' + db.add_column('djangopypi_release', 'package', self.gf('django.db.models.fields.related.ForeignKey')(default='None', related_name='releases', to=orm['djangopypi.Package']), keep_default=False) + + # Removing unique constraint on 'Release', fields ['project', 'version'] + db.delete_unique('djangopypi_release', ['project_id', 'version']) + + # Adding unique constraint on 'Release', fields ['version', 'package'] + db.create_unique('djangopypi_release', ['version', 'package_id']) + + + def backwards(self, orm): + + # Adding model 'Project' + db.create_table('djangopypi_project', ( + ('allow_comments', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True, primary_key=True)), + ('auto_hide', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + )) + db.send_create_signal('djangopypi', ['Project']) + + # Adding M2M table for field owners on 'Project' + db.create_table('djangopypi_project_owners', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('djangopypi_project_owners', ['project_id', 'user_id']) + + # Adding M2M table for field maintainers on 'Project' + db.create_table('djangopypi_project_maintainers', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), + ('user', models.ForeignKey(orm['auth.user'], null=False)) + )) + db.create_unique('djangopypi_project_maintainers', ['project_id', 'user_id']) + + # Deleting model 'Package' + db.delete_table('djangopypi_package') + + # Removing M2M table for field owners on 'Package' + db.delete_table('djangopypi_package_owners') + + # Removing M2M table for field maintainers on 'Package' + db.delete_table('djangopypi_package_maintainers') + + # Changing field 'File.filetype' + db.alter_column('djangopypi_file', 'filetype', self.gf('django.db.models.fields.CharField')(max_length=255)) + + # Changing field 'File.pyversion' + db.alter_column('djangopypi_file', 'pyversion', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)) + + # Adding field 'Release.project' + db.add_column('djangopypi_release', 'project', self.gf('django.db.models.fields.related.ForeignKey')(default='None', related_name='releases', to=orm['djangopypi.Project']), keep_default=False) + + # Deleting field 'Release.package' + db.delete_column('djangopypi_release', 'package_id') + + # Adding unique constraint on 'Release', fields ['project', 'version'] + db.create_unique('djangopypi_release', ['project_id', 'version']) + + # Removing unique constraint on 'Release', fields ['version', 'package'] + db.delete_unique('djangopypi_release', ['version', 'package_id']) + + + 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': {'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', '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']", 'symmetrical': 'False', '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'}) + }, + 'djangopypi.classifier': { + 'Meta': {'object_name': 'Classifier'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + }, + 'djangopypi.file': { + 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'File'}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'distribution': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'files'", 'to': "orm['djangopypi.Release']"}), + 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'djangopypi.package': { + 'Meta': {'object_name': 'Package'}, + 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_maintained'", 'blank': 'True', 'to': "orm['auth.User']"}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), + 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) + }, + 'djangopypi.release': { + 'Meta': {'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), + 'package_info': ('django.db.models.fields.TextField', [], {}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djangopypi.review': { + 'Meta': {'object_name': 'Review'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + } + } + + complete_apps = ['djangopypi'] diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index 1ed9f67..d652341 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -19,36 +19,25 @@ def __unicode__(self): return self.name -class Project(models.Model): +class Package(models.Model): name = models.CharField(max_length=255, unique=True, primary_key=True) auto_hide = models.BooleanField(default=True, blank=False) allow_comments = models.BooleanField(default=True, blank=False) owners = models.ManyToManyField(User, blank=True, - related_name="projects_owned") + related_name="packages_owned") maintainers = models.ManyToManyField(User, blank=True, - related_name="projects_maintained") + related_name="packages_maintained") class Meta: - verbose_name = _(u"project") - verbose_name_plural = _(u"projects") + verbose_name = _(u"package") + verbose_name_plural = _(u"packages") def __unicode__(self): return self.name @models.permalink def get_absolute_url(self): - return ('djangopypi-show_links', (), {'dist_name': self.name}) - - @models.permalink - def get_pypi_absolute_url(self): - return ('djangopypi-pypi_show_links', (), {'dist_name': self.name}) - - @property - def description(self): - latest = self.latest - if latest: - return latest.description - return u'' + return ('djangopypi-package-details', (), {'package': self.name}) @property def latest(self): @@ -65,18 +54,17 @@ def get_release(self, version): return None class Release(models.Model): - project = models.ForeignKey(Project, related_name="releases") + package = models.ForeignKey(Package, related_name="releases") version = models.CharField(max_length=128) metadata_version = models.CharField(max_length=64, default='1.0') package_info = models.TextField(blank=False) hidden = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True, editable=False) - class Meta: verbose_name = _(u"release") verbose_name_plural = _(u"releases") - unique_together = ("project", "version") + unique_together = ("package", "version") get_latest_by = 'created' ordering = ['-created'] @@ -85,28 +73,24 @@ def __unicode__(self): @property def release_name(self): - return u"%s-%s" % (self.project.name, self.version) + return u"%s-%s" % (self.package.name, self.version) @property def parsed_package_info(self): if not hasattr(self,'_parsed_package_info'): try: - self._package_info = MultiValueDict() - self._package_info.update(json.loads(self.package_info)) + self._package_info = MultiValueDict(json.loads(self.package_info)) except Exception, e: print str(e) return self._package_info - def __getattr__(self, name): - if name in self.parsed_package_info: - return self.parsed_package_info[name] - raise AttributeError() + @property + def summary(self): + return self.parsed_package_info.get('summary',u'') - def __setattr__(self, name, value): - if name in settings.DJANGOPYPI_METADATA_FIELDS.get(self.metadata_version,[]): - self.package_info[name] = value - else: - super(Release, self).__setattr__(name, value) + @property + def description(self): + return self.parsed_package_info.get('description',u'') def save(self, *args, **kwargs): if hasattr(self,'_package_info'): @@ -119,7 +103,7 @@ def save(self, *args, **kwargs): @models.permalink def get_absolute_url(self): - return ('djangopypi-show_version', (), {'project': self.project.name, + return ('djangopypi-show_version', (), {'package': self.package.name, 'version': self.version}) diff --git a/src/djangopypi/djangopypi/templates/djangopypi/project_detail.html b/src/djangopypi/djangopypi/templates/djangopypi/package_detail.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/project_detail.html rename to src/djangopypi/djangopypi/templates/djangopypi/package_detail.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_list.html b/src/djangopypi/djangopypi/templates/djangopypi/package_list.html new file mode 100644 index 0000000..98c4bd4 --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_list.html @@ -0,0 +1,13 @@ + + + Package Index + + +

Package Index

+
    + {% for package in package_list %} +
  • {{ package.name }}{% if package.latest and package.latest.summary %}: {{ package.latest.summary }}{% endif %}
  • + {% endfor %} +
+ + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/project_list.html b/src/djangopypi/djangopypi/templates/djangopypi/project_list.html deleted file mode 100644 index 1c5e3bc..0000000 --- a/src/djangopypi/djangopypi/templates/djangopypi/project_list.html +++ /dev/null @@ -1,13 +0,0 @@ - - - Project List - - -

Project List

-
    - {% for project in project_list %} -
  • {{ project.name }}{% if project.description %}: {{ project.description }}{% endif %}
  • - {% endfor %} -
- - \ No newline at end of file diff --git a/src/djangopypi/djangopypi/tests/__init__.py b/src/djangopypi/djangopypi/tests/__init__.py index 91682ae..9bbf686 100644 --- a/src/djangopypi/djangopypi/tests/__init__.py +++ b/src/djangopypi/djangopypi/tests/__init__.py @@ -1,7 +1,7 @@ import unittest import StringIO from djangopypi.views import parse_distutils_request, simple -from djangopypi.models import Project, Classifier +from djangopypi.models import Package, Classifier from django.test.client import Client from django.core.urlresolvers import reverse from django.contrib.auth.models import User @@ -104,7 +104,7 @@ class TestSearch(unittest.TestCase): def setUp(self): dummy_user = User.objects.create(username='krill', password='12345', email='krill@opera.com') - Project.objects.create(name='foo', license='Gnu', + Package.objects.create(name='foo', license='Gnu', summary="The quick brown fox jumps over the lazy dog.", owner=dummy_user) diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 60f40cf..bef7539 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -2,27 +2,15 @@ from django.conf.urls.defaults import patterns, url, include urlpatterns = patterns("djangopypi.views", - url(r'^pypi/$', "index", name="djangopypi-index"), - - url(r'^simple/(?P[\w\d_\.\-]+)/(?P[\w\.\d\-_]+)/$', - "show_version", - name="djangopypi-show_version"), - - url(r'^simple/(?P[\w\d_\.\-]+)/$', "show_links", - name="djangopypi-show_links"), - url(r'^$', "root", name="djangopypi-root"), - - url(r'^(?P[\w\d_\.\-]+)/$', "show_links", - {'template_name': 'djangopypi/pypi_show_links.html'}, - name="djangopypi-pypi_show_links"), - - url(r'^search','search',name='djangopypi-search'), + url(r'^packages/$','packages.index', name='djangopypi-package-index'), + url(r'^search/$','packages.search',name='djangopypi-search'), + url(r'^pypi/$', 'releases.index', name='djangopypi-release-index'), + url(r'^pypi/(?P[\w\d_\.\-]+)/$','packages.details', + name='djangopypi-package'), + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', + 'releases.details',name='djangopypi-release'), - url(r'^(?P[\w\d_\.\-]+)/$','projects.details', - name='djangopypi-project'), - url(r'^(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', - 'releases.details',name='djangopypi-release'), ) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index 6871ac7..7f4ab06 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -1,18 +1,8 @@ from django.conf import settings -from django.core.urlresolvers import reverse -from django.http import Http404, HttpResponseRedirect -from django.shortcuts import render_to_response -from django.template import RequestContext -from django.views.generic import list_detail - - -from djangopypi.models import Project, Release +from djangopypi.models import Package, Release from djangopypi.http import HttpResponseNotImplemented from djangopypi.http import parse_distutils_request -from djangopypi.views.dists import register_or_upload -from djangopypi.views.users import create_user -from djangopypi.views.search import search @@ -34,41 +24,3 @@ def root(request, fallback_view=None, **kwargs): return HttpResponseNotImplemented("The action %s is not implemented" % (action,)) return settings.DJANGOPYPI_ACTION_VIEWS[action](request, **kwargs) - - - - -def show_links(request, dist_name, - template_name="djangopypi/show_links.html"): - try: - project = Project.objects.get(name=dist_name) - releases = project.releases.all().order_by('-version') - except Project.DoesNotExist: - raise Http404 - - context = RequestContext(request, { - "dist_name": dist_name, - "releases": releases, - "project": project, - "title": project.name, - }) - - return render_to_response(template_name, context_instance=context) - - -def show_version(request, dist_name, version, - template_name="djangopypi/show_version.html"): - try: - project = Project.objects.get(name=dist_name) - release = project.releases.get(version=version) - except (Project.DoesNotExist, Release.DoesNotExist): - raise Http404() - - context = RequestContext(request, { - "dist_name": dist_name, - "version": version, - "release": release, - "title": dist_name, - }) - - return render_to_response(template_name, context_instance=context) diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index 8034ccb..cf65888 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -7,37 +7,37 @@ from django.contrib.auth import login from djangopypi.http import login_basic_auth, HttpResponseUnauthorized -from djangopypi.forms import ProjectForm, ReleaseForm -from djangopypi.models import Project, Release, Classifier +from djangopypi.forms import PackageForm, ReleaseForm +from djangopypi.models import Package, Release, Classifier ALREADY_EXISTS_FMT = _( "A file named '%s' already exists for %s. Please create a new release.") -def submit_project_or_release(user, post_data, files): - """Registers/updates a project or release""" +def submit_package_or_release(user, post_data, files): + """Registers/updates a package or release""" try: - project = Project.objects.get(name=post_data['name']) - if project.owner != user: + package = Package.objects.get(name=post_data['name']) + if package.owner != user: return HttpResponseForbidden( - "That project is owned by someone else!") - except Project.DoesNotExist: - project = None + "That package is owned by someone else!") + except Package.DoesNotExist: + package = None - project_form = ProjectForm(post_data, instance=project) - if project_form.is_valid(): - project = project_form.save(commit=False) - project.owner = user - project.save() + package_form = PackageForm(post_data, instance=package) + if package_form.is_valid(): + package = package_form.save(commit=False) + package.owner = user + package.save() for c in post_data.getlist('classifiers'): classifier, created = Classifier.objects.get_or_create(name=c) - project.classifiers.add(classifier) + package.classifiers.add(classifier) if files: allow_overwrite = getattr(settings, "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False) try: release = Release.objects.get(version=post_data['version'], - project=project, + package=package, distribution=UPLOAD_TO + '/' + files['distribution']._name) if not allow_overwrite: @@ -48,20 +48,20 @@ def submit_project_or_release(user, post_data, files): # If the old file already exists, django will append a _ after the # filename, however with .tar.gz files django does the "wrong" - # thing and saves it as project-0.1.2.tar_.gz. So remove it before + # thing and saves it as package-0.1.2.tar_.gz. So remove it before # django sees anything. release_form = ReleaseForm(post_data, files, instance=release) if release_form.is_valid(): if release and os.path.exists(release.distribution.path): os.remove(release.distribution.path) release = release_form.save(commit=False) - release.project = project + release.package = package release.save() else: return HttpResponseBadRequest( "ERRORS: %s" % release_form.errors) else: - return HttpResponseBadRequest("ERRORS: %s" % project_form.errors) + return HttpResponseBadRequest("ERRORS: %s" % package_form.errors) return HttpResponse() @@ -76,4 +76,4 @@ def register_or_upload(request, post_data, files): return HttpResponseForbidden( "Not logged in, or invalid username/password.") - return submit_project_or_release(user, post_data, files) + return submit_package_or_release(user, post_data, files) diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py new file mode 100644 index 0000000..09dbd6f --- /dev/null +++ b/src/djangopypi/djangopypi/views/packages.py @@ -0,0 +1,24 @@ +from django.conf import settings +from django.core.urlresolvers import reverse +from django.http import Http404, HttpResponseRedirect +from django.views.generic import list_detail +from django.shortcuts import get_object_or_404 + + +from djangopypi.models import Package + + + +def index(request, **kwargs): + print str(request.GET) + kwargs.setdefault('template_object_name','package') + kwargs.setdefault('queryset',Package.objects.all()) + return list_detail.object_list(request, **kwargs) + +def details(request, package, **kwargs): + kwargs.setdefault('template_object_name','package') + kwargs.setdefault('queryset',Package.objects.all()) + return list_detail.object_detail(request, object_id=package, **kwargs) + +def search(request): + return None \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/projects.py b/src/djangopypi/djangopypi/views/projects.py deleted file mode 100644 index 019727a..0000000 --- a/src/djangopypi/djangopypi/views/projects.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.conf import settings -from django.core.urlresolvers import reverse -from django.http import Http404, HttpResponseRedirect -from django.views.generic import list_detail -from django.shortcuts import get_object_or_404 - - -from djangopypi.models import Project - - - -def index(request, **kwargs): - kwargs.setdefault('template_object_name','project') - kwargs.setdefault('queryset',Project.objects.all()) - return list_detail.object_list(request, **kwargs) - -def details(request, project, **kwargs): - kwargs.setdefault('template_object_name','project') - kwargs.setdefault('queryset',Project.objects.all()) - return list_detail.object_detail(request, object_id=project, **kwargs) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py index c748555..32012a5 100644 --- a/src/djangopypi/djangopypi/views/releases.py +++ b/src/djangopypi/djangopypi/views/releases.py @@ -5,21 +5,22 @@ from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext -from djangopypi.models import Project, Release +from djangopypi.models import Package, Release def index(request, **kwargs): + print str(request) kwargs.setdefault('template_object_name','release') kwargs.setdefault('queryset',Release.objects.all()) return list_detail.object_list(request, **kwargs) -def details(request, project, version, **kwargs): +def details(request, package, version, **kwargs): kwargs.setdefault('template_object_name','release') kwargs.setdefault('template_name','djangopypi/release_detail.html') kwargs.setdefault('extra_context',{}) - release = get_object_or_404(Project, name=project).get_release(version) + release = get_object_or_404(Package, name=package).get_release(version) if not release: return Http404() @@ -29,6 +30,6 @@ def details(request, project, version, **kwargs): return render_to_response(kwargs['template'], kwargs['extra_context'], context_instance=RequestContext(request)) -def doap(request, project, version, **kwargs): +def doap(request, package, version, **kwargs): kwargs.setdefault('template_name','djangopypi/release_doap.xml') - return details(request, project, version, **kwargs) \ No newline at end of file + return details(request, package, version, **kwargs) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/search.py b/src/djangopypi/djangopypi/views/search.py index 5d6a76b..68c7a2d 100644 --- a/src/djangopypi/djangopypi/views/search.py +++ b/src/djangopypi/djangopypi/views/search.py @@ -2,7 +2,7 @@ from django.shortcuts import render_to_response from django.db.models.query import Q -from djangopypi.models import Project +from djangopypi.models import Package def _search_query(q): @@ -16,9 +16,9 @@ def search(request, template="djangopypi/search_results.html"): search_term = context["search_term"] = request.POST.get("search_term") if search_term: query = _search_query(search_term) - context["dists"] = Project.objects.filter(query) + context["dists"] = Package.objects.filter(query) if context["dists"] is None: - context["dists"] = Project.objects.all() + context["dists"] = Package.objects.all() return render_to_response(template, context_instance=context) From c90ffa57b07ebfe34be1f20332cb6a813a72ef92 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 3 Jun 2010 17:56:08 -0500 Subject: [PATCH 015/146] Added a decorator for basic auth handling, removed user registration action, added PackageInfoField --- src/djangopypi/djangopypi/decorators.py | 33 +++++++++++ src/djangopypi/djangopypi/models.py | 58 +++++++++++++------- src/djangopypi/djangopypi/settings.py | 2 - src/djangopypi/djangopypi/views/distutils.py | 4 +- 4 files changed, 72 insertions(+), 25 deletions(-) create mode 100644 src/djangopypi/djangopypi/decorators.py diff --git a/src/djangopypi/djangopypi/decorators.py b/src/djangopypi/djangopypi/decorators.py new file mode 100644 index 0000000..0b1e2c0 --- /dev/null +++ b/src/djangopypi/djangopypi/decorators.py @@ -0,0 +1,33 @@ +try: + from functools import update_wrapper, wraps +except ImportError: + from django.utils.functional import update_wrapper, wraps + +from django.conf import settings +from django.contrib.auth import login +from django.utils.decorators import available_attrs + +from djangopypi.http import HttpResponseUnauthorized, login_basic_auth + + + +def basic_auth(view_func): + """ + Decorator for views that need to handle basic authentication such as + distutils views. + """ + + def _wrapped_view(request, *args, **kwargs): + if request.user.is_authenticated(): + return view_func(request, *args, **kwargs) + user = login_basic_auth(request) + + if not user: + return HttpResponseUnauthorized('pypi') + + login(request, user) + if not request.user.is_authenticated(): + return HttpResponseForbidden("Not logged in, or invalid username/" + "password.") + return view_func(request, *args, **kwargs) + return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view) diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index d652341..9885e32 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -7,6 +7,40 @@ from django.contrib.auth.models import User +class PackageInfoField(models.Field): + description = u'Python Package Information Field' + __metaclass__ = models.SubfieldBase + + def __init__(self, *args, **kwargs): + kwargs['editable'] = False + super(PackageInfoField,self).__init__(*args, **kwargs) + + def to_python(self, value): + if isinstance(value, basestring): + if value: + return MultiValueDict(json.loads(self.package_info)) + else: + return MultiValueDict() + if isinstance(value, dict): + return MultiValueDict(value) + if isinstance(value,MultiValueDict): + return value + raise ValueError('Unexpected value encountered when converting data to python') + + def get_prep_value(self, value): + if isinstance(value,MultiValueDict): + return json.dumps(dict(value.iterlists())) + if isinstance(value, dict): + return json.dumps(value) + if isinstance(value, basestring) or value is None: + return value + + raise ValueError('Unexpected value encountered when preparing for database') + + def get_internal_type(self): + return 'TextField' + + class Classifier(models.Model): name = models.CharField(max_length=255, unique=True) @@ -57,7 +91,7 @@ class Release(models.Model): package = models.ForeignKey(Package, related_name="releases") version = models.CharField(max_length=128) metadata_version = models.CharField(max_length=64, default='1.0') - package_info = models.TextField(blank=False) + package_info = PackageInfoField(blank=False) hidden = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True, editable=False) @@ -75,31 +109,13 @@ def __unicode__(self): def release_name(self): return u"%s-%s" % (self.package.name, self.version) - @property - def parsed_package_info(self): - if not hasattr(self,'_parsed_package_info'): - try: - self._package_info = MultiValueDict(json.loads(self.package_info)) - except Exception, e: - print str(e) - return self._package_info - @property def summary(self): - return self.parsed_package_info.get('summary',u'') + return self.package_info.get('summary',u'') @property def description(self): - return self.parsed_package_info.get('description',u'') - - def save(self, *args, **kwargs): - if hasattr(self,'_package_info'): - try: - self.package_info = json.dumps(dict(self._package_info.iterlists())) - delattr(self,'_package_info') - except Exception, e: - print str(e) - return super(Release, self).save(*args, **kwargs) + return self.package_info.get('description',u'') @models.permalink def get_absolute_url(self): diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index 2fda860..da34052 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -71,12 +71,10 @@ if not hasattr(settings,'DJANGOPYPI_ACTION_VIEWS'): from djangopypi.views.distutils import register_or_upload - from djangopypi.views.users import create_user settings.DJANGOPYPI_ACTION_VIEWS = { "file_upload": register_or_upload, #``sdist`` command "submit": register_or_upload, #``register`` command - "user": create_user, # registering a user } if not hasattr(settings, 'DJANGOPYPI_METADATA_FIELDS'): diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index cf65888..b38385f 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _ from django.contrib.auth import login -from djangopypi.http import login_basic_auth, HttpResponseUnauthorized +from djangopypi.decorators import basic_auth from djangopypi.forms import PackageForm, ReleaseForm from djangopypi.models import Package, Release, Classifier @@ -65,7 +65,7 @@ def submit_package_or_release(user, post_data, files): return HttpResponse() - +@basic_auth def register_or_upload(request, post_data, files): user = login_basic_auth(request) if not user: From 3c3e5c0f2d7db88e401e8e5f9685210f83ee9562 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Fri, 4 Jun 2010 17:28:18 -0500 Subject: [PATCH 016/146] Merged migrations into a single one, template work, almost ready for release/merging --- .gitignore | 1 + chishop/settings.py | 6 +- .../templates/djangopypi/package_detail.html | 18 ++ .../templates/djangopypi/package_list.html | 15 ++ chishop/templates/djangopypi/pypi.html | 16 -- .../templates/djangopypi/pypi_show_links.html | 39 --- .../templates/djangopypi/release_detail.html | 15 ++ .../djangopypi/release_detail_content.html | 51 ++++ .../templates/djangopypi/release_list.html | 16 ++ chishop/templates/djangopypi/search.html | 4 +- chishop/urls.py | 2 +- src/djangopypi/djangopypi/admin.py | 2 +- src/djangopypi/djangopypi/forms.py | 2 + src/djangopypi/djangopypi/http.py | 17 +- .../djangopypi/migrations/0002_refactoring.py | 243 ++++++++---------- .../0003_renamed_project_to_package.py | 199 -------------- src/djangopypi/djangopypi/models.py | 53 ++-- src/djangopypi/djangopypi/settings.py | 18 +- .../templates/djangopypi/package_detail.html | 29 +++ .../templates/djangopypi/package_doap.xml | 55 ++++ .../templates/djangopypi/release_detail.html | 25 ++ .../templates/djangopypi/release_doap.xml | 49 ++++ .../templates/djangopypi/release_list.html | 21 ++ src/djangopypi/djangopypi/urls.py | 6 +- src/djangopypi/djangopypi/views/__init__.py | 4 +- src/djangopypi/djangopypi/views/distutils.py | 103 +++++++- src/djangopypi/djangopypi/views/packages.py | 26 +- src/djangopypi/djangopypi/views/releases.py | 8 +- src/djangopypi/docs/Changelog | 6 +- src/djangopypi/setup.py | 5 +- 30 files changed, 597 insertions(+), 457 deletions(-) create mode 100644 chishop/templates/djangopypi/package_detail.html create mode 100644 chishop/templates/djangopypi/package_list.html delete mode 100644 chishop/templates/djangopypi/pypi.html delete mode 100644 chishop/templates/djangopypi/pypi_show_links.html create mode 100644 chishop/templates/djangopypi/release_detail.html create mode 100644 chishop/templates/djangopypi/release_detail_content.html create mode 100644 chishop/templates/djangopypi/release_list.html delete mode 100644 src/djangopypi/djangopypi/migrations/0003_renamed_project_to_package.py create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml diff --git a/.gitignore b/.gitignore index 17b4857..96ed365 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ developer-eggs develop-eggs downloads .installed.cfg +chishop/media/dists/* diff --git a/chishop/settings.py b/chishop/settings.py index 82eb3cb..49b4e9d 100644 --- a/chishop/settings.py +++ b/chishop/settings.py @@ -1,4 +1,5 @@ import os +import chishop ADMINS = ( # ('Your Name', 'your_email@domain.com'), @@ -46,8 +47,7 @@ # Absolute path to the directory that holds media. # Example: "/home/media/media.lawrence.com/" -here = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) -MEDIA_ROOT = os.path.join(here, 'media') +MEDIA_ROOT = os.path.join(os.path.abspath(os.path.dirname(chishop.__file__)), 'media') # URL that handles the media served from MEDIA_ROOT. Make sure to use a # trailing slash if there is a path component (optional in other cases). @@ -120,7 +120,7 @@ MANAGERS = ADMINS DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = os.path.join(here, 'devdatabase.db') +DATABASE_NAME = os.path.join(os.path.abspath(os.path.dirname(chishop.__file__)), 'devdatabase.db') DATABASE_USER = '' DATABASE_PASSWORD = '' DATABASE_HOST = '' diff --git a/chishop/templates/djangopypi/package_detail.html b/chishop/templates/djangopypi/package_detail.html new file mode 100644 index 0000000..c912f9e --- /dev/null +++ b/chishop/templates/djangopypi/package_detail.html @@ -0,0 +1,18 @@ +{% extends "base_site.html" %} + +{% block extrahead %} + +{% endblock %} + +{% block bread_crumbs_1 %}» {{ package }}{% endblock %} + +{% block content %} +{% with package.latest as release %} +{% if release %} +{% include "djangopypi/release_detail_content.html" %} +{% else %} +
No releases yet :(
+{% endif %} +{% endwith %} +{% endblock %} \ No newline at end of file diff --git a/chishop/templates/djangopypi/package_list.html b/chishop/templates/djangopypi/package_list.html new file mode 100644 index 0000000..f4eb075 --- /dev/null +++ b/chishop/templates/djangopypi/package_list.html @@ -0,0 +1,15 @@ +{% extends "base_site.html" %} + +{% block bread_crumbs_1 %}» Packages{% endblock %} + +{% block content %} + + +{% for package in package_list %} + + + + +{% endfor %} +
PackageDescription
{{ package }}{% if package.latest %}{{ package.latest.summary|truncatewords:10 }}{% endif %}
+{% endblock %} diff --git a/chishop/templates/djangopypi/pypi.html b/chishop/templates/djangopypi/pypi.html deleted file mode 100644 index f98cf44..0000000 --- a/chishop/templates/djangopypi/pypi.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "base_site.html" %} - -{% block bread_crumbs_1 %}{% endblock %} - -{% block content %} - - -{% for dist in dists %} - - - - -{% endfor %} -
UpdatedPackageSummary
{{ dist.updated|date:"d/m/y" }} - {{ dist.name }}{{ dist.summary|truncatewords:10 }}
-{% endblock %} diff --git a/chishop/templates/djangopypi/pypi_show_links.html b/chishop/templates/djangopypi/pypi_show_links.html deleted file mode 100644 index 6e46635..0000000 --- a/chishop/templates/djangopypi/pypi_show_links.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "base_site.html" %} - -{% block content %} -

-{{ project.summary }} -

-{% load safemarkup %} -{{ project.description|saferst }} - -
- - - {% for release in releases %} - - - - - - - - - {% endfor %} -
FilenamePlatformTypeVersionUploaded OnSize
{{ release.filename }}{{ release.platform }}{{ release.type }}{{ release.version }}{{ release.upload_time }}{{ release.distribution.size|filesizeformat }}
-
- -{% endblock %} - diff --git a/chishop/templates/djangopypi/release_detail.html b/chishop/templates/djangopypi/release_detail.html new file mode 100644 index 0000000..11f5786 --- /dev/null +++ b/chishop/templates/djangopypi/release_detail.html @@ -0,0 +1,15 @@ +{% extends "base_site.html" %} + +{% block extrahead %} + +{% endblock %} + +{% block bread_crumbs_1 %} +» {{ release.package }} +» {{ release }}{% endblock %} + +{% block content %} +{% include "djangopypi/release_detail_content.html" %} +{% endblock %} + diff --git a/chishop/templates/djangopypi/release_detail_content.html b/chishop/templates/djangopypi/release_detail_content.html new file mode 100644 index 0000000..c9b5bff --- /dev/null +++ b/chishop/templates/djangopypi/release_detail_content.html @@ -0,0 +1,51 @@ +{% ifnotequal release release.package.latest %} + +{% endifnotequal %} + +
+{{ release.summary }} +
+ +
+{% load safemarkup %} +{{ release.description|saferst }} +
+ +
+ + + + + + {% for dist in release.distributions.all %} + + + + + + + + {% endfor %} + +
FilenameTypePy VersionUploaded OnSize
{{ dist.filename }}{{ dist.display_filetype }}{{ dist.pyversion }}{{ dist.created }}{{ dist.content.size|filesizeformat }}
+
+
+ +
\ No newline at end of file diff --git a/chishop/templates/djangopypi/release_list.html b/chishop/templates/djangopypi/release_list.html new file mode 100644 index 0000000..7f86fc0 --- /dev/null +++ b/chishop/templates/djangopypi/release_list.html @@ -0,0 +1,16 @@ +{% extends "base_site.html" %} + +{% block bread_crumbs_1 %}» Releases{% endblock %} + +{% block content %} + + +{% for release in release_list %} + + + + + +{% endfor %} +
UpdatedPackageSummary
{{ release.created|date:"Y-m-d" }}{{ release }}{{ release.summary|truncatewords:10 }}
+{% endblock %} diff --git a/chishop/templates/djangopypi/search.html b/chishop/templates/djangopypi/search.html index 8269508..9464126 100644 --- a/chishop/templates/djangopypi/search.html +++ b/chishop/templates/djangopypi/search.html @@ -1,4 +1,4 @@ -
- + +
\ No newline at end of file diff --git a/chishop/urls.py b/chishop/urls.py index 5a5dd77..7046fcb 100644 --- a/chishop/urls.py +++ b/chishop/urls.py @@ -16,7 +16,7 @@ urlpatterns += patterns("", # Admin interface url(r'^admin/doc/', include("django.contrib.admindocs.urls")), - url(r'^admin/(.*)', admin.site.root), + url(r'^admin/', include(admin.site.urls)), # Registration url(r'^accounts/', include('registration.backends.default.urls')), diff --git a/src/djangopypi/djangopypi/admin.py b/src/djangopypi/djangopypi/admin.py index 2f23d28..bdb43fc 100644 --- a/src/djangopypi/djangopypi/admin.py +++ b/src/djangopypi/djangopypi/admin.py @@ -4,5 +4,5 @@ admin.site.register(Package) admin.site.register(Release) admin.site.register(Classifier) -admin.site.register(File) +admin.site.register(Distribution) admin.site.register(Review) diff --git a/src/djangopypi/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py index 1519391..cdec735 100644 --- a/src/djangopypi/djangopypi/forms.py +++ b/src/djangopypi/djangopypi/forms.py @@ -4,6 +4,8 @@ from djangopypi.models import Package, Classifier, Release from django.utils.translation import ugettext_lazy as _ +class SimplePackageSearchForm(forms.Form): + query = forms.CharField(max_length=255) class PackageForm(forms.ModelForm): class Meta: diff --git a/src/djangopypi/djangopypi/http.py b/src/djangopypi/djangopypi/http.py index e3a414d..3d88260 100644 --- a/src/djangopypi/djangopypi/http.py +++ b/src/djangopypi/djangopypi/http.py @@ -32,6 +32,11 @@ def parse_distutils_request(request): request.POST = QueryDict('',mutable=True) + try: + request._files = MultiValueDict() + except Exception, e: + pass + for part in filter(lambda e: e.strip(), request.raw_post_data.split(sep)): try: header, content = part.lstrip().split('\n',1) @@ -50,19 +55,19 @@ def parse_distutils_request(request): continue if "filename" in headers: - file = TemporaryUploadedFile(name=headers["filename"], + dist = TemporaryUploadedFile(name=headers["filename"], size=len(content), - content_type="application/gzip") - file.write(content) - file.seek(0) - request.FILES.appendlist('distribution', file) + content_type="application/gzip", + charset='utf-8') + dist.write(content) + dist.seek(0) + request.FILES.appendlist(headers['name'], dist) else: request.POST.appendlist(headers["name"],content) return def parse_header(header): headers = {} - print 'parsing %s' % (header,) for kvpair in filter(lambda p: p, map(lambda p: p.strip(), header.split(';'))): diff --git a/src/djangopypi/djangopypi/migrations/0002_refactoring.py b/src/djangopypi/djangopypi/migrations/0002_refactoring.py index 7b2b264..35dde07 100644 --- a/src/djangopypi/djangopypi/migrations/0002_refactoring.py +++ b/src/djangopypi/djangopypi/migrations/0002_refactoring.py @@ -5,72 +5,62 @@ from django.db import models class Migration(SchemaMigration): - + def forwards(self, orm): - # Adding model 'File' - db.create_table('djangopypi_file', ( - ('comment', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), - ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + # Deleting model 'Project' + db.delete_table('djangopypi_project') + + # Removing M2M table for field classifiers on 'Project' + db.delete_table('djangopypi_project_classifiers') + + # Adding model 'Distribution' + db.create_table('djangopypi_distribution', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='distributions', to=orm['djangopypi.Release'])), + ('content', self.gf('django.db.models.fields.files.FileField')(max_length=100)), ('md5_digest', self.gf('django.db.models.fields.CharField')(max_length=32, blank=True)), ('filetype', self.gf('django.db.models.fields.CharField')(max_length=32)), ('pyversion', self.gf('django.db.models.fields.CharField')(max_length=16, blank=True)), - ('uploader', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), + ('comment', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), ('signature', self.gf('django.db.models.fields.TextField')(blank=True)), - ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='files', to=orm['djangopypi.Release'])), - ('distribution', self.gf('django.db.models.fields.files.FileField')(max_length=100)), - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('uploader', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), )) - db.send_create_signal('djangopypi', ['File']) + db.send_create_signal('djangopypi', ['Distribution']) # Adding model 'Review' db.create_table('djangopypi_review', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reviews', to=orm['djangopypi.Release'])), ('rating', self.gf('django.db.models.fields.PositiveSmallIntegerField')(blank=True)), - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('comment', self.gf('django.db.models.fields.TextField')(blank=True)), )) db.send_create_signal('djangopypi', ['Review']) - db.delete_column('djangopypi_project', 'license') - db.delete_column('djangopypi_project', 'updated') - db.delete_column('djangopypi_project', 'metadata_version') - db.delete_column('djangopypi_project', 'author') - db.delete_column('djangopypi_project', 'home_page') - db.delete_column('djangopypi_project', 'download_url') - db.delete_column('djangopypi_project', 'summary') - db.delete_column('djangopypi_project', 'author_email') - db.delete_column('djangopypi_project', 'owner_id') - db.delete_column('djangopypi_project', 'id') - db.delete_column('djangopypi_project', 'description') - - db.add_column('djangopypi_project', 'auto_hide', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True), keep_default=False) - db.add_column('djangopypi_project', 'allow_comments', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True), keep_default=False) - - db.alter_column('djangopypi_project', 'name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, primary_key=True)) - - db.delete_table('djangopypi_project_classifiers') + # Adding model 'Package' + db.create_table('djangopypi_package', ( + ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, primary_key=True)), + ('auto_hide', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + ('allow_comments', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), + )) + db.send_create_signal('djangopypi', ['Package']) - # Adding M2M table for field owners on 'Project' - db.create_table('djangopypi_project_owners', ( + # Adding M2M table for field owners on 'Package' + db.create_table('djangopypi_package_owners', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), + ('package', models.ForeignKey(orm['djangopypi.package'], null=False)), ('user', models.ForeignKey(orm['auth.user'], null=False)) )) - db.create_unique('djangopypi_project_owners', ['project_id', 'user_id']) + db.create_unique('djangopypi_package_owners', ['package_id', 'user_id']) - # Adding M2M table for field maintainers on 'Project' - db.create_table('djangopypi_project_maintainers', ( + # Adding M2M table for field maintainers on 'Package' + db.create_table('djangopypi_package_maintainers', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), + ('package', models.ForeignKey(orm['djangopypi.package'], null=False)), ('user', models.ForeignKey(orm['auth.user'], null=False)) )) - db.create_unique('djangopypi_project_maintainers', ['project_id', 'user_id']) - - - - # Deleting field 'Release.upload_time' - db.delete_column('djangopypi_release', 'upload_time') + db.create_unique('djangopypi_package_maintainers', ['package_id', 'user_id']) # Deleting field 'Release.md5_digest' db.delete_column('djangopypi_release', 'md5_digest') @@ -78,9 +68,15 @@ def forwards(self, orm): # Deleting field 'Release.filetype' db.delete_column('djangopypi_release', 'filetype') + # Deleting field 'Release.upload_time' + db.delete_column('djangopypi_release', 'upload_time') + # Deleting field 'Release.pyversion' db.delete_column('djangopypi_release', 'pyversion') + # Deleting field 'Release.project' + db.delete_column('djangopypi_release', 'project_id') + # Deleting field 'Release.platform' db.delete_column('djangopypi_release', 'platform') @@ -90,71 +86,46 @@ def forwards(self, orm): # Deleting field 'Release.distribution' db.delete_column('djangopypi_release', 'distribution') + # Adding field 'Release.package' + db.add_column('djangopypi_release', 'package', self.gf('django.db.models.fields.related.ForeignKey')(default='', related_name='releases', to=orm['djangopypi.Package']), keep_default=False) + # Adding field 'Release.metadata_version' db.add_column('djangopypi_release', 'metadata_version', self.gf('django.db.models.fields.CharField')(default='1.0', max_length=64), keep_default=False) - # Adding field 'Release.created' - db.add_column('djangopypi_release', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default='', blank=True), keep_default=False) - # Adding field 'Release.package_info' - db.add_column('djangopypi_release', 'package_info', self.gf('django.db.models.fields.TextField')(default=''), keep_default=False) - + db.add_column('djangopypi_release', 'package_info', self.gf('djangopypi.models.PackageInfoField')(default=''), keep_default=False) + # Adding field 'Release.hidden' db.add_column('djangopypi_release', 'hidden', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False) + # Adding field 'Release.created' + db.add_column('djangopypi_release', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default='', blank=True), keep_default=False) + # Removing unique constraint on 'Release', fields ['project', 'platform', 'distribution', 'version', 'pyversion'] db.delete_unique('djangopypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) - # Adding unique constraint on 'Release', fields ['project', 'version'] - db.create_unique('djangopypi_release', ['project_id', 'version']) - - - def backwards(self, orm): - - # Deleting model 'File' - db.delete_table('djangopypi_file') - - # Deleting model 'Review' - db.delete_table('djangopypi_review') - - # Adding field 'Project.license' - db.add_column('djangopypi_project', 'license', self.gf('django.db.models.fields.TextField')(default='', blank=True), keep_default=False) - - # Adding field 'Project.updated' - db.add_column('djangopypi_project', 'updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default='', blank=True), keep_default=False) - - # Adding field 'Project.metadata_version' - db.add_column('djangopypi_project', 'metadata_version', self.gf('django.db.models.fields.CharField')(default=1.0, max_length=64), keep_default=False) - - # Adding field 'Project.author' - db.add_column('djangopypi_project', 'author', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), keep_default=False) - - # Adding field 'Project.home_page' - db.add_column('djangopypi_project', 'home_page', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True), keep_default=False) - - # Adding field 'Project.download_url' - db.add_column('djangopypi_project', 'download_url', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True), keep_default=False) - - # Adding field 'Project.summary' - db.add_column('djangopypi_project', 'summary', self.gf('django.db.models.fields.TextField')(default='', blank=True), keep_default=False) + # Adding unique constraint on 'Release', fields ['version', 'package'] + db.create_unique('djangopypi_release', ['version', 'package_id']) - # Adding field 'Project.author_email' - db.add_column('djangopypi_project', 'author_email', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) - # Adding field 'Project.owner' - db.add_column('djangopypi_project', 'owner', self.gf('django.db.models.fields.related.ForeignKey')(default='', related_name='projects', to=orm['auth.User']), keep_default=False) - - # Adding field 'Project.id' - db.add_column('djangopypi_project', 'id', self.gf('django.db.models.fields.AutoField')(default='', primary_key=True), keep_default=False) - - # Adding field 'Project.description' - db.add_column('djangopypi_project', 'description', self.gf('django.db.models.fields.TextField')(default='', blank=True), keep_default=False) - - # Deleting field 'Project.auto_hide' - db.delete_column('djangopypi_project', 'auto_hide') - - # Deleting field 'Project.allow_comments' - db.delete_column('djangopypi_project', 'allow_comments') + def backwards(self, orm): + + # Adding model 'Project' + db.create_table('djangopypi_project', ( + ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), + ('description', self.gf('django.db.models.fields.TextField')(blank=True)), + ('metadata_version', self.gf('django.db.models.fields.CharField')(default=1.0, max_length=64)), + ('owner', self.gf('django.db.models.fields.related.ForeignKey')(related_name='projects', to=orm['auth.User'])), + ('summary', self.gf('django.db.models.fields.TextField')(blank=True)), + ('name', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True)), + ('license', self.gf('django.db.models.fields.TextField')(blank=True)), + ('author', self.gf('django.db.models.fields.CharField')(max_length=128, blank=True)), + ('home_page', self.gf('django.db.models.fields.URLField')(max_length=200, null=True, blank=True)), + ('download_url', self.gf('django.db.models.fields.CharField')(max_length=200, null=True, blank=True)), + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('author_email', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), + )) + db.send_create_signal('djangopypi', ['Project']) # Adding M2M table for field classifiers on 'Project' db.create_table('djangopypi_project_classifiers', ( @@ -164,17 +135,20 @@ def backwards(self, orm): )) db.create_unique('djangopypi_project_classifiers', ['project_id', 'classifier_id']) - # Removing M2M table for field owners on 'Project' - db.delete_table('djangopypi_project_owners') + # Deleting model 'Distribution' + db.delete_table('djangopypi_distribution') + + # Deleting model 'Review' + db.delete_table('djangopypi_review') - # Removing M2M table for field maintainers on 'Project' - db.delete_table('djangopypi_project_maintainers') + # Deleting model 'Package' + db.delete_table('djangopypi_package') - # Changing field 'Project.name' - db.alter_column('djangopypi_project', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True)) + # Removing M2M table for field owners on 'Package' + db.delete_table('djangopypi_package_owners') - # Adding field 'Release.upload_time' - db.add_column('djangopypi_release', 'upload_time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default='', blank=True), keep_default=False) + # Removing M2M table for field maintainers on 'Package' + db.delete_table('djangopypi_package_maintainers') # Adding field 'Release.md5_digest' db.add_column('djangopypi_release', 'md5_digest', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) @@ -182,9 +156,15 @@ def backwards(self, orm): # Adding field 'Release.filetype' db.add_column('djangopypi_release', 'filetype', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + # Adding field 'Release.upload_time' + db.add_column('djangopypi_release', 'upload_time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default='', blank=True), keep_default=False) + # Adding field 'Release.pyversion' db.add_column('djangopypi_release', 'pyversion', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + # Adding field 'Release.project' + db.add_column('djangopypi_release', 'project', self.gf('django.db.models.fields.related.ForeignKey')(default='', related_name='releases', to=orm['djangopypi.Project']), keep_default=False) + # Adding field 'Release.platform' db.add_column('djangopypi_release', 'platform', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) @@ -194,31 +174,34 @@ def backwards(self, orm): # Adding field 'Release.distribution' db.add_column('djangopypi_release', 'distribution', self.gf('django.db.models.fields.files.FileField')(default='', max_length=100), keep_default=False) + # Deleting field 'Release.package' + db.delete_column('djangopypi_release', 'package_id') + # Deleting field 'Release.metadata_version' db.delete_column('djangopypi_release', 'metadata_version') - # Deleting field 'Release.created' - db.delete_column('djangopypi_release', 'created') - # Deleting field 'Release.package_info' db.delete_column('djangopypi_release', 'package_info') - - # Deleting field 'Release.package_info' + + # Deleting field 'Release.hidden' db.delete_column('djangopypi_release', 'hidden') + # Deleting field 'Release.created' + db.delete_column('djangopypi_release', 'created') + # Adding unique constraint on 'Release', fields ['project', 'platform', 'distribution', 'version', 'pyversion'] db.create_unique('djangopypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) - # Removing unique constraint on 'Release', fields ['project', 'version'] - db.delete_unique('djangopypi_release', ['project_id', 'version']) - - + # Removing unique constraint on 'Release', fields ['version', 'package'] + db.delete_unique('djangopypi_release', ['version', 'package_id']) + + 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'}) + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, 'auth.permission': { 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, @@ -232,7 +215,7 @@ def backwards(self, orm): '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'}), + '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', 'blank': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), @@ -240,7 +223,7 @@ def backwards(self, orm): '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'}), + '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': { @@ -255,35 +238,35 @@ def backwards(self, orm): 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) }, - 'djangopypi.file': { - 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'File'}, + 'djangopypi.distribution': { + 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'distribution': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '32'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'files'", 'to': "orm['djangopypi.Release']"}), + 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['djangopypi.Release']"}), 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) }, - 'djangopypi.project': { - 'Meta': {'object_name': 'Project'}, + 'djangopypi.package': { + 'Meta': {'object_name': 'Package'}, 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), - 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects_maintained'", 'to': "orm['auth.User']"}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_maintained'", 'blank': 'True', 'to': "orm['auth.User']"}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), - 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'projects_owned'", 'to': "orm['auth.User']"}) + 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) }, 'djangopypi.release': { - 'Meta': {'unique_together': "(('project', 'version'),)", 'object_name': 'Release'}, + 'Meta': {'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), - 'package_info': ('django.db.models.fields.TextField', [], {}), - 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Project']"}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), + 'package_info': ('djangopypi.models.PackageInfoField', [], {}), 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) }, 'djangopypi.review': { @@ -294,5 +277,5 @@ def backwards(self, orm): 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) } } - + complete_apps = ['djangopypi'] diff --git a/src/djangopypi/djangopypi/migrations/0003_renamed_project_to_package.py b/src/djangopypi/djangopypi/migrations/0003_renamed_project_to_package.py deleted file mode 100644 index 1791558..0000000 --- a/src/djangopypi/djangopypi/migrations/0003_renamed_project_to_package.py +++ /dev/null @@ -1,199 +0,0 @@ -# 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): - - # Deleting model 'Project' - db.delete_table('djangopypi_project') - - # Removing M2M table for field owners on 'Project' - db.delete_table('djangopypi_project_owners') - - # Removing M2M table for field maintainers on 'Project' - db.delete_table('djangopypi_project_maintainers') - - # Adding model 'Package' - db.create_table('djangopypi_package', ( - ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, primary_key=True)), - ('auto_hide', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), - ('allow_comments', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), - )) - db.send_create_signal('djangopypi', ['Package']) - - # Adding M2M table for field owners on 'Package' - db.create_table('djangopypi_package_owners', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('package', models.ForeignKey(orm['djangopypi.package'], null=False)), - ('user', models.ForeignKey(orm['auth.user'], null=False)) - )) - db.create_unique('djangopypi_package_owners', ['package_id', 'user_id']) - - # Adding M2M table for field maintainers on 'Package' - db.create_table('djangopypi_package_maintainers', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('package', models.ForeignKey(orm['djangopypi.package'], null=False)), - ('user', models.ForeignKey(orm['auth.user'], null=False)) - )) - db.create_unique('djangopypi_package_maintainers', ['package_id', 'user_id']) - - # Changing field 'File.filetype' - db.alter_column('djangopypi_file', 'filetype', self.gf('django.db.models.fields.CharField')(max_length=32)) - - # Changing field 'File.pyversion' - db.alter_column('djangopypi_file', 'pyversion', self.gf('django.db.models.fields.CharField')(max_length=16, blank=True)) - - # Deleting field 'Release.project' - db.delete_column('djangopypi_release', 'project_id') - - # Adding field 'Release.package' - db.add_column('djangopypi_release', 'package', self.gf('django.db.models.fields.related.ForeignKey')(default='None', related_name='releases', to=orm['djangopypi.Package']), keep_default=False) - - # Removing unique constraint on 'Release', fields ['project', 'version'] - db.delete_unique('djangopypi_release', ['project_id', 'version']) - - # Adding unique constraint on 'Release', fields ['version', 'package'] - db.create_unique('djangopypi_release', ['version', 'package_id']) - - - def backwards(self, orm): - - # Adding model 'Project' - db.create_table('djangopypi_project', ( - ('allow_comments', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), - ('name', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True, primary_key=True)), - ('auto_hide', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), - )) - db.send_create_signal('djangopypi', ['Project']) - - # Adding M2M table for field owners on 'Project' - db.create_table('djangopypi_project_owners', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), - ('user', models.ForeignKey(orm['auth.user'], null=False)) - )) - db.create_unique('djangopypi_project_owners', ['project_id', 'user_id']) - - # Adding M2M table for field maintainers on 'Project' - db.create_table('djangopypi_project_maintainers', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), - ('user', models.ForeignKey(orm['auth.user'], null=False)) - )) - db.create_unique('djangopypi_project_maintainers', ['project_id', 'user_id']) - - # Deleting model 'Package' - db.delete_table('djangopypi_package') - - # Removing M2M table for field owners on 'Package' - db.delete_table('djangopypi_package_owners') - - # Removing M2M table for field maintainers on 'Package' - db.delete_table('djangopypi_package_maintainers') - - # Changing field 'File.filetype' - db.alter_column('djangopypi_file', 'filetype', self.gf('django.db.models.fields.CharField')(max_length=255)) - - # Changing field 'File.pyversion' - db.alter_column('djangopypi_file', 'pyversion', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)) - - # Adding field 'Release.project' - db.add_column('djangopypi_release', 'project', self.gf('django.db.models.fields.related.ForeignKey')(default='None', related_name='releases', to=orm['djangopypi.Project']), keep_default=False) - - # Deleting field 'Release.package' - db.delete_column('djangopypi_release', 'package_id') - - # Adding unique constraint on 'Release', fields ['project', 'version'] - db.create_unique('djangopypi_release', ['project_id', 'version']) - - # Removing unique constraint on 'Release', fields ['version', 'package'] - db.delete_unique('djangopypi_release', ['version', 'package_id']) - - - 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': {'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', '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']", 'symmetrical': 'False', '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'}) - }, - 'djangopypi.classifier': { - 'Meta': {'object_name': 'Classifier'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) - }, - 'djangopypi.file': { - 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'File'}, - 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'distribution': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), - 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '32'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), - 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'files'", 'to': "orm['djangopypi.Release']"}), - 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, - 'djangopypi.package': { - 'Meta': {'object_name': 'Package'}, - 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), - 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), - 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_maintained'", 'blank': 'True', 'to': "orm['auth.User']"}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), - 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) - }, - 'djangopypi.release': { - 'Meta': {'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, - 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), - 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), - 'package_info': ('django.db.models.fields.TextField', [], {}), - 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) - }, - 'djangopypi.review': { - 'Meta': {'object_name': 'Review'}, - 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) - } - } - - complete_apps = ['djangopypi'] diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index 9885e32..7b7ca75 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -7,6 +7,7 @@ from django.contrib.auth.models import User + class PackageInfoField(models.Field): description = u'Python Package Information Field' __metaclass__ = models.SubfieldBase @@ -18,7 +19,7 @@ def __init__(self, *args, **kwargs): def to_python(self, value): if isinstance(value, basestring): if value: - return MultiValueDict(json.loads(self.package_info)) + return MultiValueDict(json.loads(value)) else: return MultiValueDict() if isinstance(value, dict): @@ -71,7 +72,7 @@ def __unicode__(self): @models.permalink def get_absolute_url(self): - return ('djangopypi-package-details', (), {'package': self.name}) + return ('djangopypi-package', (), {'package': self.name}) @property def latest(self): @@ -88,7 +89,7 @@ def get_release(self, version): return None class Release(models.Model): - package = models.ForeignKey(Package, related_name="releases") + package = models.ForeignKey(Package, related_name="releases", editable=False) version = models.CharField(max_length=128) metadata_version = models.CharField(max_length=64, default='1.0') package_info = PackageInfoField(blank=False) @@ -117,16 +118,22 @@ def summary(self): def description(self): return self.package_info.get('description',u'') + @property + def classifiers(self): + return self.package_info.getlist('classifier') + @models.permalink def get_absolute_url(self): - return ('djangopypi-show_version', (), {'package': self.package.name, - 'version': self.version}) + return ('djangopypi-release', (), {'package': self.package.name, + 'version': self.version}) -class File(models.Model): - release = models.ForeignKey(Release, related_name="files") - distribution = models.FileField(upload_to=settings.DJANGOPYPI_RELEASE_UPLOAD_TO) - md5_digest = models.CharField(max_length=32, blank=True) +class Distribution(models.Model): + release = models.ForeignKey(Release, related_name="distributions", + editable=False) + content = models.FileField(upload_to=settings.DJANGOPYPI_RELEASE_UPLOAD_TO, + editable=False) + md5_digest = models.CharField(max_length=32, blank=True, editable=False) filetype = models.CharField(max_length=32, blank=False, choices=settings.DJANGOPYPI_DIST_FILE_TYPES) pyversion = models.CharField(max_length=16, blank=True, @@ -134,27 +141,33 @@ class File(models.Model): comment = models.CharField(max_length=255, blank=True) signature = models.TextField(blank=True) created = models.DateTimeField(auto_now_add=True, editable=False) - uploader = models.ForeignKey(User) + uploader = models.ForeignKey(User, editable=False) @property def filename(self): - return os.path.basename(self.distribution.name) + return os.path.basename(self.content.name) + + @property + def display_filetype(self): + for key,value in settings.DJANGOPYPI_DIST_FILE_TYPES: + if key == self.filetype: + return value + return self.filetype @property def path(self): - return self.distribution.name + return self.content.name def get_absolute_url(self): - return "%s#md5=%s" % (self.distribution.url, self.md5_digest) - + return "%s#md5=%s" % (self.content.url, self.md5_digest) class Meta: - verbose_name = _(u"file") - verbose_name_plural = _(u"files") + verbose_name = _(u"distribution") + verbose_name_plural = _(u"distributions") unique_together = ("release", "filetype", "pyversion") def __unicode__(self): - return self.distribution.name + return self.filename class Review(models.Model): release = models.ForeignKey(Release, related_name="reviews") @@ -164,3 +177,9 @@ class Review(models.Model): class Meta: verbose_name = _(u'release review') verbose_name_plural = _(u'release reviews') + +try: + from south.modelsinspector import add_introspection_rules + add_introspection_rules([], ["^djangopypi\.models\.PackageInfoField"]) +except ImportError: + pass diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index da34052..ab73a34 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -79,14 +79,14 @@ if not hasattr(settings, 'DJANGOPYPI_METADATA_FIELDS'): settings.DJANGOPYPI_METADATA_FIELDS = { - '1.0': ('platform','summary','description','keywords','home-page', - 'author','author-email', 'license'), - '1.1': ('platform','supported-platform','summary','description', - 'keywords','home-page','download-url','author','author-email', + '1.0': ('platform','summary','description','keywords','home_page', + 'author','author_email', 'license', 'classifier'), + '1.1': ('platform','supported_platform','summary','description', + 'keywords','home_page','download_url','author','author_email', 'license','classifier','requires','provides','obsoletes',), - '1.2': ('platform','supported-platform','summary','description', - 'keywords','home-page','download-url','author','author-email', - 'maintainer','maintainer-email','license','classifier', - 'requires-dist','provides-dist','obsoletes-dist', - 'requires-python','requires-external','project-url')} + '1.2': ('platform','supported_platform','summary','description', + 'keywords','home_page','download_url','author','author_email', + 'maintainer','maintainer_email','license','classifier', + 'requires_dist','provides_dist','obsoletes_dist', + 'requires_python','requires_external','project_url')} \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_detail.html b/src/djangopypi/djangopypi/templates/djangopypi/package_detail.html index e69de29..185f4e6 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/package_detail.html +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_detail.html @@ -0,0 +1,29 @@ + + + {{ package.name }} + + + +

{{ package.name }}

+ {% if not package.latest %} +
No releases yet!
+ {% endif %} + {% if package.latest %} + {% with package.latest as release %} + {% load safemarkup %} + {{ release.description|saferst }} + + {% if release.distributions.count %} +

Downloads

+
    + {% for dist in release.distributions.all %} +
  • {{ dist }} ({{ dist.content.size|filesizeformat }})
  • + {% endfor %} +
+ {% endif %} + + {% endwith %} + {% endif %} + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml b/src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml new file mode 100644 index 0000000..0c8db5c --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml @@ -0,0 +1,55 @@ + + + + {{ package.name }} + {% if package.latest %} + {% with package.latest as release %} + {{ release.summary }} + {% if release.description %} + + {{ release.description }} + + {% endif %} + {% if release.package_info.home_page %} + + {% endif %} + {% if release.package_info.download_url %} + + {% endif %} + {% if release.package_info.author %} + + + {{ release.package_info.author }} + {% if release.package_info.author_email %} + {{ release.package_info.author_email }} + {% endif %} + + + {% endif %} + {% if release.package_info.maintainer %} + + + {{ release.package_info.maintainer }} + {% if release.package_info.maintainer_email %} + {{ release.package_info.maintainer_email }} + {% endif %} + + + {% endif %} + {% if release.package_info.license %} + {{ release.package_info.license }} + {% endif %} + {% endwith %} + {% endif %} + {% for release in package.releases.all %} + + + {{ release.version }} + + + {% endfor %} + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_detail.html b/src/djangopypi/djangopypi/templates/djangopypi/release_detail.html index e69de29..915da98 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/release_detail.html +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_detail.html @@ -0,0 +1,25 @@ + + + {{ release }} + + + +

{{ release }}

+ {% ifnotequal release release.package.latest %} + + {% endifnotequal %} + {% load safemarkup %} + {{ release.description|saferst }} + + {% if release.distributions.count %} +

Downloads

+
    + {% for dist in release.distributions.all %} +
  • {{ dist }} ({{ dist.content.size|filesizeformat }})
  • + {% endfor %} +
+ {% endif %} + + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml b/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml index e69de29..af19ead 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml @@ -0,0 +1,49 @@ + + + + {{ release.package.name }} + {{ release.summary }} + {% if release.description %} + + {{ release.description }} + + {% endif %} + {% if release.package_info.home_page %} + + {% endif %} + {% if release.package_info.download_url %} + + {% endif %} + {% if release.package_info.author %} + + + {{ release.package_info.author }} + {% if release.package_info.author_email %} + {{ release.package_info.author_email }} + {% endif %} + + + {% endif %} + {% if release.package_info.maintainer %} + + + {{ release.package_info.maintainer }} + {% if release.package_info.maintainer_email %} + {{ release.package_info.maintainer_email }} + {% endif %} + + + {% endif %} + {% if release.package_info.license %} + {{ release.package_info.license }} + {% endif %} + + + {{ release.version }} + + + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_list.html b/src/djangopypi/djangopypi/templates/djangopypi/release_list.html index e69de29..f586d5e 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/release_list.html +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_list.html @@ -0,0 +1,21 @@ + + + Package Index Releases + + + + + + + + {% for release in release_list %} + + + + + {% endfor %} + +
UpdatedPackageDescription
{{ release.created|date:"Y-m-d" }} + {{ release }}{{ release.summary|truncatewords:10 }}
+ + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index bef7539..8b077af 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -8,8 +8,12 @@ url(r'^pypi/$', 'releases.index', name='djangopypi-release-index'), url(r'^pypi/(?P[\w\d_\.\-]+)/$','packages.details', name='djangopypi-package'), - url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', + url(r'^pypi/(?P[\w\d_\.\-]+)/doap.xml$','packages.doap', + name='djangopypi-package-doap'), + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', 'releases.details',name='djangopypi-release'), + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/doap.xml$', + 'releases.doap',name='djangopypi-release-doap'), diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index 7f4ab06..042007e 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -1,4 +1,5 @@ from django.conf import settings +from django.http import HttpResponseNotAllowed from djangopypi.models import Package, Release from djangopypi.http import HttpResponseNotImplemented @@ -16,11 +17,10 @@ def root(request, fallback_view=None, **kwargs): return fallback_view(request, **kwargs) parse_distutils_request(request) - print str(request.POST) action = request.POST.get(':action','') if not action in settings.DJANGOPYPI_ACTION_VIEWS: print 'unknown action: %s' % (action,) - return HttpResponseNotImplemented("The action %s is not implemented" % (action,)) + return HttpResponseNotAllowed(settings.DJANGOPYPI_ACTION_VIEW.keys()) return settings.DJANGOPYPI_ACTION_VIEWS[action](request, **kwargs) diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index b38385f..a1f787d 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -1,19 +1,23 @@ import os +import re from django.conf import settings -from django.http import (HttpResponse, HttpResponseForbidden, - HttpResponseBadRequest) +from django.db import transaction +from django.http import * +from django.utils.hashcompat import md5_constructor from django.utils.translation import ugettext_lazy as _ +from django.utils.datastructures import MultiValueDict from django.contrib.auth import login from djangopypi.decorators import basic_auth from djangopypi.forms import PackageForm, ReleaseForm -from djangopypi.models import Package, Release, Classifier +from djangopypi.models import Package, Release, Distribution, Classifier + + ALREADY_EXISTS_FMT = _( "A file named '%s' already exists for %s. Please create a new release.") - def submit_package_or_release(user, post_data, files): """Registers/updates a package or release""" try: @@ -66,14 +70,85 @@ def submit_package_or_release(user, post_data, files): return HttpResponse() @basic_auth -def register_or_upload(request, post_data, files): - user = login_basic_auth(request) - if not user: - return HttpResponseUnauthorized('pypi') - - login(request, user) - if not request.user.is_authenticated(): - return HttpResponseForbidden( - "Not logged in, or invalid username/password.") +@transaction.commit_manually +def register_or_upload(request): + if request.method != 'POST': + return HttpResponseBadRequest('Only post requests are supported') + + name = request.POST.get('name',None).strip() + + if not name: + return HttpResponseBadRequest('No package name specified') + + try: + package = Package.objects.get(name=name) + except Package.DoesNotExist: + package = Package.objects.create(name=name) + package.owners.add(request.user) + + if (request.user not in package.owners.all() and + request.user not in package.maintainers.all()): + + return HttpResponseForbidden('You are not an owner/maintainer of %s' % (package.name,)) + + version = request.POST.get('version',None).strip() + metadata_version = request.POST.get('metadata_version', None).strip() + + if not version or not metadata_version: + transaction.rollback() + return HttpResponseBadRequest('Release version and metadata version must be specified') + + if not metadata_version in settings.DJANGOPYPI_METADATA_FIELDS: + transaction.rollback() + return HttpResponseBadRequest('Metadata version must be one of: %s' + (', '.join(settings.DJANGOPYPI_METADATA_FIELDS.keys()),)) + + release, created = Release.objects.get_or_create(package=package, + version=version) + + release.metadata_version = metadata_version + + fields = settings.DJANGOPYPI_METADATA_FIELDS[metadata_version] + + if 'classifiers' in request.POST: + request.POST.setlist('classifier',request.POST.getlist('classifiers')) + + release.package_info = MultiValueDict(dict(filter(lambda t: t[0] in fields, + request.POST.iterlists()))) + + release.save() + if not 'content' in request.FILES: + transaction.commit() + return HttpResponse('release registered') + + uploaded = request.FILES.get('content') + + for dist in release.distributions.all(): + if os.path.basename(dist.content.name) == uploaded.name: + """ Need to add handling optionally deleting old and putting up new """ + transaction.rollback() + return HttpResponseBadRequest('That file has already been uploaded...') - return submit_package_or_release(user, post_data, files) + md5_digest = request.POST.get('md5_digest','') + + if not md5_digest: + digest = md5_constructor() + map(digest.update,uploaded) + md5_digest = digest.hexdigest() + + try: + new_file = Distribution.objects.create(release=release, + content=uploaded, + filetype=request.POST.get('filetype','sdist'), + pyversion=request.POST.get('pyversion',''), + uploader=request.user, + comment=request.POST.get('comment',''), + signature=request.POST.get('gpg_signature',''), + md5_digest=md5_digest) + except Exception, e: + transaction.rollback() + print str(e) + + transaction.commit() + + return HttpResponse('upload accepted') diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py index 09dbd6f..8f51704 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/packages.py @@ -1,16 +1,12 @@ -from django.conf import settings -from django.core.urlresolvers import reverse -from django.http import Http404, HttpResponseRedirect from django.views.generic import list_detail -from django.shortcuts import get_object_or_404 - +from django.db.models.query import Q from djangopypi.models import Package +from djangopypi.forms import SimplePackageSearchForm def index(request, **kwargs): - print str(request.GET) kwargs.setdefault('template_object_name','package') kwargs.setdefault('queryset',Package.objects.all()) return list_detail.object_list(request, **kwargs) @@ -20,5 +16,19 @@ def details(request, package, **kwargs): kwargs.setdefault('queryset',Package.objects.all()) return list_detail.object_detail(request, object_id=package, **kwargs) -def search(request): - return None \ No newline at end of file +def doap(request, package, **kwargs): + kwargs.setdefault('template_name','djangopypi/package_doap.xml') + kwargs.setdefault('mimetype', 'text/xml') + return details(request, package, **kwargs) + +def search(request, **kwargs): + if request.method == 'POST': + form = SimplePackageSearchForm(request.POST) + else: + form = SimplePackageSearchForm(request.GET) + + if form.is_valid(): + q = form.cleaned_data['query'] + kwargs['queryset'] = Package.objects.filter(Q(name__contains=q) | + Q(releases__package_info__contains=q)).distinct() + return index(request, **kwargs) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py index 32012a5..4f269ef 100644 --- a/src/djangopypi/djangopypi/views/releases.py +++ b/src/djangopypi/djangopypi/views/releases.py @@ -10,7 +10,6 @@ def index(request, **kwargs): - print str(request) kwargs.setdefault('template_object_name','release') kwargs.setdefault('queryset',Release.objects.all()) return list_detail.object_list(request, **kwargs) @@ -19,6 +18,7 @@ def details(request, package, version, **kwargs): kwargs.setdefault('template_object_name','release') kwargs.setdefault('template_name','djangopypi/release_detail.html') kwargs.setdefault('extra_context',{}) + kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) release = get_object_or_404(Package, name=package).get_release(version) @@ -27,9 +27,11 @@ def details(request, package, version, **kwargs): kwargs['extra_context'][kwargs['template_object_name']] = release - return render_to_response(kwargs['template'], kwargs['extra_context'], - context_instance=RequestContext(request)) + return render_to_response(kwargs['template_name'], kwargs['extra_context'], + context_instance=RequestContext(request), + mimetype=kwargs['mimetype']) def doap(request, package, version, **kwargs): kwargs.setdefault('template_name','djangopypi/release_doap.xml') + kwargs.setdefault('mimetype', 'text/xml') return details(request, package, version, **kwargs) \ No newline at end of file diff --git a/src/djangopypi/docs/Changelog b/src/djangopypi/docs/Changelog index f148013..b6b53ba 100644 --- a/src/djangopypi/docs/Changelog +++ b/src/djangopypi/docs/Changelog @@ -1,6 +1,6 @@ -============== -Change history -============== +======= +History +======= 0.2.0 :date:`2009-03-22 1:21 A.M CET` :author:askh@opera.com -------------------------------------------------------------- diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 6c64039..522a1e4 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -9,13 +9,12 @@ import os -version = '0.3' +version = '0.4' setup(name='djangopypi', version=version, description="A Django application that emulates the Python Package Index.", - long_description=open(os.path.join("docs", "README")).read() + "\n" + - open(os.path.join("docs", "Changelog")).read(), + long_description=open(os.path.join("docs", "README")).read(), classifiers=[ "Framework :: Django", "Development Status :: 3 - Alpha", From 3f940b229d82ec4041fd01abba53d138fd80acc8 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 09:11:51 -0500 Subject: [PATCH 017/146] Added eclipse magic files to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 96ed365..76aca1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.project +.pydevproject +.settings .DS_Store *.pyc *~ From 9462d5485f638d8983df15f0ad1d4274fcc71cee Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 09:15:54 -0500 Subject: [PATCH 018/146] Removing deprecated search views (merged into package views) --- src/djangopypi/djangopypi/views/search.py | 24 ----------------------- 1 file changed, 24 deletions(-) delete mode 100644 src/djangopypi/djangopypi/views/search.py diff --git a/src/djangopypi/djangopypi/views/search.py b/src/djangopypi/djangopypi/views/search.py deleted file mode 100644 index 68c7a2d..0000000 --- a/src/djangopypi/djangopypi/views/search.py +++ /dev/null @@ -1,24 +0,0 @@ -from django.template import RequestContext -from django.shortcuts import render_to_response -from django.db.models.query import Q - -from djangopypi.models import Package - - -def _search_query(q): - return Q(name__contains=q) | Q(summary__contains=q) - - -def search(request, template="djangopypi/search_results.html"): - context = RequestContext(request, {"dists": None, "search_term": ""}) - - if request.method == "POST": - search_term = context["search_term"] = request.POST.get("search_term") - if search_term: - query = _search_query(search_term) - context["dists"] = Package.objects.filter(query) - - if context["dists"] is None: - context["dists"] = Package.objects.all() - - return render_to_response(template, context_instance=context) From d0e43ffd921fcbc8f1b88780e6cfdfdd484fe32c Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 09:31:49 -0500 Subject: [PATCH 019/146] Added decorators for testing if a user owns/maintains a package and working towards a public management view for packages --- src/djangopypi/djangopypi/decorators.py | 60 +++++++++++++++++++-- src/djangopypi/djangopypi/views/packages.py | 7 ++- 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/src/djangopypi/djangopypi/decorators.py b/src/djangopypi/djangopypi/decorators.py index 0b1e2c0..f65e646 100644 --- a/src/djangopypi/djangopypi/decorators.py +++ b/src/djangopypi/djangopypi/decorators.py @@ -4,7 +4,7 @@ from django.utils.functional import update_wrapper, wraps from django.conf import settings -from django.contrib.auth import login +from django.contrib.auth import login, REDIRECT_FIELD_NAME from django.utils.decorators import available_attrs from djangopypi.http import HttpResponseUnauthorized, login_basic_auth @@ -12,10 +12,8 @@ def basic_auth(view_func): - """ - Decorator for views that need to handle basic authentication such as - distutils views. - """ + """ Decorator for views that need to handle basic authentication such as + distutils views. """ def _wrapped_view(request, *args, **kwargs): if request.user.is_authenticated(): @@ -31,3 +29,55 @@ def _wrapped_view(request, *args, **kwargs): "password.") return view_func(request, *args, **kwargs) return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view) + +try: + from functools import update_wrapper, wraps +except ImportError: + from django.utils.functional import update_wrapper, wraps # Python 2.4 fallback. + +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.http import HttpResponseRedirect +from django.utils.decorators import available_attrs +from django.utils.http import urlquote + + +def user_owns_package(login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): + """ + Decorator for views that checks whether the user owns the currently requested + package. + """ + if not login_url: + from django.conf import settings + login_url = settings.LOGIN_URL + + def decorator(view_func): + def _wrapped_view(request, package, *args, **kwargs): + if request.user.packages_owned.filter(name=package).count() > 0: + return view_func(request, package=package, *args, **kwargs) + + path = urlquote(request.get_full_path()) + tup = login_url, redirect_field_name, path + return HttpResponseRedirect('%s?%s=%s' % tup) + return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view) + return decorator + +def user_maintains_package(login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): + """ + Decorator for views that checks whether the user maintains (or owns) the + currently requested package. + """ + if not login_url: + from django.conf import settings + login_url = settings.LOGIN_URL + + def decorator(view_func): + def _wrapped_view(request, package, *args, **kwargs): + if request.user.packages_owned.filter(name=package).count() > 0 or \ + request.user.packages_maintained.filter(name=package).count() > 0: + return view_func(request, package=package, *args, **kwargs) + + path = urlquote(request.get_full_path()) + tup = login_url, redirect_field_name, path + return HttpResponseRedirect('%s?%s=%s' % tup) + return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view) + return decorator diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py index 8f51704..63d7748 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/packages.py @@ -1,6 +1,7 @@ from django.views.generic import list_detail from django.db.models.query import Q +from djangopypi.decorators import user_owns_package, user_maintains_package from djangopypi.models import Package from djangopypi.forms import SimplePackageSearchForm @@ -31,4 +32,8 @@ def search(request, **kwargs): q = form.cleaned_data['query'] kwargs['queryset'] = Package.objects.filter(Q(name__contains=q) | Q(releases__package_info__contains=q)).distinct() - return index(request, **kwargs) \ No newline at end of file + return index(request, **kwargs) + +@user_owns_package() +def manage(request, package, **kwargs): + pass From 16f9786072562d3ea6f3c89dd654fe3f08099ece Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 09:42:52 -0500 Subject: [PATCH 020/146] Added a package management view that uses the django generic create_update.update_object view --- src/djangopypi/djangopypi/forms.py | 2 +- .../templates/djangopypi/package_manage.html | 0 src/djangopypi/djangopypi/urls.py | 4 +++- src/djangopypi/djangopypi/views/packages.py | 11 ++++++++--- 4 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/package_manage.html diff --git a/src/djangopypi/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py index cdec735..3cd476c 100644 --- a/src/djangopypi/djangopypi/forms.py +++ b/src/djangopypi/djangopypi/forms.py @@ -10,7 +10,7 @@ class SimplePackageSearchForm(forms.Form): class PackageForm(forms.ModelForm): class Meta: model = Package - exclude = ['owner', 'classifiers'] + exclude = ['name'] class ReleaseForm(forms.ModelForm): diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_manage.html b/src/djangopypi/djangopypi/templates/djangopypi/package_manage.html new file mode 100644 index 0000000..e69de29 diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 8b077af..1de24f6 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -9,7 +9,9 @@ url(r'^pypi/(?P[\w\d_\.\-]+)/$','packages.details', name='djangopypi-package'), url(r'^pypi/(?P[\w\d_\.\-]+)/doap.xml$','packages.doap', - name='djangopypi-package-doap'), + name='djangopypi-package-doap'), + url(r'^pypi/(?P[\w\d_\.\-]+)/manage/$','packages.manage', + name='djangopypi-package-manage'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', 'releases.details',name='djangopypi-release'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/doap.xml$', diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py index 63d7748..5fdbbf2 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/packages.py @@ -1,9 +1,9 @@ -from django.views.generic import list_detail +from django.views.generic import list_detail, create_update from django.db.models.query import Q from djangopypi.decorators import user_owns_package, user_maintains_package from djangopypi.models import Package -from djangopypi.forms import SimplePackageSearchForm +from djangopypi.forms import SimplePackageSearchForm, PackageForm @@ -36,4 +36,9 @@ def search(request, **kwargs): @user_owns_package() def manage(request, package, **kwargs): - pass + kwargs['object_id'] = package + kwargs.setdefault('form_class', PackageForm) + kwargs.setdefault('template_name', 'djangopypi/package_manage.html') + kwargs.setdefault('template_object_name', 'package') + + return create_update.update_object(request, **kwargs) From da45fb30c12e369159ad5d30b56a7c8c8d89aee6 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 10:00:11 -0500 Subject: [PATCH 021/146] Fleshed out default package management template and added a nicer one for chishop --- chishop/templates/djangopypi/package_manage.html | 15 +++++++++++++++ .../templates/djangopypi/package_manage.html | 13 +++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 chishop/templates/djangopypi/package_manage.html diff --git a/chishop/templates/djangopypi/package_manage.html b/chishop/templates/djangopypi/package_manage.html new file mode 100644 index 0000000..6b2660b --- /dev/null +++ b/chishop/templates/djangopypi/package_manage.html @@ -0,0 +1,15 @@ +{% extends "base_site.html" %} + +{% block bread_crumbs_1 %} + » {{ package }} + » Manage +{% endblock %} + +{% block content %} +

Manage {{ package.name }}

+
    +
    + {{ form.as_p }} + +
+{% endblock content %} \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_manage.html b/src/djangopypi/djangopypi/templates/djangopypi/package_manage.html index e69de29..1966e99 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/package_manage.html +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_manage.html @@ -0,0 +1,13 @@ + + + Manage {{ package.name }} + + +

Manage {{ package.name }}

+
    + + {{ form.as_p }} + +
+ + \ No newline at end of file From 8f6db08d1ee471206fff6dcf65d0078e520ab18e Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 10:36:44 -0500 Subject: [PATCH 022/146] Switched order due to import order conflicts --- src/djangopypi/djangopypi/settings.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index ab73a34..5091f4d 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -65,18 +65,6 @@ ('3.2','3.2'), ) -if not hasattr(settings, 'DJANGOPYPI_FALLBACK_VIEW'): - from djangopypi.views import releases - settings.DJANGOPYPI_FALLBACK_VIEW = releases.index - -if not hasattr(settings,'DJANGOPYPI_ACTION_VIEWS'): - from djangopypi.views.distutils import register_or_upload - - settings.DJANGOPYPI_ACTION_VIEWS = { - "file_upload": register_or_upload, #``sdist`` command - "submit": register_or_upload, #``register`` command - } - if not hasattr(settings, 'DJANGOPYPI_METADATA_FIELDS'): settings.DJANGOPYPI_METADATA_FIELDS = { '1.0': ('platform','summary','description','keywords','home_page', @@ -89,4 +77,15 @@ 'maintainer','maintainer_email','license','classifier', 'requires_dist','provides_dist','obsoletes_dist', 'requires_python','requires_external','project_url')} - \ No newline at end of file + +if not hasattr(settings, 'DJANGOPYPI_FALLBACK_VIEW'): + from djangopypi.views import releases + settings.DJANGOPYPI_FALLBACK_VIEW = releases.index + +if not hasattr(settings,'DJANGOPYPI_ACTION_VIEWS'): + from djangopypi.views.distutils import register_or_upload + + settings.DJANGOPYPI_ACTION_VIEWS = { + "file_upload": register_or_upload, #``sdist`` command + "submit": register_or_upload, #``register`` command + } \ No newline at end of file From 30834e2dd238995e88321bf48e60e2a952b045c2 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 10:58:56 -0500 Subject: [PATCH 023/146] Added a release management form (very limited due to the changing nature of metadata versions) --- src/djangopypi/djangopypi/forms.py | 15 ++++++--- src/djangopypi/djangopypi/models.py | 5 +-- .../templates/djangopypi/release_manage.html | 13 ++++++++ src/djangopypi/djangopypi/urls.py | 3 +- src/djangopypi/djangopypi/views/releases.py | 33 ++++++++++++++----- 5 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/release_manage.html diff --git a/src/djangopypi/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py index 3cd476c..cce79f4 100644 --- a/src/djangopypi/djangopypi/forms.py +++ b/src/djangopypi/djangopypi/forms.py @@ -1,9 +1,11 @@ -import os from django import forms -from django.conf import settings -from djangopypi.models import Package, Classifier, Release from django.utils.translation import ugettext_lazy as _ +from djangopypi.settings import settings +from djangopypi.models import Package, Classifier, Release + + + class SimplePackageSearchForm(forms.Form): query = forms.CharField(max_length=255) @@ -12,8 +14,11 @@ class Meta: model = Package exclude = ['name'] - class ReleaseForm(forms.ModelForm): + metadata_version = forms.CharField(widget=forms.Select(choices=zip(settings.DJANGOPYPI_METADATA_FIELDS.keys(), + settings.DJANGOPYPI_METADATA_FIELDS.keys()))) + class Meta: model = Release - exclude = ['package'] \ No newline at end of file + exclude = ['package', 'version', 'package_info'] + diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index 7b7ca75..60264a7 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -55,7 +55,8 @@ def __unicode__(self): class Package(models.Model): - name = models.CharField(max_length=255, unique=True, primary_key=True) + name = models.CharField(max_length=255, unique=True, primary_key=True, + editable=False) auto_hide = models.BooleanField(default=True, blank=False) allow_comments = models.BooleanField(default=True, blank=False) owners = models.ManyToManyField(User, blank=True, @@ -90,7 +91,7 @@ def get_release(self, version): class Release(models.Model): package = models.ForeignKey(Package, related_name="releases", editable=False) - version = models.CharField(max_length=128) + version = models.CharField(max_length=128, editable=False) metadata_version = models.CharField(max_length=64, default='1.0') package_info = PackageInfoField(blank=False) hidden = models.BooleanField(default=False) diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_manage.html b/src/djangopypi/djangopypi/templates/djangopypi/release_manage.html new file mode 100644 index 0000000..5e8c47f --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_manage.html @@ -0,0 +1,13 @@ + + + Manage {{ release }} + + +

Manage {{ release }}

+
    + + {{ form.as_p }} + +
+ + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 1de24f6..5dada51 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -16,7 +16,8 @@ 'releases.details',name='djangopypi-release'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/doap.xml$', 'releases.doap',name='djangopypi-release-doap'), - + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/manage/$', + 'releases.manage',name='djangopypi-release-manage'), ) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py index 4f269ef..1b10363 100644 --- a/src/djangopypi/djangopypi/views/releases.py +++ b/src/djangopypi/djangopypi/views/releases.py @@ -1,30 +1,32 @@ from django.conf import settings from django.core.urlresolvers import reverse from django.http import Http404, HttpResponseRedirect -from django.views.generic import list_detail +from django.views.generic import list_detail, create_update from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext +from djangopypi.decorators import user_owns_package, user_maintains_package from djangopypi.models import Package, Release +from djangopypi.forms import ReleaseForm def index(request, **kwargs): kwargs.setdefault('template_object_name','release') - kwargs.setdefault('queryset',Release.objects.all()) + kwargs.setdefault('queryset',Release.objects.filter(hidden=False)) return list_detail.object_list(request, **kwargs) def details(request, package, version, **kwargs): - kwargs.setdefault('template_object_name','release') - kwargs.setdefault('template_name','djangopypi/release_detail.html') - kwargs.setdefault('extra_context',{}) - kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) - release = get_object_or_404(Package, name=package).get_release(version) if not release: return Http404() + kwargs.setdefault('template_object_name','release') + kwargs.setdefault('template_name','djangopypi/release_detail.html') + kwargs.setdefault('extra_context',{}) + kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) + kwargs['extra_context'][kwargs['template_object_name']] = release return render_to_response(kwargs['template_name'], kwargs['extra_context'], @@ -34,4 +36,19 @@ def details(request, package, version, **kwargs): def doap(request, package, version, **kwargs): kwargs.setdefault('template_name','djangopypi/release_doap.xml') kwargs.setdefault('mimetype', 'text/xml') - return details(request, package, version, **kwargs) \ No newline at end of file + return details(request, package, version, **kwargs) + +@user_maintains_package() +def manage(request, package, version, **kwargs): + release = get_object_or_404(Package, name=package).get_release(version) + + if not release: + return Http404() + + kwargs['object_id'] = release.pk + + kwargs.setdefault('form_class', ReleaseForm) + kwargs.setdefault('template_name', 'djangopypi/release_manage.html') + kwargs.setdefault('template_object_name', 'release') + + return create_update.update_object(request, **kwargs) \ No newline at end of file From c38471bc848b91d6af915fa0167cbecee5188026 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 11:43:24 -0500 Subject: [PATCH 024/146] Added more detection of variables for metadata version determination due to a bug in distutils sending 1.0 when it should be 1.1 --- src/djangopypi/djangopypi/views/distutils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index a1f787d..6fe16f0 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -106,6 +106,10 @@ def register_or_upload(request): release, created = Release.objects.get_or_create(package=package, version=version) + if (('classifiers' in request.POST or 'download_url' in request.POST) and + metadata_version == '1.0'): + metadata_version = '1.1' + release.metadata_version = metadata_version fields = settings.DJANGOPYPI_METADATA_FIELDS[metadata_version] From bb7171f3eaf1f25354d5a9bb5b54b9c1c20c6700 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 12:14:54 -0500 Subject: [PATCH 025/146] Added dummy metadata forms and fleshed out version 1.0 form --- src/djangopypi/djangopypi/forms.py | 75 +++++++++++++++++++++++++++ src/djangopypi/djangopypi/settings.py | 9 +++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/djangopypi/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py index cce79f4..2785531 100644 --- a/src/djangopypi/djangopypi/forms.py +++ b/src/djangopypi/djangopypi/forms.py @@ -22,3 +22,78 @@ class Meta: model = Release exclude = ['package', 'version', 'package_info'] +metadata10licenses = ('Artistic', 'BSD', 'DFSG', 'GNU GPL', 'GNU LGPL', + 'MIT', 'Mozilla PL', 'public domain', 'Python', + 'Qt', 'PL', 'Zope PL', 'unknown', 'nocommercial', 'nosell', + 'nosource', 'shareware', 'other') + +class Metadata10Form(forms.Form): + platform = forms.TextField(required=False, + help_text=_(u'A comma-separated list of platform ' + 'specifications, summarizing the ' + 'operating systems supported by the ' + 'package.')) + + summary = forms.CharField(max_length=255, + help_text=_(u'A one-line summary of what the ' + 'package does.')) + + description = forms.TextField(required=False, + help_text=_(u'A longer description of the ' + 'package that can run to several ' + 'paragraphs. If this is in ' + 'reStructuredText format, it will ' + 'be rendered nicely on display.')) + + keywords = forms.CharField(max_length=255, + help_text=_(u'A list of additional keywords to ' + 'be used to assist searching for the ' + 'package in a larger catalog')) + + home_page = forms.URLField(max_length=255, required=False, + verify_exists=True, + help_text=_(u'A string containing the URL for ' + 'the package\'s home page.')) + + author = forms.TextField(required=False, + help_text=_(u'A string containing at a minimum the ' + 'author\'s name. Contact information ' + 'can also be added, separating each ' + 'line with newlines.')) + + author_email = forms.CharField(max_length=255, + help_text=_(u'A string containing the ' + 'author\'s e-mail address. It ' + 'can contain a name and e-mail ' + 'address in the legal forms for ' + 'a RFC-822 \'From:\' header.')) + + license = forms.CharField(max_length=32, + help_text=_(u'A string selected from a short list ' + 'of choices, specifying the license ' + 'covering the package.'), + widget=forms.Select(choices=(zip(metadata10licenses, + metadata10licenses)))) + +class Metadata11Form(Metadata10Form): + supported_platform = False + download_url = False + license = False + classifier = False + requires = False + provides = False + obsoletes = False + +class Metadata12Form(Metadata10Form): + supported_platform = False + download_url = False + license = False + classifier = False + maintainer = False + maintainer_email = False + requires_dist = False + provides_dist = False + obsoletes_dist = False + requires_python = False + requires_external = False + project_url = False \ No newline at end of file diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index 5091f4d..0a94c1b 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -68,7 +68,7 @@ if not hasattr(settings, 'DJANGOPYPI_METADATA_FIELDS'): settings.DJANGOPYPI_METADATA_FIELDS = { '1.0': ('platform','summary','description','keywords','home_page', - 'author','author_email', 'license', 'classifier'), + 'author','author_email', 'license', ), '1.1': ('platform','supported_platform','summary','description', 'keywords','home_page','download_url','author','author_email', 'license','classifier','requires','provides','obsoletes',), @@ -78,6 +78,13 @@ 'requires_dist','provides_dist','obsoletes_dist', 'requires_python','requires_external','project_url')} +if not hasattr(settings, 'DJANGOPYPI_METADATA_FORMS'): + from djangopypi.forms import Metadata10Form, Metadata11Form, Metadata12Form + settings.DJANGOPYPI_METADATA_FORMS = { + '1.0': Metadata10Form, + '1.1': Metadata11Form, + '1.2': Metadata12Form} + if not hasattr(settings, 'DJANGOPYPI_FALLBACK_VIEW'): from djangopypi.views import releases settings.DJANGOPYPI_FALLBACK_VIEW = releases.index From 2de25ffa4404a93f64db5971cd5a4ebf075caa07 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 13:50:23 -0500 Subject: [PATCH 026/146] Filled out the metadata forms for versions 1.0-1.2 as per PEP specs (214, 341, 345) --- src/djangopypi/djangopypi/forms.py | 162 +++++++++++++++++++++++------ 1 file changed, 132 insertions(+), 30 deletions(-) diff --git a/src/djangopypi/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py index 2785531..8fa7857 100644 --- a/src/djangopypi/djangopypi/forms.py +++ b/src/djangopypi/djangopypi/forms.py @@ -28,41 +28,37 @@ class Meta: 'nosource', 'shareware', 'other') class Metadata10Form(forms.Form): - platform = forms.TextField(required=False, + platform = forms.CharField(required=False, widget=forms.Textarea(), help_text=_(u'A comma-separated list of platform ' 'specifications, summarizing the ' 'operating systems supported by the ' 'package.')) - summary = forms.CharField(max_length=255, - help_text=_(u'A one-line summary of what the ' + summary = forms.CharField(help_text=_(u'A one-line summary of what the ' 'package does.')) - description = forms.TextField(required=False, + description = forms.CharField(required=False, widget=forms.Textarea(), help_text=_(u'A longer description of the ' 'package that can run to several ' 'paragraphs. If this is in ' 'reStructuredText format, it will ' 'be rendered nicely on display.')) - keywords = forms.CharField(max_length=255, - help_text=_(u'A list of additional keywords to ' + keywords = forms.CharField(help_text=_(u'A list of additional keywords to ' 'be used to assist searching for the ' 'package in a larger catalog')) - home_page = forms.URLField(max_length=255, required=False, - verify_exists=True, + home_page = forms.URLField(required=False, verify_exists=True, help_text=_(u'A string containing the URL for ' 'the package\'s home page.')) - author = forms.TextField(required=False, + author = forms.CharField(required=False, widget=forms.Textarea(), help_text=_(u'A string containing at a minimum the ' 'author\'s name. Contact information ' 'can also be added, separating each ' 'line with newlines.')) - author_email = forms.CharField(max_length=255, - help_text=_(u'A string containing the ' + author_email = forms.CharField(help_text=_(u'A string containing the ' 'author\'s e-mail address. It ' 'can contain a name and e-mail ' 'address in the legal forms for ' @@ -76,24 +72,130 @@ class Metadata10Form(forms.Form): metadata10licenses)))) class Metadata11Form(Metadata10Form): - supported_platform = False - download_url = False - license = False - classifier = False - requires = False - provides = False - obsoletes = False + supported_platform = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'The OS and CPU for which ' + 'the binary package was ' + 'compiled.')) + + keywords = forms.CharField(required=False, + help_text=_(u'A list of additional keywords to ' + 'be used to assist searching for the ' + 'package in a larger catalog')) + + download_url = forms.URLField(required=False, verify_exists=True, + help_text=_(u'A string containing the URL for ' + 'the package\'s home page.')) + + license = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Text indicating the license ' + 'covering the package where the ' + 'license is not a selection from the ' + '"License" Trove classifiers.')) + + classifier = forms.ModelMultipleChoiceField(required=False, + queryset=Classifier.objects.all(), + help_text=_(u'Trove classifiers')) + + requires = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Each line contains a string ' + 'describing some other module or ' + 'package required by this package.')) + + provides = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Each line contains a string ' + 'describing a package or module that ' + 'will be provided by this package ' + 'once it is installed')) + + obsoletes = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Each line contains a string ' + 'describing a package or module that ' + 'this package renders obsolete, ' + 'meaning that the two packages ' + 'should not be installed at the ' + 'same time')) class Metadata12Form(Metadata10Form): - supported_platform = False - download_url = False - license = False - classifier = False - maintainer = False - maintainer_email = False - requires_dist = False - provides_dist = False - obsoletes_dist = False - requires_python = False - requires_external = False - project_url = False \ No newline at end of file + supported_platform = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'The OS and CPU for which ' + 'the binary package was ' + 'compiled.')) + + keywords = forms.CharField(required=False, + help_text=_(u'A list of additional keywords to ' + 'be used to assist searching for the ' + 'package in a larger catalog')) + + download_url = forms.URLField(required=False, + verify_exists=True, + help_text=_(u'A string containing the URL for ' + 'the package\'s home page.')) + + author_email = forms.CharField(required=False, + help_text=_(u'A string containing the ' + 'author\'s e-mail address. It ' + 'can contain a name and e-mail ' + 'address in the legal forms for ' + 'a RFC-822 \'From:\' header.')) + + maintainer = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'A string containing at a minimum ' + 'the maintainer\'s name. Contact ' + 'information can also be added, ' + 'separating each line with ' + 'newlines.')) + maintainer_email = forms.CharField(required=False, + help_text=_(u'A string containing the ' + 'maintainer\'s e-mail address. ' + 'It can contain a name and ' + 'e-mail address in the legal ' + 'forms for a RFC-822 ' + '\'From:\' header.')) + + license = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Text indicating the license ' + 'covering the package where the ' + 'license is not a selection from the ' + '"License" Trove classifiers.')) + + classifier = forms.ModelMultipleChoiceField(required=False, + queryset=Classifier.objects.all(), + help_text=_(u'Trove classifiers')) + + requires_dist = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Each line contains a string ' + 'describing some other module or ' + 'package required by this package.')) + + provides_dist = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Each line contains a string ' + 'describing a package or module that ' + 'will be provided by this package ' + 'once it is installed')) + + obsoletes_dist = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Each line contains a string ' + 'describing a package or module that ' + 'this package renders obsolete, ' + 'meaning that the two packages ' + 'should not be installed at the ' + 'same time')) + + requires_python = forms.CharField(max_length=255, required=False, + help_text=_(u'This field specifies the ' + 'Python version(s) that the ' + 'distribution is guaranteed ' + 'to be compatible with.')) + + requires_external = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Each line contains a ' + 'string describing some ' + 'dependency in the system ' + 'that the distribution is ' + 'to be used.')) + project_url = forms.CharField(required=False, widget=forms.Textarea(), + help_text=_(u'Each line is a string containing ' + 'a browsable URL for the project ' + 'and a label for it, separated ' + 'by a comma: "Bug Tracker, ' + 'http://bugs.project.com"')) From cdbc0a1e1f8a1e374b8d036e4dfa94d9807b7592 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 7 Jun 2010 13:52:51 -0500 Subject: [PATCH 027/146] Switched classifiers to using name as the primary key --- .../djangopypi/migrations/0002_refactoring.py | 15 +++++++++++++-- src/djangopypi/djangopypi/models.py | 4 ++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/djangopypi/djangopypi/migrations/0002_refactoring.py b/src/djangopypi/djangopypi/migrations/0002_refactoring.py index 35dde07..eccb058 100644 --- a/src/djangopypi/djangopypi/migrations/0002_refactoring.py +++ b/src/djangopypi/djangopypi/migrations/0002_refactoring.py @@ -62,6 +62,12 @@ def forwards(self, orm): )) db.create_unique('djangopypi_package_maintainers', ['package_id', 'user_id']) + # Deleting field 'Classifier.id' + db.delete_column('djangopypi_classifier', 'id') + + # Changing field 'Classifier.name' + db.alter_column('djangopypi_classifier', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, primary_key=True)) + # Deleting field 'Release.md5_digest' db.delete_column('djangopypi_release', 'md5_digest') @@ -150,6 +156,12 @@ def backwards(self, orm): # Removing M2M table for field maintainers on 'Package' db.delete_table('djangopypi_package_maintainers') + # Adding field 'Classifier.id' + db.add_column('djangopypi_classifier', 'id', self.gf('django.db.models.fields.AutoField')(default='', primary_key=True), keep_default=False) + + # Changing field 'Classifier.name' + db.alter_column('djangopypi_classifier', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True)) + # Adding field 'Release.md5_digest' db.add_column('djangopypi_release', 'md5_digest', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) @@ -235,8 +247,7 @@ def backwards(self, orm): }, 'djangopypi.classifier': { 'Meta': {'object_name': 'Classifier'}, - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}) }, 'djangopypi.distribution': { 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index 60264a7..25987ae 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -44,12 +44,12 @@ def get_internal_type(self): class Classifier(models.Model): - name = models.CharField(max_length=255, unique=True) + name = models.CharField(max_length=255, primary_key=True) class Meta: verbose_name = _(u"classifier") verbose_name_plural = _(u"classifiers") - + def __unicode__(self): return self.name From b156fd96d0a809400f6c355ffdece5387d3c233c Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Tue, 8 Jun 2010 11:30:19 -0500 Subject: [PATCH 028/146] Cleaned up TODO and created a separate TODO for chishop --- TODO | 8 +++++++ src/djangopypi/docs/TODO | 52 ++++++++++------------------------------ 2 files changed, 21 insertions(+), 39 deletions(-) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000..986c837 --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ +TODO +==== + +This file is for the chishop package specifically, for djangopypi, see +src/djangopypi/docs/TODO. + +* Random Monty Python quotes :-) +* Whoosh full text indexing integration, maybe called djangopypi.whoosh? \ No newline at end of file diff --git a/src/djangopypi/docs/TODO b/src/djangopypi/docs/TODO index f8ed064..baf7840 100644 --- a/src/djangopypi/docs/TODO +++ b/src/djangopypi/docs/TODO @@ -1,51 +1,25 @@ -PyPI feature replication -======================== +Roadmap +======= -* Make it possible to register users via distutils. - There should be a setting to turn this feature on/off for private PyPIs. - [taken-by: sverrej] +1.0 +--- -* Roles (co-owners/maintainers) - - One possible solution: - http://github.com/initcrash/django-object-permissions/tree - I'm not sure what the difference between a co-owner and maintainer is, - maybe it's just a label. -* Package author admin interface (submit, edit, view) -* Documentation upload -* Ratings -* Random Monty Python quotes :-) -* Comments :-) +* Optional user registration via distutils (if django-registration is installed) +* Finish non-staff package admin interface +* Ratings/comments -Post-PyPI -========= - -* PEP-381: Mirroring infrastructure for PyPI - [taken-by: jezdez] +Future +------ +* Documentation hosting/upload +* PEP-381: Mirroring integration for PyPI * API to submit test reports for smoke test bots. Like CPAN Testers. Platform/version/matrix etc. - -* Different listings: Author listings, classifier listings, etc. - -* Search metadata - -* Automatic generation of Sphinx for modules (so you can view them directly -on pypi, like CPAN), Module listing etc. - -* Listing of special files: README, LICENSE, Changefile/Changes, TODO, - MANIFEST. - -* Dependency graphs. - +* Dependency graphs * Package file browser (like CPAN) - - - Documentation -============= +------------- * Write a tutorial on how to set up the server, registering projects, and how to upload releases. - - From 6628cdbafa6400a082bad6e6707b93404ed0951e Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 10:09:54 -0500 Subject: [PATCH 029/146] Added a view for release metadata editing along with metadata forms for the various metadata versions --- src/djangopypi/djangopypi/forms.py | 102 +++++++++++--------- src/djangopypi/djangopypi/urls.py | 3 +- src/djangopypi/djangopypi/views/releases.py | 54 ++++++++++- src/djangopypi/docs/TODO | 1 + 4 files changed, 113 insertions(+), 47 deletions(-) diff --git a/src/djangopypi/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py index 8fa7857..536256d 100644 --- a/src/djangopypi/djangopypi/forms.py +++ b/src/djangopypi/djangopypi/forms.py @@ -27,17 +27,28 @@ class Meta: 'Qt', 'PL', 'Zope PL', 'unknown', 'nocommercial', 'nosell', 'nosource', 'shareware', 'other') +class LinesField(forms.CharField): + def __init__(self, *args, **kwargs): + kwargs.setdefault('widget', forms.Textarea()) + super(LinesField, self).__init__(*args, **kwargs) + + def to_python(self, value): + return map(lambda s: s.strip(), + super(LinesField, self).to_python(value).split('\n')) + class Metadata10Form(forms.Form): - platform = forms.CharField(required=False, widget=forms.Textarea(), - help_text=_(u'A comma-separated list of platform ' - 'specifications, summarizing the ' - 'operating systems supported by the ' - 'package.')) + platform = LinesField(required=False, + help_text=_(u'A comma-separated list of platform ' + 'specifications, summarizing the ' + 'operating systems supported by the ' + 'package.')) summary = forms.CharField(help_text=_(u'A one-line summary of what the ' 'package does.')) - description = forms.CharField(required=False, widget=forms.Textarea(), + description = forms.CharField(required=False, + widget=forms.Textarea(attrs=dict(rows=40, + columns=40)), help_text=_(u'A longer description of the ' 'package that can run to several ' 'paragraphs. If this is in ' @@ -52,7 +63,9 @@ class Metadata10Form(forms.Form): help_text=_(u'A string containing the URL for ' 'the package\'s home page.')) - author = forms.CharField(required=False, widget=forms.Textarea(), + author = forms.CharField(required=False, + widget=forms.Textarea(attrs=dict(rows=3, + columns=20)), help_text=_(u'A string containing at a minimum the ' 'author\'s name. Contact information ' 'can also be added, separating each ' @@ -96,24 +109,23 @@ class Metadata11Form(Metadata10Form): queryset=Classifier.objects.all(), help_text=_(u'Trove classifiers')) - requires = forms.CharField(required=False, widget=forms.Textarea(), - help_text=_(u'Each line contains a string ' - 'describing some other module or ' - 'package required by this package.')) - - provides = forms.CharField(required=False, widget=forms.Textarea(), - help_text=_(u'Each line contains a string ' - 'describing a package or module that ' - 'will be provided by this package ' - 'once it is installed')) - - obsoletes = forms.CharField(required=False, widget=forms.Textarea(), - help_text=_(u'Each line contains a string ' - 'describing a package or module that ' - 'this package renders obsolete, ' - 'meaning that the two packages ' - 'should not be installed at the ' - 'same time')) + requires = LinesField(required=False, + help_text=_(u'Each line contains a string describing ' + 'some other module or package required by ' + 'this package.')) + + provides = LinesField(required=False, + help_text=_(u'Each line contains a string describing ' + 'a package or module that will be ' + 'provided by this package once it is ' + 'installed')) + + obsoletes = LinesField(required=False, + help_text=_(u'Each line contains a string describing ' + 'a package or module that this package ' + 'renders obsolete, meaning that the two ' + 'packages should not be installed at the ' + 'same time')) class Metadata12Form(Metadata10Form): supported_platform = forms.CharField(required=False, widget=forms.Textarea(), @@ -162,26 +174,26 @@ class Metadata12Form(Metadata10Form): queryset=Classifier.objects.all(), help_text=_(u'Trove classifiers')) - requires_dist = forms.CharField(required=False, widget=forms.Textarea(), - help_text=_(u'Each line contains a string ' - 'describing some other module or ' - 'package required by this package.')) - - provides_dist = forms.CharField(required=False, widget=forms.Textarea(), - help_text=_(u'Each line contains a string ' - 'describing a package or module that ' - 'will be provided by this package ' - 'once it is installed')) - - obsoletes_dist = forms.CharField(required=False, widget=forms.Textarea(), - help_text=_(u'Each line contains a string ' - 'describing a package or module that ' - 'this package renders obsolete, ' - 'meaning that the two packages ' - 'should not be installed at the ' - 'same time')) - - requires_python = forms.CharField(max_length=255, required=False, + requires_dist = LinesField(required=False, + help_text=_(u'Each line contains a string ' + 'describing some other module or ' + 'package required by this package.')) + + provides_dist = LinesField(required=False, + help_text=_(u'Each line contains a string ' + 'describing a package or module that ' + 'will be provided by this package ' + 'once it is installed')) + + obsoletes_dist = LinesField(required=False, + help_text=_(u'Each line contains a string ' + 'describing a package or module that ' + 'this package renders obsolete, ' + 'meaning that the two packages ' + 'should not be installed at the ' + 'same time')) + + requires_python = forms.CharField(required=False, help_text=_(u'This field specifies the ' 'Python version(s) that the ' 'distribution is guaranteed ' diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 5dada51..8c16fd9 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -18,6 +18,7 @@ 'releases.doap',name='djangopypi-release-doap'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/manage/$', 'releases.manage',name='djangopypi-release-manage'), - + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/manage/metadata/$', + 'releases.manage_metadata',name='djangopypi-release-manage-metadata'), ) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py index 1b10363..28f3569 100644 --- a/src/djangopypi/djangopypi/views/releases.py +++ b/src/djangopypi/djangopypi/views/releases.py @@ -51,4 +51,56 @@ def manage(request, package, version, **kwargs): kwargs.setdefault('template_name', 'djangopypi/release_manage.html') kwargs.setdefault('template_object_name', 'release') - return create_update.update_object(request, **kwargs) \ No newline at end of file + return create_update.update_object(request, **kwargs) + +@user_maintains_package() +def manage_metadata(request, package, version, **kwargs): + kwargs.setdefault('template_name', 'djangopypi/release_manage.html') + kwargs.setdefault('template_object_name', 'release') + kwargs.setdefault('extra_context',{}) + kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) + + release = get_object_or_404(Package, name=package).get_release(version) + + if not release: + return Http404() + + if not release.metadata_version in settings.DJANGOPYPI_METADATA_FORMS: + #TODO: Need to change this to a more meaningful error + return Http404() + + kwargs['extra_context'][kwargs['template_object_name']] = release + + form_class = settings.DJANGOPYPI_METADATA_FORMS.get(release.metadata_version) + + initial = {} + multivalue = ('classifier',) + + for key, values in release.package_info.iterlists(): + if key in multivalue: + initial[key] = values + else: + initial[key] = '\n'.join(values) + + if request.method == 'POST': + form = form_class(data=request.POST, initial=initial) + + if form.is_valid(): + for key, value in form.cleaned_data.iteritems(): + if isinstance(value, basestring): + release.package_info[key] = value + elif hasattr(value, '__iter__'): + release.package_info.setlist(key, list(value)) + + release.save() + print str(release.package_info) + return create_update.redirect(kwargs.get('post_save_redirect',None), + release) + else: + form = form_class(initial=initial) + + kwargs['extra_context']['form'] = form + + return render_to_response(kwargs['template_name'], kwargs['extra_context'], + context_instance=RequestContext(request), + mimetype=kwargs['mimetype']) \ No newline at end of file diff --git a/src/djangopypi/docs/TODO b/src/djangopypi/docs/TODO index baf7840..1d42848 100644 --- a/src/djangopypi/docs/TODO +++ b/src/djangopypi/docs/TODO @@ -4,6 +4,7 @@ Roadmap 1.0 --- +* PKG-INFO view of a release (auto generated from package_info field) * Optional user registration via distutils (if django-registration is installed) * Finish non-staff package admin interface * Ratings/comments From 918d2957802d0e5024b300dd512aa98e30e8c576 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 10:27:09 -0500 Subject: [PATCH 030/146] Pushed version back to 0.3 and removed ununsed utilities --- src/djangopypi/djangopypi/utils.py | 28 +--------------------------- src/djangopypi/setup.py | 2 +- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/djangopypi/djangopypi/utils.py b/src/djangopypi/djangopypi/utils.py index ba2013e..2a3d032 100644 --- a/src/djangopypi/djangopypi/utils.py +++ b/src/djangopypi/djangopypi/utils.py @@ -1,31 +1,5 @@ -import sys -import traceback +import sys, traceback -from django.core.files.uploadedfile import SimpleUploadedFile -from django.utils.datastructures import MultiValueDict - - -def transmute(f): - if hasattr(f, "filename") and f.filename: - v = SimpleUploadedFile(f.filename, f.value, f.type) - else: - v = f.value.decode("utf-8") - return v - - -def decode_fs(fs): - POST, FILES = {}, {} - for k in fs.keys(): - v = transmute(fs[k]) - if isinstance(v, SimpleUploadedFile): - FILES[k] = [v] - else: - # Distutils sends UNKNOWN for empty fields (e.g platform) - # [russell.sim@gmail.com] - if v == "UNKNOWN": - v = None - POST[k] = [v] - return MultiValueDict(POST), MultiValueDict(FILES) def debug(func): diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 522a1e4..3de0e59 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -9,7 +9,7 @@ import os -version = '0.4' +version = '0.3' setup(name='djangopypi', version=version, From 8836ca4c700f15e693fd40c7f921a8fda926adfe Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 11:08:45 -0500 Subject: [PATCH 031/146] Added signals for auto-hiding of releases --- src/djangopypi/djangopypi/__init__.py | 1 + src/djangopypi/djangopypi/signals.py | 45 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/djangopypi/djangopypi/signals.py diff --git a/src/djangopypi/djangopypi/__init__.py b/src/djangopypi/djangopypi/__init__.py index f831b93..2251754 100644 --- a/src/djangopypi/djangopypi/__init__.py +++ b/src/djangopypi/djangopypi/__init__.py @@ -1 +1,2 @@ from djangopypi import settings +from djangopypi import signals diff --git a/src/djangopypi/djangopypi/signals.py b/src/djangopypi/djangopypi/signals.py new file mode 100644 index 0000000..3856399 --- /dev/null +++ b/src/djangopypi/djangopypi/signals.py @@ -0,0 +1,45 @@ +from django.db.models import signals + +from djangopypi.models import Package, Release + + +def autohide_new_release_handler(sender, instance, created, *args, **kwargs): + """ Autohide other releases on the creation of a new release when the + package 'auto-hide' is True""" + if not created or not instance.package.auto_hide: + return + + for release in instance.package.releases.exclude(pk=instance.pk).filter(hidden=False): + release.hidden = True + release.save() + + if instance.hidden: + instance.hidden = False + instance.save() + +def autohide_save_release_handler(sender, instance, *args, **kwargs): + """ When saving a release, check to see if it should be hidden or not """ + if instance.pk is None: + return + + if not instance.package.auto_hide: + return + + try: + latest = instance.package.releases.latest('created') + except Release.DoesNotExist: + return + + if instance != latest and not instance.hidden: + instance.hidden = True + +def autohide_save_package_handler(sender, instance, *args, **kwargs): + if not instance.auto_hide: + return + + for release in instance.releases.filter(hidden=False): + release.save() + +signals.post_save.connect(autohide_new_release_handler, sender=Release) +signals.pre_save.connect(autohide_save_release_handler, sender=Release) +signals.pre_save.connect(autohide_save_package_handler, sender=Package) From e8ab6c99b4912f2106215fc943bf3896f936b6bf Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 11:13:49 -0500 Subject: [PATCH 032/146] Added an if statement around the output of files table to make sure there are files --- chishop/templates/djangopypi/release_detail_content.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chishop/templates/djangopypi/release_detail_content.html b/chishop/templates/djangopypi/release_detail_content.html index c9b5bff..5287433 100644 --- a/chishop/templates/djangopypi/release_detail_content.html +++ b/chishop/templates/djangopypi/release_detail_content.html @@ -11,6 +11,7 @@ {{ release.description|saferst }} +{% if release.distributions.count %}
@@ -29,6 +30,7 @@
+{% endif %}
  • License: {{ release.package_info.license }}
  • From c42479065fa36ef360db833411f301a7fc093a55 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 12:29:26 -0500 Subject: [PATCH 033/146] Added view for managing releases of a package, stub for managing files also --- .../djangopypi/package_manage_versions.html | 36 +++++++++++++++++ src/djangopypi/djangopypi/urls.py | 9 ++++- src/djangopypi/djangopypi/views/packages.py | 40 ++++++++++++++++++- src/djangopypi/djangopypi/views/releases.py | 6 ++- 4 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html b/src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html new file mode 100644 index 0000000..4c3bca6 --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html @@ -0,0 +1,36 @@ + + + Manage {{ package.name }} Versions + + +

    Manage {{ package.name }} Versions

    + + {{ formset.management_form }} + + + + + + + + + + + {% for form in formset.forms %} + {% for field in form %}{% if field.is_hidden %}{{ field }}{% endif %}{% endfor %} + {% with form.instance as release %} + + + + + + + + + {% endwith %} + {% endfor %} + +
    Remove?VersionHide?Links
    {{ form.DELETE }}{{ release.version }}{{ form.hidden }}ShowEditFiles
    +
    + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 8c16fd9..0fdae2e 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -6,19 +6,24 @@ url(r'^packages/$','packages.index', name='djangopypi-package-index'), url(r'^search/$','packages.search',name='djangopypi-search'), url(r'^pypi/$', 'releases.index', name='djangopypi-release-index'), + url(r'^pypi/(?P[\w\d_\.\-]+)/$','packages.details', name='djangopypi-package'), url(r'^pypi/(?P[\w\d_\.\-]+)/doap.xml$','packages.doap', name='djangopypi-package-doap'), url(r'^pypi/(?P[\w\d_\.\-]+)/manage/$','packages.manage', name='djangopypi-package-manage'), + url(r'^pypi/(?P[\w\d_\.\-]+)/manage/versions/$','packages.manage_versions', + name='djangopypi-package-manage-versions'), + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', 'releases.details',name='djangopypi-release'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/doap.xml$', 'releases.doap',name='djangopypi-release-doap'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/manage/$', 'releases.manage',name='djangopypi-release-manage'), - url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/manage/metadata/$', + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/metadata/$', 'releases.manage_metadata',name='djangopypi-release-manage-metadata'), - + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/files/$', + 'releases.manage_files',name='djangopypi-release-manage-files'), ) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py index 5fdbbf2..c350f64 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/packages.py @@ -1,8 +1,12 @@ -from django.views.generic import list_detail, create_update +from django.conf import settings from django.db.models.query import Q +from django.forms.models import modelformset_factory, inlineformset_factory +from django.shortcuts import get_object_or_404, render_to_response +from django.template import RequestContext +from django.views.generic import list_detail, create_update from djangopypi.decorators import user_owns_package, user_maintains_package -from djangopypi.models import Package +from djangopypi.models import Package, Release from djangopypi.forms import SimplePackageSearchForm, PackageForm @@ -42,3 +46,35 @@ def manage(request, package, **kwargs): kwargs.setdefault('template_object_name', 'package') return create_update.update_object(request, **kwargs) + +@user_maintains_package() +def manage_versions(request, package, **kwargs): + package = get_object_or_404(Package, name=package) + kwargs.setdefault('formset_factory_kwargs',{}) + kwargs['formset_factory_kwargs'].setdefault('fields', ('hidden',)) + kwargs['formset_factory_kwargs']['extra'] = 0 + + kwargs.setdefault('formset_factory', inlineformset_factory(Package, Release, **kwargs['formset_factory_kwargs'])) + kwargs.setdefault('template_name', 'djangopypi/package_manage_versions.html') + kwargs.setdefault('template_object_name', 'package') + kwargs.setdefault('extra_context',{}) + kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) + kwargs['extra_context'][kwargs['template_object_name']] = package + kwargs.setdefault('formset_kwargs',{}) + #kwargs['formset_kwargs']['queryset'] = package.releases.all() + kwargs['formset_kwargs']['instance'] = package + + if request.method == 'POST': + formset = kwargs['formset_factory'](data=request.POST, **kwargs['formset_kwargs']) + if formset.is_valid(): + formset.save() + return create_update.redirect(kwargs.get('post_save_redirect',None), + package) + + formset = kwargs['formset_factory'](**kwargs['formset_kwargs']) + + kwargs['extra_context']['formset'] = formset + + return render_to_response(kwargs['template_name'], kwargs['extra_context'], + context_instance=RequestContext(request), + mimetype=kwargs['mimetype']) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py index 28f3569..b1ba0fd 100644 --- a/src/djangopypi/djangopypi/views/releases.py +++ b/src/djangopypi/djangopypi/views/releases.py @@ -103,4 +103,8 @@ def manage_metadata(request, package, version, **kwargs): return render_to_response(kwargs['template_name'], kwargs['extra_context'], context_instance=RequestContext(request), - mimetype=kwargs['mimetype']) \ No newline at end of file + mimetype=kwargs['mimetype']) + +@user_maintains_package() +def manage_files(request, package, version, **kwargs): + pass \ No newline at end of file From 44a55b8aa95726c28cc937515594b27971884864 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 12:42:16 -0500 Subject: [PATCH 034/146] Removed extra import from package views and fleshed out file management view --- .../djangopypi/release_manage_files.html | 41 +++++++++++++++++++ src/djangopypi/djangopypi/views/packages.py | 3 +- src/djangopypi/djangopypi/views/releases.py | 39 +++++++++++++++++- 3 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html b/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html new file mode 100644 index 0000000..f55d7b4 --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html @@ -0,0 +1,41 @@ + + + Manage {{ release }} Files + + +

    Manage {{ release }} Files

    + + {{ formset.management_form }} + + + + + + + + + + + + + + + {% for form in formset.forms %} + {% for field in form %}{% if field.is_hidden %}{{ field }}{% endif %}{% endfor %} + {% with form.instance as dist %} + + + + + + + + + + {% endwith %} + {% endfor %} + +
    Remove?TypePy VersionCommentDownloadSizeMD5 Digest
    {{ form.DELETE }}{{ dist.display_filetype }}{{ dist.pyversion }}{{ dist.comment }}{{ dist.filename }}{{ dist.content.size|filesizeformat }}{{ dist.md5_digest }}
    +
    + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py index c350f64..4ed4ec0 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/packages.py @@ -1,6 +1,6 @@ from django.conf import settings from django.db.models.query import Q -from django.forms.models import modelformset_factory, inlineformset_factory +from django.forms.models import inlineformset_factory from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext from django.views.generic import list_detail, create_update @@ -61,7 +61,6 @@ def manage_versions(request, package, **kwargs): kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) kwargs['extra_context'][kwargs['template_object_name']] = package kwargs.setdefault('formset_kwargs',{}) - #kwargs['formset_kwargs']['queryset'] = package.releases.all() kwargs['formset_kwargs']['instance'] = package if request.method == 'POST': diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py index b1ba0fd..1fc6228 100644 --- a/src/djangopypi/djangopypi/views/releases.py +++ b/src/djangopypi/djangopypi/views/releases.py @@ -1,12 +1,13 @@ from django.conf import settings from django.core.urlresolvers import reverse +from django.forms.models import inlineformset_factory from django.http import Http404, HttpResponseRedirect from django.views.generic import list_detail, create_update from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext from djangopypi.decorators import user_owns_package, user_maintains_package -from djangopypi.models import Package, Release +from djangopypi.models import Package, Release, Distribution from djangopypi.forms import ReleaseForm @@ -107,4 +108,38 @@ def manage_metadata(request, package, version, **kwargs): @user_maintains_package() def manage_files(request, package, version, **kwargs): - pass \ No newline at end of file + package = get_object_or_404(Package, name=package) + try: + release = package.releases.get(version=version) + except Release.DoesNotExist: + return Http404() + + kwargs.setdefault('formset_factory_kwargs',{}) + kwargs['formset_factory_kwargs'].setdefault('fields', ('comment',)) + kwargs['formset_factory_kwargs']['extra'] = 0 + + kwargs.setdefault('formset_factory', inlineformset_factory(Release, Distribution, **kwargs['formset_factory_kwargs'])) + kwargs.setdefault('template_name', 'djangopypi/release_manage_files.html') + kwargs.setdefault('template_object_name', 'release') + kwargs.setdefault('extra_context',{}) + kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) + kwargs['extra_context'][kwargs['template_object_name']] = release + kwargs.setdefault('formset_kwargs',{}) + kwargs['formset_kwargs']['instance'] = release + + if request.method == 'POST': + formset = kwargs['formset_factory'](data=request.POST, + files=request.FILES, + **kwargs['formset_kwargs']) + if formset.is_valid(): + formset.save() + return create_update.redirect(kwargs.get('post_save_redirect',None), + release) + + formset = kwargs['formset_factory'](**kwargs['formset_kwargs']) + + kwargs['extra_context']['formset'] = formset + + return render_to_response(kwargs['template_name'], kwargs['extra_context'], + context_instance=RequestContext(request), + mimetype=kwargs['mimetype']) \ No newline at end of file From c412060696e9797243bb78c4d53da9a7e4c781bb Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 12:43:42 -0500 Subject: [PATCH 035/146] Switched comment to an input box in template --- .../djangopypi/templates/djangopypi/release_manage_files.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html b/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html index f55d7b4..78edfa8 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html @@ -16,7 +16,6 @@

    Manage {{ release }} Files

    Download Size MD5 Digest - @@ -27,7 +26,7 @@

    Manage {{ release }} Files

    {{ form.DELETE }} {{ dist.display_filetype }} {{ dist.pyversion }} - {{ dist.comment }} + {{ form.comment }} {{ dist.filename }} {{ dist.content.size|filesizeformat }} {{ dist.md5_digest }} From 298ded5161be1772b0badda5216e996991dc6312 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 13:26:14 -0500 Subject: [PATCH 036/146] Switched pypi/ to be the root view and fallback --- src/djangopypi/djangopypi/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 0fdae2e..e4a66e2 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -5,7 +5,7 @@ url(r'^$', "root", name="djangopypi-root"), url(r'^packages/$','packages.index', name='djangopypi-package-index'), url(r'^search/$','packages.search',name='djangopypi-search'), - url(r'^pypi/$', 'releases.index', name='djangopypi-release-index'), + url(r'^pypi/$', 'root', name='djangopypi-release-index'), url(r'^pypi/(?P[\w\d_\.\-]+)/$','packages.details', name='djangopypi-package'), From 74ddf6867910d50d3bf304cd90643f94d5db80c7 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 13:27:10 -0500 Subject: [PATCH 037/146] Removed user views (registration is still undergoing major API changes), made path for docs more explicit --- src/djangopypi/djangopypi/views/users.py | 28 ------------------------ src/djangopypi/setup.py | 2 +- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 src/djangopypi/djangopypi/views/users.py diff --git a/src/djangopypi/djangopypi/views/users.py b/src/djangopypi/djangopypi/views/users.py deleted file mode 100644 index 6e6c988..0000000 --- a/src/djangopypi/djangopypi/views/users.py +++ /dev/null @@ -1,28 +0,0 @@ -from django.http import HttpResponse, HttpResponseBadRequest - -try: - from registration.forms import RegistrationForm - from registration.backends import get_backend - - DEFAULT_BACKEND = "registration.backends.default.DefaultBackend" - - - def create_user(request, post_data, files, backend_name=DEFAULT_BACKEND): - """Create new user from a distutil client request""" - form = RegistrationForm({"username": post_data["name"], - "email": post_data["email"], - "password1": post_data["password"], - "password2": post_data["password"]}) - if not form.is_valid(): - # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg" - # Which is HTTP/WSGI incompatible, so we're just returning a empty 400. - return HttpResponseBadRequest() - - backend = get_backend(backend_name) - if not backend.registration_allowed(request): - return HttpResponseBadRequest() - new_user = backend.register(request, **form.cleaned_data) - return HttpResponse("OK\n", status=200, mimetype='text/plain') -except ImportError: - def create_user(request, *args, **kwargs): - return HttpResponseBadRequest('no registration') \ No newline at end of file diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 3de0e59..044b7bf 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -14,7 +14,7 @@ setup(name='djangopypi', version=version, description="A Django application that emulates the Python Package Index.", - long_description=open(os.path.join("docs", "README")).read(), + long_description=open(os.path.join(os.path.dirname(__file__), "docs", "README")).read(), classifiers=[ "Framework :: Django", "Development Status :: 3 - Alpha", From b51426b4792f5a6a2e71a6ce27c7322f6e22adb5 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 13:52:42 -0500 Subject: [PATCH 038/146] Added missing close form tags --- .../djangopypi/templates/djangopypi/package_manage_versions.html | 1 + .../djangopypi/templates/djangopypi/release_manage_files.html | 1 + 2 files changed, 2 insertions(+) diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html b/src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html index 4c3bca6..44109af 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html @@ -32,5 +32,6 @@

    Manage {{ package.name }} Versions

    + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html b/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html index 78edfa8..2fd036c 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html @@ -36,5 +36,6 @@

    Manage {{ release }} Files

    + \ No newline at end of file From d6ce27e2cdd9ca4b4460c5acfae484390832193f Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 13:53:29 -0500 Subject: [PATCH 039/146] Added chishop specific templates for release and package management --- .../djangopypi/package_manage_versions.html | 39 ++++++++++++++++ .../templates/djangopypi/release_manage.html | 16 +++++++ .../djangopypi/release_manage_files.html | 44 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 chishop/templates/djangopypi/package_manage_versions.html create mode 100644 chishop/templates/djangopypi/release_manage.html create mode 100644 chishop/templates/djangopypi/release_manage_files.html diff --git a/chishop/templates/djangopypi/package_manage_versions.html b/chishop/templates/djangopypi/package_manage_versions.html new file mode 100644 index 0000000..3f2a8f5 --- /dev/null +++ b/chishop/templates/djangopypi/package_manage_versions.html @@ -0,0 +1,39 @@ +{% extends "base_site.html" %} + +{% block bread_crumbs_1 %} + » {{ package }} + » Manage Releases +{% endblock %} + +{% block content %} +

    Manage {{ package.name }} Releases

    +
    + {{ formset.management_form }} + + + + + + + + + + + {% for form in formset.forms %} + {% for field in form %}{% if field.is_hidden %}{{ field }}{% endif %}{% endfor %} + {% with form.instance as release %} + + + + + + + + + {% endwith %} + {% endfor %} + +
    Remove?VersionHide?Links
    {{ form.DELETE }}{{ release.version }}{{ form.hidden }}ShowEditFiles
    +
    +
    +{% endblock content %} \ No newline at end of file diff --git a/chishop/templates/djangopypi/release_manage.html b/chishop/templates/djangopypi/release_manage.html new file mode 100644 index 0000000..b53dfdb --- /dev/null +++ b/chishop/templates/djangopypi/release_manage.html @@ -0,0 +1,16 @@ +{% extends "base_site.html" %} + +{% block bread_crumbs_1 %} +» {{ release.package }}{{ release }} +» Manage{% endblock %} + +{% block content %} +

    Manage {{ release }}

    +
      +
      + {{ form.as_p }} + +
    +{% endblock %} + diff --git a/chishop/templates/djangopypi/release_manage_files.html b/chishop/templates/djangopypi/release_manage_files.html new file mode 100644 index 0000000..8165102 --- /dev/null +++ b/chishop/templates/djangopypi/release_manage_files.html @@ -0,0 +1,44 @@ +{% extends "base_site.html" %} + +{% block bread_crumbs_1 %} +» {{ release.package }}{{ release }} +» Manage Files{% endblock %} + +{% block content %} +

    Manage {{ release }} Files

    + + {{ formset.management_form }} + + + + + + + + + + + + + + {% for form in formset.forms %} + {% for field in form %}{% if field.is_hidden %}{{ field }}{% endif %}{% endfor %} + {% with form.instance as dist %} + + + + + + + + + + {% endwith %} + {% endfor %} + +
    Remove?TypePy VersionCommentDownloadSizeMD5 Digest
    {{ form.DELETE }}{{ dist.display_filetype }}{{ dist.pyversion }}{{ form.comment }}{{ dist.filename }}{{ dist.content.size|filesizeformat }}{{ dist.md5_digest }}
    +
    + +{% endblock %} + From 841c8fbc00442e3c0e3d9c8409fc22cc7b2a90ad Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 14:32:48 -0500 Subject: [PATCH 040/146] Filtering incoming data to remove 'UNKNOWN' that register command likes to add --- src/djangopypi/djangopypi/views/distutils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index 6fe16f0..b50d176 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -120,6 +120,10 @@ def register_or_upload(request): release.package_info = MultiValueDict(dict(filter(lambda t: t[0] in fields, request.POST.iterlists()))) + for key, value in release.package_info.iterlists(): + release.package_info.setlist(key, + filter(lambda v: v != 'UNKNOWN', value)) + release.save() if not 'content' in request.FILES: transaction.commit() From d7e1882660c45f1e1a3fe8ee82c47adb78cfeca2 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 14:33:09 -0500 Subject: [PATCH 041/146] Updated documentation for the split in chishop and djangopypi --- src/djangopypi/docs/Changelog | 55 +++++++++++----------- src/djangopypi/docs/README | 86 +++++++++++++++++++++-------------- src/djangopypi/docs/TODO | 12 +++-- src/djangopypi/setup.py | 8 ++-- 4 files changed, 90 insertions(+), 71 deletions(-) diff --git a/src/djangopypi/docs/Changelog b/src/djangopypi/docs/Changelog index b6b53ba..4fcc870 100644 --- a/src/djangopypi/docs/Changelog +++ b/src/djangopypi/docs/Changelog @@ -1,41 +1,38 @@ -======= History ======= -0.2.0 :date:`2009-03-22 1:21 A.M CET` :author:askh@opera.com --------------------------------------------------------------- - - Backwards incompatible changes - ------------------------------- - - * Projects now has an associated owner, so old projects - must be exported and imported to a new database. +0.3 (2010-06-09) +---------------- - * Registering projects and uploading releases now requires - authentication. Use the admin interface to create new users, - registering users via distutils won't be available until - :version:`0.3.0`. +* Splitting djangopypi off of chishop +* Switched most views to using django generic views - * Every project now has an owner, so only the user registering the - project can add releases. +Backwards incompatible changes +______________________________ - * md5sum is now properly listed in the release link. +* Refactored package/project model to support multiple owners/maintainers +* Refactored release to match the metadata only that exists on pypi.python.org +* Created a Distribution model for distribution files on a release - * Project names can now have dots (`.`) in them. +0.2.0 (2009-03-22) +------------------ - * Fixed a bug where filenames was mangled if the distribution file - already existed. If someone uploaded version `1.0` of project `grail` - twice, the new filename was renamed to `grail-1.0.tar_.gz`, and a - backup of the old release was kept. Pip couldn't handle these filenames, - so we delete the old release first. +* Registering projects and uploading releases now requires authentication. +* Every project now has an owner, so only the user registering the project can + add releases. +* md5sum is now properly listed in the release link. +* Project names can now have dots ('.') in them. +* Fixed a bug where filenames was mangled if the distribution file already existed. +* Releases now list both project name and version, instead of just version in the admin interface. +* Added a sample buildout.cfg. Thanks to Rune Halvorsen (runeh@opera.com). - * Releases now list both project name and version, instead of just version - in the admin interface. +Backwards incompatible changes +______________________________ - * Added a sample buildout.cfg. Thanks to Rune Halvorsen (runeh@opera.com). +* Projects now has an associated owner, so old projects must be exported and + imported to a new database. - -0.1.0 :date:`2009-03-22 1:21 A.M CET` :author:askh@opera.com --------------------------------------------------------------- +0.1.0 (2009-03-22) +------------------ - * Initial release +* Initial release diff --git a/src/djangopypi/docs/README b/src/djangopypi/docs/README index ca3b5e1..2cbf1c7 100644 --- a/src/djangopypi/docs/README +++ b/src/djangopypi/docs/README @@ -1,62 +1,74 @@ -========================================= -ChiShop/DjangoPyPI -========================================= -:Version: 0.1 +DjangoPyPI +========== + +DjangoPyPI is a Django application that provides a re-implementation of the +`Python Package Index `_. Installation -============ +------------ + +Path +____ + +The first step is to get ``djangopypi`` into your Python path. + +Buildout +++++++++ + +Simply add ``djangopypi`` to your list of ``eggs`` and run buildout again it +should downloaded and installed properly. -Install dependencies:: +EasyInstall/Setuptools +++++++++++++++++++++++ - $ python bootstrap.py --distribute - $ ./bin/buildout +If you have setuptools installed, you can use ``easy_install djangopypi`` -Initial configuration ---------------------- -:: +Manual +++++++ - $ $EDITOR chishop/settings.py - $ ./bin/django syncdb +Download and unpack the source then run:: -Run the PyPI server -------------------- -:: + $ python setup.py install - $ ./bin/django runserver +Django Settings +_______________ -Please note that ``chishop/media/dists`` has to be writable by the -user the web-server is running as. +Add ``djangopypi`` to your ``INSTALLED_APPS`` setting and run ``syncdb`` again +to get the database tables [#]_. -In production -------------- +Then add an include in your url config for ``djangopypi.urls``:: -You may want to copy the file ``chishop/production_example.py`` and modify -for use as your production settings; you will also need to modify -``bin/django.wsgi`` to refer to your production settings. + urlpatterns = patterns("", + ... + url(r'', include("djangopypi.urls")) + ) -Using Setuptools -================ +This will make the repository interface be accessible at ``/pypi/``. -Add the following to your ``~/.pypirc`` file:: + + +Uploading to your PyPI +---------------------- + +Assuming you are running your Django site locally for now, add the following to +your ``~/.pypirc`` file:: [distutils] index-servers = pypi local - [pypi] username:user password:secret [local] - username:user password:secret - repository:http://localhost:8000 + repository:http://localhost:8000/pypi Uploading a package: Python >=2.6 --------------------------------------------- +_________________________________ To push the package to the local pypi:: @@ -64,16 +76,20 @@ To push the package to the local pypi:: Uploading a package: Python <2.6 -------------------------------------------- +________________________________ -If you don't have Python 2.6 please run the command below to install the backport of the extension:: +If you don't have Python 2.6 please run the command below to install the +backport of the extension for multiple repositories:: $ easy_install -U collective.dist -instead of using register and dist command, you can use "mregister" and "mupload", that are a backport of python 2.6 register and upload commands, that supports multiple servers. +Instead of using register and dist command, you can use ``mregister`` and +``mupload`` which are a backport of python 2.6 register and upload commands +that supports multiple servers. To push the package to the local pypi:: $ python setup.py mregister -r local sdist mupload -r local -.. # vim: syntax=rst expandtab tabstop=4 shiftwidth=4 shiftround +.. [#] ``djangopypi`` is South enabled, if you are using South then you will need + to run the South ``migrate`` command to get the tables. \ No newline at end of file diff --git a/src/djangopypi/docs/TODO b/src/djangopypi/docs/TODO index 1d42848..ce3b078 100644 --- a/src/djangopypi/docs/TODO +++ b/src/djangopypi/docs/TODO @@ -1,13 +1,17 @@ Roadmap ======= -1.0 +0.4 --- +* Distribution uploads (files for releases) +* 'list_classifiers' action handler + +0.5 +--- + +* Ratings/comments views/admin * PKG-INFO view of a release (auto generated from package_info field) -* Optional user registration via distutils (if django-registration is installed) -* Finish non-staff package admin interface -* Ratings/comments Future ------ diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 044b7bf..da13717 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -10,11 +10,14 @@ import os version = '0.3' +docs = os.path.join(os.path.dirname(__file__), "docs") setup(name='djangopypi', version=version, description="A Django application that emulates the Python Package Index.", - long_description=open(os.path.join(os.path.dirname(__file__), "docs", "README")).read(), + long_description=open(os.path.join(docs, "README")).read() + "\n\n" + + open(os.path.join(docs, 'Changelog')).read() + "\n\n" + + open(os.path.join(docs, 'TODO')).read(), classifiers=[ "Framework :: Django", "Development Status :: 3 - Alpha", @@ -28,8 +31,7 @@ "License :: OSI Approved :: BSD License", "Topic :: System :: Software Distribution", "Programming Language :: Python", - "Topic :: Software Development :: Libraries :: Python Modules", - ], + "Topic :: Software Development :: Libraries :: Python Modules",], keywords='django pypi packaging index', author='Ask Solem', author_email='askh@opera.com', From accdd44a3e19ff657ab4700b07f240580bed9ac3 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 14:41:46 -0500 Subject: [PATCH 042/146] Added to changelog and TODO --- src/djangopypi/docs/Changelog | 1 + src/djangopypi/docs/TODO | 1 + 2 files changed, 2 insertions(+) diff --git a/src/djangopypi/docs/Changelog b/src/djangopypi/docs/Changelog index 4fcc870..c9e7f56 100644 --- a/src/djangopypi/docs/Changelog +++ b/src/djangopypi/docs/Changelog @@ -4,6 +4,7 @@ History 0.3 (2010-06-09) ---------------- +* Added DOAP views of packages and releases * Splitting djangopypi off of chishop * Switched most views to using django generic views diff --git a/src/djangopypi/docs/TODO b/src/djangopypi/docs/TODO index ce3b078..801e0cc 100644 --- a/src/djangopypi/docs/TODO +++ b/src/djangopypi/docs/TODO @@ -5,6 +5,7 @@ Roadmap --- * Distribution uploads (files for releases) +* RSS support for release index, packages, releases * 'list_classifiers' action handler 0.5 From 15ee2a1bda9946176e21e992b67132cee2bf273f Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 16:26:56 -0500 Subject: [PATCH 043/146] Switched back to relative paths due to a problem installing --- src/djangopypi/setup.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index da13717..19e6f15 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -9,15 +9,14 @@ import os -version = '0.3' -docs = os.path.join(os.path.dirname(__file__), "docs") +version = '0.3.1' setup(name='djangopypi', version=version, description="A Django application that emulates the Python Package Index.", - long_description=open(os.path.join(docs, "README")).read() + "\n\n" + - open(os.path.join(docs, 'Changelog')).read() + "\n\n" + - open(os.path.join(docs, 'TODO')).read(), + long_description=open(os.path.join('docs', "README")).read() + "\n\n" + + open(os.path.join('docs', 'Changelog')).read() + "\n\n" + + open(os.path.join('docs', 'TODO')).read(), classifiers=[ "Framework :: Django", "Development Status :: 3 - Alpha", From 6e5683f37832a7b4236da9cc7666c1397a1bd9cc Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Wed, 9 Jun 2010 16:28:08 -0500 Subject: [PATCH 044/146] Added install bug fix to changelog --- src/djangopypi/docs/Changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/djangopypi/docs/Changelog b/src/djangopypi/docs/Changelog index c9e7f56..5429731 100644 --- a/src/djangopypi/docs/Changelog +++ b/src/djangopypi/docs/Changelog @@ -1,6 +1,11 @@ History ======= +0.3.1 (2010-06-09) +------------------ + +* Installation bugfix + 0.3 (2010-06-09) ---------------- From 1f09e3d7555b324d491a27c2a241abc3205eb52c Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 10 Jun 2010 13:04:28 -0500 Subject: [PATCH 045/146] Added simple index and package detail pages, changed doap addresses to doap.rdf and expanded doap listings --- .../templates/djangopypi/package_doap.xml | 108 +++++++++--------- .../templates/djangopypi/release_doap.xml | 51 +-------- src/djangopypi/djangopypi/urls.py | 8 +- src/djangopypi/djangopypi/views/packages.py | 8 ++ 4 files changed, 72 insertions(+), 103 deletions(-) diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml b/src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml index 0c8db5c..8f37419 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml @@ -1,55 +1,59 @@ - - - {{ package.name }} - {% if package.latest %} - {% with package.latest as release %} - {{ release.summary }} - {% if release.description %} - - {{ release.description }} - - {% endif %} - {% if release.package_info.home_page %} - - {% endif %} - {% if release.package_info.download_url %} - - {% endif %} - {% if release.package_info.author %} - - - {{ release.package_info.author }} - {% if release.package_info.author_email %} - {{ release.package_info.author_email }} - {% endif %} - - - {% endif %} - {% if release.package_info.maintainer %} - - - {{ release.package_info.maintainer }} - {% if release.package_info.maintainer_email %} - {{ release.package_info.maintainer_email }} - {% endif %} - - - {% endif %} - {% if release.package_info.license %} - {{ release.package_info.license }} - {% endif %} - {% endwith %} - {% endif %} - {% for release in package.releases.all %} - - - {{ release.version }} - - - {% endfor %} - - \ No newline at end of file + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + rdf:about="{{ package.get_absolute_url }}"> + {{ package.name }} + {% for release in package.releases.all %}{% if forloop.last %} + {{ release.created|date:"Y-m-d" }} + {% endif %}{% endfor %} + + {% if package.latest %} + {% with package.latest as release %} + {{ release.summary }} + {% if release.description %} + + {{ release.description }} + + {% endif %} + {% if release.package_info.home_page %} + + {% endif %} + {% if release.package_info.download_url %} + + {% endif %} + {% if release.package_info.author or release.package_info.author_email %} + + + {% if release.package_info.author %}{{ release.package_info.author }}{% endif %} + {% if release.package_info.author_email %}{% endif %} + + + {% endif %} + {% if release.package_info.maintainer or release.package_info.maintainer_email %} + + + {% if release.package_info.maintainer %}{{ release.package_info.maintainer }}{% endif %} + {% if release.package_info.maintainer_email %}{% endif %} + + + {% endif %} + {% if release.package_info.license %} + {{ release.package_info.license }} + {% endif %} + {% if release.classifiers %} + {% for classifier in release.classifiers %} + {{ classifier }} + {% endfor %} + {% endif %} + {% endwith %} + {% endif %} + {% if release %} + {% include "djangopypi/release_doap_fragment.xml" %} + {% else %} + {% for release in package.releases.all %} + {% include "djangopypi/release_doap_fragment.xml" %} + {% endfor %} + {% endif %} + diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml b/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml index af19ead..a30fa6e 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml @@ -1,49 +1,2 @@ - - - - {{ release.package.name }} - {{ release.summary }} - {% if release.description %} - - {{ release.description }} - - {% endif %} - {% if release.package_info.home_page %} - - {% endif %} - {% if release.package_info.download_url %} - - {% endif %} - {% if release.package_info.author %} - - - {{ release.package_info.author }} - {% if release.package_info.author_email %} - {{ release.package_info.author_email }} - {% endif %} - - - {% endif %} - {% if release.package_info.maintainer %} - - - {{ release.package_info.maintainer }} - {% if release.package_info.maintainer_email %} - {{ release.package_info.maintainer_email }} - {% endif %} - - - {% endif %} - {% if release.package_info.license %} - {{ release.package_info.license }} - {% endif %} - - - {{ release.version }} - - - - \ No newline at end of file +{% with release.package as package %}{% include "djangopypi/package_doap.xml" %}{% endwith %} + diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index e4a66e2..b1449f4 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -4,12 +4,16 @@ urlpatterns = patterns("djangopypi.views", url(r'^$', "root", name="djangopypi-root"), url(r'^packages/$','packages.index', name='djangopypi-package-index'), + url(r'^simple/$','packages.simple_index', name='djangopypi-package-index-simple'), url(r'^search/$','packages.search',name='djangopypi-search'), url(r'^pypi/$', 'root', name='djangopypi-release-index'), + url(r'^simple/(?P[\w\d_\.\-]+)/$','packages.simple_details', + name='djangopypi-package-simple'), + url(r'^pypi/(?P[\w\d_\.\-]+)/$','packages.details', name='djangopypi-package'), - url(r'^pypi/(?P[\w\d_\.\-]+)/doap.xml$','packages.doap', + url(r'^pypi/(?P[\w\d_\.\-]+)/doap.rdf$','packages.doap', name='djangopypi-package-doap'), url(r'^pypi/(?P[\w\d_\.\-]+)/manage/$','packages.manage', name='djangopypi-package-manage'), @@ -18,7 +22,7 @@ url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', 'releases.details',name='djangopypi-release'), - url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/doap.xml$', + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/doap.rdf$', 'releases.doap',name='djangopypi-release-doap'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/manage/$', 'releases.manage',name='djangopypi-release-manage'), diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py index 4ed4ec0..ed19bdb 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/packages.py @@ -16,11 +16,19 @@ def index(request, **kwargs): kwargs.setdefault('queryset',Package.objects.all()) return list_detail.object_list(request, **kwargs) +def simple_index(request, **kwargs): + kwargs.setdefault('template_name','djangopypi/package_list_simple.html') + return index(request, **kwargs) + def details(request, package, **kwargs): kwargs.setdefault('template_object_name','package') kwargs.setdefault('queryset',Package.objects.all()) return list_detail.object_detail(request, object_id=package, **kwargs) +def simple_details(request, package, **kwargs): + kwargs.setdefault('template_name', 'djangopypi/package_detail_simple.html') + return details(request, package, **kwargs) + def doap(request, package, **kwargs): kwargs.setdefault('template_name','djangopypi/package_doap.xml') kwargs.setdefault('mimetype', 'text/xml') From 182e48f14ccc5cd275983adbb7aae2face9c6232 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 10 Jun 2010 13:04:45 -0500 Subject: [PATCH 046/146] Added simple index and package detail pages, changed doap addresses to doap.rdf and expanded doap listings --- .../djangopypi/package_detail_simple.html | 14 ++++++++++++++ .../templates/djangopypi/package_list_simple.html | 10 ++++++++++ .../templates/djangopypi/release_doap_fragment.xml | 10 ++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/package_detail_simple.html create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/package_list_simple.html create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/release_doap_fragment.xml diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_detail_simple.html b/src/djangopypi/djangopypi/templates/djangopypi/package_detail_simple.html new file mode 100644 index 0000000..b8eaa8c --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_detail_simple.html @@ -0,0 +1,14 @@ + + +Links for {{ package.name }} + + +

    Links for {{ package.name }}

    +{% for release in package.releases.all %} +{% for dist in release.distributions.all %} +{{ dist.filename }}
    {% endfor %} +{% if release.package_info.home_page %}{{ release.version }} home-page
    {% endif %} +{% if release.package_info.download_url %}{{ release.version }} download-url
    {% endif %} +{% endfor %} + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_list_simple.html b/src/djangopypi/djangopypi/templates/djangopypi/package_list_simple.html new file mode 100644 index 0000000..9883f0d --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/package_list_simple.html @@ -0,0 +1,10 @@ + + +Simple Package Index + + +{% for package in package_list %} +{{ package.name }}
    +{% endfor %} + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_doap_fragment.xml b/src/djangopypi/djangopypi/templates/djangopypi/release_doap_fragment.xml new file mode 100644 index 0000000..33d4119 --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_doap_fragment.xml @@ -0,0 +1,10 @@ + + + {{ release.package.name }} + {{ release.created|date:"Y-m-d" }} + {{ release.version }} + {% for dist in release.distributions.all %} + {{ dist.filename }} + {% endfor %} + + \ No newline at end of file From ac532fbc7dc578585e852b53b41ed1cfdc76f207 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 10 Jun 2010 17:05:02 -0500 Subject: [PATCH 047/146] Added the 'list_classifiers' action. Added GET request handling to root view --- src/djangopypi/djangopypi/models.py | 1 + src/djangopypi/djangopypi/settings.py | 9 +++++---- src/djangopypi/djangopypi/views/__init__.py | 11 +++++++---- src/djangopypi/djangopypi/views/distutils.py | 5 +++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index 25987ae..e8d2c08 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -49,6 +49,7 @@ class Classifier(models.Model): class Meta: verbose_name = _(u"classifier") verbose_name_plural = _(u"classifiers") + ordering = ('name',) def __unicode__(self): return self.name diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index 0a94c1b..5fe595e 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -90,9 +90,10 @@ settings.DJANGOPYPI_FALLBACK_VIEW = releases.index if not hasattr(settings,'DJANGOPYPI_ACTION_VIEWS'): - from djangopypi.views.distutils import register_or_upload + from djangopypi.views import distutils settings.DJANGOPYPI_ACTION_VIEWS = { - "file_upload": register_or_upload, #``sdist`` command - "submit": register_or_upload, #``register`` command - } \ No newline at end of file + "file_upload": distutils.register_or_upload, #``sdist`` command + "submit": distutils.register_or_upload, #``register`` command + "list_classifiers": distutils.list_classifiers, #``list_classifiers`` command + } diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index 042007e..c9b4d36 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -11,14 +11,17 @@ def root(request, fallback_view=None, **kwargs): """ Root view of the package index, handle incoming actions from distutils or redirect to a more user friendly view """ - if request.method != 'POST': + if request.method == 'POST': + parse_distutils_request(request) + action = request.POST.get(':action','') + else: + action = request.GET.get(':action','') + + if not action: if fallback_view is None: fallback_view = settings.DJANGOPYPI_FALLBACK_VIEW return fallback_view(request, **kwargs) - parse_distutils_request(request) - action = request.POST.get(':action','') - if not action in settings.DJANGOPYPI_ACTION_VIEWS: print 'unknown action: %s' % (action,) return HttpResponseNotAllowed(settings.DJANGOPYPI_ACTION_VIEW.keys()) diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index b50d176..7e2c4e7 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -160,3 +160,8 @@ def register_or_upload(request): transaction.commit() return HttpResponse('upload accepted') + +def list_classifiers(request, mimetype='text/plain'): + response = HttpResponse(mimetype=mimetype) + response.write(u'\n'.join(map(lambda c: c.name,Classifier.objects.all()))) + return response From d2f0a863f8dc02899e9aeadf776146b7b8378f2e Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 10 Jun 2010 18:33:07 -0500 Subject: [PATCH 048/146] Some initial work on RSS feeds --- src/djangopypi/djangopypi/feeds.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/djangopypi/djangopypi/feeds.py diff --git a/src/djangopypi/djangopypi/feeds.py b/src/djangopypi/djangopypi/feeds.py new file mode 100644 index 0000000..1d58fa7 --- /dev/null +++ b/src/djangopypi/djangopypi/feeds.py @@ -0,0 +1,26 @@ +from djangopypi.models import Package, Release + +try: + from django.contrib.syndication.views import Feed, FeedDoesNotExist +except ImportError: + from django.contrib.syndication.feeds import Feed as BaseFeed, FeedDoesNotExist + from django.http import HttpResponse, Http404 + from django.core.exceptions import ObjectDoesNotExist + + class Feed(BaseFeed): + def __call__(self, request, *args, **kwargs): + try: + obj = self.get_object(request, *args, **kwargs) + except ObjectDoesNotExist: + raise Http404('Feed object does not exist.') + feedgen = self.get_feed(obj, request) + response = HttpResponse(mimetype=feedgen.mime_type) + feedgen.write(response, 'utf-8') + return response + +class ReleaseFeed(Feed): + """ A feed of releases either for the site in general or for a specific + package. """ + pass + + From 2fcc98535eaef8f931403550d4444cef9677fb06 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Fri, 11 Jun 2010 12:02:02 -0500 Subject: [PATCH 049/146] Closes issue #3, fix decorator imports --- src/djangopypi/djangopypi/decorators.py | 28 +++++++++++-------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/djangopypi/djangopypi/decorators.py b/src/djangopypi/djangopypi/decorators.py index f65e646..9a2fff9 100644 --- a/src/djangopypi/djangopypi/decorators.py +++ b/src/djangopypi/djangopypi/decorators.py @@ -1,11 +1,18 @@ +from django.conf import settings +from django.contrib.auth import login, REDIRECT_FIELD_NAME +from django.http import HttpResponseRedirect +from django.utils.http import urlquote + try: - from functools import update_wrapper, wraps + from functools import update_wrapper, wraps, WRAPPER_ASSIGNMENTS except ImportError: - from django.utils.functional import update_wrapper, wraps + from django.utils.functional import update_wrapper, wraps, WRAPPER_ASSIGNMENTS -from django.conf import settings -from django.contrib.auth import login, REDIRECT_FIELD_NAME -from django.utils.decorators import available_attrs +try: + from django.utils.decorators import available_attrs +except ImportError: + def available_attrs(fn): + return tuple(a for a in WRAPPER_ASSIGNMENTS if hasattr(fn, a)) from djangopypi.http import HttpResponseUnauthorized, login_basic_auth @@ -30,17 +37,6 @@ def _wrapped_view(request, *args, **kwargs): return view_func(request, *args, **kwargs) return wraps(view_func, assigned=available_attrs(view_func))(_wrapped_view) -try: - from functools import update_wrapper, wraps -except ImportError: - from django.utils.functional import update_wrapper, wraps # Python 2.4 fallback. - -from django.contrib.auth import REDIRECT_FIELD_NAME -from django.http import HttpResponseRedirect -from django.utils.decorators import available_attrs -from django.utils.http import urlquote - - def user_owns_package(login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): """ Decorator for views that checks whether the user owns the currently requested From b8429e89f02ce5be364654875155e418eb78aeea Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Fri, 11 Jun 2010 13:27:24 -0500 Subject: [PATCH 050/146] RSS feeds working (tested in Django 1.2 only) --- src/djangopypi/djangopypi/feeds.py | 41 ++++++++++++++++++++++++++---- src/djangopypi/djangopypi/urls.py | 4 +++ src/djangopypi/docs/Changelog | 7 +++++ src/djangopypi/docs/TODO | 2 -- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/djangopypi/djangopypi/feeds.py b/src/djangopypi/djangopypi/feeds.py index 1d58fa7..9f9da4c 100644 --- a/src/djangopypi/djangopypi/feeds.py +++ b/src/djangopypi/djangopypi/feeds.py @@ -1,5 +1,4 @@ -from djangopypi.models import Package, Release - +from django.shortcuts import get_object_or_404 try: from django.contrib.syndication.views import Feed, FeedDoesNotExist except ImportError: @@ -18,9 +17,41 @@ def __call__(self, request, *args, **kwargs): feedgen.write(response, 'utf-8') return response +from djangopypi.models import Package, Release + + + class ReleaseFeed(Feed): """ A feed of releases either for the site in general or for a specific package. """ - pass - - + + def get_object(self, request, package=None, **kwargs): + if package: + return get_object_or_404(Package, name=package) + return request.build_absolute_uri('/') + + def link(self, obj): + if isinstance(obj, Package): + return obj.get_absolute_url() + return obj + + def title(self, obj): + if isinstance(obj, Package): + return u'Releases for %s' % (obj.name,) + return u'Package index releases' + + def description(self, obj): + if isinstance(obj, Package): + return u'Recent releases for the package: %s' % (obj.name,) + return u'Recent releases on the package index server' + + def items(self, obj): + if isinstance(obj, Package): + return obj.releases.filter(hidden=False).order_by('-created')[:25] + return Release.objects.filter(hidden=False).order_by('-created')[:40] + + def item_description(self, item): + if isinstance(item, Release): + if item.summary: + return item.summary + return super(ReleaseFeed, self).item_description(item) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index b1449f4..df6c8cb 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from django.conf.urls.defaults import patterns, url, include +from djangopypi.feeds import ReleaseFeed urlpatterns = patterns("djangopypi.views", url(r'^$', "root", name="djangopypi-root"), @@ -7,12 +8,15 @@ url(r'^simple/$','packages.simple_index', name='djangopypi-package-index-simple'), url(r'^search/$','packages.search',name='djangopypi-search'), url(r'^pypi/$', 'root', name='djangopypi-release-index'), + url(r'^rss/$', ReleaseFeed(), name='djangopypi-rss'), url(r'^simple/(?P[\w\d_\.\-]+)/$','packages.simple_details', name='djangopypi-package-simple'), url(r'^pypi/(?P[\w\d_\.\-]+)/$','packages.details', name='djangopypi-package'), + url(r'^pypi/(?P[\w\d_\.\-]+)/rss/$', ReleaseFeed(), + name='djangopypi-package-rss'), url(r'^pypi/(?P[\w\d_\.\-]+)/doap.rdf$','packages.doap', name='djangopypi-package-doap'), url(r'^pypi/(?P[\w\d_\.\-]+)/manage/$','packages.manage', diff --git a/src/djangopypi/docs/Changelog b/src/djangopypi/docs/Changelog index 5429731..a640581 100644 --- a/src/djangopypi/docs/Changelog +++ b/src/djangopypi/docs/Changelog @@ -1,6 +1,13 @@ History ======= +0.4 (Unreleased) +---------------- + +* 'list_classifiers' action handler +* Issue #3: decorators imports incompatible with Django 1.0, 1.1 +* RSS support for release index, packages + 0.3.1 (2010-06-09) ------------------ diff --git a/src/djangopypi/docs/TODO b/src/djangopypi/docs/TODO index 801e0cc..611d134 100644 --- a/src/djangopypi/docs/TODO +++ b/src/djangopypi/docs/TODO @@ -5,8 +5,6 @@ Roadmap --- * Distribution uploads (files for releases) -* RSS support for release index, packages, releases -* 'list_classifiers' action handler 0.5 --- From e829a7698e0679a817930cff23bd76be590b098a Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 14 Jun 2010 12:57:09 -0500 Subject: [PATCH 051/146] Fixed a bug for non-authenticated users --- src/djangopypi/djangopypi/decorators.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/djangopypi/djangopypi/decorators.py b/src/djangopypi/djangopypi/decorators.py index 9a2fff9..1a03313 100644 --- a/src/djangopypi/djangopypi/decorators.py +++ b/src/djangopypi/djangopypi/decorators.py @@ -68,8 +68,9 @@ def user_maintains_package(login_url=None, redirect_field_name=REDIRECT_FIELD_NA def decorator(view_func): def _wrapped_view(request, package, *args, **kwargs): - if request.user.packages_owned.filter(name=package).count() > 0 or \ - request.user.packages_maintained.filter(name=package).count() > 0: + if (request.user.is_authenticated() and + (request.user.packages_owned.filter(name=package).count() > 0 or + request.user.packages_maintained.filter(name=package).count() > 0)): return view_func(request, package=package, *args, **kwargs) path = urlquote(request.get_full_path()) From 0e4a4fc48fa1936bb7e37dd0c7bbf6acf00aa3e9 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 14 Jun 2010 12:58:10 -0500 Subject: [PATCH 052/146] Made the content be editable since Django 1.2 locks out editable=False fields from any form editing, not just admin --- src/djangopypi/djangopypi/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index e8d2c08..e654e17 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -133,8 +133,7 @@ def get_absolute_url(self): class Distribution(models.Model): release = models.ForeignKey(Release, related_name="distributions", editable=False) - content = models.FileField(upload_to=settings.DJANGOPYPI_RELEASE_UPLOAD_TO, - editable=False) + content = models.FileField(upload_to=settings.DJANGOPYPI_RELEASE_UPLOAD_TO) md5_digest = models.CharField(max_length=32, blank=True, editable=False) filetype = models.CharField(max_length=32, blank=False, choices=settings.DJANGOPYPI_DIST_FILE_TYPES) From c81e05f7ccbb0ceef421bad56c156de1d2815cdc Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 14 Jun 2010 12:59:06 -0500 Subject: [PATCH 053/146] Moved the md5 hash generation to a signal handler for pre_save on Distributions --- src/djangopypi/djangopypi/signals.py | 16 +++++++++++++++- src/djangopypi/djangopypi/views/distutils.py | 6 ------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/djangopypi/djangopypi/signals.py b/src/djangopypi/djangopypi/signals.py index 3856399..02ecfa3 100644 --- a/src/djangopypi/djangopypi/signals.py +++ b/src/djangopypi/djangopypi/signals.py @@ -1,6 +1,7 @@ from django.db.models import signals +from django.utils.hashcompat import md5_constructor -from djangopypi.models import Package, Release +from djangopypi.models import Package, Release, Distribution def autohide_new_release_handler(sender, instance, created, *args, **kwargs): @@ -40,6 +41,19 @@ def autohide_save_package_handler(sender, instance, *args, **kwargs): for release in instance.releases.filter(hidden=False): release.save() +def distribution_hash(sender, instance, *args, **kwargs): + if not instance.md5_digest and instance.content: + digest = md5_constructor() + try: + fh = instance.content.storage.open(instance.content.name) + map(digest.update,fh.readlines()) + fh.close() + instance.md5_digest = digest.hexdigest() + instance.save() + except Exception, e: + print str(e) + signals.post_save.connect(autohide_new_release_handler, sender=Release) signals.pre_save.connect(autohide_save_release_handler, sender=Release) signals.pre_save.connect(autohide_save_package_handler, sender=Package) +signals.post_save.connect(distribution_hash, sender=Distribution) diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index 7e2c4e7..8187d7a 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -4,7 +4,6 @@ from django.conf import settings from django.db import transaction from django.http import * -from django.utils.hashcompat import md5_constructor from django.utils.translation import ugettext_lazy as _ from django.utils.datastructures import MultiValueDict from django.contrib.auth import login @@ -139,11 +138,6 @@ def register_or_upload(request): md5_digest = request.POST.get('md5_digest','') - if not md5_digest: - digest = md5_constructor() - map(digest.update,uploaded) - md5_digest = digest.hexdigest() - try: new_file = Distribution.objects.create(release=release, content=uploaded, From 501f7d9bbe1516149815cfc78e86d56cf183270f Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 14 Jun 2010 13:05:22 -0500 Subject: [PATCH 054/146] Added an upload form to the manage filed view and added an upload file view that handles the file upload as well as a form that checks for duplicate filenames --- .../djangopypi/release_manage_files.html | 10 +++ .../djangopypi/release_upload_file.html | 18 +++++ src/djangopypi/djangopypi/forms.py | 30 +++++++- .../djangopypi/release_manage_files.html | 12 ++- .../djangopypi/release_upload_file.html | 12 +++ src/djangopypi/djangopypi/urls.py | 2 + src/djangopypi/djangopypi/views/releases.py | 73 +++++++++++++++---- 7 files changed, 139 insertions(+), 18 deletions(-) create mode 100644 chishop/templates/djangopypi/release_upload_file.html create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/release_upload_file.html diff --git a/chishop/templates/djangopypi/release_manage_files.html b/chishop/templates/djangopypi/release_manage_files.html index 8165102..80a32d3 100644 --- a/chishop/templates/djangopypi/release_manage_files.html +++ b/chishop/templates/djangopypi/release_manage_files.html @@ -7,6 +7,7 @@ {% block content %}

    Manage {{ release }} Files

    +{% if formset.forms %}
    {{ formset.management_form }} @@ -40,5 +41,14 @@

    Manage {{ release }} Files

    +{% endif %} +{% if upload_form %} +

    Upload a Distribution

    +
    + {{ upload_form.as_p }} +
    +
    +{% endif %} {% endblock %} diff --git a/chishop/templates/djangopypi/release_upload_file.html b/chishop/templates/djangopypi/release_upload_file.html new file mode 100644 index 0000000..9085500 --- /dev/null +++ b/chishop/templates/djangopypi/release_upload_file.html @@ -0,0 +1,18 @@ +{% extends "base_site.html" %} + +{% block bread_crumbs_1 %} +» {{ release.package }}{{ release }} +» Upload a File{% endblock %} + +{% block content %} +

    Upload a File for {{ release }}

    +{% if form %} +

    Upload a File

    +
    + {{ form.as_p }} +
    +
    +{% endif %} +{% endblock %} + diff --git a/src/djangopypi/djangopypi/forms.py b/src/djangopypi/djangopypi/forms.py index 536256d..bd345c4 100644 --- a/src/djangopypi/djangopypi/forms.py +++ b/src/djangopypi/djangopypi/forms.py @@ -1,8 +1,11 @@ +from os.path import basename + from django import forms +from django.conf import settings from django.utils.translation import ugettext_lazy as _ from djangopypi.settings import settings -from djangopypi.models import Package, Classifier, Release +from djangopypi.models import Package, Classifier, Release, Distribution @@ -14,6 +17,31 @@ class Meta: model = Package exclude = ['name'] +class DistributionUploadForm(forms.ModelForm): + class Meta: + model = Distribution + fields = ('content','comment','filetype','pyversion',) + + def clean_content(self): + content = self.cleaned_data['content'] + storage = self.instance.content.storage + field = self.instance.content.field + + name = field.generate_filename(instance=self.instance, + filename=content.name) + + if not storage.exists(name): + print '%s does not exist' % (name,) + return content + + if settings.DJANGOPYPI_ALLOW_VERSION_OVERWRITE: + raise forms.ValidationError('Version overwrite is not yet handled') + + raise forms.ValidationError('That distribution already exists, please ' + 'delete it first before uploading a new ' + 'version.') + + class ReleaseForm(forms.ModelForm): metadata_version = forms.CharField(widget=forms.Select(choices=zip(settings.DJANGOPYPI_METADATA_FIELDS.keys(), settings.DJANGOPYPI_METADATA_FIELDS.keys()))) diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html b/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html index 2fd036c..4f7ce44 100644 --- a/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html @@ -4,6 +4,7 @@

    Manage {{ release }} Files

    + {% if formset.forms %}
    {{ formset.management_form }} @@ -24,7 +25,7 @@

    Manage {{ release }} Files

    {% with form.instance as dist %} - + @@ -37,5 +38,14 @@

    Manage {{ release }} Files

    {{ form.DELETE }}{{ dist.display_filetype }}{{ dist.get_filetype_display }} {{ dist.pyversion }} {{ form.comment }} {{ dist.filename }}
    + {% endif %} + {% if upload_form %} +

    Upload a Distribution

    +
    + {{ upload_form.as_p }} +
    +
    + {% endif %} \ No newline at end of file diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_upload_file.html b/src/djangopypi/djangopypi/templates/djangopypi/release_upload_file.html new file mode 100644 index 0000000..724a80c --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/release_upload_file.html @@ -0,0 +1,12 @@ + + + Manage {{ release }} Files + + +

    Upload a File to {{ release }}

    +
    + {{ form.as_p }} +
    +
    + + \ No newline at end of file diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index df6c8cb..90abe81 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -34,4 +34,6 @@ 'releases.manage_metadata',name='djangopypi-release-manage-metadata'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/files/$', 'releases.manage_files',name='djangopypi-release-manage-files'), + url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/files/upload/$', + 'releases.upload_file',name='djangopypi-release-upload-file'), ) \ No newline at end of file diff --git a/src/djangopypi/djangopypi/views/releases.py b/src/djangopypi/djangopypi/views/releases.py index 1fc6228..3a398c9 100644 --- a/src/djangopypi/djangopypi/views/releases.py +++ b/src/djangopypi/djangopypi/views/releases.py @@ -8,7 +8,7 @@ from djangopypi.decorators import user_owns_package, user_maintains_package from djangopypi.models import Package, Release, Distribution -from djangopypi.forms import ReleaseForm +from djangopypi.forms import ReleaseForm, DistributionUploadForm @@ -21,7 +21,8 @@ def details(request, package, version, **kwargs): release = get_object_or_404(Package, name=package).get_release(version) if not release: - return Http404() + raise Http404('Version %s does not exist for %s' % (version, + package,)) kwargs.setdefault('template_object_name','release') kwargs.setdefault('template_name','djangopypi/release_detail.html') @@ -44,7 +45,8 @@ def manage(request, package, version, **kwargs): release = get_object_or_404(Package, name=package).get_release(version) if not release: - return Http404() + raise Http404('Version %s does not exist for %s' % (version, + package,)) kwargs['object_id'] = release.pk @@ -64,11 +66,12 @@ def manage_metadata(request, package, version, **kwargs): release = get_object_or_404(Package, name=package).get_release(version) if not release: - return Http404() + raise Http404('Version %s does not exist for %s' % (version, + package,)) if not release.metadata_version in settings.DJANGOPYPI_METADATA_FORMS: #TODO: Need to change this to a more meaningful error - return Http404() + raise Http404() kwargs['extra_context'][kwargs['template_object_name']] = release @@ -94,7 +97,6 @@ def manage_metadata(request, package, version, **kwargs): release.package_info.setlist(key, list(value)) release.save() - print str(release.package_info) return create_update.redirect(kwargs.get('post_save_redirect',None), release) else: @@ -108,11 +110,11 @@ def manage_metadata(request, package, version, **kwargs): @user_maintains_package() def manage_files(request, package, version, **kwargs): - package = get_object_or_404(Package, name=package) - try: - release = package.releases.get(version=version) - except Release.DoesNotExist: - return Http404() + release = get_object_or_404(Package, name=package).get_release(version) + + if not release: + raise Http404('Version %s does not exist for %s' % (version, + package,)) kwargs.setdefault('formset_factory_kwargs',{}) kwargs['formset_factory_kwargs'].setdefault('fields', ('comment',)) @@ -126,6 +128,7 @@ def manage_files(request, package, version, **kwargs): kwargs['extra_context'][kwargs['template_object_name']] = release kwargs.setdefault('formset_kwargs',{}) kwargs['formset_kwargs']['instance'] = release + kwargs.setdefault('upload_form_factory', DistributionUploadForm) if request.method == 'POST': formset = kwargs['formset_factory'](data=request.POST, @@ -133,13 +136,51 @@ def manage_files(request, package, version, **kwargs): **kwargs['formset_kwargs']) if formset.is_valid(): formset.save() - return create_update.redirect(kwargs.get('post_save_redirect',None), - release) - - formset = kwargs['formset_factory'](**kwargs['formset_kwargs']) + formset = kwargs['formset_factory'](**kwargs['formset_kwargs']) + else: + formset = kwargs['formset_factory'](**kwargs['formset_kwargs']) kwargs['extra_context']['formset'] = formset + kwargs['extra_context'].setdefault('upload_form', + kwargs['upload_form_factory']()) return render_to_response(kwargs['template_name'], kwargs['extra_context'], context_instance=RequestContext(request), - mimetype=kwargs['mimetype']) \ No newline at end of file + mimetype=kwargs['mimetype']) + +@user_maintains_package() +def upload_file(request, package, version, **kwargs): + release = get_object_or_404(Package, name=package).get_release(version) + + if not release: + raise Http404('Version %s does not exist for %s' % (version, + package,)) + + kwargs.setdefault('form_factory', DistributionUploadForm) + kwargs.setdefault('post_save_redirect', reverse('djangopypi-release-manage-files', + kwargs={'package': package, + 'version': version})) + kwargs.setdefault('template_name', 'djangopypi/release_upload_file.html') + kwargs.setdefault('template_object_name', 'release') + kwargs.setdefault('extra_context',{}) + kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) + kwargs['extra_context'][kwargs['template_object_name']] = release + + if request.method == 'POST': + form = kwargs['form_factory'](data=request.POST, files=request.FILES) + if form.is_valid(): + dist = form.save(commit=False) + dist.release = release + dist.uploader = request.user + dist.save() + + return create_update.redirect(kwargs.get('post_save_redirect'), + release) + else: + form = kwargs['form_factory']() + + kwargs['extra_context']['form'] = form + + return render_to_response(kwargs['template_name'], kwargs['extra_context'], + context_instance=RequestContext(request), + mimetype=kwargs['mimetype']) From d57b70c5734e7c0a55a504f4f745b68d20c10ba5 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 14 Jun 2010 13:07:58 -0500 Subject: [PATCH 055/146] Updated docs for progress on 0.4 and increased version in setup.py to version 0.4 --- src/djangopypi/docs/Changelog | 3 ++- src/djangopypi/docs/TODO | 5 ----- src/djangopypi/setup.py | 5 ++--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/djangopypi/docs/Changelog b/src/djangopypi/docs/Changelog index a640581..0e70710 100644 --- a/src/djangopypi/docs/Changelog +++ b/src/djangopypi/docs/Changelog @@ -1,12 +1,13 @@ History ======= -0.4 (Unreleased) +0.4 (2010-06-14) ---------------- * 'list_classifiers' action handler * Issue #3: decorators imports incompatible with Django 1.0, 1.1 * RSS support for release index, packages +* Distribution uploads (files for releases) 0.3.1 (2010-06-09) ------------------ diff --git a/src/djangopypi/docs/TODO b/src/djangopypi/docs/TODO index 611d134..2f93a8a 100644 --- a/src/djangopypi/docs/TODO +++ b/src/djangopypi/docs/TODO @@ -1,11 +1,6 @@ Roadmap ======= -0.4 ---- - -* Distribution uploads (files for releases) - 0.5 --- diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 19e6f15..275bd2f 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -9,7 +9,7 @@ import os -version = '0.3.1' +version = '0.4' setup(name='djangopypi', version=version, @@ -19,8 +19,7 @@ open(os.path.join('docs', 'TODO')).read(), classifiers=[ "Framework :: Django", - "Development Status :: 3 - Alpha", - #"Development Status :: 4 - Beta", + "Development Status :: 4 - Beta", #"Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Environment :: Web Environment", From 5d024cd28599bff6f80cf185f2098e5aaede7cc7 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 16 Jun 2010 20:27:16 +0800 Subject: [PATCH 056/146] Updated setup file a little and moved doc files to root dir of djangopypi app. --- src/djangopypi/{docs => }/AUTHORS | 3 +- src/djangopypi/{docs => }/Changelog | 0 src/djangopypi/{docs => }/LICENSE | 0 src/djangopypi/{docs/README => README.rst} | 0 src/djangopypi/{docs => }/TODO | 0 src/djangopypi/setup.py | 58 ++++++++++------------ 6 files changed, 29 insertions(+), 32 deletions(-) rename src/djangopypi/{docs => }/AUTHORS (92%) rename src/djangopypi/{docs => }/Changelog (100%) rename src/djangopypi/{docs => }/LICENSE (100%) rename src/djangopypi/{docs/README => README.rst} (100%) rename src/djangopypi/{docs => }/TODO (100%) diff --git a/src/djangopypi/docs/AUTHORS b/src/djangopypi/AUTHORS similarity index 92% rename from src/djangopypi/docs/AUTHORS rename to src/djangopypi/AUTHORS index 8ed898d..306e0b2 100644 --- a/src/djangopypi/docs/AUTHORS +++ b/src/djangopypi/AUTHORS @@ -11,4 +11,5 @@ Vanderson Mota dos Santos Stefan Foulis Michael Richardson Benjamin Liles -Halldor \ No newline at end of file +Halldor +Jannis Leidel diff --git a/src/djangopypi/docs/Changelog b/src/djangopypi/Changelog similarity index 100% rename from src/djangopypi/docs/Changelog rename to src/djangopypi/Changelog diff --git a/src/djangopypi/docs/LICENSE b/src/djangopypi/LICENSE similarity index 100% rename from src/djangopypi/docs/LICENSE rename to src/djangopypi/LICENSE diff --git a/src/djangopypi/docs/README b/src/djangopypi/README.rst similarity index 100% rename from src/djangopypi/docs/README rename to src/djangopypi/README.rst diff --git a/src/djangopypi/docs/TODO b/src/djangopypi/TODO similarity index 100% rename from src/djangopypi/docs/TODO rename to src/djangopypi/TODO diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 275bd2f..68375eb 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -1,23 +1,17 @@ import os +from setuptools import setup, find_packages -try: - from setuptools import setup, find_packages -except ImportError: - from ez_setup import use_setuptools - use_setuptools() - from setuptools import setup, find_packages - -import os +def fread(fname): + return open(os.path.join(os.path.dirname(__file__), '', fname)).read() version = '0.4' -setup(name='djangopypi', - version=version, - description="A Django application that emulates the Python Package Index.", - long_description=open(os.path.join('docs', "README")).read() + "\n\n" + - open(os.path.join('docs', 'Changelog')).read() + "\n\n" + - open(os.path.join('docs', 'TODO')).read(), - classifiers=[ +setup( + name='djangopypi', + version=version, + description="A Django application that emulates the Python Package Index.", + long_description=fread("README")+"\n\n"+fread('Changelog')+"\n\n"+fread('TODO'), + classifiers=[ "Framework :: Django", "Development Status :: 4 - Beta", #"Development Status :: 5 - Production/Stable", @@ -29,19 +23,21 @@ "License :: OSI Approved :: BSD License", "Topic :: System :: Software Distribution", "Programming Language :: Python", - "Topic :: Software Development :: Libraries :: Python Modules",], - keywords='django pypi packaging index', - author='Ask Solem', - author_email='askh@opera.com', - maintainer='Benjamin Liles', - maintainer_email='benliles@gmail.com', - url='http://github.com/benliles/chishop', - license=open(os.path.join("docs", "LICENSE")).read(), - packages=find_packages(), - include_package_data=True, - zip_safe=False, - install_requires=[ - 'setuptools', - 'django>=1.0', - 'docutils',], - ) + "Topic :: Software Development :: Libraries :: Python Modules", + ], + keywords='django pypi packaging index', + author='Ask Solem', + author_email='askh@opera.com', + maintainer='Benjamin Liles', + maintainer_email='benliles@gmail.com', + url='http://github.com/benliles/chishop', + license='BSD', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + install_requires=[ + 'setuptools', + 'django>=1.0', + 'docutils', + ], +) From b427476a871ab1302d9fc68efdd8334ca92cd0c0 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 16 Jun 2010 21:07:33 +0800 Subject: [PATCH 057/146] Updated buildout config a little. --- buildout.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildout.cfg b/buildout.cfg index b85efc0..2786304 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -11,7 +11,7 @@ develop = [django] recipe = djangorecipe -version = 1.2 +version = 1.2.1 settings = settings eggs = ${buildout:eggs} test = djangopypi From 795deb2a9e517de6355d841b6358e40b3cb90588 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 16 Jun 2010 21:07:55 +0800 Subject: [PATCH 058/146] Added django-registration to default settings of chishop. --- chishop/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/chishop/settings.py b/chishop/settings.py index 49b4e9d..f673662 100644 --- a/chishop/settings.py +++ b/chishop/settings.py @@ -100,6 +100,7 @@ 'django.contrib.admin', 'django.contrib.markup', 'django.contrib.admindocs', + 'registration', 'djangopypi', 'south', ) From 402b622687904d69e8a108d2158a7ed4214dee84 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 16 Jun 2010 21:08:17 +0800 Subject: [PATCH 059/146] Added optional requirements file for pip lovers. --- chishop/requirements.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 chishop/requirements.txt diff --git a/chishop/requirements.txt b/chishop/requirements.txt new file mode 100644 index 0000000..44dd460 --- /dev/null +++ b/chishop/requirements.txt @@ -0,0 +1,6 @@ +--find-links=http://bitbucket.org/ubernostrum/django-registration/downloads/ + +djangopypi>=0.4 +South==0.7.1 +Django==1.2.1 +django-registration==0.8-alpha-1 From 310abb113ffef16b98bafadfe31232e9070ff656 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Wed, 16 Jun 2010 21:22:25 +0800 Subject: [PATCH 060/146] Minor bugfix. --- src/djangopypi/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 68375eb..d3fafdf 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages def fread(fname): - return open(os.path.join(os.path.dirname(__file__), '', fname)).read() + return open(os.path.join(os.path.dirname(__file__), fname)).read() version = '0.4' @@ -10,7 +10,7 @@ def fread(fname): name='djangopypi', version=version, description="A Django application that emulates the Python Package Index.", - long_description=fread("README")+"\n\n"+fread('Changelog')+"\n\n"+fread('TODO'), + long_description=fread("README.rst")+"\n\n"+fread('Changelog')+"\n\n"+fread('TODO'), classifiers=[ "Framework :: Django", "Development Status :: 4 - Beta", From 6cf8c65b2824938425aa6689fef4adf8a71e3120 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 10:13:28 -0500 Subject: [PATCH 061/146] Renamed README file, if it's not in docs folder, then github and pypi.python.org expect it to be at root as README --- src/djangopypi/{README.rst => README} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/djangopypi/{README.rst => README} (100%) diff --git a/src/djangopypi/README.rst b/src/djangopypi/README similarity index 100% rename from src/djangopypi/README.rst rename to src/djangopypi/README From a4fc9df9882fdc4361d8e17f175e882371436366 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 10:13:52 -0500 Subject: [PATCH 062/146] Fixed file reading in setup.py for README name change --- src/djangopypi/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index d3fafdf..8d38ada 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -10,7 +10,7 @@ def fread(fname): name='djangopypi', version=version, description="A Django application that emulates the Python Package Index.", - long_description=fread("README.rst")+"\n\n"+fread('Changelog')+"\n\n"+fread('TODO'), + long_description=fread("README")+"\n\n"+fread('Changelog')+"\n\n"+fread('TODO'), classifiers=[ "Framework :: Django", "Development Status :: 4 - Beta", From 0adbedf03f0eb1ccb8ee15aa69d1cdfc60f92aaa Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 10:26:54 -0500 Subject: [PATCH 063/146] Added django-haystack and Whoosh to buildout eggs --- buildout.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/buildout.cfg b/buildout.cfg index 2786304..e00aa9f 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -6,6 +6,8 @@ eggs = djangopypi South django-registration==0.8-alpha-1 + django-haystack + Whoosh develop = src/djangopypi From 790199194024d6c29902e446dcf3db90510b2fc6 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 10:47:26 -0500 Subject: [PATCH 064/146] Incorporated whitespace cleanup from jezdez, ignoring the addition of absolute_url on classifiers as it required haystack --- src/djangopypi/djangopypi/models.py | 51 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index e654e17..b1d4c96 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -11,11 +11,11 @@ class PackageInfoField(models.Field): description = u'Python Package Information Field' __metaclass__ = models.SubfieldBase - + def __init__(self, *args, **kwargs): kwargs['editable'] = False super(PackageInfoField,self).__init__(*args, **kwargs) - + def to_python(self, value): if isinstance(value, basestring): if value: @@ -27,7 +27,7 @@ def to_python(self, value): if isinstance(value,MultiValueDict): return value raise ValueError('Unexpected value encountered when converting data to python') - + def get_prep_value(self, value): if isinstance(value,MultiValueDict): return json.dumps(dict(value.iterlists())) @@ -35,14 +35,12 @@ def get_prep_value(self, value): return json.dumps(value) if isinstance(value, basestring) or value is None: return value - + raise ValueError('Unexpected value encountered when preparing for database') - + def get_internal_type(self): return 'TextField' - - class Classifier(models.Model): name = models.CharField(max_length=255, primary_key=True) @@ -50,11 +48,10 @@ class Meta: verbose_name = _(u"classifier") verbose_name_plural = _(u"classifiers") ordering = ('name',) - + def __unicode__(self): return self.name - class Package(models.Model): name = models.CharField(max_length=255, unique=True, primary_key=True, editable=False) @@ -64,7 +61,7 @@ class Package(models.Model): related_name="packages_owned") maintainers = models.ManyToManyField(User, blank=True, related_name="packages_maintained") - + class Meta: verbose_name = _(u"package") verbose_name_plural = _(u"packages") @@ -75,14 +72,14 @@ def __unicode__(self): @models.permalink def get_absolute_url(self): return ('djangopypi-package', (), {'package': self.name}) - + @property def latest(self): try: return self.releases.latest() except Release.DoesNotExist: return None - + def get_release(self, version): """Return the release object for version, or None""" try: @@ -97,7 +94,7 @@ class Release(models.Model): package_info = PackageInfoField(blank=False) hidden = models.BooleanField(default=False) created = models.DateTimeField(auto_now_add=True, editable=False) - + class Meta: verbose_name = _(u"release") verbose_name_plural = _(u"releases") @@ -107,23 +104,23 @@ class Meta: def __unicode__(self): return self.release_name - + @property def release_name(self): return u"%s-%s" % (self.package.name, self.version) - + @property def summary(self): - return self.package_info.get('summary',u'') - + return self.package_info.get('summary', u'') + @property def description(self): - return self.package_info.get('description',u'') - + return self.package_info.get('description', u'') + @property def classifiers(self): return self.package_info.getlist('classifier') - + @models.permalink def get_absolute_url(self): return ('djangopypi-release', (), {'package': self.package.name, @@ -143,30 +140,30 @@ class Distribution(models.Model): signature = models.TextField(blank=True) created = models.DateTimeField(auto_now_add=True, editable=False) uploader = models.ForeignKey(User, editable=False) - + @property def filename(self): return os.path.basename(self.content.name) - + @property def display_filetype(self): for key,value in settings.DJANGOPYPI_DIST_FILE_TYPES: if key == self.filetype: return value return self.filetype - + @property def path(self): return self.content.name - + def get_absolute_url(self): return "%s#md5=%s" % (self.content.url, self.md5_digest) - + class Meta: verbose_name = _(u"distribution") verbose_name_plural = _(u"distributions") unique_together = ("release", "filetype", "pyversion") - + def __unicode__(self): return self.filename @@ -174,7 +171,7 @@ class Review(models.Model): release = models.ForeignKey(Release, related_name="reviews") rating = models.PositiveSmallIntegerField(blank=True) comment = models.TextField(blank=True) - + class Meta: verbose_name = _(u'release review') verbose_name_plural = _(u'release reviews') From c8c6833842ae0046a8ac9540a61379c115b3f06a Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 10:49:59 -0500 Subject: [PATCH 065/146] Incorporated whitespace cleanup from jezdez --- src/djangopypi/djangopypi/views/packages.py | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py index ed19bdb..77a3ea3 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/packages.py @@ -12,17 +12,17 @@ def index(request, **kwargs): - kwargs.setdefault('template_object_name','package') - kwargs.setdefault('queryset',Package.objects.all()) + kwargs.setdefault('template_object_name', 'package') + kwargs.setdefault('queryset', Package.objects.all()) return list_detail.object_list(request, **kwargs) def simple_index(request, **kwargs): - kwargs.setdefault('template_name','djangopypi/package_list_simple.html') + kwargs.setdefault('template_name', 'djangopypi/package_list_simple.html') return index(request, **kwargs) def details(request, package, **kwargs): - kwargs.setdefault('template_object_name','package') - kwargs.setdefault('queryset',Package.objects.all()) + kwargs.setdefault('template_object_name', 'package') + kwargs.setdefault('queryset', Package.objects.all()) return list_detail.object_detail(request, object_id=package, **kwargs) def simple_details(request, package, **kwargs): @@ -30,7 +30,7 @@ def simple_details(request, package, **kwargs): return details(request, package, **kwargs) def doap(request, package, **kwargs): - kwargs.setdefault('template_name','djangopypi/package_doap.xml') + kwargs.setdefault('template_name', 'djangopypi/package_doap.xml') kwargs.setdefault('mimetype', 'text/xml') return details(request, package, **kwargs) @@ -52,16 +52,16 @@ def manage(request, package, **kwargs): kwargs.setdefault('form_class', PackageForm) kwargs.setdefault('template_name', 'djangopypi/package_manage.html') kwargs.setdefault('template_object_name', 'package') - + return create_update.update_object(request, **kwargs) @user_maintains_package() def manage_versions(request, package, **kwargs): package = get_object_or_404(Package, name=package) - kwargs.setdefault('formset_factory_kwargs',{}) + kwargs.setdefault('formset_factory_kwargs', {}) kwargs['formset_factory_kwargs'].setdefault('fields', ('hidden',)) kwargs['formset_factory_kwargs']['extra'] = 0 - + kwargs.setdefault('formset_factory', inlineformset_factory(Package, Release, **kwargs['formset_factory_kwargs'])) kwargs.setdefault('template_name', 'djangopypi/package_manage_versions.html') kwargs.setdefault('template_object_name', 'package') @@ -70,18 +70,18 @@ def manage_versions(request, package, **kwargs): kwargs['extra_context'][kwargs['template_object_name']] = package kwargs.setdefault('formset_kwargs',{}) kwargs['formset_kwargs']['instance'] = package - + if request.method == 'POST': formset = kwargs['formset_factory'](data=request.POST, **kwargs['formset_kwargs']) if formset.is_valid(): formset.save() - return create_update.redirect(kwargs.get('post_save_redirect',None), + return create_update.redirect(kwargs.get('post_save_redirect', None), package) - + formset = kwargs['formset_factory'](**kwargs['formset_kwargs']) - + kwargs['extra_context']['formset'] = formset - + return render_to_response(kwargs['template_name'], kwargs['extra_context'], context_instance=RequestContext(request), - mimetype=kwargs['mimetype']) \ No newline at end of file + mimetype=kwargs['mimetype']) From be19b8d6e28d1624681096f814f1ef88628494b3 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 11:29:27 -0500 Subject: [PATCH 066/146] Haystack Package search index with fields for authors (incl. owners, maintainers and package info), classifiers, summary, description and a catch all text field --- src/djangopypi/djangopypi/search_indexes.py | 36 +++++++++++++++++++ .../djangopypi/haystack/package_text.txt | 8 +++++ 2 files changed, 44 insertions(+) create mode 100644 src/djangopypi/djangopypi/search_indexes.py create mode 100644 src/djangopypi/djangopypi/templates/djangopypi/haystack/package_text.txt diff --git a/src/djangopypi/djangopypi/search_indexes.py b/src/djangopypi/djangopypi/search_indexes.py new file mode 100644 index 0000000..c523561 --- /dev/null +++ b/src/djangopypi/djangopypi/search_indexes.py @@ -0,0 +1,36 @@ +from django.conf import settings + +from djangopypi.models import Package + +if 'haystack' in settings.INSTALLED_APPS: + from haystack import site + from haystack.indexes import SearchIndex + from haystack.fields import CharField, MultiValueField + + class PackageSearchIndex(SearchIndex): + name = CharField(model_attr='name') + text = CharField(document=True, use_template=True, null=True, + template_name='djangopypi/haystack/package_text.txt') + author = MultiValueField(store=False, null=True) + classifier = MultiValueField(store=False, null=True, + model_attr='latest__classifiers') + summary = CharField(store=False, null=True, + model_attr='latest__summary') + description = CharField(store=False, null=True, + model_attr='latest__description') + + def prepare_author(self, obj): + output = [] + for user in obj.owners.all() + obj.maintainers.all(): + output.append(user.get_full_name()) + if user.email: + output.append(user.email) + if obj.latest: + info = obj.latest.package_info + for field in ('author','author_email', 'maintainer', + 'maintainer_email',): + if info.get(field): + output.append(info.get(field)) + return output + + site.register(Package, PackageSearchIndex) diff --git a/src/djangopypi/djangopypi/templates/djangopypi/haystack/package_text.txt b/src/djangopypi/djangopypi/templates/djangopypi/haystack/package_text.txt new file mode 100644 index 0000000..ad0bfac --- /dev/null +++ b/src/djangopypi/djangopypi/templates/djangopypi/haystack/package_text.txt @@ -0,0 +1,8 @@ +{{ object.name }} + +{% if object.latest %} +{{ object.latest.release_name }} +{% for key, list in object.latest.package_info.iterlists %} +{{ key }}: {% for item in list %}{{ item }}{% if not forloop.last %}; {% endif %}{% endfor %} +{% endfor %} +{% endif %} From 71a12a9ec4e327f67fa070484b74389315ef1354 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 11:48:03 -0500 Subject: [PATCH 067/146] Fixed a few typos/bugs with the Package search index --- src/djangopypi/djangopypi/search_indexes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/djangopypi/djangopypi/search_indexes.py b/src/djangopypi/djangopypi/search_indexes.py index c523561..4809922 100644 --- a/src/djangopypi/djangopypi/search_indexes.py +++ b/src/djangopypi/djangopypi/search_indexes.py @@ -9,19 +9,19 @@ class PackageSearchIndex(SearchIndex): name = CharField(model_attr='name') - text = CharField(document=True, use_template=True, null=True, + text = CharField(document=True, use_template=True, null=True, stored=False, template_name='djangopypi/haystack/package_text.txt') - author = MultiValueField(store=False, null=True) - classifier = MultiValueField(store=False, null=True, + author = MultiValueField(stored=False, null=True) + classifier = MultiValueField(stored=False, null=True, model_attr='latest__classifiers') - summary = CharField(store=False, null=True, + summary = CharField(stored=False, null=True, model_attr='latest__summary') - description = CharField(store=False, null=True, + description = CharField(stored=False, null=True, model_attr='latest__description') def prepare_author(self, obj): output = [] - for user in obj.owners.all() + obj.maintainers.all(): + for user in list(obj.owners.all()) + list(obj.maintainers.all()): output.append(user.get_full_name()) if user.email: output.append(user.email) From 23806ad20f60f932c2755c8c64322f97b1611c0d Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 11:48:44 -0500 Subject: [PATCH 068/146] Incorporated Haystack code from jezdez --- .gitignore | 1 + chishop/media/style/djangopypi.css | 4 ++ chishop/requirements.txt | 2 + chishop/search_sites.py | 3 + chishop/settings.py | 5 ++ chishop/templates/djangopypi/search.html | 7 ++- .../templates/djangopypi/search_results.html | 62 +++++++++++-------- chishop/urls.py | 6 +- 8 files changed, 61 insertions(+), 29 deletions(-) create mode 100644 chishop/search_sites.py diff --git a/.gitignore b/.gitignore index 76aca1f..82aed01 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ develop-eggs downloads .installed.cfg chishop/media/dists/* +chishop/haystack/* diff --git a/chishop/media/style/djangopypi.css b/chishop/media/style/djangopypi.css index e6fbfd9..95180db 100644 --- a/chishop/media/style/djangopypi.css +++ b/chishop/media/style/djangopypi.css @@ -1,4 +1,8 @@ .search { text-align:right; margin-right: 10px; +} + +.highlighted { + background-color: #FEEF00; } \ No newline at end of file diff --git a/chishop/requirements.txt b/chishop/requirements.txt index 44dd460..de3b721 100644 --- a/chishop/requirements.txt +++ b/chishop/requirements.txt @@ -4,3 +4,5 @@ djangopypi>=0.4 South==0.7.1 Django==1.2.1 django-registration==0.8-alpha-1 +django-haystack +Whoosh diff --git a/chishop/search_sites.py b/chishop/search_sites.py new file mode 100644 index 0000000..59580c7 --- /dev/null +++ b/chishop/search_sites.py @@ -0,0 +1,3 @@ +import haystack + +haystack.autodiscover() diff --git a/chishop/settings.py b/chishop/settings.py index f673662..7a62d67 100644 --- a/chishop/settings.py +++ b/chishop/settings.py @@ -103,6 +103,7 @@ 'registration', 'djangopypi', 'south', + 'haystack', ) @@ -126,3 +127,7 @@ DATABASE_PASSWORD = '' DATABASE_HOST = '' DATABASE_PORT = '' + +HAYSTACK_SEARCH_ENGINE = 'whoosh' +HAYSTACK_SITECONF = 'chishop.search_sites' +HAYSTACK_WHOOSH_PATH = os.path.join(os.path.dirname(chishop.__file__), 'haystack') diff --git a/chishop/templates/djangopypi/search.html b/chishop/templates/djangopypi/search.html index 9464126..6d188d6 100644 --- a/chishop/templates/djangopypi/search.html +++ b/chishop/templates/djangopypi/search.html @@ -1,4 +1,5 @@ -
    - - +{% load i18n %} + + +
    \ No newline at end of file diff --git a/chishop/templates/djangopypi/search_results.html b/chishop/templates/djangopypi/search_results.html index dccf61f..8d54603 100644 --- a/chishop/templates/djangopypi/search_results.html +++ b/chishop/templates/djangopypi/search_results.html @@ -1,31 +1,43 @@ {% extends "base_site.html" %} +{% load i18n highlight %} -{% block bread_crumbs_1 %}›Search{% endblock %} +{% block bread_crumbs_1 %}» Search{% endblock %} {% block content %} - {% ifnotequal search_term ''%} -

    Index of Packages Matching '{{ search_term }}'

    - {% else %} -

    You need to supply a search term.

    - {% endifnotequal %} - {% if dists %} - - - - - - - - {% for dist in dists %} - - - - - {% endfor %} - -
    UpdatedPackageSummary
    {{ dist.updated|date:"d/m/y" }} - {{ dist.name }}{{ dist.summary|truncatewords:10 }}
    - {% else %} - There were no matches. +

    Search

    + +
    + + {{ form.as_table }} + + + + +
      + +
    + + {% if query %} +

    {% blocktrans %}Index of Packages Matching '{{ query }}'{% endblocktrans %}

    +
      + {% for result in page.object_list %} +
    • + {{ result.object }} + {% if result.object.latest %} +

      {% highlight result.object.latest.summary with query %}

      + {% endif %} +
    • + {% empty %} +
    • {% trans "There were no matches." %}
    • + {% endfor %} +
    + {% if page.has_previous or page.has_next %} +
    + {% if page.has_previous %}{% endif %}« Previous{% if page.has_previous %}{% endif %} + | + {% if page.has_next %}{% endif %}Next »{% if page.has_next %}{% endif %} +
    + {% endif %} {% endif %} +
    {% endblock content %} \ No newline at end of file diff --git a/chishop/urls.py b/chishop/urls.py index 7046fcb..8f86544 100644 --- a/chishop/urls.py +++ b/chishop/urls.py @@ -21,6 +21,10 @@ # Registration url(r'^accounts/', include('registration.backends.default.urls')), + url(r'^search/', 'haystack.views.basic_search', { + 'template': 'djangopypi/search_results.html', + }, name='haystack_search'), + # The Chishop - url(r'', include("djangopypi.urls")) + url(r'', include("djangopypi.urls")), ) From de69a553e7b1e0684d2bd35113000a975bf85571 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Thu, 17 Jun 2010 11:53:46 -0500 Subject: [PATCH 069/146] Version 0.4.1 --- src/djangopypi/Changelog | 5 +++++ src/djangopypi/setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/djangopypi/Changelog b/src/djangopypi/Changelog index 0e70710..423785d 100644 --- a/src/djangopypi/Changelog +++ b/src/djangopypi/Changelog @@ -1,6 +1,11 @@ History ======= +0.4.1 (2010-06-17) +------------------ + +* Added conditional support for django-haystack searching + 0.4 (2010-06-14) ---------------- diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 8d38ada..d9f9df6 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4' +version = '0.4.1' setup( name='djangopypi', From 8be5d087d7035a45c74e95f26813d9e719f4fd85 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Wed, 28 Jul 2010 10:27:29 -0500 Subject: [PATCH 070/146] Except the distutils view from CSRF handling. --- src/djangopypi/djangopypi/decorators.py | 9 ++++++++- src/djangopypi/djangopypi/views/__init__.py | 6 +++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/djangopypi/djangopypi/decorators.py b/src/djangopypi/djangopypi/decorators.py index 1a03313..cbef6e8 100644 --- a/src/djangopypi/djangopypi/decorators.py +++ b/src/djangopypi/djangopypi/decorators.py @@ -16,7 +16,14 @@ def available_attrs(fn): from djangopypi.http import HttpResponseUnauthorized, login_basic_auth - +# Find us a csrf exempt decorator that'll work with Django 1.0+ +try: + from django.views.decorators.csrf import csrf_exempt +except ImportError: + try: + from django.contrib.csrf.middleware import csrf_exempt + except ImportError: + def csrf_except(view_func): return view_func def basic_auth(view_func): """ Decorator for views that need to handle basic authentication such as diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index c9b4d36..683540a 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -1,12 +1,12 @@ from django.conf import settings from django.http import HttpResponseNotAllowed -from djangopypi.models import Package, Release +from djangopypi.decorators import csrf_exempt from djangopypi.http import HttpResponseNotImplemented from djangopypi.http import parse_distutils_request +from djangopypi.models import Package, Release - - +@csrf_exempt def root(request, fallback_view=None, **kwargs): """ Root view of the package index, handle incoming actions from distutils or redirect to a more user friendly view """ From d6076b948afa0f6bc3e8b768e42ac0d3137200b4 Mon Sep 17 00:00:00 2001 From: Ask Solem Date: Thu, 22 Jul 2010 09:11:41 +0200 Subject: [PATCH 071/146] Found full-name for contributor Halldor --- src/djangopypi/AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/AUTHORS b/src/djangopypi/AUTHORS index 306e0b2..8fd6bd2 100644 --- a/src/djangopypi/AUTHORS +++ b/src/djangopypi/AUTHORS @@ -11,5 +11,5 @@ Vanderson Mota dos Santos Stefan Foulis Michael Richardson Benjamin Liles -Halldor +Halldór Rúnarsson Jannis Leidel From 1de0bd69424ad3249d96e83a2b16beb0c2b039b5 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Tue, 26 Oct 2010 11:55:33 -0400 Subject: [PATCH 072/146] Added the basic handlers for xml rpc requests. Working: * list_packages * package_releases * release_urls * release_data Not Working: * search * changelog * ratings --- src/djangopypi/djangopypi/xmlrpc.py | 156 ++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/djangopypi/djangopypi/xmlrpc.py diff --git a/src/djangopypi/djangopypi/xmlrpc.py b/src/djangopypi/djangopypi/xmlrpc.py new file mode 100644 index 0000000..f910a44 --- /dev/null +++ b/src/djangopypi/djangopypi/xmlrpc.py @@ -0,0 +1,156 @@ +import xmlrpclib +from djangopypi.models import Package, Release +from django.http import HttpResponseNotAllowed, HttpResponse +from django.contrib.sites.models import Site + +SITE_NAME = Site.objects.get_current().domain + +def list_packages(): + pkg_list = list(Package.objects.all().values_list('name', flat=True)) + xml_out = xmlrpclib.dumps((pkg_list,), methodresponse=True) + return HttpResponse(xml_out, content_type='text/xml') + +def package_releases(package_name, show_hidden=False): + releases = [] + try: + pkg = Package.objects.get(name=package_name) + releases = list(pkg.releases.filter(hidden=show_hidden).values_list('version', flat=True)) + except Package.DoesNotExist: + pass + except Exception, e: + print e + + return HttpResponse(xmlrpclib.dumps((releases,), methodresponse=True)) + +def release_urls(package_name, version): + output = [] + try: + pkg = Package.objects.get(name=package_name) + release = pkg.releases.get(version=version) + + for dist in release.distributions.all(): + output.append({ + 'url': 'http://%s%s' % (SITE_NAME, dist.get_absolute_url()), + 'packagetype': dist.filetype, + 'filename': dist.filename, + 'size': dist.content.size, + 'md5_digest': dist.md5_digest, + 'downloads': 0, + 'has_sig': len(dist.signature)>0, + 'python_version': dist.pyversion, + 'comment_text': dist.comment + }) + except (Package.DoesNotExist, Release.DoesNotExist): + pass + except Exception, e: + print e + + return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) + +def release_data(package_name, version): + output = { + 'name': '', + 'version': '', + 'stable_version': '', + 'author': '', + 'author_email': '', + 'maintainer': '', + 'maintainer_email': '', + 'home_page': '', + 'license': '', + 'summary': '', + 'description': '', + 'keywords': '', + 'platform': '', + 'download_url': '', + 'classifiers': '', + 'requires': '', + 'requires_dist': '', + 'provides': '', + 'provides_dist': '', + 'requires_external': '', + 'requires_python': '', + 'obsoletes': '', + 'obsoletes_dist': '', + 'project_url': '', + } + try: + pkg = Package.objects.get(name=package_name) + release = pkg.releases.get(version=version) + metadata = release.package_info + output.update({'name': pkg.name, 'version': release.version,}) + output.update(metadata) + except (Package.DoesNotExist, Release.DoesNotExist): + pass + except Exception, e: + print e + + return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) + +# +""" +search(spec[, operator]) + +Search the package database using the indicated search spec. +The spec may include any of the keywords described in the above list (except 'stable_version' and 'classifiers'), for example: {'description': 'spam'} will search description fields. Within the spec, a field's value can be a string or a list of strings (the values within the list are combined with an OR), for example: {'name': ['foo', 'bar']}. Valid keys for the spec dict are listed here. Invalid keys are ignored: +name +version +author +author_email +maintainer +maintainer_email +home_page +license +summary +description +keywords +platform +download_url +Arguments for different fields are combined using either "and" (the default) or "or". Example: search({'name': 'foo', 'description': 'bar'}, 'or'). The results are returned as a list of dicts {'name': package name, 'version': package release version, 'summary': package release summary} + +changelog(since) + +Retrieve a list of four-tuples (name, version, timestamp, action) since the given timestamp. All timestamps are UTC values. The argument is a UTC integer seconds since the epoch. + +""" +def search(spec, operator='or'): + output = { + 'name': '', + 'version': '', + 'summary': '', + } + return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) +# +def changelog(since): + output = { + 'name': '', + 'version': '', + 'timestamp': '', + 'action': '', + } + return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) + +def ratings(name, version, since): + return HttpResponse(xmlrpclib.dumps(([],), methodresponse=True)) + + +PYPI_COMMANDS = { + 'list_packages': list_packages, + 'package_releases': package_releases, + 'release_urls': release_urls, + 'release_data': release_data, + 'search': search, + 'changelog': changelog, + 'ratings': ratings, +} + + +def parse_xmlrpc_request(request): + """ + Parse the request and dispatch to the appropriate view + """ + args, command = xmlrpclib.loads(request.raw_post_data) + if command in PYPI_COMMANDS: + return PYPI_COMMANDS[command](*args) + else: + return HttpResponseNotAllowed(settings.DJANGOPYPI_ACTION_VIEW.keys()) From 3ff5827015705fe7cc2ce4ef136317277f3f9f53 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Tue, 26 Oct 2010 11:55:52 -0400 Subject: [PATCH 073/146] Made mangage.py executable --- chishop/manage.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 chishop/manage.py diff --git a/chishop/manage.py b/chishop/manage.py old mode 100644 new mode 100755 From 2d9064ac7879b411a1d734ac2ff632acf91b854f Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Tue, 26 Oct 2010 11:56:51 -0400 Subject: [PATCH 074/146] Got some tests working for xml rpc. Had a difficult time getting mock files working so I could test distribution information. --- src/djangopypi/djangopypi/tests/__init__.py | 159 ++++++++++++++------ 1 file changed, 116 insertions(+), 43 deletions(-) diff --git a/src/djangopypi/djangopypi/tests/__init__.py b/src/djangopypi/djangopypi/tests/__init__.py index 9bbf686..81f507e 100644 --- a/src/djangopypi/djangopypi/tests/__init__.py +++ b/src/djangopypi/djangopypi/tests/__init__.py @@ -1,7 +1,9 @@ +import os import unittest +import xmlrpclib import StringIO -from djangopypi.views import parse_distutils_request, simple -from djangopypi.models import Package, Classifier +#from djangopypi.views import parse_distutils_request, simple +from djangopypi.models import Package, Classifier, Release, PackageInfoField, Distribution from django.test.client import Client from django.core.urlresolvers import reverse from django.contrib.auth.models import User @@ -72,43 +74,44 @@ def create_request(data): return body.getvalue() -class MockRequest(object): - - def __init__(self, raw_post_data): - self.raw_post_data = raw_post_data - self.META = {} - - -class TestParseWeirdPostData(unittest.TestCase): - - def test_weird_post_data(self): - data = create_post_data("submit") - raw_post_data = create_request(data) - post, files = parse_distutils_request(MockRequest(raw_post_data)) - self.assertTrue(post) - - for key in post.keys(): - if isinstance(data[key], list): - self.assertEquals(data[key], post.getlist(key)) - elif data[key] == "UNKNOWN": - self.assertTrue(post[key] is None) - else: - self.assertEquals(post[key], data[key]) - - +# class MockRequest(object): +# +# def __init__(self, raw_post_data): +# self.raw_post_data = raw_post_data +# self.META = {} +# +# +# class TestParseWeirdPostData(unittest.TestCase): +# +# def test_weird_post_data(self): +# data = create_post_data("submit") +# raw_post_data = create_request(data) +# post, files = parse_distutils_request(MockRequest(raw_post_data)) +# self.assertTrue(post) +# +# for key in post.keys(): +# if isinstance(data[key], list): +# self.assertEquals(data[key], post.getlist(key)) +# elif data[key] == "UNKNOWN": +# self.assertTrue(post[key] is None) +# else: +# self.assertEquals(post[key], data[key]) client = Client() class TestSearch(unittest.TestCase): def setUp(self): - dummy_user = User.objects.create(username='krill', password='12345', + self.dummy_user = User.objects.create(username='krill', password='12345', email='krill@opera.com') - Package.objects.create(name='foo', license='Gnu', - summary="The quick brown fox jumps over the lazy dog.", - owner=dummy_user) - - def test_search_for_package(self): + self.pkg = Package.objects.create(name='foo') + self.pkg.owners.add(self.dummy_user) + + def tearDown(self): + self.pkg.delete() + self.dummy_user.delete() + + def test_search_for_package(self): response = client.post(reverse('djangopypi-search'), {'search_term': 'foo'}) self.assertTrue("The quick brown fox jumps over the lazy dog." in response.content) @@ -124,16 +127,86 @@ def create_distutils_httprequest(self, user_data={}): request.raw_post_data = self.raw_post_data return request - def test_user_registration(self): - request = self.create_distutils_httprequest({'name': 'peter_parker', 'email':'parker@dailybugle.com', - 'password':'spiderman'}) - response = simple(request) - self.assertEquals(200, response.status_code) - - def test_user_registration_with_wrong_data(self): - request = self.create_distutils_httprequest({'name': 'peter_parker', 'email':'parker@dailybugle.com', - 'password':'',}) - response = simple(request) - self.assertEquals(400, response.status_code) + # def test_user_registration(self): + # request = self.create_distutils_httprequest({'name': 'peter_parker', 'email':'parker@dailybugle.com', + # 'password':'spiderman'}) + # response = simple(request) + # self.assertEquals(200, response.status_code) + # + # def test_user_registration_with_wrong_data(self): + # request = self.create_distutils_httprequest({'name': 'peter_parker', 'email':'parker@dailybugle.com', + # 'password':'',}) + # response = simple(request) + # self.assertEquals(400, response.status_code) + +from django.test.client import MULTIPART_CONTENT +class XmlRpcClient(Client): + def __init__(self, *args, **kwargs): + self.extra_headers = {} + super(XmlRpcClient, self).__init__(*args, **kwargs) + + def putheader(self, key, value): + self.extra_headers[key] = value + def endheaders(self, request_body): + pass + def getresponse(self, buffering=True): + return self.response + def post(self, path, data={}, content_type="text/xml", + follow=False, **extra): + """ + Requests a response from the server using POST. + """ + extra.update(self.extra_headers) + response = super(XmlRpcClient, self).post(path, data, content_type, follow, **extra) + return response.content + + +class ProxiedTransport(xmlrpclib.Transport): + def set_proxy(self, proxy): + self.proxy = proxy + def make_connection(self, host): + return XmlRpcClient() + def single_request(self, host, handler, request_body, verbose=0): + connection = self.make_connection(host) + response = connection.post('/pypi/', request_body) + data, methodname = xmlrpclib.loads(response) + return data + + def send_host(self, connection, host): + pass + + def send_user_agent(self, *args, **kwargs): + pass + +class TestXmlRpc(unittest.TestCase): + """ + Test that the server responds to xmlrpc requests + """ + def setUp(self): + from django.core.files.base import ContentFile + self.dummy_file = ContentFile("gibberish") + self.dummy_user = User.objects.create(username='bobby', password='tables', + email='bobby@tables.com') + self.pkg = Package.objects.create(name='foo') + self.pkg.owners.add(self.dummy_user) + self.release = Release.objects.create(package=self.pkg, version="1.0") + + def tearDown(self): + self.release.delete() + self.pkg.delete() + self.dummy_user.delete() + + def test_list_package(self): + pypi = xmlrpclib.ServerProxy("http://localhost/pypi/", ProxiedTransport()) + pypi_hits = pypi.list_packages() + expected = ['foo'] + self.assertEqual(pypi_hits, expected) + + def test_package_releases(self): + pypi = xmlrpclib.ServerProxy("http://localhost/pypi/", ProxiedTransport()) + pypi_hits = pypi.package_releases('foo') + expected = ['1.0'] + self.assertEqual(pypi_hits, expected) + \ No newline at end of file From 5aa611736e5737db879093f69dfcb2f1b91725a4 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Tue, 26 Oct 2010 11:57:30 -0400 Subject: [PATCH 075/146] Added a check for 'text/xml' content type and forward the request to the xmlrpc handler. --- src/djangopypi/djangopypi/views/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index 683540a..bc698fc 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -5,6 +5,7 @@ from djangopypi.http import HttpResponseNotImplemented from djangopypi.http import parse_distutils_request from djangopypi.models import Package, Release +from djangopypi.xmlrpc import parse_xmlrpc_request @csrf_exempt def root(request, fallback_view=None, **kwargs): @@ -12,6 +13,8 @@ def root(request, fallback_view=None, **kwargs): or redirect to a more user friendly view """ if request.method == 'POST': + if request.META['CONTENT_TYPE'] == 'text/xml': + return parse_xmlrpc_request(request) parse_distutils_request(request) action = request.POST.get(':action','') else: From 7b0526e270e7601489beb61233d7a8d8eedc39a7 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 27 Oct 2010 07:09:02 -0400 Subject: [PATCH 076/146] Added a MANIFEST.in file to include the misc files setup.py requires --- src/djangopypi/MANIFEST.in | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/djangopypi/MANIFEST.in diff --git a/src/djangopypi/MANIFEST.in b/src/djangopypi/MANIFEST.in new file mode 100644 index 0000000..fcd1a25 --- /dev/null +++ b/src/djangopypi/MANIFEST.in @@ -0,0 +1,5 @@ +include AUTHORS +include LICENSE +include README +include TODO +include Changelog \ No newline at end of file From 767e356527edc166abe066b4108fc27baaa78ce8 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 27 Oct 2010 07:09:29 -0400 Subject: [PATCH 077/146] Bumped the version so it didn't conflict with the current version --- src/djangopypi/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index d9f9df6..630ab6f 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.1' +version = '0.4.2beta1' setup( name='djangopypi', From 12add5a5c3b11f7226bfff7e048a1f3d01174c0e Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 1 Dec 2010 11:53:04 -0500 Subject: [PATCH 078/146] deleted a strange empty comment --- src/djangopypi/djangopypi/xmlrpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/djangopypi/xmlrpc.py b/src/djangopypi/djangopypi/xmlrpc.py index f910a44..6a40257 100644 --- a/src/djangopypi/djangopypi/xmlrpc.py +++ b/src/djangopypi/djangopypi/xmlrpc.py @@ -120,7 +120,7 @@ def search(spec, operator='or'): 'summary': '', } return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) -# + def changelog(since): output = { 'name': '', From 29ac3f8612159a98905c68fdb5099e1ebb824aba Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 1 Dec 2010 11:57:12 -0500 Subject: [PATCH 079/146] The authentication for the owners now allows for multiple owners. --- src/djangopypi/djangopypi/views/distutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index 8187d7a..618be15 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -21,7 +21,7 @@ def submit_package_or_release(user, post_data, files): """Registers/updates a package or release""" try: package = Package.objects.get(name=post_data['name']) - if package.owner != user: + if user not in package.owners.all(): return HttpResponseForbidden( "That package is owned by someone else!") except Package.DoesNotExist: From 7da55d4784d9761e2b67e0fbb3a8877284e0a66b Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 1 Dec 2010 11:57:47 -0500 Subject: [PATCH 080/146] Added some ordering to the package model and set the get latest by --- src/djangopypi/djangopypi/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index b1d4c96..affeda7 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -65,6 +65,8 @@ class Package(models.Model): class Meta: verbose_name = _(u"package") verbose_name_plural = _(u"packages") + get_latest_by = "releases__latest" + ordering = ['name',] def __unicode__(self): return self.name From 6cef0a6367c5d4c5f68bc1049ad76bec62f62ac2 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 1 Dec 2010 11:58:09 -0500 Subject: [PATCH 081/146] Version bump to 0.4.2beta2 --- src/djangopypi/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 630ab6f..6b6756f 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.2beta1' +version = '0.4.2beta2' setup( name='djangopypi', From b88e8f8a39aa6eaa90acb0a0f5cede93eb1bd059 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Tue, 8 Feb 2011 19:30:32 +0800 Subject: [PATCH 082/146] Fixed CSRF handling. --- src/djangopypi/djangopypi/views/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index c9b4d36..8730862 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -1,16 +1,15 @@ from django.conf import settings from django.http import HttpResponseNotAllowed +from django.views.decorators.csrf import csrf_exempt -from djangopypi.models import Package, Release -from djangopypi.http import HttpResponseNotImplemented from djangopypi.http import parse_distutils_request - +@csrf_exempt def root(request, fallback_view=None, **kwargs): """ Root view of the package index, handle incoming actions from distutils or redirect to a more user friendly view """ - + if request.method == 'POST': parse_distutils_request(request) action = request.POST.get(':action','') From dafee265b011a75ff8dd9270b80097e963caf435 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Tue, 8 Feb 2011 19:33:55 +0800 Subject: [PATCH 083/146] Pep8 cleaning. --- src/djangopypi/djangopypi/decorators.py | 28 ++++++++++++------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/djangopypi/djangopypi/decorators.py b/src/djangopypi/djangopypi/decorators.py index 1a03313..e597264 100644 --- a/src/djangopypi/djangopypi/decorators.py +++ b/src/djangopypi/djangopypi/decorators.py @@ -1,12 +1,11 @@ -from django.conf import settings from django.contrib.auth import login, REDIRECT_FIELD_NAME -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponseForbidden from django.utils.http import urlquote try: - from functools import update_wrapper, wraps, WRAPPER_ASSIGNMENTS + from functools import wraps, WRAPPER_ASSIGNMENTS except ImportError: - from django.utils.functional import update_wrapper, wraps, WRAPPER_ASSIGNMENTS + from django.utils.functional import wraps, WRAPPER_ASSIGNMENTS try: from django.utils.decorators import available_attrs @@ -17,19 +16,18 @@ def available_attrs(fn): from djangopypi.http import HttpResponseUnauthorized, login_basic_auth - def basic_auth(view_func): - """ Decorator for views that need to handle basic authentication such as + """ Decorator for views that need to handle basic authentication such as distutils views. """ - + def _wrapped_view(request, *args, **kwargs): if request.user.is_authenticated(): return view_func(request, *args, **kwargs) user = login_basic_auth(request) - + if not user: return HttpResponseUnauthorized('pypi') - + login(request, user) if not request.user.is_authenticated(): return HttpResponseForbidden("Not logged in, or invalid username/" @@ -50,7 +48,7 @@ def decorator(view_func): def _wrapped_view(request, package, *args, **kwargs): if request.user.packages_owned.filter(name=package).count() > 0: return view_func(request, package=package, *args, **kwargs) - + path = urlquote(request.get_full_path()) tup = login_url, redirect_field_name, path return HttpResponseRedirect('%s?%s=%s' % tup) @@ -59,20 +57,20 @@ def _wrapped_view(request, package, *args, **kwargs): def user_maintains_package(login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): """ - Decorator for views that checks whether the user maintains (or owns) the + Decorator for views that checks whether the user maintains (or owns) the currently requested package. """ if not login_url: from django.conf import settings login_url = settings.LOGIN_URL - + def decorator(view_func): def _wrapped_view(request, package, *args, **kwargs): - if (request.user.is_authenticated() and - (request.user.packages_owned.filter(name=package).count() > 0 or + if (request.user.is_authenticated() and + (request.user.packages_owned.filter(name=package).count() > 0 or request.user.packages_maintained.filter(name=package).count() > 0)): return view_func(request, package=package, *args, **kwargs) - + path = urlquote(request.get_full_path()) tup = login_url, redirect_field_name, path return HttpResponseRedirect('%s?%s=%s' % tup) From 4f2779191a0c6682059360e41df128de399be06c Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Tue, 8 Feb 2011 20:07:15 +0800 Subject: [PATCH 084/146] Added Django 1.1 compatibile CSRF exempt decorator. --- src/djangopypi/djangopypi/views/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index 8730862..ff22ba5 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -1,6 +1,10 @@ from django.conf import settings from django.http import HttpResponseNotAllowed -from django.views.decorators.csrf import csrf_exempt +try: + from django.views.decorators.csrf import csrf_exempt +except ImportError: + # Django < 1.2 + from django.contrib.csrf.middleware import csrf_exempt from djangopypi.http import parse_distutils_request From bcc2971558115180ca0b5d6403912acf93834ccd Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Tue, 8 Feb 2011 20:08:19 +0800 Subject: [PATCH 085/146] Bumped version. --- src/djangopypi/setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index d9f9df6..36a99bd 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.1' +version = '0.4.2' setup( name='djangopypi', @@ -37,7 +37,6 @@ def fread(fname): zip_safe=False, install_requires=[ 'setuptools', - 'django>=1.0', 'docutils', ], ) From ec49f64dc7843a39db8945bf03cae699ab359761 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Tue, 8 Feb 2011 21:13:59 +0800 Subject: [PATCH 086/146] Updated changelog for version 0.4.2. --- src/djangopypi/Changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/djangopypi/Changelog b/src/djangopypi/Changelog index 423785d..d93dcca 100644 --- a/src/djangopypi/Changelog +++ b/src/djangopypi/Changelog @@ -1,6 +1,11 @@ History ======= +0.4.2 (2011-02-08) +------------------ + +* Added CSRF support for Django>=1.2 + 0.4.1 (2010-06-17) ------------------ From a284e5d6bad4366b0e898a9d54f409c56357cbd2 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Tue, 8 Feb 2011 21:16:14 +0800 Subject: [PATCH 087/146] Added myself to the authors. --- src/djangopypi/AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/src/djangopypi/AUTHORS b/src/djangopypi/AUTHORS index 306e0b2..6e02bf3 100644 --- a/src/djangopypi/AUTHORS +++ b/src/djangopypi/AUTHORS @@ -13,3 +13,4 @@ Michael Richardson Benjamin Liles Halldor Jannis Leidel +Sebastien Fievet From 5fdb98a7cb3639022f2ec0cb0fd60b3c94f02242 Mon Sep 17 00:00:00 2001 From: zyegfryed Date: Tue, 8 Feb 2011 22:02:53 +0800 Subject: [PATCH 088/146] Added manifest and setup.cdg. --- src/djangopypi/MANIFEST.in | 6 ++++++ src/djangopypi/setup.cfg | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 src/djangopypi/MANIFEST.in create mode 100644 src/djangopypi/setup.cfg diff --git a/src/djangopypi/MANIFEST.in b/src/djangopypi/MANIFEST.in new file mode 100644 index 0000000..118b4d6 --- /dev/null +++ b/src/djangopypi/MANIFEST.in @@ -0,0 +1,6 @@ +include README +include AUTHORS +include CHANGELOG +include LICENSE +include TODO +include MANIFEST.in \ No newline at end of file diff --git a/src/djangopypi/setup.cfg b/src/djangopypi/setup.cfg new file mode 100644 index 0000000..5786d4d --- /dev/null +++ b/src/djangopypi/setup.cfg @@ -0,0 +1,2 @@ +[bdist_rpm] +doc_files = AUTHORS CHANGELOG LICENSE README TODO \ No newline at end of file From c7f2b87ec94f895907732016bfb638b30113518a Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 2 Dec 2010 00:57:12 +0800 Subject: [PATCH 089/146] The authentication for the owners now allows for multiple owners. --- src/djangopypi/djangopypi/views/distutils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/djangopypi/views/distutils.py b/src/djangopypi/djangopypi/views/distutils.py index 8187d7a..618be15 100644 --- a/src/djangopypi/djangopypi/views/distutils.py +++ b/src/djangopypi/djangopypi/views/distutils.py @@ -21,7 +21,7 @@ def submit_package_or_release(user, post_data, files): """Registers/updates a package or release""" try: package = Package.objects.get(name=post_data['name']) - if package.owner != user: + if user not in package.owners.all(): return HttpResponseForbidden( "That package is owned by someone else!") except Package.DoesNotExist: From dc5f60835c03e61d69e2561823feabe85828b6e2 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 2 Dec 2010 00:57:47 +0800 Subject: [PATCH 090/146] Added some ordering to the package model and set the get latest by --- src/djangopypi/djangopypi/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/djangopypi/djangopypi/models.py b/src/djangopypi/djangopypi/models.py index b1d4c96..affeda7 100644 --- a/src/djangopypi/djangopypi/models.py +++ b/src/djangopypi/djangopypi/models.py @@ -65,6 +65,8 @@ class Package(models.Model): class Meta: verbose_name = _(u"package") verbose_name_plural = _(u"packages") + get_latest_by = "releases__latest" + ordering = ['name',] def __unicode__(self): return self.name From ab7558d11fc540cb14b127a409689726e2777af3 Mon Sep 17 00:00:00 2001 From: Ask Solem Date: Thu, 22 Jul 2010 15:11:41 +0800 Subject: [PATCH 091/146] Found full-name for contributor Halldor --- src/djangopypi/AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/djangopypi/AUTHORS b/src/djangopypi/AUTHORS index 6e02bf3..24801b5 100644 --- a/src/djangopypi/AUTHORS +++ b/src/djangopypi/AUTHORS @@ -11,6 +11,6 @@ Vanderson Mota dos Santos Stefan Foulis Michael Richardson Benjamin Liles -Halldor +Halldór Rúnarsson Jannis Leidel Sebastien Fievet From 9b790330201084e2fd3e5469033b3afe4e254136 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Tue, 26 Oct 2010 23:55:33 +0800 Subject: [PATCH 092/146] Added the basic handlers for xml rpc requests. Working: * list_packages * package_releases * release_urls * release_data Not Working: * search * changelog * ratings --- src/djangopypi/djangopypi/xmlrpc.py | 156 ++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/djangopypi/djangopypi/xmlrpc.py diff --git a/src/djangopypi/djangopypi/xmlrpc.py b/src/djangopypi/djangopypi/xmlrpc.py new file mode 100644 index 0000000..f910a44 --- /dev/null +++ b/src/djangopypi/djangopypi/xmlrpc.py @@ -0,0 +1,156 @@ +import xmlrpclib +from djangopypi.models import Package, Release +from django.http import HttpResponseNotAllowed, HttpResponse +from django.contrib.sites.models import Site + +SITE_NAME = Site.objects.get_current().domain + +def list_packages(): + pkg_list = list(Package.objects.all().values_list('name', flat=True)) + xml_out = xmlrpclib.dumps((pkg_list,), methodresponse=True) + return HttpResponse(xml_out, content_type='text/xml') + +def package_releases(package_name, show_hidden=False): + releases = [] + try: + pkg = Package.objects.get(name=package_name) + releases = list(pkg.releases.filter(hidden=show_hidden).values_list('version', flat=True)) + except Package.DoesNotExist: + pass + except Exception, e: + print e + + return HttpResponse(xmlrpclib.dumps((releases,), methodresponse=True)) + +def release_urls(package_name, version): + output = [] + try: + pkg = Package.objects.get(name=package_name) + release = pkg.releases.get(version=version) + + for dist in release.distributions.all(): + output.append({ + 'url': 'http://%s%s' % (SITE_NAME, dist.get_absolute_url()), + 'packagetype': dist.filetype, + 'filename': dist.filename, + 'size': dist.content.size, + 'md5_digest': dist.md5_digest, + 'downloads': 0, + 'has_sig': len(dist.signature)>0, + 'python_version': dist.pyversion, + 'comment_text': dist.comment + }) + except (Package.DoesNotExist, Release.DoesNotExist): + pass + except Exception, e: + print e + + return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) + +def release_data(package_name, version): + output = { + 'name': '', + 'version': '', + 'stable_version': '', + 'author': '', + 'author_email': '', + 'maintainer': '', + 'maintainer_email': '', + 'home_page': '', + 'license': '', + 'summary': '', + 'description': '', + 'keywords': '', + 'platform': '', + 'download_url': '', + 'classifiers': '', + 'requires': '', + 'requires_dist': '', + 'provides': '', + 'provides_dist': '', + 'requires_external': '', + 'requires_python': '', + 'obsoletes': '', + 'obsoletes_dist': '', + 'project_url': '', + } + try: + pkg = Package.objects.get(name=package_name) + release = pkg.releases.get(version=version) + metadata = release.package_info + output.update({'name': pkg.name, 'version': release.version,}) + output.update(metadata) + except (Package.DoesNotExist, Release.DoesNotExist): + pass + except Exception, e: + print e + + return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) + +# +""" +search(spec[, operator]) + +Search the package database using the indicated search spec. +The spec may include any of the keywords described in the above list (except 'stable_version' and 'classifiers'), for example: {'description': 'spam'} will search description fields. Within the spec, a field's value can be a string or a list of strings (the values within the list are combined with an OR), for example: {'name': ['foo', 'bar']}. Valid keys for the spec dict are listed here. Invalid keys are ignored: +name +version +author +author_email +maintainer +maintainer_email +home_page +license +summary +description +keywords +platform +download_url +Arguments for different fields are combined using either "and" (the default) or "or". Example: search({'name': 'foo', 'description': 'bar'}, 'or'). The results are returned as a list of dicts {'name': package name, 'version': package release version, 'summary': package release summary} + +changelog(since) + +Retrieve a list of four-tuples (name, version, timestamp, action) since the given timestamp. All timestamps are UTC values. The argument is a UTC integer seconds since the epoch. + +""" +def search(spec, operator='or'): + output = { + 'name': '', + 'version': '', + 'summary': '', + } + return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) +# +def changelog(since): + output = { + 'name': '', + 'version': '', + 'timestamp': '', + 'action': '', + } + return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) + +def ratings(name, version, since): + return HttpResponse(xmlrpclib.dumps(([],), methodresponse=True)) + + +PYPI_COMMANDS = { + 'list_packages': list_packages, + 'package_releases': package_releases, + 'release_urls': release_urls, + 'release_data': release_data, + 'search': search, + 'changelog': changelog, + 'ratings': ratings, +} + + +def parse_xmlrpc_request(request): + """ + Parse the request and dispatch to the appropriate view + """ + args, command = xmlrpclib.loads(request.raw_post_data) + if command in PYPI_COMMANDS: + return PYPI_COMMANDS[command](*args) + else: + return HttpResponseNotAllowed(settings.DJANGOPYPI_ACTION_VIEW.keys()) From 2c37f4337bdd3814edeb5bc91fbdba2d810c29bb Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Tue, 26 Oct 2010 23:55:52 +0800 Subject: [PATCH 093/146] Made mangage.py executable --- chishop/manage.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 chishop/manage.py diff --git a/chishop/manage.py b/chishop/manage.py old mode 100644 new mode 100755 From 3a6a7b92265326e53bc4ef0ec3b880a7e204801a Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Tue, 26 Oct 2010 23:56:51 +0800 Subject: [PATCH 094/146] Got some tests working for xml rpc. Had a difficult time getting mock files working so I could test distribution information. --- src/djangopypi/djangopypi/tests/__init__.py | 159 ++++++++++++++------ 1 file changed, 116 insertions(+), 43 deletions(-) diff --git a/src/djangopypi/djangopypi/tests/__init__.py b/src/djangopypi/djangopypi/tests/__init__.py index 9bbf686..81f507e 100644 --- a/src/djangopypi/djangopypi/tests/__init__.py +++ b/src/djangopypi/djangopypi/tests/__init__.py @@ -1,7 +1,9 @@ +import os import unittest +import xmlrpclib import StringIO -from djangopypi.views import parse_distutils_request, simple -from djangopypi.models import Package, Classifier +#from djangopypi.views import parse_distutils_request, simple +from djangopypi.models import Package, Classifier, Release, PackageInfoField, Distribution from django.test.client import Client from django.core.urlresolvers import reverse from django.contrib.auth.models import User @@ -72,43 +74,44 @@ def create_request(data): return body.getvalue() -class MockRequest(object): - - def __init__(self, raw_post_data): - self.raw_post_data = raw_post_data - self.META = {} - - -class TestParseWeirdPostData(unittest.TestCase): - - def test_weird_post_data(self): - data = create_post_data("submit") - raw_post_data = create_request(data) - post, files = parse_distutils_request(MockRequest(raw_post_data)) - self.assertTrue(post) - - for key in post.keys(): - if isinstance(data[key], list): - self.assertEquals(data[key], post.getlist(key)) - elif data[key] == "UNKNOWN": - self.assertTrue(post[key] is None) - else: - self.assertEquals(post[key], data[key]) - - +# class MockRequest(object): +# +# def __init__(self, raw_post_data): +# self.raw_post_data = raw_post_data +# self.META = {} +# +# +# class TestParseWeirdPostData(unittest.TestCase): +# +# def test_weird_post_data(self): +# data = create_post_data("submit") +# raw_post_data = create_request(data) +# post, files = parse_distutils_request(MockRequest(raw_post_data)) +# self.assertTrue(post) +# +# for key in post.keys(): +# if isinstance(data[key], list): +# self.assertEquals(data[key], post.getlist(key)) +# elif data[key] == "UNKNOWN": +# self.assertTrue(post[key] is None) +# else: +# self.assertEquals(post[key], data[key]) client = Client() class TestSearch(unittest.TestCase): def setUp(self): - dummy_user = User.objects.create(username='krill', password='12345', + self.dummy_user = User.objects.create(username='krill', password='12345', email='krill@opera.com') - Package.objects.create(name='foo', license='Gnu', - summary="The quick brown fox jumps over the lazy dog.", - owner=dummy_user) - - def test_search_for_package(self): + self.pkg = Package.objects.create(name='foo') + self.pkg.owners.add(self.dummy_user) + + def tearDown(self): + self.pkg.delete() + self.dummy_user.delete() + + def test_search_for_package(self): response = client.post(reverse('djangopypi-search'), {'search_term': 'foo'}) self.assertTrue("The quick brown fox jumps over the lazy dog." in response.content) @@ -124,16 +127,86 @@ def create_distutils_httprequest(self, user_data={}): request.raw_post_data = self.raw_post_data return request - def test_user_registration(self): - request = self.create_distutils_httprequest({'name': 'peter_parker', 'email':'parker@dailybugle.com', - 'password':'spiderman'}) - response = simple(request) - self.assertEquals(200, response.status_code) - - def test_user_registration_with_wrong_data(self): - request = self.create_distutils_httprequest({'name': 'peter_parker', 'email':'parker@dailybugle.com', - 'password':'',}) - response = simple(request) - self.assertEquals(400, response.status_code) + # def test_user_registration(self): + # request = self.create_distutils_httprequest({'name': 'peter_parker', 'email':'parker@dailybugle.com', + # 'password':'spiderman'}) + # response = simple(request) + # self.assertEquals(200, response.status_code) + # + # def test_user_registration_with_wrong_data(self): + # request = self.create_distutils_httprequest({'name': 'peter_parker', 'email':'parker@dailybugle.com', + # 'password':'',}) + # response = simple(request) + # self.assertEquals(400, response.status_code) + +from django.test.client import MULTIPART_CONTENT +class XmlRpcClient(Client): + def __init__(self, *args, **kwargs): + self.extra_headers = {} + super(XmlRpcClient, self).__init__(*args, **kwargs) + + def putheader(self, key, value): + self.extra_headers[key] = value + def endheaders(self, request_body): + pass + def getresponse(self, buffering=True): + return self.response + def post(self, path, data={}, content_type="text/xml", + follow=False, **extra): + """ + Requests a response from the server using POST. + """ + extra.update(self.extra_headers) + response = super(XmlRpcClient, self).post(path, data, content_type, follow, **extra) + return response.content + + +class ProxiedTransport(xmlrpclib.Transport): + def set_proxy(self, proxy): + self.proxy = proxy + def make_connection(self, host): + return XmlRpcClient() + def single_request(self, host, handler, request_body, verbose=0): + connection = self.make_connection(host) + response = connection.post('/pypi/', request_body) + data, methodname = xmlrpclib.loads(response) + return data + + def send_host(self, connection, host): + pass + + def send_user_agent(self, *args, **kwargs): + pass + +class TestXmlRpc(unittest.TestCase): + """ + Test that the server responds to xmlrpc requests + """ + def setUp(self): + from django.core.files.base import ContentFile + self.dummy_file = ContentFile("gibberish") + self.dummy_user = User.objects.create(username='bobby', password='tables', + email='bobby@tables.com') + self.pkg = Package.objects.create(name='foo') + self.pkg.owners.add(self.dummy_user) + self.release = Release.objects.create(package=self.pkg, version="1.0") + + def tearDown(self): + self.release.delete() + self.pkg.delete() + self.dummy_user.delete() + + def test_list_package(self): + pypi = xmlrpclib.ServerProxy("http://localhost/pypi/", ProxiedTransport()) + pypi_hits = pypi.list_packages() + expected = ['foo'] + self.assertEqual(pypi_hits, expected) + + def test_package_releases(self): + pypi = xmlrpclib.ServerProxy("http://localhost/pypi/", ProxiedTransport()) + pypi_hits = pypi.package_releases('foo') + expected = ['1.0'] + self.assertEqual(pypi_hits, expected) + \ No newline at end of file From 830bce2918b0a4551ec266e52813b8415b9f014d Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 21 Feb 2011 15:27:10 -0600 Subject: [PATCH 095/146] Various bug fixes and one new feature for version 0.4.2 --- buildout.cfg | 2 +- chishop/templates/registration/login.html | 4 ++-- chishop/templates/registration/registration_form.html | 5 +++-- src/djangopypi/Changelog | 3 ++- src/djangopypi/djangopypi/settings.py | 10 ++++++++++ src/djangopypi/djangopypi/urls.py | 2 +- src/djangopypi/djangopypi/views/packages.py | 10 +++++++++- src/djangopypi/setup.cfg | 2 +- 8 files changed, 29 insertions(+), 9 deletions(-) diff --git a/buildout.cfg b/buildout.cfg index e00aa9f..8b91acf 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -13,7 +13,7 @@ develop = [django] recipe = djangorecipe -version = 1.2.1 +version = 1.2.5 settings = settings eggs = ${buildout:eggs} test = djangopypi diff --git a/chishop/templates/registration/login.html b/chishop/templates/registration/login.html index 6c7f799..a8ab11b 100644 --- a/chishop/templates/registration/login.html +++ b/chishop/templates/registration/login.html @@ -1,8 +1,8 @@ {% extends "base_site.html" %} {% block content %} -
    +{% csrf_token %} {{form.as_p}} - +
    {% endblock %} diff --git a/chishop/templates/registration/registration_form.html b/chishop/templates/registration/registration_form.html index 719a875..bccf8d1 100644 --- a/chishop/templates/registration/registration_form.html +++ b/chishop/templates/registration/registration_form.html @@ -2,7 +2,8 @@ {% block content %}

    Register

    -
    +{% csrf_token %} {{form.as_p}} - + +
    {% endblock %} diff --git a/src/djangopypi/Changelog b/src/djangopypi/Changelog index d93dcca..fb30d19 100644 --- a/src/djangopypi/Changelog +++ b/src/djangopypi/Changelog @@ -1,10 +1,11 @@ History ======= -0.4.2 (2011-02-08) +0.4.2 (2011-02-21) ------------------ * Added CSRF support for Django>=1.2 +* Added conditional support to proxy packages not indexed 0.4.1 (2010-06-17) ------------------ diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index 5fe595e..83a8493 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -97,3 +97,13 @@ "submit": distutils.register_or_upload, #``register`` command "list_classifiers": distutils.list_classifiers, #``list_classifiers`` command } + +""" These settings enable proxying of packages that are not in the local index +to another index, http://pypi.python.org/ by default. This feature is disabled +by default and can be enabled by setting DJANGOPYPI_PROXY_MISSING to True in +your settings file. """ +if not hasattr(settings, 'DJANGOPYPI_PROXY_BASE_URL'): + settings.DJANGOPYPI_PROXY_BASE_URL = 'http://pypi.python.org/simple' + +if not hasattr(settings, 'DJANGOPYPI_PROXY_MISSING'): + settings.DJANGOPYPI_PROXY_MISSING = False diff --git a/src/djangopypi/djangopypi/urls.py b/src/djangopypi/djangopypi/urls.py index 90abe81..f7001f2 100644 --- a/src/djangopypi/djangopypi/urls.py +++ b/src/djangopypi/djangopypi/urls.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from django.conf.urls.defaults import patterns, url, include +from django.conf.urls.defaults import patterns, url from djangopypi.feeds import ReleaseFeed urlpatterns = patterns("djangopypi.views", diff --git a/src/djangopypi/djangopypi/views/packages.py b/src/djangopypi/djangopypi/views/packages.py index 77a3ea3..70b2ba8 100644 --- a/src/djangopypi/djangopypi/views/packages.py +++ b/src/djangopypi/djangopypi/views/packages.py @@ -1,5 +1,6 @@ from django.conf import settings from django.db.models.query import Q +from django.http import Http404, HttpResponseRedirect from django.forms.models import inlineformset_factory from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext @@ -27,7 +28,14 @@ def details(request, package, **kwargs): def simple_details(request, package, **kwargs): kwargs.setdefault('template_name', 'djangopypi/package_detail_simple.html') - return details(request, package, **kwargs) + try: + return details(request, package, **kwargs) + except Http404, e: + if settings.DJANGOPYPI_PROXY_MISSING: + return HttpResponseRedirect('%s/%s/' % + (settings.DJANGOPYPI_PROXY_BASE_URL.rstrip('/'), + package)) + raise e def doap(request, package, **kwargs): kwargs.setdefault('template_name', 'djangopypi/package_doap.xml') diff --git a/src/djangopypi/setup.cfg b/src/djangopypi/setup.cfg index 5786d4d..bd36a8d 100644 --- a/src/djangopypi/setup.cfg +++ b/src/djangopypi/setup.cfg @@ -1,2 +1,2 @@ [bdist_rpm] -doc_files = AUTHORS CHANGELOG LICENSE README TODO \ No newline at end of file +doc_files = AUTHORS Changelog LICENSE README TODO \ No newline at end of file From 14e252eb65edd634c907f4f43fe9fc4186a3d4e8 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Tue, 22 Feb 2011 14:09:12 -0600 Subject: [PATCH 096/146] Version 0.4.3, see Changelog for details --- src/djangopypi/Changelog | 7 + src/djangopypi/djangopypi/settings.py | 13 ++ src/djangopypi/djangopypi/views/__init__.py | 2 +- src/djangopypi/djangopypi/views/xmlrpc.py | 140 +++++++++++++++++ src/djangopypi/djangopypi/xmlrpc.py | 161 -------------------- src/djangopypi/setup.py | 2 +- 6 files changed, 162 insertions(+), 163 deletions(-) create mode 100644 src/djangopypi/djangopypi/views/xmlrpc.py delete mode 100644 src/djangopypi/djangopypi/xmlrpc.py diff --git a/src/djangopypi/Changelog b/src/djangopypi/Changelog index fb30d19..1870e5c 100644 --- a/src/djangopypi/Changelog +++ b/src/djangopypi/Changelog @@ -1,6 +1,13 @@ History ======= +0.4.3 (2011-02-22) +------------------ + +* Moved xmlrpc views into views folder +* Moved xmlrpc command settings to the settings file +* Cleaned up xmlrpc views to remove django.contrib.sites dependency + 0.4.2 (2011-02-21) ------------------ diff --git a/src/djangopypi/djangopypi/settings.py b/src/djangopypi/djangopypi/settings.py index 83a8493..99fc82b 100644 --- a/src/djangopypi/djangopypi/settings.py +++ b/src/djangopypi/djangopypi/settings.py @@ -98,6 +98,19 @@ "list_classifiers": distutils.list_classifiers, #``list_classifiers`` command } +if not hasattr(settings,'DJANGOPYPI_XMLRPC_COMMANDS'): + from djangopypi.views import xmlrpc + + settings.DJANGOPYPI_XMLRPC_COMMANDS = { + 'list_packages': xmlrpc.list_packages, + 'package_releases': xmlrpc.package_releases, + 'release_urls': xmlrpc.release_urls, + 'release_data': xmlrpc.release_data, + #'search': xmlrpc.search, Not done yet + #'changelog': xmlrpc.changelog, Not done yet + #'ratings': xmlrpc.ratings, Not done yet + } + """ These settings enable proxying of packages that are not in the local index to another index, http://pypi.python.org/ by default. This feature is disabled by default and can be enabled by setting DJANGOPYPI_PROXY_MISSING to True in diff --git a/src/djangopypi/djangopypi/views/__init__.py b/src/djangopypi/djangopypi/views/__init__.py index 042530f..e324e91 100644 --- a/src/djangopypi/djangopypi/views/__init__.py +++ b/src/djangopypi/djangopypi/views/__init__.py @@ -4,7 +4,7 @@ from djangopypi.decorators import csrf_exempt from djangopypi.http import parse_distutils_request from djangopypi.models import Package, Release -from djangopypi.xmlrpc import parse_xmlrpc_request +from djangopypi.views.xmlrpc import parse_xmlrpc_request @csrf_exempt def root(request, fallback_view=None, **kwargs): diff --git a/src/djangopypi/djangopypi/views/xmlrpc.py b/src/djangopypi/djangopypi/views/xmlrpc.py new file mode 100644 index 0000000..3874434 --- /dev/null +++ b/src/djangopypi/djangopypi/views/xmlrpc.py @@ -0,0 +1,140 @@ +import xmlrpclib + +from django.conf import settings +from django.http import HttpResponseNotAllowed, HttpResponse + +from djangopypi.models import Package, Release + + + +class XMLRPCResponse(HttpResponse): + """ A wrapper around the base HttpResponse that dumps the output for xmlrpc + use """ + def __init__(self, params=(), methodresponse=True, *args, **kwargs): + super(XMLRPCResponse, self).__init__(xmlrpclib.dumps(params, + methodresponse=methodresponse), + *args, **kwargs) + +def parse_xmlrpc_request(request): + """ + Parse the request and dispatch to the appropriate view + """ + args, command = xmlrpclib.loads(request.raw_post_data) + + if command in settings.DJANGOPYPI_XMLRPC_COMMANDS: + return settings.DJANGOPYPI_XMLRPC_COMMANDS[command](request, *args) + else: + return HttpResponseNotAllowed(settings.DJANGOPYPI_XMLRPC_COMMANDS.keys()) + +def list_packages(request): + return XMLRPCResponse(params=(list(Package.objects.all().values_list('name', flat=True)),), + content_type='text/xml') + +def package_releases(request, package_name, show_hidden=False): + try: + return XMLRPCResponse(params=(list(Package.objects.get(name=package_name).releases.filter(hidden=show_hidden).values_list('version', flat=True)),)) + except Package.DoesNotExist: + return XMLRPCResponse(params=([],)) + +def release_urls(request, package_name, version): + base_url = '%s://%s' % (request.is_secure() and 'https' or 'http', + request.get_host()) + dists = [] + try: + for dist in Package.objects.get(name=package_name).releases.get(version=version).distributions.all(): + dists.append({ + 'url': '%s%s' % (base_url, dist.get_absolute_url()), + 'packagetype': dist.filetype, + 'filename': dist.filename, + 'size': dist.content.size, + 'md5_digest': dist.md5_digest, + 'downloads': 0, + 'has_sig': len(dist.signature)>0, + 'python_version': dist.pyversion, + 'comment_text': dist.comment + }) + except (Package.DoesNotExist, Release.DoesNotExist): + pass + + return XMLRPCResponse(params=(dists,)) + +def release_data(request, package_name, version): + output = { + 'name': '', + 'version': '', + 'stable_version': '', + 'author': '', + 'author_email': '', + 'maintainer': '', + 'maintainer_email': '', + 'home_page': '', + 'license': '', + 'summary': '', + 'description': '', + 'keywords': '', + 'platform': '', + 'download_url': '', + 'classifiers': '', + 'requires': '', + 'requires_dist': '', + 'provides': '', + 'provides_dist': '', + 'requires_external': '', + 'requires_python': '', + 'obsoletes': '', + 'obsoletes_dist': '', + 'project_url': '', + } + try: + release = Package.objects.get(name=package_name).releases.get(version=version) + output.update({'name': package_name, 'version': version,}) + output.update(release.package_info) + except (Package.DoesNotExist, Release.DoesNotExist): + pass + + return XMLRPCResponse(params=(output,)) + +def search(request, spec, operator='or'): + """ + search(spec[, operator]) + + Search the package database using the indicated search spec. + The spec may include any of the keywords described in the above list (except 'stable_version' and 'classifiers'), for example: {'description': 'spam'} will search description fields. Within the spec, a field's value can be a string or a list of strings (the values within the list are combined with an OR), for example: {'name': ['foo', 'bar']}. Valid keys for the spec dict are listed here. Invalid keys are ignored: + name + version + author + author_email + maintainer + maintainer_email + home_page + license + summary + description + keywords + platform + download_url + Arguments for different fields are combined using either "and" (the default) or "or". Example: search({'name': 'foo', 'description': 'bar'}, 'or'). The results are returned as a list of dicts {'name': package name, 'version': package release version, 'summary': package release summary} + + changelog(since) + + Retrieve a list of four-tuples (name, version, timestamp, action) since the given timestamp. All timestamps are UTC values. The argument is a UTC integer seconds since the epoch. + """ + + output = { + 'name': '', + 'version': '', + 'summary': '', + } + return XMLRPCResponse(params=(output,)) + +def changelog(since): + output = { + 'name': '', + 'version': '', + 'timestamp': '', + 'action': '', + } + return XMLRPCResponse(params=(output,)) + +def ratings(request, name, version, since): + return XMLRPCResponse(params=([],)) diff --git a/src/djangopypi/djangopypi/xmlrpc.py b/src/djangopypi/djangopypi/xmlrpc.py deleted file mode 100644 index 225e765..0000000 --- a/src/djangopypi/djangopypi/xmlrpc.py +++ /dev/null @@ -1,161 +0,0 @@ -import xmlrpclib - -from django.conf import settings -from django.http import HttpResponseNotAllowed, HttpResponse -from django.contrib.sites.models import Site - -from djangopypi.models import Package, Release - - - -SITE_NAME = Site.objects.get_current().domain - -def list_packages(): - pkg_list = list(Package.objects.all().values_list('name', flat=True)) - xml_out = xmlrpclib.dumps((pkg_list,), methodresponse=True) - return HttpResponse(xml_out, content_type='text/xml') - -def package_releases(package_name, show_hidden=False): - releases = [] - try: - pkg = Package.objects.get(name=package_name) - releases = list(pkg.releases.filter(hidden=show_hidden).values_list('version', flat=True)) - except Package.DoesNotExist: - pass - except Exception, e: - print e - - return HttpResponse(xmlrpclib.dumps((releases,), methodresponse=True)) - -def release_urls(package_name, version): - output = [] - try: - pkg = Package.objects.get(name=package_name) - release = pkg.releases.get(version=version) - - for dist in release.distributions.all(): - output.append({ - 'url': 'http://%s%s' % (SITE_NAME, dist.get_absolute_url()), - 'packagetype': dist.filetype, - 'filename': dist.filename, - 'size': dist.content.size, - 'md5_digest': dist.md5_digest, - 'downloads': 0, - 'has_sig': len(dist.signature)>0, - 'python_version': dist.pyversion, - 'comment_text': dist.comment - }) - except (Package.DoesNotExist, Release.DoesNotExist): - pass - except Exception, e: - print e - - return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) - -def release_data(package_name, version): - output = { - 'name': '', - 'version': '', - 'stable_version': '', - 'author': '', - 'author_email': '', - 'maintainer': '', - 'maintainer_email': '', - 'home_page': '', - 'license': '', - 'summary': '', - 'description': '', - 'keywords': '', - 'platform': '', - 'download_url': '', - 'classifiers': '', - 'requires': '', - 'requires_dist': '', - 'provides': '', - 'provides_dist': '', - 'requires_external': '', - 'requires_python': '', - 'obsoletes': '', - 'obsoletes_dist': '', - 'project_url': '', - } - try: - pkg = Package.objects.get(name=package_name) - release = pkg.releases.get(version=version) - metadata = release.package_info - output.update({'name': pkg.name, 'version': release.version,}) - output.update(metadata) - except (Package.DoesNotExist, Release.DoesNotExist): - pass - except Exception, e: - print e - - return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) - -# -""" -search(spec[, operator]) - -Search the package database using the indicated search spec. -The spec may include any of the keywords described in the above list (except 'stable_version' and 'classifiers'), for example: {'description': 'spam'} will search description fields. Within the spec, a field's value can be a string or a list of strings (the values within the list are combined with an OR), for example: {'name': ['foo', 'bar']}. Valid keys for the spec dict are listed here. Invalid keys are ignored: -name -version -author -author_email -maintainer -maintainer_email -home_page -license -summary -description -keywords -platform -download_url -Arguments for different fields are combined using either "and" (the default) or "or". Example: search({'name': 'foo', 'description': 'bar'}, 'or'). The results are returned as a list of dicts {'name': package name, 'version': package release version, 'summary': package release summary} - -changelog(since) - -Retrieve a list of four-tuples (name, version, timestamp, action) since the given timestamp. All timestamps are UTC values. The argument is a UTC integer seconds since the epoch. - -""" -def search(spec, operator='or'): - output = { - 'name': '', - 'version': '', - 'summary': '', - } - return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) - -def changelog(since): - output = { - 'name': '', - 'version': '', - 'timestamp': '', - 'action': '', - } - return HttpResponse(xmlrpclib.dumps((output,), methodresponse=True)) - -def ratings(name, version, since): - return HttpResponse(xmlrpclib.dumps(([],), methodresponse=True)) - - -PYPI_COMMANDS = { - 'list_packages': list_packages, - 'package_releases': package_releases, - 'release_urls': release_urls, - 'release_data': release_data, - 'search': search, - 'changelog': changelog, - 'ratings': ratings, -} - - -def parse_xmlrpc_request(request): - """ - Parse the request and dispatch to the appropriate view - """ - args, command = xmlrpclib.loads(request.raw_post_data) - if command in PYPI_COMMANDS: - return PYPI_COMMANDS[command](*args) - else: - return HttpResponseNotAllowed(settings.DJANGOPYPI_ACTION_VIEW.keys()) diff --git a/src/djangopypi/setup.py b/src/djangopypi/setup.py index 36a99bd..8738d88 100644 --- a/src/djangopypi/setup.py +++ b/src/djangopypi/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.2' +version = '0.4.3' setup( name='djangopypi', From 448fc9603a376a9e3a6f17fea3a85de482a4e90d Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Sun, 11 Sep 2011 09:53:48 -0500 Subject: [PATCH 097/146] Removed chiship specific stuffs --- TODO | 8 -- chishop/__init__.py | 2 - chishop/manage.py | 11 -- chishop/media/__init__.py | 0 chishop/media/dists/__init__.py | 0 chishop/media/style/djangopypi.css | 8 -- chishop/production_example.py | 18 --- chishop/requirements.txt | 8 -- chishop/search_sites.py | 3 - chishop/settings.py | 133 ------------------ chishop/templates/404.html | 12 -- chishop/templates/500.html | 12 -- chishop/templates/admin/base_site.html | 10 -- chishop/templates/base.html | 77 ---------- chishop/templates/base_site.html | 14 -- .../templates/djangopypi/package_detail.html | 18 --- .../templates/djangopypi/package_list.html | 15 -- .../templates/djangopypi/package_manage.html | 15 -- .../djangopypi/package_manage_versions.html | 39 ----- .../templates/djangopypi/release_detail.html | 15 -- .../djangopypi/release_detail_content.html | 53 ------- .../templates/djangopypi/release_list.html | 16 --- .../templates/djangopypi/release_manage.html | 16 --- .../djangopypi/release_manage_files.html | 54 ------- .../djangopypi/release_upload_file.html | 18 --- chishop/templates/djangopypi/search.html | 5 - .../templates/djangopypi/search_results.html | 43 ------ chishop/templates/djangopypi/show_links.html | 7 - .../templates/djangopypi/show_version.html | 5 - chishop/templates/djangopypi/simple.html | 5 - chishop/templates/registration/activate.html | 8 -- .../registration/activation_complete.html | 9 -- .../registration/activation_email.txt | 6 - .../registration/activation_email_subject.txt | 1 - chishop/templates/registration/login.html | 8 -- chishop/templates/registration/logout.html | 7 - .../registration/registration_closed.html | 8 -- .../registration/registration_complete.html | 8 -- .../registration/registration_form.html | 9 -- chishop/urls.py | 30 ---- index.html | 86 ----------- 41 files changed, 820 deletions(-) delete mode 100644 TODO delete mode 100644 chishop/__init__.py delete mode 100755 chishop/manage.py delete mode 100644 chishop/media/__init__.py delete mode 100644 chishop/media/dists/__init__.py delete mode 100644 chishop/media/style/djangopypi.css delete mode 100644 chishop/production_example.py delete mode 100644 chishop/requirements.txt delete mode 100644 chishop/search_sites.py delete mode 100644 chishop/settings.py delete mode 100644 chishop/templates/404.html delete mode 100644 chishop/templates/500.html delete mode 100644 chishop/templates/admin/base_site.html delete mode 100644 chishop/templates/base.html delete mode 100644 chishop/templates/base_site.html delete mode 100644 chishop/templates/djangopypi/package_detail.html delete mode 100644 chishop/templates/djangopypi/package_list.html delete mode 100644 chishop/templates/djangopypi/package_manage.html delete mode 100644 chishop/templates/djangopypi/package_manage_versions.html delete mode 100644 chishop/templates/djangopypi/release_detail.html delete mode 100644 chishop/templates/djangopypi/release_detail_content.html delete mode 100644 chishop/templates/djangopypi/release_list.html delete mode 100644 chishop/templates/djangopypi/release_manage.html delete mode 100644 chishop/templates/djangopypi/release_manage_files.html delete mode 100644 chishop/templates/djangopypi/release_upload_file.html delete mode 100644 chishop/templates/djangopypi/search.html delete mode 100644 chishop/templates/djangopypi/search_results.html delete mode 100644 chishop/templates/djangopypi/show_links.html delete mode 100644 chishop/templates/djangopypi/show_version.html delete mode 100644 chishop/templates/djangopypi/simple.html delete mode 100644 chishop/templates/registration/activate.html delete mode 100644 chishop/templates/registration/activation_complete.html delete mode 100644 chishop/templates/registration/activation_email.txt delete mode 100644 chishop/templates/registration/activation_email_subject.txt delete mode 100644 chishop/templates/registration/login.html delete mode 100644 chishop/templates/registration/logout.html delete mode 100644 chishop/templates/registration/registration_closed.html delete mode 100644 chishop/templates/registration/registration_complete.html delete mode 100644 chishop/templates/registration/registration_form.html delete mode 100644 chishop/urls.py delete mode 100644 index.html diff --git a/TODO b/TODO deleted file mode 100644 index 986c837..0000000 --- a/TODO +++ /dev/null @@ -1,8 +0,0 @@ -TODO -==== - -This file is for the chishop package specifically, for djangopypi, see -src/djangopypi/docs/TODO. - -* Random Monty Python quotes :-) -* Whoosh full text indexing integration, maybe called djangopypi.whoosh? \ No newline at end of file diff --git a/chishop/__init__.py b/chishop/__init__.py deleted file mode 100644 index b56a51c..0000000 --- a/chishop/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -VERSION = (0, 1, 1) -__version__ = ".".join(map(str, VERSION)) diff --git a/chishop/manage.py b/chishop/manage.py deleted file mode 100755 index 5e78ea9..0000000 --- a/chishop/manage.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python -from django.core.management import execute_manager -try: - import settings # Assumed to be in the same directory. -except ImportError: - import sys - sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) - sys.exit(1) - -if __name__ == "__main__": - execute_manager(settings) diff --git a/chishop/media/__init__.py b/chishop/media/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chishop/media/dists/__init__.py b/chishop/media/dists/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/chishop/media/style/djangopypi.css b/chishop/media/style/djangopypi.css deleted file mode 100644 index 95180db..0000000 --- a/chishop/media/style/djangopypi.css +++ /dev/null @@ -1,8 +0,0 @@ -.search { - text-align:right; - margin-right: 10px; -} - -.highlighted { - background-color: #FEEF00; -} \ No newline at end of file diff --git a/chishop/production_example.py b/chishop/production_example.py deleted file mode 100644 index b64623e..0000000 --- a/chishop/production_example.py +++ /dev/null @@ -1,18 +0,0 @@ -from conf.default import * -import os - -DEBUG = False -TEMPLATE_DEBUG = DEBUG - -ADMINS = ( - ('chishop', 'example@example.org'), -) - -MANAGERS = ADMINS - -DATABASE_ENGINE = 'postgresql_psycopg2' -DATABASE_NAME = 'chishop' -DATABASE_USER = 'chishop' -DATABASE_PASSWORD = 'chishop' -DATABASE_HOST = '' -DATABASE_PORT = '' diff --git a/chishop/requirements.txt b/chishop/requirements.txt deleted file mode 100644 index de3b721..0000000 --- a/chishop/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ ---find-links=http://bitbucket.org/ubernostrum/django-registration/downloads/ - -djangopypi>=0.4 -South==0.7.1 -Django==1.2.1 -django-registration==0.8-alpha-1 -django-haystack -Whoosh diff --git a/chishop/search_sites.py b/chishop/search_sites.py deleted file mode 100644 index 59580c7..0000000 --- a/chishop/search_sites.py +++ /dev/null @@ -1,3 +0,0 @@ -import haystack - -haystack.autodiscover() diff --git a/chishop/settings.py b/chishop/settings.py deleted file mode 100644 index 7a62d67..0000000 --- a/chishop/settings.py +++ /dev/null @@ -1,133 +0,0 @@ -import os -import chishop - -ADMINS = ( - # ('Your Name', 'your_email@domain.com'), -) - -DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False -DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' - -# change to False if you do not want Django's default server to serve static pages -LOCAL_DEVELOPMENT = True - -REGISTRATION_OPEN = True -ACCOUNT_ACTIVATION_DAYS = 7 -LOGIN_REDIRECT_URL = "/" - -EMAIL_HOST = '' -DEFAULT_FROM_EMAIL = '' -SERVER_EMAIL = DEFAULT_FROM_EMAIL - -MANAGERS = ADMINS - -DATABASE_ENGINE = '' -DATABASE_NAME = '' -DATABASE_USER = '' -DATABASE_PASSWORD = '' -DATABASE_HOST = '' -DATABASE_PORT = '' - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# If running in a Windows environment this must be set to the same as your -# system time zone. -TIME_ZONE = 'America/Chicago' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# Absolute path to the directory that holds media. -# Example: "/home/media/media.lawrence.com/" -MEDIA_ROOT = os.path.join(os.path.abspath(os.path.dirname(chishop.__file__)), 'media') - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash if there is a path component (optional in other cases). -# Examples: "http://media.lawrence.com", "http://example.com/media/" -MEDIA_URL = '/media/' - -# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a -# trailing slash. -# Examples: "http://foo.com/media/", "/media/". -ADMIN_MEDIA_PREFIX = '/admin-media/' - -# Make this unique, and don't share it with anybody. -SECRET_KEY = 'w_#0r2hh)=!zbynb*gg&969@)sy#^-^ia3m*+sd4@lst$zyaxu' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.load_template_source', - 'django.template.loaders.app_directories.load_template_source', - 'django.template.loaders.eggs.load_template_source', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', -) - -ROOT_URLCONF = 'urls' - -TEMPLATE_CONTEXT_PROCESSORS = ( - "django.core.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.core.context_processors.request", -) - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - os.path.join(os.path.dirname(__file__), "templates"), -) - -INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.admin', - 'django.contrib.markup', - 'django.contrib.admindocs', - 'registration', - 'djangopypi', - 'south', - 'haystack', -) - - -DEBUG = True -TEMPLATE_DEBUG = DEBUG -LOCAL_DEVELOPMENT = True - -if LOCAL_DEVELOPMENT: - import sys - sys.path.append(os.path.dirname(__file__)) - -ADMINS = ( - ('chishop', 'example@example.org'), -) - -MANAGERS = ADMINS - -DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = os.path.join(os.path.abspath(os.path.dirname(chishop.__file__)), 'devdatabase.db') -DATABASE_USER = '' -DATABASE_PASSWORD = '' -DATABASE_HOST = '' -DATABASE_PORT = '' - -HAYSTACK_SEARCH_ENGINE = 'whoosh' -HAYSTACK_SITECONF = 'chishop.search_sites' -HAYSTACK_WHOOSH_PATH = os.path.join(os.path.dirname(chishop.__file__), 'haystack') diff --git a/chishop/templates/404.html b/chishop/templates/404.html deleted file mode 100644 index 9bf4293..0000000 --- a/chishop/templates/404.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n %} - -{% block title %}{% trans 'Page not found' %}{% endblock %} - -{% block content %} - -

    {% trans 'Page not found' %}

    - -

    {% trans "We're sorry, but the requested page could not be found." %}

    - -{% endblock %} diff --git a/chishop/templates/500.html b/chishop/templates/500.html deleted file mode 100644 index b30e431..0000000 --- a/chishop/templates/500.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "admin/base_site.html" %} -{% load i18n %} - -{% block breadcrumbs %}{% endblock %} - -{% block title %}{% trans 'Server error (500)' %}{% endblock %} - -{% block content %} -

    {% trans 'Server Error (500)' %}

    -

    {% trans "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." %}

    - -{% endblock %} diff --git a/chishop/templates/admin/base_site.html b/chishop/templates/admin/base_site.html deleted file mode 100644 index 9443eb2..0000000 --- a/chishop/templates/admin/base_site.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "admin/base.html" %} -{% load i18n %} - -{% block title %}{{ title }} | {% trans 'Chishop site admin' %}{% endblock %} - -{% block branding %} -

    {% trans 'Chishop' %}

    -{% endblock %} - -{% block nav-global %}{% endblock %} diff --git a/chishop/templates/base.html b/chishop/templates/base.html deleted file mode 100644 index dd797e7..0000000 --- a/chishop/templates/base.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - -{% block extrastyle %}{% endblock %} - -{% block title %}{% endblock %} - -{% block site_extrahead %}{% endblock %} -{% block extrahead %}{% endblock %} - - - - -
    - - - - - - - {% block messagelist %} - {% if messages %} -
      - {% for message in messages %} -
    • {{ message }}
    • - {% endfor %} -
    - {% endif %} - {% endblock %} - - -
    - {% block pretitle %}{% endblock %} -

    {% block content_title %}{% endblock %}

    -
    - {% block objecttools %}{% endblock %} - {% block sidebar %}{% endblock %} - {% block content %}{{ content }}{% endblock %} -
    -
    -
    - - - - - -
    - - - - diff --git a/chishop/templates/base_site.html b/chishop/templates/base_site.html deleted file mode 100644 index 5257255..0000000 --- a/chishop/templates/base_site.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "base.html" %} - -{% block title %}{{ title }} | Chishop{% endblock %} - -{% block bread_crumbs_1 %} -› {{ title }} -{% endblock %} -{% block site_name_header %} -Chishop -{% endblock %} - -{% block content_title %}{{ title }}{% endblock %} - -{% block nav-global %}{% endblock %} diff --git a/chishop/templates/djangopypi/package_detail.html b/chishop/templates/djangopypi/package_detail.html deleted file mode 100644 index c912f9e..0000000 --- a/chishop/templates/djangopypi/package_detail.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base_site.html" %} - -{% block extrahead %} - -{% endblock %} - -{% block bread_crumbs_1 %}» {{ package }}{% endblock %} - -{% block content %} -{% with package.latest as release %} -{% if release %} -{% include "djangopypi/release_detail_content.html" %} -{% else %} -
    No releases yet :(
    -{% endif %} -{% endwith %} -{% endblock %} \ No newline at end of file diff --git a/chishop/templates/djangopypi/package_list.html b/chishop/templates/djangopypi/package_list.html deleted file mode 100644 index f4eb075..0000000 --- a/chishop/templates/djangopypi/package_list.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base_site.html" %} - -{% block bread_crumbs_1 %}» Packages{% endblock %} - -{% block content %} - - -{% for package in package_list %} - - - - -{% endfor %} -
    PackageDescription
    {{ package }}{% if package.latest %}{{ package.latest.summary|truncatewords:10 }}{% endif %}
    -{% endblock %} diff --git a/chishop/templates/djangopypi/package_manage.html b/chishop/templates/djangopypi/package_manage.html deleted file mode 100644 index 6b2660b..0000000 --- a/chishop/templates/djangopypi/package_manage.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base_site.html" %} - -{% block bread_crumbs_1 %} - » {{ package }} - » Manage -{% endblock %} - -{% block content %} -

    Manage {{ package.name }}

    -
      -
      - {{ form.as_p }} - -
    -{% endblock content %} \ No newline at end of file diff --git a/chishop/templates/djangopypi/package_manage_versions.html b/chishop/templates/djangopypi/package_manage_versions.html deleted file mode 100644 index 3f2a8f5..0000000 --- a/chishop/templates/djangopypi/package_manage_versions.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "base_site.html" %} - -{% block bread_crumbs_1 %} - » {{ package }} - » Manage Releases -{% endblock %} - -{% block content %} -

    Manage {{ package.name }} Releases

    - - {{ formset.management_form }} - - - - - - - - - - - {% for form in formset.forms %} - {% for field in form %}{% if field.is_hidden %}{{ field }}{% endif %}{% endfor %} - {% with form.instance as release %} - - - - - - - - - {% endwith %} - {% endfor %} - -
    Remove?VersionHide?Links
    {{ form.DELETE }}{{ release.version }}{{ form.hidden }}ShowEditFiles
    -
    - -{% endblock content %} \ No newline at end of file diff --git a/chishop/templates/djangopypi/release_detail.html b/chishop/templates/djangopypi/release_detail.html deleted file mode 100644 index 11f5786..0000000 --- a/chishop/templates/djangopypi/release_detail.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "base_site.html" %} - -{% block extrahead %} - -{% endblock %} - -{% block bread_crumbs_1 %} -» {{ release.package }} -» {{ release }}{% endblock %} - -{% block content %} -{% include "djangopypi/release_detail_content.html" %} -{% endblock %} - diff --git a/chishop/templates/djangopypi/release_detail_content.html b/chishop/templates/djangopypi/release_detail_content.html deleted file mode 100644 index 5287433..0000000 --- a/chishop/templates/djangopypi/release_detail_content.html +++ /dev/null @@ -1,53 +0,0 @@ -{% ifnotequal release release.package.latest %} - -{% endifnotequal %} - -
    -{{ release.summary }} -
    - -
    -{% load safemarkup %} -{{ release.description|saferst }} -
    - -{% if release.distributions.count %} -
    - - - - - - {% for dist in release.distributions.all %} - - - - - - - - {% endfor %} - -
    FilenameTypePy VersionUploaded OnSize
    {{ dist.filename }}{{ dist.display_filetype }}{{ dist.pyversion }}{{ dist.created }}{{ dist.content.size|filesizeformat }}
    -
    -{% endif %} -
    - -
    \ No newline at end of file diff --git a/chishop/templates/djangopypi/release_list.html b/chishop/templates/djangopypi/release_list.html deleted file mode 100644 index 7f86fc0..0000000 --- a/chishop/templates/djangopypi/release_list.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "base_site.html" %} - -{% block bread_crumbs_1 %}» Releases{% endblock %} - -{% block content %} - - -{% for release in release_list %} - - - - - -{% endfor %} -
    UpdatedPackageSummary
    {{ release.created|date:"Y-m-d" }}{{ release }}{{ release.summary|truncatewords:10 }}
    -{% endblock %} diff --git a/chishop/templates/djangopypi/release_manage.html b/chishop/templates/djangopypi/release_manage.html deleted file mode 100644 index b53dfdb..0000000 --- a/chishop/templates/djangopypi/release_manage.html +++ /dev/null @@ -1,16 +0,0 @@ -{% extends "base_site.html" %} - -{% block bread_crumbs_1 %} -» {{ release.package }}{{ release }} -» Manage{% endblock %} - -{% block content %} -

    Manage {{ release }}

    -
      -
      - {{ form.as_p }} - -
    -{% endblock %} - diff --git a/chishop/templates/djangopypi/release_manage_files.html b/chishop/templates/djangopypi/release_manage_files.html deleted file mode 100644 index 80a32d3..0000000 --- a/chishop/templates/djangopypi/release_manage_files.html +++ /dev/null @@ -1,54 +0,0 @@ -{% extends "base_site.html" %} - -{% block bread_crumbs_1 %} -» {{ release.package }}{{ release }} -» Manage Files{% endblock %} - -{% block content %} -

    Manage {{ release }} Files

    -{% if formset.forms %} - - {{ formset.management_form }} - - - - - - - - - - - - - - {% for form in formset.forms %} - {% for field in form %}{% if field.is_hidden %}{{ field }}{% endif %}{% endfor %} - {% with form.instance as dist %} - - - - - - - - - - {% endwith %} - {% endfor %} - -
    Remove?TypePy VersionCommentDownloadSizeMD5 Digest
    {{ form.DELETE }}{{ dist.display_filetype }}{{ dist.pyversion }}{{ form.comment }}{{ dist.filename }}{{ dist.content.size|filesizeformat }}{{ dist.md5_digest }}
    -
    - -{% endif %} -{% if upload_form %} -

    Upload a Distribution

    -
    - {{ upload_form.as_p }} -
    -
    -{% endif %} -{% endblock %} - diff --git a/chishop/templates/djangopypi/release_upload_file.html b/chishop/templates/djangopypi/release_upload_file.html deleted file mode 100644 index 9085500..0000000 --- a/chishop/templates/djangopypi/release_upload_file.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "base_site.html" %} - -{% block bread_crumbs_1 %} -» {{ release.package }}{{ release }} -» Upload a File{% endblock %} - -{% block content %} -

    Upload a File for {{ release }}

    -{% if form %} -

    Upload a File

    -
    - {{ form.as_p }} -
    -
    -{% endif %} -{% endblock %} - diff --git a/chishop/templates/djangopypi/search.html b/chishop/templates/djangopypi/search.html deleted file mode 100644 index 6d188d6..0000000 --- a/chishop/templates/djangopypi/search.html +++ /dev/null @@ -1,5 +0,0 @@ -{% load i18n %} -
    - - -
    \ No newline at end of file diff --git a/chishop/templates/djangopypi/search_results.html b/chishop/templates/djangopypi/search_results.html deleted file mode 100644 index 8d54603..0000000 --- a/chishop/templates/djangopypi/search_results.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends "base_site.html" %} -{% load i18n highlight %} - -{% block bread_crumbs_1 %}» Search{% endblock %} - -{% block content %} -

    Search

    - -
    - - {{ form.as_table }} - - - - -
      - -
    - - {% if query %} -

    {% blocktrans %}Index of Packages Matching '{{ query }}'{% endblocktrans %}

    -
      - {% for result in page.object_list %} -
    • - {{ result.object }} - {% if result.object.latest %} -

      {% highlight result.object.latest.summary with query %}

      - {% endif %} -
    • - {% empty %} -
    • {% trans "There were no matches." %}
    • - {% endfor %} -
    - {% if page.has_previous or page.has_next %} -
    - {% if page.has_previous %}{% endif %}« Previous{% if page.has_previous %}{% endif %} - | - {% if page.has_next %}{% endif %}Next »{% if page.has_next %}{% endif %} -
    - {% endif %} - {% endif %} -
    -{% endblock content %} \ No newline at end of file diff --git a/chishop/templates/djangopypi/show_links.html b/chishop/templates/djangopypi/show_links.html deleted file mode 100644 index 57a1268..0000000 --- a/chishop/templates/djangopypi/show_links.html +++ /dev/null @@ -1,7 +0,0 @@ -Links for {{ dist_name }} -

    Links for {{ dist_name }}

    - -{% for release in releases %} -{{ release.filename }}
    -{% endfor %} - diff --git a/chishop/templates/djangopypi/show_version.html b/chishop/templates/djangopypi/show_version.html deleted file mode 100644 index d3b393a..0000000 --- a/chishop/templates/djangopypi/show_version.html +++ /dev/null @@ -1,5 +0,0 @@ -Links for {{ dist_name }} {{ version }} -

    Links for {{ dist_name }} {{ version }}

    - -{{ release.filename }}
    - diff --git a/chishop/templates/djangopypi/simple.html b/chishop/templates/djangopypi/simple.html deleted file mode 100644 index 6a21d5c..0000000 --- a/chishop/templates/djangopypi/simple.html +++ /dev/null @@ -1,5 +0,0 @@ -Simple Index -{% for dist in dists %} -{{ dist.name }}
    -{% endfor %} - diff --git a/chishop/templates/registration/activate.html b/chishop/templates/registration/activate.html deleted file mode 100644 index bc67771..0000000 --- a/chishop/templates/registration/activate.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base_site.html" %} - -{% block content %} -

    Activation Failed

    -

    - Activation with key {{activation_key}} failed. -

    -{% endblock %} diff --git a/chishop/templates/registration/activation_complete.html b/chishop/templates/registration/activation_complete.html deleted file mode 100644 index c8e8aca..0000000 --- a/chishop/templates/registration/activation_complete.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base_site.html" %} - -{% block content %} -

    Activation complete.

    -

    - Hello {{user}}, you are registered. - Go here to get back to the main page. -

    -{% endblock %} diff --git a/chishop/templates/registration/activation_email.txt b/chishop/templates/registration/activation_email.txt deleted file mode 100644 index 0a25329..0000000 --- a/chishop/templates/registration/activation_email.txt +++ /dev/null @@ -1,6 +0,0 @@ -Welcome to Chishop. - -Please click here to activate your account: -http://{{site}}/accounts/activate/{{activation_key}}/ - -Account has to be activated within {{expiration_days}} days. diff --git a/chishop/templates/registration/activation_email_subject.txt b/chishop/templates/registration/activation_email_subject.txt deleted file mode 100644 index 93618cc..0000000 --- a/chishop/templates/registration/activation_email_subject.txt +++ /dev/null @@ -1 +0,0 @@ -Account Activation - {{ site }} diff --git a/chishop/templates/registration/login.html b/chishop/templates/registration/login.html deleted file mode 100644 index a8ab11b..0000000 --- a/chishop/templates/registration/login.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base_site.html" %} - -{% block content %} -
    {% csrf_token %} - {{form.as_p}} - -
    -{% endblock %} diff --git a/chishop/templates/registration/logout.html b/chishop/templates/registration/logout.html deleted file mode 100644 index 06483a8..0000000 --- a/chishop/templates/registration/logout.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "base_site.html" %} - -{% block main_content %} -

    - {%trans "Logged out."%} -

    -{% endblock %} diff --git a/chishop/templates/registration/registration_closed.html b/chishop/templates/registration/registration_closed.html deleted file mode 100644 index c92e80b..0000000 --- a/chishop/templates/registration/registration_closed.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base_site.html" %} - -{% block content %} -

    Registration Closed

    -

    - Registration is disabled. -

    -{% endblock %} diff --git a/chishop/templates/registration/registration_complete.html b/chishop/templates/registration/registration_complete.html deleted file mode 100644 index d9a19cf..0000000 --- a/chishop/templates/registration/registration_complete.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base_site.html" %} - -{% block content %} -

    Registration complete

    -

    - An activation mail has been sent to you. -

    -{% endblock %} diff --git a/chishop/templates/registration/registration_form.html b/chishop/templates/registration/registration_form.html deleted file mode 100644 index bccf8d1..0000000 --- a/chishop/templates/registration/registration_form.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "base_site.html" %} - -{% block content %} -

    Register

    -
    {% csrf_token %} - {{form.as_p}} - -
    -{% endblock %} diff --git a/chishop/urls.py b/chishop/urls.py deleted file mode 100644 index 8f86544..0000000 --- a/chishop/urls.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -from django.conf.urls.defaults import patterns, url, include, handler404, handler500 -from django.conf import settings -from django.contrib import admin - -admin.autodiscover() - -urlpatterns = patterns('') - -# Serve static pages. -if settings.LOCAL_DEVELOPMENT: - urlpatterns += patterns("django.views", - url(r"^%s(?P.*)$" % settings.MEDIA_URL[1:], "static.serve", { - "document_root": settings.MEDIA_ROOT})) - -urlpatterns += patterns("", - # Admin interface - url(r'^admin/doc/', include("django.contrib.admindocs.urls")), - url(r'^admin/', include(admin.site.urls)), - - # Registration - url(r'^accounts/', include('registration.backends.default.urls')), - - url(r'^search/', 'haystack.views.basic_search', { - 'template': 'djangopypi/search_results.html', - }, name='haystack_search'), - - # The Chishop - url(r'', include("djangopypi.urls")), -) diff --git a/index.html b/index.html deleted file mode 100644 index 29cef02..0000000 --- a/index.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - ask/chishop @ GitHub - - - - - - - Fork me on GitHub - -
    - -
    - - - - -
    - -

    chishop - by ask

    - -
    - Simple PyPI server written in Django. -
    - -

    Simple PyPI server written in Django. Supports register/upload new projects and releases using distutils and installation of distributions on server using easy_install/pip.

    Dependencies

    -

    Django >= 1.0.2

    -

    Install

    -

    see README.

    -

    License

    -

    BSD

    -

    Authors

    -

    Ask Solem (askh@modwheel.net)

    -

    Contact

    -

    Ask Solem Hoel (ask@modwheel.net)

    - - -

    Download

    -

    - You can download this project in either - zip or - tar formats. -

    -

    You can also clone the project with Git - by running: -

    $ git clone git://github.com/ask/chishop
    -

    - - - -
    - - - - From 69923e397ab8c692837d28ac8ba3b0e8099dd303 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Sun, 11 Sep 2011 10:03:01 -0500 Subject: [PATCH 098/146] Moving djangopypi to the root --- src/djangopypi/AUTHORS => AUTHORS | 0 src/djangopypi/Changelog => Changelog | 0 src/djangopypi/LICENSE => LICENSE | 0 src/djangopypi/MANIFEST.in => MANIFEST.in | 0 src/djangopypi/README => README | 0 src/djangopypi/TODO => TODO | 0 {src/djangopypi/djangopypi => djangopypi}/__init__.py | 0 {src/djangopypi/djangopypi => djangopypi}/admin.py | 0 {src/djangopypi/djangopypi => djangopypi}/decorators.py | 0 {src/djangopypi/djangopypi => djangopypi}/feeds.py | 0 {src/djangopypi/djangopypi => djangopypi}/forms.py | 0 {src/djangopypi/djangopypi => djangopypi}/http.py | 0 {src/djangopypi/djangopypi => djangopypi}/management/__init__.py | 0 .../djangopypi => djangopypi}/management/commands/__init__.py | 0 .../management/commands/loadclassifiers.py | 0 .../djangopypi => djangopypi}/management/commands/ppadd.py | 0 .../djangopypi => djangopypi}/migrations/0001_initial.py | 0 .../djangopypi => djangopypi}/migrations/0002_refactoring.py | 0 {src/djangopypi/djangopypi => djangopypi}/migrations/__init__.py | 0 {src/djangopypi/djangopypi => djangopypi}/models.py | 0 {src/djangopypi/djangopypi => djangopypi}/search_indexes.py | 0 {src/djangopypi/djangopypi => djangopypi}/settings.py | 0 {src/djangopypi/djangopypi => djangopypi}/signals.py | 0 .../templates/djangopypi/haystack/package_text.txt | 0 .../templates/djangopypi/package_detail.html | 0 .../templates/djangopypi/package_detail_simple.html | 0 .../templates/djangopypi/package_doap.xml | 0 .../templates/djangopypi/package_list.html | 0 .../templates/djangopypi/package_list_simple.html | 0 .../templates/djangopypi/package_manage.html | 0 .../templates/djangopypi/package_manage_versions.html | 0 .../templates/djangopypi/release_detail.html | 0 .../templates/djangopypi/release_doap.xml | 0 .../templates/djangopypi/release_doap_fragment.xml | 0 .../templates/djangopypi/release_list.html | 0 .../templates/djangopypi/release_manage.html | 0 .../templates/djangopypi/release_manage_files.html | 0 .../templates/djangopypi/release_upload_file.html | 0 .../djangopypi/djangopypi => djangopypi}/templatetags/__init__.py | 0 .../djangopypi => djangopypi}/templatetags/safemarkup.py | 0 {src/djangopypi/djangopypi => djangopypi}/tests/__init__.py | 0 {src/djangopypi/djangopypi => djangopypi}/urls.py | 0 {src/djangopypi/djangopypi => djangopypi}/utils.py | 0 {src/djangopypi/djangopypi => djangopypi}/views/__init__.py | 0 {src/djangopypi/djangopypi => djangopypi}/views/distutils.py | 0 {src/djangopypi/djangopypi => djangopypi}/views/packages.py | 0 {src/djangopypi/djangopypi => djangopypi}/views/releases.py | 0 {src/djangopypi/djangopypi => djangopypi}/views/xmlrpc.py | 0 src/djangopypi/setup.cfg => setup.cfg | 0 src/djangopypi/setup.py => setup.py | 0 50 files changed, 0 insertions(+), 0 deletions(-) rename src/djangopypi/AUTHORS => AUTHORS (100%) rename src/djangopypi/Changelog => Changelog (100%) rename src/djangopypi/LICENSE => LICENSE (100%) rename src/djangopypi/MANIFEST.in => MANIFEST.in (100%) rename src/djangopypi/README => README (100%) rename src/djangopypi/TODO => TODO (100%) rename {src/djangopypi/djangopypi => djangopypi}/__init__.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/admin.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/decorators.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/feeds.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/forms.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/http.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/management/__init__.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/management/commands/__init__.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/management/commands/loadclassifiers.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/management/commands/ppadd.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/migrations/0001_initial.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/migrations/0002_refactoring.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/migrations/__init__.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/models.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/search_indexes.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/settings.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/signals.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/haystack/package_text.txt (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/package_detail.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/package_detail_simple.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/package_doap.xml (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/package_list.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/package_list_simple.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/package_manage.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/package_manage_versions.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/release_detail.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/release_doap.xml (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/release_doap_fragment.xml (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/release_list.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/release_manage.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/release_manage_files.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templates/djangopypi/release_upload_file.html (100%) rename {src/djangopypi/djangopypi => djangopypi}/templatetags/__init__.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/templatetags/safemarkup.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/tests/__init__.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/urls.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/utils.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/views/__init__.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/views/distutils.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/views/packages.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/views/releases.py (100%) rename {src/djangopypi/djangopypi => djangopypi}/views/xmlrpc.py (100%) rename src/djangopypi/setup.cfg => setup.cfg (100%) rename src/djangopypi/setup.py => setup.py (100%) diff --git a/src/djangopypi/AUTHORS b/AUTHORS similarity index 100% rename from src/djangopypi/AUTHORS rename to AUTHORS diff --git a/src/djangopypi/Changelog b/Changelog similarity index 100% rename from src/djangopypi/Changelog rename to Changelog diff --git a/src/djangopypi/LICENSE b/LICENSE similarity index 100% rename from src/djangopypi/LICENSE rename to LICENSE diff --git a/src/djangopypi/MANIFEST.in b/MANIFEST.in similarity index 100% rename from src/djangopypi/MANIFEST.in rename to MANIFEST.in diff --git a/src/djangopypi/README b/README similarity index 100% rename from src/djangopypi/README rename to README diff --git a/src/djangopypi/TODO b/TODO similarity index 100% rename from src/djangopypi/TODO rename to TODO diff --git a/src/djangopypi/djangopypi/__init__.py b/djangopypi/__init__.py similarity index 100% rename from src/djangopypi/djangopypi/__init__.py rename to djangopypi/__init__.py diff --git a/src/djangopypi/djangopypi/admin.py b/djangopypi/admin.py similarity index 100% rename from src/djangopypi/djangopypi/admin.py rename to djangopypi/admin.py diff --git a/src/djangopypi/djangopypi/decorators.py b/djangopypi/decorators.py similarity index 100% rename from src/djangopypi/djangopypi/decorators.py rename to djangopypi/decorators.py diff --git a/src/djangopypi/djangopypi/feeds.py b/djangopypi/feeds.py similarity index 100% rename from src/djangopypi/djangopypi/feeds.py rename to djangopypi/feeds.py diff --git a/src/djangopypi/djangopypi/forms.py b/djangopypi/forms.py similarity index 100% rename from src/djangopypi/djangopypi/forms.py rename to djangopypi/forms.py diff --git a/src/djangopypi/djangopypi/http.py b/djangopypi/http.py similarity index 100% rename from src/djangopypi/djangopypi/http.py rename to djangopypi/http.py diff --git a/src/djangopypi/djangopypi/management/__init__.py b/djangopypi/management/__init__.py similarity index 100% rename from src/djangopypi/djangopypi/management/__init__.py rename to djangopypi/management/__init__.py diff --git a/src/djangopypi/djangopypi/management/commands/__init__.py b/djangopypi/management/commands/__init__.py similarity index 100% rename from src/djangopypi/djangopypi/management/commands/__init__.py rename to djangopypi/management/commands/__init__.py diff --git a/src/djangopypi/djangopypi/management/commands/loadclassifiers.py b/djangopypi/management/commands/loadclassifiers.py similarity index 100% rename from src/djangopypi/djangopypi/management/commands/loadclassifiers.py rename to djangopypi/management/commands/loadclassifiers.py diff --git a/src/djangopypi/djangopypi/management/commands/ppadd.py b/djangopypi/management/commands/ppadd.py similarity index 100% rename from src/djangopypi/djangopypi/management/commands/ppadd.py rename to djangopypi/management/commands/ppadd.py diff --git a/src/djangopypi/djangopypi/migrations/0001_initial.py b/djangopypi/migrations/0001_initial.py similarity index 100% rename from src/djangopypi/djangopypi/migrations/0001_initial.py rename to djangopypi/migrations/0001_initial.py diff --git a/src/djangopypi/djangopypi/migrations/0002_refactoring.py b/djangopypi/migrations/0002_refactoring.py similarity index 100% rename from src/djangopypi/djangopypi/migrations/0002_refactoring.py rename to djangopypi/migrations/0002_refactoring.py diff --git a/src/djangopypi/djangopypi/migrations/__init__.py b/djangopypi/migrations/__init__.py similarity index 100% rename from src/djangopypi/djangopypi/migrations/__init__.py rename to djangopypi/migrations/__init__.py diff --git a/src/djangopypi/djangopypi/models.py b/djangopypi/models.py similarity index 100% rename from src/djangopypi/djangopypi/models.py rename to djangopypi/models.py diff --git a/src/djangopypi/djangopypi/search_indexes.py b/djangopypi/search_indexes.py similarity index 100% rename from src/djangopypi/djangopypi/search_indexes.py rename to djangopypi/search_indexes.py diff --git a/src/djangopypi/djangopypi/settings.py b/djangopypi/settings.py similarity index 100% rename from src/djangopypi/djangopypi/settings.py rename to djangopypi/settings.py diff --git a/src/djangopypi/djangopypi/signals.py b/djangopypi/signals.py similarity index 100% rename from src/djangopypi/djangopypi/signals.py rename to djangopypi/signals.py diff --git a/src/djangopypi/djangopypi/templates/djangopypi/haystack/package_text.txt b/djangopypi/templates/djangopypi/haystack/package_text.txt similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/haystack/package_text.txt rename to djangopypi/templates/djangopypi/haystack/package_text.txt diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_detail.html b/djangopypi/templates/djangopypi/package_detail.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/package_detail.html rename to djangopypi/templates/djangopypi/package_detail.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_detail_simple.html b/djangopypi/templates/djangopypi/package_detail_simple.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/package_detail_simple.html rename to djangopypi/templates/djangopypi/package_detail_simple.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml b/djangopypi/templates/djangopypi/package_doap.xml similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/package_doap.xml rename to djangopypi/templates/djangopypi/package_doap.xml diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_list.html b/djangopypi/templates/djangopypi/package_list.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/package_list.html rename to djangopypi/templates/djangopypi/package_list.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_list_simple.html b/djangopypi/templates/djangopypi/package_list_simple.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/package_list_simple.html rename to djangopypi/templates/djangopypi/package_list_simple.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_manage.html b/djangopypi/templates/djangopypi/package_manage.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/package_manage.html rename to djangopypi/templates/djangopypi/package_manage.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html b/djangopypi/templates/djangopypi/package_manage_versions.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/package_manage_versions.html rename to djangopypi/templates/djangopypi/package_manage_versions.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_detail.html b/djangopypi/templates/djangopypi/release_detail.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/release_detail.html rename to djangopypi/templates/djangopypi/release_detail.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml b/djangopypi/templates/djangopypi/release_doap.xml similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/release_doap.xml rename to djangopypi/templates/djangopypi/release_doap.xml diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_doap_fragment.xml b/djangopypi/templates/djangopypi/release_doap_fragment.xml similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/release_doap_fragment.xml rename to djangopypi/templates/djangopypi/release_doap_fragment.xml diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_list.html b/djangopypi/templates/djangopypi/release_list.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/release_list.html rename to djangopypi/templates/djangopypi/release_list.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_manage.html b/djangopypi/templates/djangopypi/release_manage.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/release_manage.html rename to djangopypi/templates/djangopypi/release_manage.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html b/djangopypi/templates/djangopypi/release_manage_files.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/release_manage_files.html rename to djangopypi/templates/djangopypi/release_manage_files.html diff --git a/src/djangopypi/djangopypi/templates/djangopypi/release_upload_file.html b/djangopypi/templates/djangopypi/release_upload_file.html similarity index 100% rename from src/djangopypi/djangopypi/templates/djangopypi/release_upload_file.html rename to djangopypi/templates/djangopypi/release_upload_file.html diff --git a/src/djangopypi/djangopypi/templatetags/__init__.py b/djangopypi/templatetags/__init__.py similarity index 100% rename from src/djangopypi/djangopypi/templatetags/__init__.py rename to djangopypi/templatetags/__init__.py diff --git a/src/djangopypi/djangopypi/templatetags/safemarkup.py b/djangopypi/templatetags/safemarkup.py similarity index 100% rename from src/djangopypi/djangopypi/templatetags/safemarkup.py rename to djangopypi/templatetags/safemarkup.py diff --git a/src/djangopypi/djangopypi/tests/__init__.py b/djangopypi/tests/__init__.py similarity index 100% rename from src/djangopypi/djangopypi/tests/__init__.py rename to djangopypi/tests/__init__.py diff --git a/src/djangopypi/djangopypi/urls.py b/djangopypi/urls.py similarity index 100% rename from src/djangopypi/djangopypi/urls.py rename to djangopypi/urls.py diff --git a/src/djangopypi/djangopypi/utils.py b/djangopypi/utils.py similarity index 100% rename from src/djangopypi/djangopypi/utils.py rename to djangopypi/utils.py diff --git a/src/djangopypi/djangopypi/views/__init__.py b/djangopypi/views/__init__.py similarity index 100% rename from src/djangopypi/djangopypi/views/__init__.py rename to djangopypi/views/__init__.py diff --git a/src/djangopypi/djangopypi/views/distutils.py b/djangopypi/views/distutils.py similarity index 100% rename from src/djangopypi/djangopypi/views/distutils.py rename to djangopypi/views/distutils.py diff --git a/src/djangopypi/djangopypi/views/packages.py b/djangopypi/views/packages.py similarity index 100% rename from src/djangopypi/djangopypi/views/packages.py rename to djangopypi/views/packages.py diff --git a/src/djangopypi/djangopypi/views/releases.py b/djangopypi/views/releases.py similarity index 100% rename from src/djangopypi/djangopypi/views/releases.py rename to djangopypi/views/releases.py diff --git a/src/djangopypi/djangopypi/views/xmlrpc.py b/djangopypi/views/xmlrpc.py similarity index 100% rename from src/djangopypi/djangopypi/views/xmlrpc.py rename to djangopypi/views/xmlrpc.py diff --git a/src/djangopypi/setup.cfg b/setup.cfg similarity index 100% rename from src/djangopypi/setup.cfg rename to setup.cfg diff --git a/src/djangopypi/setup.py b/setup.py similarity index 100% rename from src/djangopypi/setup.py rename to setup.py From 6121fd07237c54cb341efaa429310575b9e1679e Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Sun, 11 Sep 2011 10:08:01 -0500 Subject: [PATCH 099/146] Formatted authors.rst, updated setup files --- AUTHORS | 16 ---------------- AUTHORS.rst | 19 +++++++++++++++++++ Changelog => Changelog.rst | 0 README => README.rst | 0 setup.cfg | 2 +- setup.py | 2 +- 6 files changed, 21 insertions(+), 18 deletions(-) delete mode 100644 AUTHORS create mode 100644 AUTHORS.rst rename Changelog => Changelog.rst (100%) rename README => README.rst (100%) diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 24801b5..0000000 --- a/AUTHORS +++ /dev/null @@ -1,16 +0,0 @@ -Ask Solem -Rune Halvorsen -Russell Sim -Brian Rosner -Hugo Lopes Tavares -Sverre Johansen -Bo Shi -Carl Meyer -Vinícius das Chagas Silva -Vanderson Mota dos Santos -Stefan Foulis -Michael Richardson -Benjamin Liles -Halldór Rúnarsson -Jannis Leidel -Sebastien Fievet diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..7e741f2 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,19 @@ +Authors/Contributors +-------------------- + +* Ask Solem +* Rune Halvorsen +* Russell Sim +* Brian Rosner +* Hugo Lopes Tavares +* Sverre Johansen +* Bo Shi +* Carl Meyer +* Vinícius das Chagas Silva +* Vanderson Mota dos Santos +* Stefan Foulis +* Michael Richardson +* Benjamin Liles +* Halldór Rúnarsson +* Jannis Leidel +* Sebastien Fievet diff --git a/Changelog b/Changelog.rst similarity index 100% rename from Changelog rename to Changelog.rst diff --git a/README b/README.rst similarity index 100% rename from README rename to README.rst diff --git a/setup.cfg b/setup.cfg index bd36a8d..306ec45 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ [bdist_rpm] -doc_files = AUTHORS Changelog LICENSE README TODO \ No newline at end of file +doc_files = AUTHORS.rst Changelog.rst LICENSE README.rst diff --git a/setup.py b/setup.py index 8738d88..c9114a5 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def fread(fname): name='djangopypi', version=version, description="A Django application that emulates the Python Package Index.", - long_description=fread("README")+"\n\n"+fread('Changelog')+"\n\n"+fread('TODO'), + long_description=fread("README.rst")+"\n\n"+fread('Changelog.rst')+"\n\n"+fread('AUTHORS.rst'), classifiers=[ "Framework :: Django", "Development Status :: 4 - Beta", From db8546235146bbbc09504783925d212c02ed3e31 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Sun, 11 Sep 2011 10:17:40 -0500 Subject: [PATCH 100/146] Removed bootstrap/buildout (now in buildout branch), removed TODO in favor of github issues --- TODO | 24 ---------- bootstrap.py | 121 --------------------------------------------------- buildout.cfg | 21 --------- 3 files changed, 166 deletions(-) delete mode 100644 TODO delete mode 100644 bootstrap.py delete mode 100644 buildout.cfg diff --git a/TODO b/TODO deleted file mode 100644 index 2f93a8a..0000000 --- a/TODO +++ /dev/null @@ -1,24 +0,0 @@ -Roadmap -======= - -0.5 ---- - -* Ratings/comments views/admin -* PKG-INFO view of a release (auto generated from package_info field) - -Future ------- - -* Documentation hosting/upload -* PEP-381: Mirroring integration for PyPI -* API to submit test reports for smoke test bots. Like CPAN Testers. - Platform/version/matrix etc. -* Dependency graphs -* Package file browser (like CPAN) - -Documentation -------------- - -* Write a tutorial on how to set up the server, registering projects, and - how to upload releases. diff --git a/bootstrap.py b/bootstrap.py deleted file mode 100644 index b964024..0000000 --- a/bootstrap.py +++ /dev/null @@ -1,121 +0,0 @@ -############################################################################## -# -# Copyright (c) 2006 Zope Corporation and Contributors. -# All Rights Reserved. -# -# This software is subject to the provisions of the Zope Public License, -# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. -# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED -# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS -# FOR A PARTICULAR PURPOSE. -# -############################################################################## -"""Bootstrap a buildout-based project - -Simply run this script in a directory containing a buildout.cfg. -The script accepts buildout command-line options, so you can -use the -c option to specify an alternate configuration file. - -$Id$ -""" - -import os, shutil, sys, tempfile, urllib2 -from optparse import OptionParser - -tmpeggs = tempfile.mkdtemp() - -is_jython = sys.platform.startswith('java') - -# parsing arguments -parser = OptionParser() -parser.add_option("-v", "--version", dest="version", - help="use a specific zc.buildout version") -parser.add_option("-d", "--distribute", - action="store_true", dest="distribute", default=False, - help="Use Disribute rather than Setuptools.") - -parser.add_option("-c", None, action="store", dest="config_file", - help=("Specify the path to the buildout configuration " - "file to be used.")) - -options, args = parser.parse_args() - -# if -c was provided, we push it back into args for buildout' main function -if options.config_file is not None: - args += ['-c', options.config_file] - -if options.version is not None: - VERSION = '==%s' % options.version -else: - VERSION = '' - -USE_DISTRIBUTE = options.distribute -args = args + ['bootstrap'] - -to_reload = False -try: - import pkg_resources - if not hasattr(pkg_resources, '_distribute'): - to_reload = True - raise ImportError -except ImportError: - ez = {} - if USE_DISTRIBUTE: - exec urllib2.urlopen('http://python-distribute.org/distribute_setup.py' - ).read() in ez - ez['use_setuptools'](to_dir=tmpeggs, download_delay=0, no_fake=True) - else: - exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' - ).read() in ez - ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) - - if to_reload: - reload(pkg_resources) - else: - import pkg_resources - -if sys.platform == 'win32': - def quote(c): - if ' ' in c: - return '"%s"' % c # work around spawn lamosity on windows - else: - return c -else: - def quote (c): - return c - -cmd = 'from setuptools.command.easy_install import main; main()' -ws = pkg_resources.working_set - -if USE_DISTRIBUTE: - requirement = 'distribute' -else: - requirement = 'setuptools' - -if is_jython: - import subprocess - - assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', - quote(tmpeggs), 'zc.buildout' + VERSION], - env=dict(os.environ, - PYTHONPATH= - ws.find(pkg_resources.Requirement.parse(requirement)).location - ), - ).wait() == 0 - -else: - assert os.spawnle( - os.P_WAIT, sys.executable, quote (sys.executable), - '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout' + VERSION, - dict(os.environ, - PYTHONPATH= - ws.find(pkg_resources.Requirement.parse(requirement)).location - ), - ) == 0 - -ws.add_entry(tmpeggs) -ws.require('zc.buildout' + VERSION) -import zc.buildout.buildout -zc.buildout.buildout.main(args) -shutil.rmtree(tmpeggs) diff --git a/buildout.cfg b/buildout.cfg deleted file mode 100644 index 8b91acf..0000000 --- a/buildout.cfg +++ /dev/null @@ -1,21 +0,0 @@ -[buildout] -parts = django -unzip = true -find-links = http://bitbucket.org/ubernostrum/django-registration/downloads/django-registration-0.8-alpha-1.tar.gz -eggs = - djangopypi - South - django-registration==0.8-alpha-1 - django-haystack - Whoosh -develop = - src/djangopypi - -[django] -recipe = djangorecipe -version = 1.2.5 -settings = settings -eggs = ${buildout:eggs} -test = djangopypi -project = chishop -wsgi = true From 357c3b9730a9cdd04778b2c960e9c7d641cf0b5d Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Sun, 11 Sep 2011 14:03:46 -0500 Subject: [PATCH 101/146] Added masterindex and mirrorlog --- .../0003_add_masterindex_mirrorlog.py | 142 ++++++++++++++++++ djangopypi/models.py | 10 ++ 2 files changed, 152 insertions(+) create mode 100644 djangopypi/migrations/0003_add_masterindex_mirrorlog.py diff --git a/djangopypi/migrations/0003_add_masterindex_mirrorlog.py b/djangopypi/migrations/0003_add_masterindex_mirrorlog.py new file mode 100644 index 0000000..80bb25c --- /dev/null +++ b/djangopypi/migrations/0003_add_masterindex_mirrorlog.py @@ -0,0 +1,142 @@ +# 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 'MirrorLog' + db.create_table('djangopypi_mirrorlog', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('master', self.gf('django.db.models.fields.related.ForeignKey')(related_name='logs', to=orm['djangopypi.MasterIndex'])), + ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + )) + db.send_create_signal('djangopypi', ['MirrorLog']) + + # Adding M2M table for field releases_added on 'MirrorLog' + db.create_table('djangopypi_mirrorlog_releases_added', ( + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), + ('mirrorlog', models.ForeignKey(orm['djangopypi.mirrorlog'], null=False)), + ('release', models.ForeignKey(orm['djangopypi.release'], null=False)) + )) + db.create_unique('djangopypi_mirrorlog_releases_added', ['mirrorlog_id', 'release_id']) + + # Adding model 'MasterIndex' + db.create_table('djangopypi_masterindex', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), + ('url', self.gf('django.db.models.fields.CharField')(max_length=255)), + )) + db.send_create_signal('djangopypi', ['MasterIndex']) + + + def backwards(self, orm): + + # Deleting model 'MirrorLog' + db.delete_table('djangopypi_mirrorlog') + + # Removing M2M table for field releases_added on 'MirrorLog' + db.delete_table('djangopypi_mirrorlog_releases_added') + + # Deleting model 'MasterIndex' + db.delete_table('djangopypi_masterindex') + + + 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'}) + }, + 'djangopypi.classifier': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Classifier'}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}) + }, + 'djangopypi.distribution': { + 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['djangopypi.Release']"}), + 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'djangopypi.masterindex': { + 'Meta': {'object_name': 'MasterIndex'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'djangopypi.mirrorlog': { + 'Meta': {'object_name': 'MirrorLog'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['djangopypi.MasterIndex']"}), + 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'mirror_sources'", 'symmetrical': 'False', 'to': "orm['djangopypi.Release']"}) + }, + 'djangopypi.package': { + 'Meta': {'ordering': "['name']", 'object_name': 'Package'}, + 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_maintained'", 'blank': 'True', 'to': "orm['auth.User']"}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), + 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) + }, + 'djangopypi.release': { + 'Meta': {'ordering': "['-created']", 'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), + 'package_info': ('djangopypi.models.PackageInfoField', [], {}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djangopypi.review': { + 'Meta': {'object_name': 'Review'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + } + } + + complete_apps = ['djangopypi'] diff --git a/djangopypi/models.py b/djangopypi/models.py index affeda7..5196650 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -183,3 +183,13 @@ class Meta: add_introspection_rules([], ["^djangopypi\.models\.PackageInfoField"]) except ImportError: pass + + +class MasterIndex(models.Model): + title = models.CharField(max_length=255) + url = models.CharField(max_length=255) + +class MirrorLog(models.Model): + master = models.ForeignKey(MasterIndex, related_name='logs') + created = models.DateTimeField(auto_now_add=True) + releases_added = models.ManyToManyField(Release, related_name='mirror_sources') From 1e42aa59c0b74624767da4e71bee5ce1177492f1 Mon Sep 17 00:00:00 2001 From: Benjamin Liles Date: Mon, 12 Sep 2011 08:44:34 -0500 Subject: [PATCH 102/146] Added new models to admin and removed user requirement on distributions Somewhat working update_mirrors admin command --- djangopypi/admin.py | 8 ++ .../management/commands/update_mirrors.py | 82 +++++++++++++ .../0004_allow_anonymous_distributions.py | 115 ++++++++++++++++++ .../0005_allow_null_distribution_uploader.py | 115 ++++++++++++++++++ djangopypi/models.py | 16 ++- 5 files changed, 333 insertions(+), 3 deletions(-) create mode 100644 djangopypi/management/commands/update_mirrors.py create mode 100644 djangopypi/migrations/0004_allow_anonymous_distributions.py create mode 100644 djangopypi/migrations/0005_allow_null_distribution_uploader.py diff --git a/djangopypi/admin.py b/djangopypi/admin.py index bdb43fc..b1d4ee9 100644 --- a/djangopypi/admin.py +++ b/djangopypi/admin.py @@ -1,8 +1,16 @@ +from django.conf import settings from django.contrib import admin + from djangopypi.models import * + + admin.site.register(Package) admin.site.register(Release) admin.site.register(Classifier) admin.site.register(Distribution) admin.site.register(Review) + +if getattr(settings,'DJANGOPYPI_MIRRORING', False): + admin.site.register(MasterIndex) + admin.site.register(MirrorLog) diff --git a/djangopypi/management/commands/update_mirrors.py b/djangopypi/management/commands/update_mirrors.py new file mode 100644 index 0000000..dc670a9 --- /dev/null +++ b/djangopypi/management/commands/update_mirrors.py @@ -0,0 +1,82 @@ +""" + +""" + +from datetime import datetime +import urllib +import os.path +from time import mktime +from xmlrpclib import ServerProxy + +from django.core.files import File +from django.core.management.base import BaseCommand +from djangopypi.models import * + + + +class Command(BaseCommand): + help = """Load all classifiers from pypi. If any arguments are given, +they will be used as paths or urls for classifiers instead of using the +official pypi list url""" + + def handle(self, *args, **options): + for index in MasterIndex.objects.all(): + rpc = ServerProxy(index.url) + try: + last = index.logs.latest() + except: + print 'Error getting latest update for %s' % (str(index),) + continue + + newer = MirrorLog.objects.create(master=index, created=datetime.now()) + + print 'Looking at changes since: %d' % (mktime(last.created.timetuple()),) + for update in rpc.changelog(int(mktime(last.created.timetuple()))): + print str(update) + package, created = Package.objects.get_or_create(name=update[0]) + + if update[3] == 'new release': + release, created = Release.objects.get_or_create( + package=package, version=update[1]) + + if created: + newer.releases_added.add(release) + + pkg_data = rpc.release_data(update[0],update[1]) + print 'Retrieved release data: %s' % (str(pkg_data),) + if 'name' in pkg_data: + del pkg_data['name'] + if 'version' in pkg_data: + del pkg_data['version'] + + for key, value in pkg_data.iteritems(): + if key != 'classifier': + release.package_info[key] = value + else: + release.package_info.setlist(key, value) + + release.save() + elif update[3] == 'remove': + Release.objects.filter(package=package, version=update[1]).delete() + elif update[3].startswith('add source file'): + try: + release = Release.objects.get(package=package, + version=update[1]) + except Release.DoesNotExist: + continue + downloads = rpc.release_urls(update[0], update[1]) + for download in downloads: + print 'Download data: %s' % (str(download),) + dist, created = Distribution.objects.get_or_create(release=release, + filetype=download['packagetype'], + pyversion=download['python_version']) + + if not created and dist.md5_digest != download['md5_digest']: + dist.md5_digest = download['md5_digest'] + dist.content = File(urllib.urlopen(download['url'])) + + dist.comment = download['comment_text'] + + dist.save() + + \ No newline at end of file diff --git a/djangopypi/migrations/0004_allow_anonymous_distributions.py b/djangopypi/migrations/0004_allow_anonymous_distributions.py new file mode 100644 index 0000000..581da47 --- /dev/null +++ b/djangopypi/migrations/0004_allow_anonymous_distributions.py @@ -0,0 +1,115 @@ +# 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): + + # Changing field 'MirrorLog.created' + db.alter_column('djangopypi_mirrorlog', 'created', self.gf('django.db.models.fields.DateTimeField')()) + + + def backwards(self, orm): + + # Changing field 'MirrorLog.created' + db.alter_column('djangopypi_mirrorlog', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True)) + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'djangopypi.classifier': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Classifier'}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}) + }, + 'djangopypi.distribution': { + 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['djangopypi.Release']"}), + 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'blank': 'True'}) + }, + 'djangopypi.masterindex': { + 'Meta': {'object_name': 'MasterIndex'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'djangopypi.mirrorlog': { + 'Meta': {'object_name': 'MirrorLog'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'default': "'now'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['djangopypi.MasterIndex']"}), + 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'mirror_sources'", 'blank': 'True', 'to': "orm['djangopypi.Release']"}) + }, + 'djangopypi.package': { + 'Meta': {'ordering': "['name']", 'object_name': 'Package'}, + 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_maintained'", 'blank': 'True', 'to': "orm['auth.User']"}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), + 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) + }, + 'djangopypi.release': { + 'Meta': {'ordering': "['-created']", 'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), + 'package_info': ('djangopypi.models.PackageInfoField', [], {}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djangopypi.review': { + 'Meta': {'object_name': 'Review'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + } + } + + complete_apps = ['djangopypi'] diff --git a/djangopypi/migrations/0005_allow_null_distribution_uploader.py b/djangopypi/migrations/0005_allow_null_distribution_uploader.py new file mode 100644 index 0000000..99687e3 --- /dev/null +++ b/djangopypi/migrations/0005_allow_null_distribution_uploader.py @@ -0,0 +1,115 @@ +# 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): + + # Changing field 'Distribution.uploader' + db.alter_column('djangopypi_distribution', 'uploader_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)) + + + def backwards(self, orm): + + # User chose to not deal with backwards NULL issues for 'Distribution.uploader' + raise RuntimeError("Cannot reverse this migration. 'Distribution.uploader' and its values cannot be restored.") + + + 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'}) + }, + 'djangopypi.classifier': { + 'Meta': {'ordering': "('name',)", 'object_name': 'Classifier'}, + 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}) + }, + 'djangopypi.distribution': { + 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), + 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), + 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['djangopypi.Release']"}), + 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) + }, + 'djangopypi.masterindex': { + 'Meta': {'object_name': 'MasterIndex'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) + }, + 'djangopypi.mirrorlog': { + 'Meta': {'object_name': 'MirrorLog'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'default': "'now'"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['djangopypi.MasterIndex']"}), + 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'mirror_sources'", 'blank': 'True', 'to': "orm['djangopypi.Release']"}) + }, + 'djangopypi.package': { + 'Meta': {'ordering': "['name']", 'object_name': 'Package'}, + 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'maintainers': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_maintained'", 'blank': 'True', 'to': "orm['auth.User']"}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), + 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) + }, + 'djangopypi.release': { + 'Meta': {'ordering': "['-created']", 'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, + 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), + 'package_info': ('djangopypi.models.PackageInfoField', [], {}), + 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'djangopypi.review': { + 'Meta': {'object_name': 'Review'}, + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + } + } + + complete_apps = ['djangopypi'] diff --git a/djangopypi/models.py b/djangopypi/models.py index 5196650..1f5d4bd 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -141,7 +141,7 @@ class Distribution(models.Model): comment = models.CharField(max_length=255, blank=True) signature = models.TextField(blank=True) created = models.DateTimeField(auto_now_add=True, editable=False) - uploader = models.ForeignKey(User, editable=False) + uploader = models.ForeignKey(User, editable=False, blank=True, null=True) @property def filename(self): @@ -188,8 +188,18 @@ class Meta: class MasterIndex(models.Model): title = models.CharField(max_length=255) url = models.CharField(max_length=255) + + def __unicode__(self): + return self.title class MirrorLog(models.Model): master = models.ForeignKey(MasterIndex, related_name='logs') - created = models.DateTimeField(auto_now_add=True) - releases_added = models.ManyToManyField(Release, related_name='mirror_sources') + created = models.DateTimeField(default='now') + releases_added = models.ManyToManyField(Release, blank=True, + related_name='mirror_sources') + + def __unicode__(self): + return '%s (%s)' % (self.master, str(self.created),) + + class Meta: + get_latest_by = "created" From fd39451bf78d1b5164c4b83723f000d3640fa8c9 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 11 Mar 2012 16:08:33 -0400 Subject: [PATCH 103/146] Updated the Manifiest.in to have the appropriate names. --- MANIFEST.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index b4b66ef..8ff353b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ -include README -include AUTHORS -include Changelog +include README.rst +include AUTHORS.rst +include Changelog.rst include LICENSE include TODO -include MANIFEST.in \ No newline at end of file +# include MANIFEST.in \ No newline at end of file From fa969140bcfd5d032c1f3066c996fd0d0cf9c06b Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 11 Mar 2012 16:09:39 -0400 Subject: [PATCH 104/146] Initial change to add multi-user support: User FK, and name-user uniqueness. Added a private flag as well. --- djangopypi/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/djangopypi/models.py b/djangopypi/models.py index 1f5d4bd..ba6603f 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -53,20 +53,20 @@ def __unicode__(self): return self.name class Package(models.Model): - name = models.CharField(max_length=255, unique=True, primary_key=True, - editable=False) + owner = models.ForeignKey(User, related_name="packages_owned") + name = models.CharField(max_length=255) auto_hide = models.BooleanField(default=True, blank=False) allow_comments = models.BooleanField(default=True, blank=False) - owners = models.ManyToManyField(User, blank=True, - related_name="packages_owned") maintainers = models.ManyToManyField(User, blank=True, related_name="packages_maintained") + private = models.BooleanField(default=False) class Meta: verbose_name = _(u"package") verbose_name_plural = _(u"packages") get_latest_by = "releases__latest" ordering = ['name',] + unique_together = ('owner', 'name',) def __unicode__(self): return self.name From df1675d3f38aa534560209f3086676c51203b449 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 11 Mar 2012 16:09:55 -0400 Subject: [PATCH 105/146] Version bump to 0.4.4b2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c9114a5..e969dc8 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.3' +version = '0.4.4b2' setup( name='djangopypi', From 38b30650a934f8838de004072b4609864b68bd49 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 30 Mar 2012 19:30:58 -0400 Subject: [PATCH 106/146] Don't need comments in this. --- djangopypi/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangopypi/models.py b/djangopypi/models.py index ba6603f..0540391 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -56,7 +56,7 @@ class Package(models.Model): owner = models.ForeignKey(User, related_name="packages_owned") name = models.CharField(max_length=255) auto_hide = models.BooleanField(default=True, blank=False) - allow_comments = models.BooleanField(default=True, blank=False) + # allow_comments = models.BooleanField(default=True, blank=False) maintainers = models.ManyToManyField(User, blank=True, related_name="packages_maintained") private = models.BooleanField(default=False) From af38bd7912ac8b554e611bafe552103956bae33f Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 30 Mar 2012 19:32:19 -0400 Subject: [PATCH 107/146] Adding in user parameters for all the views --- djangopypi/views/distutils.py | 18 ++++++------------ djangopypi/views/packages.py | 18 ++++++++++++------ djangopypi/views/releases.py | 26 +++++++++++++++----------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/djangopypi/views/distutils.py b/djangopypi/views/distutils.py index 618be15..54d70f2 100644 --- a/djangopypi/views/distutils.py +++ b/djangopypi/views/distutils.py @@ -20,10 +20,7 @@ def submit_package_or_release(user, post_data, files): """Registers/updates a package or release""" try: - package = Package.objects.get(name=post_data['name']) - if user not in package.owners.all(): - return HttpResponseForbidden( - "That package is owned by someone else!") + package = Package.objects.get(owner=user, name=post_data['name']) except Package.DoesNotExist: package = None @@ -70,22 +67,19 @@ def submit_package_or_release(user, post_data, files): @basic_auth @transaction.commit_manually -def register_or_upload(request): +def register_or_upload(request, username=None): if request.method != 'POST': return HttpResponseBadRequest('Only post requests are supported') - - name = request.POST.get('name',None).strip() + name = request.POST.get('name', None).strip() if not name: return HttpResponseBadRequest('No package name specified') - try: - package = Package.objects.get(name=name) + package = Package.objects.get(owner=request.user, name=name) except Package.DoesNotExist: - package = Package.objects.create(name=name) - package.owners.add(request.user) + package = Package.objects.create(owner=request.user, name=name) - if (request.user not in package.owners.all() and + if (request.user != package.owner and request.user not in package.maintainers.all()): return HttpResponseForbidden('You are not an owner/maintainer of %s' % (package.name,)) diff --git a/djangopypi/views/packages.py b/djangopypi/views/packages.py index 70b2ba8..97fbfc2 100644 --- a/djangopypi/views/packages.py +++ b/djangopypi/views/packages.py @@ -11,23 +11,26 @@ from djangopypi.forms import SimplePackageSearchForm, PackageForm - def index(request, **kwargs): kwargs.setdefault('template_object_name', 'package') - kwargs.setdefault('queryset', Package.objects.all()) + kwargs.setdefault('queryset', Package.objects.filter(owner=request.user)) + kwargs.pop('username') return list_detail.object_list(request, **kwargs) def simple_index(request, **kwargs): + kwargs.pop('username') kwargs.setdefault('template_name', 'djangopypi/package_list_simple.html') return index(request, **kwargs) def details(request, package, **kwargs): kwargs.setdefault('template_object_name', 'package') - kwargs.setdefault('queryset', Package.objects.all()) + kwargs.setdefault('queryset', Package.objects.filter(owner=request.user)) + kwargs.pop('username') return list_detail.object_detail(request, object_id=package, **kwargs) def simple_details(request, package, **kwargs): kwargs.setdefault('template_name', 'djangopypi/package_detail_simple.html') + kwargs.pop('username') try: return details(request, package, **kwargs) except Http404, e: @@ -40,6 +43,7 @@ def simple_details(request, package, **kwargs): def doap(request, package, **kwargs): kwargs.setdefault('template_name', 'djangopypi/package_doap.xml') kwargs.setdefault('mimetype', 'text/xml') + kwargs.pop('username') return details(request, package, **kwargs) def search(request, **kwargs): @@ -47,15 +51,16 @@ def search(request, **kwargs): form = SimplePackageSearchForm(request.POST) else: form = SimplePackageSearchForm(request.GET) - + kwargs.pop('username') if form.is_valid(): q = form.cleaned_data['query'] - kwargs['queryset'] = Package.objects.filter(Q(name__contains=q) | + kwargs['queryset'] = Package.objects.filter(owner=request.user).filter(Q(name__contains=q) | Q(releases__package_info__contains=q)).distinct() return index(request, **kwargs) @user_owns_package() def manage(request, package, **kwargs): + kwargs.pop('username') kwargs['object_id'] = package kwargs.setdefault('form_class', PackageForm) kwargs.setdefault('template_name', 'djangopypi/package_manage.html') @@ -65,7 +70,8 @@ def manage(request, package, **kwargs): @user_maintains_package() def manage_versions(request, package, **kwargs): - package = get_object_or_404(Package, name=package) + kwargs.pop('username') + package = get_object_or_404(Package, owner=request.user, name=package) kwargs.setdefault('formset_factory_kwargs', {}) kwargs['formset_factory_kwargs'].setdefault('fields', ('hidden',)) kwargs['formset_factory_kwargs']['extra'] = 0 diff --git a/djangopypi/views/releases.py b/djangopypi/views/releases.py index 3a398c9..21ecf22 100644 --- a/djangopypi/views/releases.py +++ b/djangopypi/views/releases.py @@ -14,19 +14,21 @@ def index(request, **kwargs): kwargs.setdefault('template_object_name','release') - kwargs.setdefault('queryset',Release.objects.filter(hidden=False)) + params = dict(package__owner=request.user, hidden=False) + kwargs.setdefault('queryset', Release.objects.filter(**params)) + kwargs.pop('username') return list_detail.object_list(request, **kwargs) def details(request, package, version, **kwargs): - release = get_object_or_404(Package, name=package).get_release(version) + release = get_object_or_404(Package, owner=request.user, name=package).get_release(version) if not release: raise Http404('Version %s does not exist for %s' % (version, package,)) - kwargs.setdefault('template_object_name','release') - kwargs.setdefault('template_name','djangopypi/release_detail.html') - kwargs.setdefault('extra_context',{}) + kwargs.setdefault('template_object_name', 'release') + kwargs.setdefault('template_name', 'djangopypi/release_detail.html') + kwargs.setdefault('extra_context', {}) kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) kwargs['extra_context'][kwargs['template_object_name']] = release @@ -42,7 +44,8 @@ def doap(request, package, version, **kwargs): @user_maintains_package() def manage(request, package, version, **kwargs): - release = get_object_or_404(Package, name=package).get_release(version) + kwargs.pop('username') + release = get_object_or_404(Package, owner=request.user, name=package).get_release(version) if not release: raise Http404('Version %s does not exist for %s' % (version, @@ -58,12 +61,13 @@ def manage(request, package, version, **kwargs): @user_maintains_package() def manage_metadata(request, package, version, **kwargs): + kwargs.pop('username') kwargs.setdefault('template_name', 'djangopypi/release_manage.html') kwargs.setdefault('template_object_name', 'release') - kwargs.setdefault('extra_context',{}) - kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) + kwargs.setdefault('extra_context', {}) + kwargs.setdefault('mimetype', settings.DEFAULT_CONTENT_TYPE) - release = get_object_or_404(Package, name=package).get_release(version) + release = get_object_or_404(Package, owner=request.user, name=package).get_release(version) if not release: raise Http404('Version %s does not exist for %s' % (version, @@ -110,7 +114,7 @@ def manage_metadata(request, package, version, **kwargs): @user_maintains_package() def manage_files(request, package, version, **kwargs): - release = get_object_or_404(Package, name=package).get_release(version) + release = get_object_or_404(Package, owner=request.user, name=package).get_release(version) if not release: raise Http404('Version %s does not exist for %s' % (version, @@ -150,7 +154,7 @@ def manage_files(request, package, version, **kwargs): @user_maintains_package() def upload_file(request, package, version, **kwargs): - release = get_object_or_404(Package, name=package).get_release(version) + release = get_object_or_404(Package, owner=request.user, name=package).get_release(version) if not release: raise Http404('Version %s does not exist for %s' % (version, From c93f804cecf85248d8ffa6fb1bcc65dc5d684dc8 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 30 Mar 2012 19:32:37 -0400 Subject: [PATCH 108/146] Using the new url structure. --- djangopypi/templates/djangopypi/release_list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djangopypi/templates/djangopypi/release_list.html b/djangopypi/templates/djangopypi/release_list.html index f586d5e..77d6b02 100644 --- a/djangopypi/templates/djangopypi/release_list.html +++ b/djangopypi/templates/djangopypi/release_list.html @@ -11,7 +11,7 @@ {% for release in release_list %} {{ release.created|date:"Y-m-d" }} - {{ release }} + {{request.user}} {{release.package.name}} {{release.version}} {{ release }} {{ release.summary|truncatewords:10 }} {% endfor %} From e126deeb4cd37da88d6a57510050ceb0e14636b8 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 5 Apr 2012 05:09:31 -0400 Subject: [PATCH 109/146] Convert existing settings into a dict and raise deprecation warnings about existing settings. --- djangopypi/settings.py | 134 +++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/djangopypi/settings.py b/djangopypi/settings.py index 99fc82b..4f3ca6b 100644 --- a/djangopypi/settings.py +++ b/djangopypi/settings.py @@ -1,17 +1,14 @@ from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.utils.importlib import import_module -# This is disabled on pypi.python.org, can be useful if you make mistakes -if not hasattr(settings,'DJANGOPYPI_ALLOW_VERSION_OVERWRITE'): - settings.DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False +import warnings -""" The upload_to argument for the file field in releases. This can either be -a string for a path relative to your media folder or a callable. For more -information, see http://docs.djangoproject.com/ """ -if not hasattr(settings,'DJANGOPYPI_RELEASE_UPLOAD_TO'): - settings.DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' - -if not hasattr(settings,'DJANGOPYPI_OS_NAMES'): - settings.DJANGOPYPI_OS_NAMES = ( +DEFAULT_SETTINGS = { + 'ALLOW_VERSION_OVERWRITE': False, # This is disabled on pypi.python.org, can be useful if you make mistakes + 'RELEASE_UPLOAD_TO': 'dists', # The upload_to argument for the file field in releases. This can either be a string for a path relative to your media folder or a callable. + 'RELEASE_FILE_STORAGE': settings.DEFAULT_FILE_STORAGE, + 'OS_NAMES': ( ("aix", "AIX"), ("beos", "BeOS"), ("debian", "Debian Linux"), @@ -28,30 +25,24 @@ ("solaris", "SUN Solaris"), ("suse", "SuSE Linux"), ("yellowdog", "Yellow Dog Linux"), - ) - -if not hasattr(settings,'DJANGOPYPI_ARCHITECTURES'): - settings.DJANGOPYPI_ARCHITECTURES = ( + ), + 'ARCHITECTURES': ( ("alpha", "Alpha"), ("hppa", "HPPA"), ("ix86", "Intel"), ("powerpc", "PowerPC"), ("sparc", "Sparc"), ("ultrasparc", "UltraSparc"), - ) - -if not hasattr(settings,'DJANGOPYPI_DIST_FILE_TYPES'): - settings.DJANGOPYPI_DIST_FILE_TYPES = ( + ), + 'DIST_FILE_TYPES': ( ('sdist','Source'), ('bdist_dumb','"dumb" binary'), ('bdist_rpm','RPM'), ('bdist_wininst','MS Windows installer'), ('bdist_egg','Python Egg'), ('bdist_dmg','OS X Disk Image'), - ) - -if not hasattr(settings,'DJANGOPYPI_PYTHON_VERSIONS'): - settings.DJANGOPYPI_PYTHON_VERSIONS = ( + ), + 'PYTHON_VERSIONS': ( ('any','Any i.e. pure python'), ('2.1','2.1'), ('2.2','2.2'), @@ -63,10 +54,8 @@ ('3.0','3.0'), ('3.1','3.1'), ('3.2','3.2'), - ) - -if not hasattr(settings, 'DJANGOPYPI_METADATA_FIELDS'): - settings.DJANGOPYPI_METADATA_FIELDS = { + ), + 'METADATA_FIELDS': { '1.0': ('platform','summary','description','keywords','home_page', 'author','author_email', 'license', ), '1.1': ('platform','supported_platform','summary','description', @@ -76,47 +65,60 @@ 'keywords','home_page','download_url','author','author_email', 'maintainer','maintainer_email','license','classifier', 'requires_dist','provides_dist','obsoletes_dist', - 'requires_python','requires_external','project_url')} - -if not hasattr(settings, 'DJANGOPYPI_METADATA_FORMS'): - from djangopypi.forms import Metadata10Form, Metadata11Form, Metadata12Form - settings.DJANGOPYPI_METADATA_FORMS = { - '1.0': Metadata10Form, - '1.1': Metadata11Form, - '1.2': Metadata12Form} - -if not hasattr(settings, 'DJANGOPYPI_FALLBACK_VIEW'): - from djangopypi.views import releases - settings.DJANGOPYPI_FALLBACK_VIEW = releases.index - -if not hasattr(settings,'DJANGOPYPI_ACTION_VIEWS'): - from djangopypi.views import distutils - - settings.DJANGOPYPI_ACTION_VIEWS = { - "file_upload": distutils.register_or_upload, #``sdist`` command - "submit": distutils.register_or_upload, #``register`` command - "list_classifiers": distutils.list_classifiers, #``list_classifiers`` command - } - -if not hasattr(settings,'DJANGOPYPI_XMLRPC_COMMANDS'): - from djangopypi.views import xmlrpc - - settings.DJANGOPYPI_XMLRPC_COMMANDS = { - 'list_packages': xmlrpc.list_packages, - 'package_releases': xmlrpc.package_releases, - 'release_urls': xmlrpc.release_urls, - 'release_data': xmlrpc.release_data, + 'requires_python','requires_external','project_url') + }, + 'METADATA_FORMS': { + '1.0': 'djangopypi.forms.Metadata10Form', + '1.1': 'djangopypi.forms.Metadata11Form', + '1.2': 'djangopypi.forms.Metadata12Form' + }, + 'FALLBACK_VIEW': 'djangopypi.views.releases.index', + 'ACTION_VIEWS': { + "file_upload": 'djangopypi.views.distutils.register_or_upload', #``sdist`` command + "submit": 'djangopypi.views.distutils.register_or_upload', #``register`` command + "list_classifiers": 'djangopypi.views.distutils.list_classifiers', #``list_classifiers`` command + }, + 'XMLRPC_COMMANDS': { + 'list_packages': 'djangopypi.views.xmlrpc.list_packages', + 'package_releases': 'djangopypi.views.xmlrpc.package_releases', + 'release_urls': 'djangopypi.views.xmlrpc.release_urls', + 'release_data': 'djangopypi.views.xmlrpc.release_data', #'search': xmlrpc.search, Not done yet #'changelog': xmlrpc.changelog, Not done yet #'ratings': xmlrpc.ratings, Not done yet - } + }, + 'PROXY_BASE_URL': 'http://pypi.python.org/simple', + 'PROXY_MISSING': False, + 'MIRRORING': False, +} + +USER_SETTINGS = DEFAULT_SETTINGS.copy() +USER_SETTINGS.update(getattr(settings, 'DJANGOPYPI_SETTINGS', {})) + +ORIGINAL_SETTINGS = ( + 'DJANGOPYPI_ALLOW_VERSION_OVERWRITE', + 'DJANGOPYPI_RELEASE_UPLOAD_TO', + 'DJANGOPYPI_OS_NAMES', + 'DJANGOPYPI_ARCHITECTURES', + 'DJANGOPYPI_DIST_FILE_TYPES', + 'DJANGOPYPI_PYTHON_VERSIONS', + 'DJANGOPYPI_METADATA_FIELDS', + 'DJANGOPYPI_METADATA_FORMS', + 'DJANGOPYPI_FALLBACK_VIEW', + 'DJANGOPYPI_ACTION_VIEWS', + 'DJANGOPYPI_XMLRPC_COMMANDS', + 'DJANGOPYPI_PROXY_BASE_URL', + 'DJANGOPYPI_PROXY_MISSING', + 'DJANGOPYPI_MIRRORING', +) -""" These settings enable proxying of packages that are not in the local index -to another index, http://pypi.python.org/ by default. This feature is disabled -by default and can be enabled by setting DJANGOPYPI_PROXY_MISSING to True in -your settings file. """ -if not hasattr(settings, 'DJANGOPYPI_PROXY_BASE_URL'): - settings.DJANGOPYPI_PROXY_BASE_URL = 'http://pypi.python.org/simple' +for setting in ORIGINAL_SETTINGS: + value = getattr(settings, setting, False) + if value: + message = "%s is deprecated. Please use DJANGOPYPI_SETTINGS[%s] instead." + new_setting = setting.replace('DJANGOPYPI_', '') + warnings.warn(message % (setting, new_setting), DeprecationWarning) + USER_SETTINGS[new_setting] = value + globals()[setting] = value -if not hasattr(settings, 'DJANGOPYPI_PROXY_MISSING'): - settings.DJANGOPYPI_PROXY_MISSING = False +globals().update(USER_SETTINGS) From 883d6ea40f21dedbb7eeafdb1b0f968c97a7c478 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 5 Apr 2012 05:11:17 -0400 Subject: [PATCH 110/146] Updated imports of new settings --- djangopypi/admin.py | 4 ++-- djangopypi/forms.py | 8 ++++---- djangopypi/models.py | 13 +++++++++---- djangopypi/views/distutils.py | 12 +++++------- djangopypi/views/packages.py | 5 +++-- setup.py | 2 +- 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/djangopypi/admin.py b/djangopypi/admin.py index b1d4ee9..8bb668f 100644 --- a/djangopypi/admin.py +++ b/djangopypi/admin.py @@ -1,7 +1,7 @@ -from django.conf import settings from django.contrib import admin from djangopypi.models import * +from djangopypi.settings import MIRRORING @@ -11,6 +11,6 @@ admin.site.register(Distribution) admin.site.register(Review) -if getattr(settings,'DJANGOPYPI_MIRRORING', False): +if MIRRORING: admin.site.register(MasterIndex) admin.site.register(MirrorLog) diff --git a/djangopypi/forms.py b/djangopypi/forms.py index bd345c4..9f15438 100644 --- a/djangopypi/forms.py +++ b/djangopypi/forms.py @@ -4,7 +4,7 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ -from djangopypi.settings import settings +from djangopypi.settings import (ALLOW_VERSION_OVERWRITE, METADATA_FIELDS, ) from djangopypi.models import Package, Classifier, Release, Distribution @@ -34,7 +34,7 @@ def clean_content(self): print '%s does not exist' % (name,) return content - if settings.DJANGOPYPI_ALLOW_VERSION_OVERWRITE: + if ALLOW_VERSION_OVERWRITE: raise forms.ValidationError('Version overwrite is not yet handled') raise forms.ValidationError('That distribution already exists, please ' @@ -43,8 +43,8 @@ def clean_content(self): class ReleaseForm(forms.ModelForm): - metadata_version = forms.CharField(widget=forms.Select(choices=zip(settings.DJANGOPYPI_METADATA_FIELDS.keys(), - settings.DJANGOPYPI_METADATA_FIELDS.keys()))) + metadata_version = forms.CharField(widget=forms.Select(choices=zip(METADATA_FIELDS.keys(), + METADATA_FIELDS.keys()))) class Meta: model = Release diff --git a/djangopypi/models.py b/djangopypi/models.py index 0540391..3b7e0a5 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -6,7 +6,12 @@ from django.utils.datastructures import MultiValueDict from django.contrib.auth.models import User +from djangopypi.settings import (RELEASE_UPLOAD_TO, DIST_FILE_TYPES, + PYTHON_VERSIONS, DIST_FILE_TYPES, RELEASE_FILE_STORAGE) +from django.core.files.storage import get_storage_class + +FILE_STORAGE = get_storage_class(RELEASE_FILE_STORAGE) class PackageInfoField(models.Field): description = u'Python Package Information Field' @@ -132,12 +137,12 @@ def get_absolute_url(self): class Distribution(models.Model): release = models.ForeignKey(Release, related_name="distributions", editable=False) - content = models.FileField(upload_to=settings.DJANGOPYPI_RELEASE_UPLOAD_TO) + content = models.FileField(upload_to=RELEASE_UPLOAD_TO, storage=FILE_STORAGE()) md5_digest = models.CharField(max_length=32, blank=True, editable=False) filetype = models.CharField(max_length=32, blank=False, - choices=settings.DJANGOPYPI_DIST_FILE_TYPES) + choices=DIST_FILE_TYPES) pyversion = models.CharField(max_length=16, blank=True, - choices=settings.DJANGOPYPI_PYTHON_VERSIONS) + choices=PYTHON_VERSIONS) comment = models.CharField(max_length=255, blank=True) signature = models.TextField(blank=True) created = models.DateTimeField(auto_now_add=True, editable=False) @@ -149,7 +154,7 @@ def filename(self): @property def display_filetype(self): - for key,value in settings.DJANGOPYPI_DIST_FILE_TYPES: + for key,value in DIST_FILE_TYPES: if key == self.filetype: return value return self.filetype diff --git a/djangopypi/views/distutils.py b/djangopypi/views/distutils.py index 54d70f2..2eace3c 100644 --- a/djangopypi/views/distutils.py +++ b/djangopypi/views/distutils.py @@ -1,7 +1,6 @@ import os import re -from django.conf import settings from django.db import transaction from django.http import * from django.utils.translation import ugettext_lazy as _ @@ -11,7 +10,7 @@ from djangopypi.decorators import basic_auth from djangopypi.forms import PackageForm, ReleaseForm from djangopypi.models import Package, Release, Distribution, Classifier - +from djangopypi.settings import ALLOW_VERSION_OVERWRITE, METADATA_FIELDS ALREADY_EXISTS_FMT = _( @@ -33,8 +32,7 @@ def submit_package_or_release(user, post_data, files): classifier, created = Classifier.objects.get_or_create(name=c) package.classifiers.add(classifier) if files: - allow_overwrite = getattr(settings, - "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False) + allow_overwrite = ALLOW_VERSION_OVERWRITE try: release = Release.objects.get(version=post_data['version'], package=package, @@ -91,10 +89,10 @@ def register_or_upload(request, username=None): transaction.rollback() return HttpResponseBadRequest('Release version and metadata version must be specified') - if not metadata_version in settings.DJANGOPYPI_METADATA_FIELDS: + if not metadata_version in METADATA_FIELDS: transaction.rollback() return HttpResponseBadRequest('Metadata version must be one of: %s' - (', '.join(settings.DJANGOPYPI_METADATA_FIELDS.keys()),)) + (', '.join(METADATA_FIELDS.keys()),)) release, created = Release.objects.get_or_create(package=package, version=version) @@ -105,7 +103,7 @@ def register_or_upload(request, username=None): release.metadata_version = metadata_version - fields = settings.DJANGOPYPI_METADATA_FIELDS[metadata_version] + fields = METADATA_FIELDS[metadata_version] if 'classifiers' in request.POST: request.POST.setlist('classifier',request.POST.getlist('classifiers')) diff --git a/djangopypi/views/packages.py b/djangopypi/views/packages.py index 97fbfc2..56d17aa 100644 --- a/djangopypi/views/packages.py +++ b/djangopypi/views/packages.py @@ -9,6 +9,7 @@ from djangopypi.decorators import user_owns_package, user_maintains_package from djangopypi.models import Package, Release from djangopypi.forms import SimplePackageSearchForm, PackageForm +from djangopypi.settings import PROXY_MISSING, PROXY_BASE_URL def index(request, **kwargs): @@ -34,9 +35,9 @@ def simple_details(request, package, **kwargs): try: return details(request, package, **kwargs) except Http404, e: - if settings.DJANGOPYPI_PROXY_MISSING: + if PROXY_MISSING: return HttpResponseRedirect('%s/%s/' % - (settings.DJANGOPYPI_PROXY_BASE_URL.rstrip('/'), + (PROXY_BASE_URL.rstrip('/'), package)) raise e diff --git a/setup.py b/setup.py index e969dc8..9cc7f92 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.4b2' +version = '0.4.4b3' setup( name='djangopypi', From 052e52eecf99e19c335e307703b2a56ee54b68f2 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 5 Apr 2012 05:12:30 -0400 Subject: [PATCH 111/146] Created a function to allow settings that include paths to get imported. Based on django's get_storage_class. --- djangopypi/utils.py | 20 +++++++++++++++++--- djangopypi/views/__init__.py | 11 ++++++----- djangopypi/views/releases.py | 8 ++++---- djangopypi/views/xmlrpc.py | 11 +++++------ 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/djangopypi/utils.py b/djangopypi/utils.py index 2a3d032..3700e00 100644 --- a/djangopypi/utils.py +++ b/djangopypi/utils.py @@ -1,7 +1,5 @@ import sys, traceback - - def debug(func): # @debug is handy when debugging distutils requests def _wrapped(*args, **kwargs): @@ -9,4 +7,20 @@ def _wrapped(*args, **kwargs): return func(*args, **kwargs) except: traceback.print_exception(*sys.exc_info()) - return _wrapped \ No newline at end of file + return _wrapped + + +def get_class(import_path): + try: + dot = import_path.rindex('.') + except ValueError: + raise ImproperlyConfigured("'%s' isn't a valid python path." % import_path) + module, classname = import_path[:dot], import_path[dot+1:] + try: + mod = import_module(module) + except ImportError, e: + raise ImproperlyConfigured('Error importing module %s: "%s"' % (module, e)) + try: + return getattr(mod, classname) + except AttributeError: + raise ImproperlyConfigured('Module "%s" does not define "%s"' % (module, classname)) diff --git a/djangopypi/views/__init__.py b/djangopypi/views/__init__.py index e324e91..9ddfc8e 100644 --- a/djangopypi/views/__init__.py +++ b/djangopypi/views/__init__.py @@ -1,10 +1,11 @@ -from django.conf import settings from django.http import HttpResponseNotAllowed from djangopypi.decorators import csrf_exempt from djangopypi.http import parse_distutils_request from djangopypi.models import Package, Release from djangopypi.views.xmlrpc import parse_xmlrpc_request +from djangopypi.settings import (FALLBACK_VIEW, ACTION_VIEWS) +from djangopypi.utils import get_class @csrf_exempt def root(request, fallback_view=None, **kwargs): @@ -21,11 +22,11 @@ def root(request, fallback_view=None, **kwargs): if not action: if fallback_view is None: - fallback_view = settings.DJANGOPYPI_FALLBACK_VIEW + fallback_view = get_class(FALLBACK_VIEW) return fallback_view(request, **kwargs) - if not action in settings.DJANGOPYPI_ACTION_VIEWS: + if not action in ACTION_VIEWS: print 'unknown action: %s' % (action,) - return HttpResponseNotAllowed(settings.DJANGOPYPI_ACTION_VIEW.keys()) + return HttpResponseNotAllowed(ACTION_VIEWS.keys()) - return settings.DJANGOPYPI_ACTION_VIEWS[action](request, **kwargs) + return get_class(ACTION_VIEWS[action])(request, **kwargs) diff --git a/djangopypi/views/releases.py b/djangopypi/views/releases.py index 21ecf22..3b15f65 100644 --- a/djangopypi/views/releases.py +++ b/djangopypi/views/releases.py @@ -9,8 +9,8 @@ from djangopypi.decorators import user_owns_package, user_maintains_package from djangopypi.models import Package, Release, Distribution from djangopypi.forms import ReleaseForm, DistributionUploadForm - - +from djangopypi.settings import METADATA_FORMS +from djangopypi.utils import get_class def index(request, **kwargs): kwargs.setdefault('template_object_name','release') @@ -73,13 +73,13 @@ def manage_metadata(request, package, version, **kwargs): raise Http404('Version %s does not exist for %s' % (version, package,)) - if not release.metadata_version in settings.DJANGOPYPI_METADATA_FORMS: + if not release.metadata_version in METADATA_FORMS: #TODO: Need to change this to a more meaningful error raise Http404() kwargs['extra_context'][kwargs['template_object_name']] = release - form_class = settings.DJANGOPYPI_METADATA_FORMS.get(release.metadata_version) + form_class = get_class(METADATA_FORMS.get(release.metadata_version)) initial = {} multivalue = ('classifier',) diff --git a/djangopypi/views/xmlrpc.py b/djangopypi/views/xmlrpc.py index 3874434..a65fb01 100644 --- a/djangopypi/views/xmlrpc.py +++ b/djangopypi/views/xmlrpc.py @@ -1,11 +1,10 @@ import xmlrpclib -from django.conf import settings from django.http import HttpResponseNotAllowed, HttpResponse from djangopypi.models import Package, Release - - +from djangopypi.settings import XMLRPC_COMMANDS +from djangopypi.utils import get_class class XMLRPCResponse(HttpResponse): """ A wrapper around the base HttpResponse that dumps the output for xmlrpc @@ -21,10 +20,10 @@ def parse_xmlrpc_request(request): """ args, command = xmlrpclib.loads(request.raw_post_data) - if command in settings.DJANGOPYPI_XMLRPC_COMMANDS: - return settings.DJANGOPYPI_XMLRPC_COMMANDS[command](request, *args) + if command in XMLRPC_COMMANDS: + return get_class(XMLRPC_COMMANDS[command])(request, *args) else: - return HttpResponseNotAllowed(settings.DJANGOPYPI_XMLRPC_COMMANDS.keys()) + return HttpResponseNotAllowed(XMLRPC_COMMANDS.keys()) def list_packages(request): return XMLRPCResponse(params=(list(Package.objects.all().values_list('name', flat=True)),), From a80ff8a7487b82e2ec3dfc96a1095467ec19a0c0 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 6 Apr 2012 15:00:20 -0400 Subject: [PATCH 112/146] Allowing optional trailing slashes since pip doesn't request them. Trying to save on redirects. --- djangopypi/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/djangopypi/urls.py b/djangopypi/urls.py index f7001f2..c7e8fa5 100644 --- a/djangopypi/urls.py +++ b/djangopypi/urls.py @@ -10,10 +10,10 @@ url(r'^pypi/$', 'root', name='djangopypi-release-index'), url(r'^rss/$', ReleaseFeed(), name='djangopypi-rss'), - url(r'^simple/(?P[\w\d_\.\-]+)/$','packages.simple_details', + url(r'^simple/(?P[\w\d_\.\-]+)/?$','packages.simple_details', name='djangopypi-package-simple'), - url(r'^pypi/(?P[\w\d_\.\-]+)/$','packages.details', + url(r'^pypi/(?P[\w\d_\.\-]+)/?$','packages.details', name='djangopypi-package'), url(r'^pypi/(?P[\w\d_\.\-]+)/rss/$', ReleaseFeed(), name='djangopypi-package-rss'), From 7a1e0d1084776e3ca54b7fd59ea333897de99726 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 6 Apr 2012 15:01:16 -0400 Subject: [PATCH 113/146] Fixed an issue with the class loader --- djangopypi/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/djangopypi/utils.py b/djangopypi/utils.py index 3700e00..5513c0d 100644 --- a/djangopypi/utils.py +++ b/djangopypi/utils.py @@ -1,4 +1,6 @@ import sys, traceback +from django.utils.importlib import import_module +from django.core.exceptions import ImproperlyConfigured def debug(func): # @debug is handy when debugging distutils requests From 367ae857bf949b37a07125cfeaba0d4078736d40 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 6 Apr 2012 15:01:40 -0400 Subject: [PATCH 114/146] Some style fixes and import fixes. --- djangopypi/views/distutils.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/djangopypi/views/distutils.py b/djangopypi/views/distutils.py index 2eace3c..1275da3 100644 --- a/djangopypi/views/distutils.py +++ b/djangopypi/views/distutils.py @@ -1,8 +1,8 @@ import os -import re from django.db import transaction -from django.http import * +from django.http import (HttpResponseForbidden, HttpResponseBadRequest, + HttpResponse) from django.utils.translation import ugettext_lazy as _ from django.utils.datastructures import MultiValueDict from django.contrib.auth import login @@ -10,7 +10,7 @@ from djangopypi.decorators import basic_auth from djangopypi.forms import PackageForm, ReleaseForm from djangopypi.models import Package, Release, Distribution, Classifier -from djangopypi.settings import ALLOW_VERSION_OVERWRITE, METADATA_FIELDS +from djangopypi.settings import (ALLOW_VERSION_OVERWRITE, METADATA_FIELDS, RELEASE_UPLOAD_TO) ALREADY_EXISTS_FMT = _( @@ -36,7 +36,7 @@ def submit_package_or_release(user, post_data, files): try: release = Release.objects.get(version=post_data['version'], package=package, - distribution=UPLOAD_TO + '/' + + distribution=RELEASE_UPLOAD_TO + '/' + files['distribution']._name) if not allow_overwrite: return HttpResponseForbidden(ALREADY_EXISTS_FMT % ( @@ -67,10 +67,12 @@ def submit_package_or_release(user, post_data, files): @transaction.commit_manually def register_or_upload(request, username=None): if request.method != 'POST': + transaction.rollback() return HttpResponseBadRequest('Only post requests are supported') name = request.POST.get('name', None).strip() if not name: + transaction.rollback() return HttpResponseBadRequest('No package name specified') try: package = Package.objects.get(owner=request.user, name=name) @@ -79,15 +81,17 @@ def register_or_upload(request, username=None): if (request.user != package.owner and request.user not in package.maintainers.all()): - - return HttpResponseForbidden('You are not an owner/maintainer of %s' % (package.name,)) + transaction.rollback() + return HttpResponseForbidden( + 'You are not an owner/maintainer of %s' % (package.name,)) - version = request.POST.get('version',None).strip() + version = request.POST.get('version', None).strip() metadata_version = request.POST.get('metadata_version', None).strip() if not version or not metadata_version: transaction.rollback() - return HttpResponseBadRequest('Release version and metadata version must be specified') + return HttpResponseBadRequest( + 'Release version and metadata version must be specified') if not metadata_version in METADATA_FIELDS: transaction.rollback() @@ -106,7 +110,7 @@ def register_or_upload(request, username=None): fields = METADATA_FIELDS[metadata_version] if 'classifiers' in request.POST: - request.POST.setlist('classifier',request.POST.getlist('classifiers')) + request.POST.setlist('classifier', request.POST.getlist('classifiers')) release.package_info = MultiValueDict(dict(filter(lambda t: t[0] in fields, request.POST.iterlists()))) @@ -116,6 +120,7 @@ def register_or_upload(request, username=None): filter(lambda v: v != 'UNKNOWN', value)) release.save() + if not 'content' in request.FILES: transaction.commit() return HttpResponse('release registered') @@ -127,7 +132,7 @@ def register_or_upload(request, username=None): """ Need to add handling optionally deleting old and putting up new """ transaction.rollback() return HttpResponseBadRequest('That file has already been uploaded...') - + md5_digest = request.POST.get('md5_digest','') try: @@ -141,7 +146,8 @@ def register_or_upload(request, username=None): md5_digest=md5_digest) except Exception, e: transaction.rollback() - print str(e) + print "Issue creating a Distribution", str(e) + raise transaction.commit() @@ -149,5 +155,5 @@ def register_or_upload(request, username=None): def list_classifiers(request, mimetype='text/plain'): response = HttpResponse(mimetype=mimetype) - response.write(u'\n'.join(map(lambda c: c.name,Classifier.objects.all()))) + response.write(u'\n'.join(map(lambda c: c.name, Classifier.objects.all()))) return response From 6e5f22270145f21fd19caaaaf3eebd40a6bb19b4 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 6 Apr 2012 15:03:03 -0400 Subject: [PATCH 115/146] Version bump to 0.4.4b4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 9cc7f92..cb72480 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.4b3' +version = '0.4.4b4' setup( name='djangopypi', From 3a4d38f8ee6d1806af674cdc97acdeceecf8ae6a Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 13 Apr 2012 13:26:21 -0400 Subject: [PATCH 116/146] Making packages private as default --- djangopypi/models.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/djangopypi/models.py b/djangopypi/models.py index 3b7e0a5..f368710 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py @@ -64,7 +64,7 @@ class Package(models.Model): # allow_comments = models.BooleanField(default=True, blank=False) maintainers = models.ManyToManyField(User, blank=True, related_name="packages_maintained") - private = models.BooleanField(default=False) + private = models.BooleanField(default=True) class Meta: verbose_name = _(u"package") diff --git a/setup.py b/setup.py index cb72480..695caf6 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.4b4' +version = '0.4.4b5' setup( name='djangopypi', From 30087d0c206751ef2577bcbc93130fefd86bd771 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 13 Apr 2012 14:29:02 -0400 Subject: [PATCH 117/146] Renamed djangopypi to userpypi due to the amount of restructuring I'm doing and its new focus --- {djangopypi => userpypi}/__init__.py | 0 {djangopypi => userpypi}/admin.py | 0 {djangopypi => userpypi}/decorators.py | 0 {djangopypi => userpypi}/feeds.py | 0 {djangopypi => userpypi}/forms.py | 0 {djangopypi => userpypi}/http.py | 0 {djangopypi => userpypi}/management/__init__.py | 0 {djangopypi => userpypi}/management/commands/__init__.py | 0 {djangopypi => userpypi}/management/commands/loadclassifiers.py | 0 {djangopypi => userpypi}/management/commands/ppadd.py | 0 {djangopypi => userpypi}/management/commands/update_mirrors.py | 0 {djangopypi => userpypi}/migrations/0001_initial.py | 0 {djangopypi => userpypi}/migrations/0002_refactoring.py | 0 .../migrations/0003_add_masterindex_mirrorlog.py | 0 .../migrations/0004_allow_anonymous_distributions.py | 0 .../migrations/0005_allow_null_distribution_uploader.py | 0 {djangopypi => userpypi}/migrations/__init__.py | 0 {djangopypi => userpypi}/models.py | 0 {djangopypi => userpypi}/search_indexes.py | 0 {djangopypi => userpypi}/settings.py | 0 {djangopypi => userpypi}/signals.py | 0 .../templates/djangopypi/haystack/package_text.txt | 0 {djangopypi => userpypi}/templates/djangopypi/package_detail.html | 0 .../templates/djangopypi/package_detail_simple.html | 0 {djangopypi => userpypi}/templates/djangopypi/package_doap.xml | 0 {djangopypi => userpypi}/templates/djangopypi/package_list.html | 0 .../templates/djangopypi/package_list_simple.html | 0 {djangopypi => userpypi}/templates/djangopypi/package_manage.html | 0 .../templates/djangopypi/package_manage_versions.html | 0 {djangopypi => userpypi}/templates/djangopypi/release_detail.html | 0 {djangopypi => userpypi}/templates/djangopypi/release_doap.xml | 0 .../templates/djangopypi/release_doap_fragment.xml | 0 {djangopypi => userpypi}/templates/djangopypi/release_list.html | 0 {djangopypi => userpypi}/templates/djangopypi/release_manage.html | 0 .../templates/djangopypi/release_manage_files.html | 0 .../templates/djangopypi/release_upload_file.html | 0 {djangopypi => userpypi}/templatetags/__init__.py | 0 {djangopypi => userpypi}/templatetags/safemarkup.py | 0 {djangopypi => userpypi}/tests/__init__.py | 0 {djangopypi => userpypi}/urls.py | 0 {djangopypi => userpypi}/utils.py | 0 {djangopypi => userpypi}/views/__init__.py | 0 {djangopypi => userpypi}/views/distutils.py | 0 {djangopypi => userpypi}/views/packages.py | 0 {djangopypi => userpypi}/views/releases.py | 0 {djangopypi => userpypi}/views/xmlrpc.py | 0 46 files changed, 0 insertions(+), 0 deletions(-) rename {djangopypi => userpypi}/__init__.py (100%) rename {djangopypi => userpypi}/admin.py (100%) rename {djangopypi => userpypi}/decorators.py (100%) rename {djangopypi => userpypi}/feeds.py (100%) rename {djangopypi => userpypi}/forms.py (100%) rename {djangopypi => userpypi}/http.py (100%) rename {djangopypi => userpypi}/management/__init__.py (100%) rename {djangopypi => userpypi}/management/commands/__init__.py (100%) rename {djangopypi => userpypi}/management/commands/loadclassifiers.py (100%) rename {djangopypi => userpypi}/management/commands/ppadd.py (100%) rename {djangopypi => userpypi}/management/commands/update_mirrors.py (100%) rename {djangopypi => userpypi}/migrations/0001_initial.py (100%) rename {djangopypi => userpypi}/migrations/0002_refactoring.py (100%) rename {djangopypi => userpypi}/migrations/0003_add_masterindex_mirrorlog.py (100%) rename {djangopypi => userpypi}/migrations/0004_allow_anonymous_distributions.py (100%) rename {djangopypi => userpypi}/migrations/0005_allow_null_distribution_uploader.py (100%) rename {djangopypi => userpypi}/migrations/__init__.py (100%) rename {djangopypi => userpypi}/models.py (100%) rename {djangopypi => userpypi}/search_indexes.py (100%) rename {djangopypi => userpypi}/settings.py (100%) rename {djangopypi => userpypi}/signals.py (100%) rename {djangopypi => userpypi}/templates/djangopypi/haystack/package_text.txt (100%) rename {djangopypi => userpypi}/templates/djangopypi/package_detail.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/package_detail_simple.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/package_doap.xml (100%) rename {djangopypi => userpypi}/templates/djangopypi/package_list.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/package_list_simple.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/package_manage.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/package_manage_versions.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/release_detail.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/release_doap.xml (100%) rename {djangopypi => userpypi}/templates/djangopypi/release_doap_fragment.xml (100%) rename {djangopypi => userpypi}/templates/djangopypi/release_list.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/release_manage.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/release_manage_files.html (100%) rename {djangopypi => userpypi}/templates/djangopypi/release_upload_file.html (100%) rename {djangopypi => userpypi}/templatetags/__init__.py (100%) rename {djangopypi => userpypi}/templatetags/safemarkup.py (100%) rename {djangopypi => userpypi}/tests/__init__.py (100%) rename {djangopypi => userpypi}/urls.py (100%) rename {djangopypi => userpypi}/utils.py (100%) rename {djangopypi => userpypi}/views/__init__.py (100%) rename {djangopypi => userpypi}/views/distutils.py (100%) rename {djangopypi => userpypi}/views/packages.py (100%) rename {djangopypi => userpypi}/views/releases.py (100%) rename {djangopypi => userpypi}/views/xmlrpc.py (100%) diff --git a/djangopypi/__init__.py b/userpypi/__init__.py similarity index 100% rename from djangopypi/__init__.py rename to userpypi/__init__.py diff --git a/djangopypi/admin.py b/userpypi/admin.py similarity index 100% rename from djangopypi/admin.py rename to userpypi/admin.py diff --git a/djangopypi/decorators.py b/userpypi/decorators.py similarity index 100% rename from djangopypi/decorators.py rename to userpypi/decorators.py diff --git a/djangopypi/feeds.py b/userpypi/feeds.py similarity index 100% rename from djangopypi/feeds.py rename to userpypi/feeds.py diff --git a/djangopypi/forms.py b/userpypi/forms.py similarity index 100% rename from djangopypi/forms.py rename to userpypi/forms.py diff --git a/djangopypi/http.py b/userpypi/http.py similarity index 100% rename from djangopypi/http.py rename to userpypi/http.py diff --git a/djangopypi/management/__init__.py b/userpypi/management/__init__.py similarity index 100% rename from djangopypi/management/__init__.py rename to userpypi/management/__init__.py diff --git a/djangopypi/management/commands/__init__.py b/userpypi/management/commands/__init__.py similarity index 100% rename from djangopypi/management/commands/__init__.py rename to userpypi/management/commands/__init__.py diff --git a/djangopypi/management/commands/loadclassifiers.py b/userpypi/management/commands/loadclassifiers.py similarity index 100% rename from djangopypi/management/commands/loadclassifiers.py rename to userpypi/management/commands/loadclassifiers.py diff --git a/djangopypi/management/commands/ppadd.py b/userpypi/management/commands/ppadd.py similarity index 100% rename from djangopypi/management/commands/ppadd.py rename to userpypi/management/commands/ppadd.py diff --git a/djangopypi/management/commands/update_mirrors.py b/userpypi/management/commands/update_mirrors.py similarity index 100% rename from djangopypi/management/commands/update_mirrors.py rename to userpypi/management/commands/update_mirrors.py diff --git a/djangopypi/migrations/0001_initial.py b/userpypi/migrations/0001_initial.py similarity index 100% rename from djangopypi/migrations/0001_initial.py rename to userpypi/migrations/0001_initial.py diff --git a/djangopypi/migrations/0002_refactoring.py b/userpypi/migrations/0002_refactoring.py similarity index 100% rename from djangopypi/migrations/0002_refactoring.py rename to userpypi/migrations/0002_refactoring.py diff --git a/djangopypi/migrations/0003_add_masterindex_mirrorlog.py b/userpypi/migrations/0003_add_masterindex_mirrorlog.py similarity index 100% rename from djangopypi/migrations/0003_add_masterindex_mirrorlog.py rename to userpypi/migrations/0003_add_masterindex_mirrorlog.py diff --git a/djangopypi/migrations/0004_allow_anonymous_distributions.py b/userpypi/migrations/0004_allow_anonymous_distributions.py similarity index 100% rename from djangopypi/migrations/0004_allow_anonymous_distributions.py rename to userpypi/migrations/0004_allow_anonymous_distributions.py diff --git a/djangopypi/migrations/0005_allow_null_distribution_uploader.py b/userpypi/migrations/0005_allow_null_distribution_uploader.py similarity index 100% rename from djangopypi/migrations/0005_allow_null_distribution_uploader.py rename to userpypi/migrations/0005_allow_null_distribution_uploader.py diff --git a/djangopypi/migrations/__init__.py b/userpypi/migrations/__init__.py similarity index 100% rename from djangopypi/migrations/__init__.py rename to userpypi/migrations/__init__.py diff --git a/djangopypi/models.py b/userpypi/models.py similarity index 100% rename from djangopypi/models.py rename to userpypi/models.py diff --git a/djangopypi/search_indexes.py b/userpypi/search_indexes.py similarity index 100% rename from djangopypi/search_indexes.py rename to userpypi/search_indexes.py diff --git a/djangopypi/settings.py b/userpypi/settings.py similarity index 100% rename from djangopypi/settings.py rename to userpypi/settings.py diff --git a/djangopypi/signals.py b/userpypi/signals.py similarity index 100% rename from djangopypi/signals.py rename to userpypi/signals.py diff --git a/djangopypi/templates/djangopypi/haystack/package_text.txt b/userpypi/templates/djangopypi/haystack/package_text.txt similarity index 100% rename from djangopypi/templates/djangopypi/haystack/package_text.txt rename to userpypi/templates/djangopypi/haystack/package_text.txt diff --git a/djangopypi/templates/djangopypi/package_detail.html b/userpypi/templates/djangopypi/package_detail.html similarity index 100% rename from djangopypi/templates/djangopypi/package_detail.html rename to userpypi/templates/djangopypi/package_detail.html diff --git a/djangopypi/templates/djangopypi/package_detail_simple.html b/userpypi/templates/djangopypi/package_detail_simple.html similarity index 100% rename from djangopypi/templates/djangopypi/package_detail_simple.html rename to userpypi/templates/djangopypi/package_detail_simple.html diff --git a/djangopypi/templates/djangopypi/package_doap.xml b/userpypi/templates/djangopypi/package_doap.xml similarity index 100% rename from djangopypi/templates/djangopypi/package_doap.xml rename to userpypi/templates/djangopypi/package_doap.xml diff --git a/djangopypi/templates/djangopypi/package_list.html b/userpypi/templates/djangopypi/package_list.html similarity index 100% rename from djangopypi/templates/djangopypi/package_list.html rename to userpypi/templates/djangopypi/package_list.html diff --git a/djangopypi/templates/djangopypi/package_list_simple.html b/userpypi/templates/djangopypi/package_list_simple.html similarity index 100% rename from djangopypi/templates/djangopypi/package_list_simple.html rename to userpypi/templates/djangopypi/package_list_simple.html diff --git a/djangopypi/templates/djangopypi/package_manage.html b/userpypi/templates/djangopypi/package_manage.html similarity index 100% rename from djangopypi/templates/djangopypi/package_manage.html rename to userpypi/templates/djangopypi/package_manage.html diff --git a/djangopypi/templates/djangopypi/package_manage_versions.html b/userpypi/templates/djangopypi/package_manage_versions.html similarity index 100% rename from djangopypi/templates/djangopypi/package_manage_versions.html rename to userpypi/templates/djangopypi/package_manage_versions.html diff --git a/djangopypi/templates/djangopypi/release_detail.html b/userpypi/templates/djangopypi/release_detail.html similarity index 100% rename from djangopypi/templates/djangopypi/release_detail.html rename to userpypi/templates/djangopypi/release_detail.html diff --git a/djangopypi/templates/djangopypi/release_doap.xml b/userpypi/templates/djangopypi/release_doap.xml similarity index 100% rename from djangopypi/templates/djangopypi/release_doap.xml rename to userpypi/templates/djangopypi/release_doap.xml diff --git a/djangopypi/templates/djangopypi/release_doap_fragment.xml b/userpypi/templates/djangopypi/release_doap_fragment.xml similarity index 100% rename from djangopypi/templates/djangopypi/release_doap_fragment.xml rename to userpypi/templates/djangopypi/release_doap_fragment.xml diff --git a/djangopypi/templates/djangopypi/release_list.html b/userpypi/templates/djangopypi/release_list.html similarity index 100% rename from djangopypi/templates/djangopypi/release_list.html rename to userpypi/templates/djangopypi/release_list.html diff --git a/djangopypi/templates/djangopypi/release_manage.html b/userpypi/templates/djangopypi/release_manage.html similarity index 100% rename from djangopypi/templates/djangopypi/release_manage.html rename to userpypi/templates/djangopypi/release_manage.html diff --git a/djangopypi/templates/djangopypi/release_manage_files.html b/userpypi/templates/djangopypi/release_manage_files.html similarity index 100% rename from djangopypi/templates/djangopypi/release_manage_files.html rename to userpypi/templates/djangopypi/release_manage_files.html diff --git a/djangopypi/templates/djangopypi/release_upload_file.html b/userpypi/templates/djangopypi/release_upload_file.html similarity index 100% rename from djangopypi/templates/djangopypi/release_upload_file.html rename to userpypi/templates/djangopypi/release_upload_file.html diff --git a/djangopypi/templatetags/__init__.py b/userpypi/templatetags/__init__.py similarity index 100% rename from djangopypi/templatetags/__init__.py rename to userpypi/templatetags/__init__.py diff --git a/djangopypi/templatetags/safemarkup.py b/userpypi/templatetags/safemarkup.py similarity index 100% rename from djangopypi/templatetags/safemarkup.py rename to userpypi/templatetags/safemarkup.py diff --git a/djangopypi/tests/__init__.py b/userpypi/tests/__init__.py similarity index 100% rename from djangopypi/tests/__init__.py rename to userpypi/tests/__init__.py diff --git a/djangopypi/urls.py b/userpypi/urls.py similarity index 100% rename from djangopypi/urls.py rename to userpypi/urls.py diff --git a/djangopypi/utils.py b/userpypi/utils.py similarity index 100% rename from djangopypi/utils.py rename to userpypi/utils.py diff --git a/djangopypi/views/__init__.py b/userpypi/views/__init__.py similarity index 100% rename from djangopypi/views/__init__.py rename to userpypi/views/__init__.py diff --git a/djangopypi/views/distutils.py b/userpypi/views/distutils.py similarity index 100% rename from djangopypi/views/distutils.py rename to userpypi/views/distutils.py diff --git a/djangopypi/views/packages.py b/userpypi/views/packages.py similarity index 100% rename from djangopypi/views/packages.py rename to userpypi/views/packages.py diff --git a/djangopypi/views/releases.py b/userpypi/views/releases.py similarity index 100% rename from djangopypi/views/releases.py rename to userpypi/views/releases.py diff --git a/djangopypi/views/xmlrpc.py b/userpypi/views/xmlrpc.py similarity index 100% rename from djangopypi/views/xmlrpc.py rename to userpypi/views/xmlrpc.py From 9b78b8a31d56424558129736ad7a36d51b19e2f6 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sat, 14 Apr 2012 20:06:57 -0400 Subject: [PATCH 118/146] Major refactoring to include owner in views. --- Changelog.rst | 6 ++ setup.py | 13 +-- userpypi/__init__.py | 29 +++++- userpypi/models.py | 41 ++++---- userpypi/requirements.txt | 2 + userpypi/urls.py | 89 ++++++++++++------ userpypi/views/packages.py | 185 +++++++++++++++++++++++++------------ userpypi/views/releases.py | 102 ++++++++++++-------- 8 files changed, 316 insertions(+), 151 deletions(-) create mode 100644 userpypi/requirements.txt diff --git a/Changelog.rst b/Changelog.rst index 1870e5c..ef14054 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -1,6 +1,12 @@ History ======= +0.5 (2012-04) +------------- + +* Renamed fork to userpypi +* Added multi-user support with private indexes + 0.4.3 (2011-02-22) ------------------ diff --git a/setup.py b/setup.py index 695caf6..3d34757 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,18 @@ import os from setuptools import setup, find_packages +import userpypi def fread(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -version = '0.4.4b5' +try: + reqs = open(os.path.join(os.path.dirname(__file__), 'requirements.txt')).read() +except (IOError, OSError): + reqs = '' setup( name='djangopypi', - version=version, + version=userpypi.get_version(), description="A Django application that emulates the Python Package Index.", long_description=fread("README.rst")+"\n\n"+fread('Changelog.rst')+"\n\n"+fread('AUTHORS.rst'), classifiers=[ @@ -35,8 +39,5 @@ def fread(fname): packages=find_packages(), include_package_data=True, zip_safe=False, - install_requires=[ - 'setuptools', - 'docutils', - ], + install_requires=reqs, ) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index 2251754..d6c0338 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -1,2 +1,27 @@ -from djangopypi import settings -from djangopypi import signals +""" +UserPyPI: A Python package index for individual users. +""" +__version_info__ = { + 'major': 0, + 'minor': 5, + 'micro': 0, + 'releaselevel': 'beta', + 'serial': 1 +} + +def get_version(short=False): + assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final') + vers = ["%(major)i.%(minor)i" % __version_info__, ] + if __version_info__['micro']: + vers.append(".%(micro)i" % __version_info__) + if __version_info__['releaselevel'] != 'final' and not short: + vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial'])) + return ''.join(vers) + +__version__ = get_version() + +try: + from djangopypi import settings + from djangopypi import signals +except ImportError: + pass diff --git a/userpypi/models.py b/userpypi/models.py index f368710..ddc3d6f 100644 --- a/userpypi/models.py +++ b/userpypi/models.py @@ -6,7 +6,7 @@ from django.utils.datastructures import MultiValueDict from django.contrib.auth.models import User -from djangopypi.settings import (RELEASE_UPLOAD_TO, DIST_FILE_TYPES, +from userpypi.settings import (RELEASE_UPLOAD_TO, DIST_FILE_TYPES, PYTHON_VERSIONS, DIST_FILE_TYPES, RELEASE_FILE_STORAGE) from django.core.files.storage import get_storage_class @@ -53,17 +53,20 @@ class Meta: verbose_name = _(u"classifier") verbose_name_plural = _(u"classifiers") ordering = ('name',) - + def __unicode__(self): return self.name + class Package(models.Model): owner = models.ForeignKey(User, related_name="packages_owned") name = models.CharField(max_length=255) auto_hide = models.BooleanField(default=True, blank=False) - # allow_comments = models.BooleanField(default=True, blank=False) - maintainers = models.ManyToManyField(User, blank=True, - related_name="packages_maintained") + maintainers = models.ManyToManyField( + User, + blank=True, + related_name="packages_maintained", + through=Maintainer) private = models.BooleanField(default=True) class Meta: @@ -78,7 +81,10 @@ def __unicode__(self): @models.permalink def get_absolute_url(self): - return ('djangopypi-package', (), {'package': self.name}) + return ('userpypi-package', (), { + 'owner': self.owner.username, + 'package': self.name + }) @property def latest(self): @@ -94,6 +100,12 @@ def get_release(self, version): except Release.DoesNotExist: return None +class Maintainer(models.Model): + package = models.ForeignKey(Package) + user = models.ForeignKey(User) + permission = models.BigIntegerField(blank=True, null=True) + + class Release(models.Model): package = models.ForeignKey(Package, related_name="releases", editable=False) version = models.CharField(max_length=128, editable=False) @@ -130,8 +142,11 @@ def classifiers(self): @models.permalink def get_absolute_url(self): - return ('djangopypi-release', (), {'package': self.package.name, - 'version': self.version}) + return ('userpypi-release', (), { + 'owner': selfpackage.owner.username, + 'package': self.package.name, + 'version': self.version + }) class Distribution(models.Model): @@ -174,18 +189,10 @@ class Meta: def __unicode__(self): return self.filename -class Review(models.Model): - release = models.ForeignKey(Release, related_name="reviews") - rating = models.PositiveSmallIntegerField(blank=True) - comment = models.TextField(blank=True) - - class Meta: - verbose_name = _(u'release review') - verbose_name_plural = _(u'release reviews') try: from south.modelsinspector import add_introspection_rules - add_introspection_rules([], ["^djangopypi\.models\.PackageInfoField"]) + add_introspection_rules([], ["^userpypi\.models\.PackageInfoField"]) except ImportError: pass diff --git a/userpypi/requirements.txt b/userpypi/requirements.txt new file mode 100644 index 0000000..4a68384 --- /dev/null +++ b/userpypi/requirements.txt @@ -0,0 +1,2 @@ +setuptools +docutils diff --git a/userpypi/urls.py b/userpypi/urls.py index c7e8fa5..d198071 100644 --- a/userpypi/urls.py +++ b/userpypi/urls.py @@ -1,39 +1,72 @@ # -*- coding: utf-8 -*- from django.conf.urls.defaults import patterns, url -from djangopypi.feeds import ReleaseFeed +from userpypi.feeds import ReleaseFeed -urlpatterns = patterns("djangopypi.views", - url(r'^$', "root", name="djangopypi-root"), - url(r'^packages/$','packages.index', name='djangopypi-package-index'), - url(r'^simple/$','packages.simple_index', name='djangopypi-package-index-simple'), - url(r'^search/$','packages.search',name='djangopypi-search'), - url(r'^pypi/$', 'root', name='djangopypi-release-index'), - url(r'^rss/$', ReleaseFeed(), name='djangopypi-rss'), +from .views.packages import PackageListView, PackageDetailView, PackageManageView +from .views.releases import ReleaseDetailView + +urlpatterns = patterns('', + + # Basic Package Indexes + url(r'^(?P[^/]+)/$', + PackageListView.as_view(), + name="userpypi-index"), + url(r'^(?P[^/]+)/packages/$', + PackageListView.as_view(), + name='userpypi-package-index'), + + url(r'^(?P[^/]+)/search/$', + 'packages.search', + name='userpypi-search'), + url(r'^(?P[^/]+)/rss/$', + ReleaseFeed(), + name='userpypi-rss'), - url(r'^simple/(?P[\w\d_\.\-]+)/?$','packages.simple_details', - name='djangopypi-package-simple'), + # Simple indexes + url(r'^(?P[^/]+)/simple/$', + PackageListView.as_view(simple=True), + name='userpypi-package-index-simple'), + url(r'^(?P[^/]+)/simple/(?P[\w\d_\.\-]+)/$', + PackageDetailView.as_view(simple=True), + name='userpypi-package-simple'), - url(r'^pypi/(?P[\w\d_\.\-]+)/?$','packages.details', - name='djangopypi-package'), - url(r'^pypi/(?P[\w\d_\.\-]+)/rss/$', ReleaseFeed(), - name='djangopypi-package-rss'), - url(r'^pypi/(?P[\w\d_\.\-]+)/doap.rdf$','packages.doap', - name='djangopypi-package-doap'), - url(r'^pypi/(?P[\w\d_\.\-]+)/manage/$','packages.manage', - name='djangopypi-package-manage'), - url(r'^pypi/(?P[\w\d_\.\-]+)/manage/versions/$','packages.manage_versions', - name='djangopypi-package-manage-versions'), + # Regular Package Indexes + url(r'^(?P[^/]+)/pypi/$', + 'userpypi.views.root', + name="userpypi-root"), + url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/$', + PackageDetailView.as_view(), + name='userpypi-package'), + url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/rss/$', + ReleaseFeed(), + name='userpypi-package-rss'), + url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/doap.rdf$', + PackageDetailView.as_view(doap=True), + name='userpypi-package-doap'), + url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/manage/$', + PackageManageView.as_view(), + name='userpypi-package-manage'), + url(r'^pypi/(?P[\w\d_\.\-]+)/manage/versions/$', + 'userpypi.views.packages.manage_versions', + name='userpypi-package-manage-versions'), - url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', - 'releases.details',name='djangopypi-release'), - url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/doap.rdf$', - 'releases.doap',name='djangopypi-release-doap'), + # Release Indexes + url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/$', + ReleaseDetailView.as_view(), + name='userpypi-release'), + url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/doap.rdf$', + ReleaseDetailView.as_view(doap=True), + name='userpypi-release-doap'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/manage/$', - 'releases.manage',name='djangopypi-release-manage'), + 'userpypi.views.releases.manage', + name='userpypi-release-manage'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/metadata/$', - 'releases.manage_metadata',name='djangopypi-release-manage-metadata'), + 'userpypi.views.releases.manage_metadata', + name='userpypi-release-manage-metadata'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/files/$', - 'releases.manage_files',name='djangopypi-release-manage-files'), + 'userpypi.views.releases.manage_files', + name='userpypi-release-manage-files'), url(r'^pypi/(?P[\w\d_\.\-]+)/(?P[\w\d_\.\-]+)/files/upload/$', - 'releases.upload_file',name='djangopypi-release-upload-file'), + 'userpypi.views.releases.upload_file', + name='userpypi-release-upload-file'), ) \ No newline at end of file diff --git a/userpypi/views/packages.py b/userpypi/views/packages.py index 56d17aa..111c277 100644 --- a/userpypi/views/packages.py +++ b/userpypi/views/packages.py @@ -1,89 +1,154 @@ from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist from django.db.models.query import Q from django.http import Http404, HttpResponseRedirect from django.forms.models import inlineformset_factory from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext -from django.views.generic import list_detail, create_update - -from djangopypi.decorators import user_owns_package, user_maintains_package -from djangopypi.models import Package, Release -from djangopypi.forms import SimplePackageSearchForm, PackageForm -from djangopypi.settings import PROXY_MISSING, PROXY_BASE_URL - - -def index(request, **kwargs): - kwargs.setdefault('template_object_name', 'package') - kwargs.setdefault('queryset', Package.objects.filter(owner=request.user)) - kwargs.pop('username') - return list_detail.object_list(request, **kwargs) - -def simple_index(request, **kwargs): - kwargs.pop('username') - kwargs.setdefault('template_name', 'djangopypi/package_list_simple.html') - return index(request, **kwargs) - -def details(request, package, **kwargs): - kwargs.setdefault('template_object_name', 'package') - kwargs.setdefault('queryset', Package.objects.filter(owner=request.user)) - kwargs.pop('username') - return list_detail.object_detail(request, object_id=package, **kwargs) - -def simple_details(request, package, **kwargs): - kwargs.setdefault('template_name', 'djangopypi/package_detail_simple.html') - kwargs.pop('username') - try: - return details(request, package, **kwargs) - except Http404, e: - if PROXY_MISSING: - return HttpResponseRedirect('%s/%s/' % - (PROXY_BASE_URL.rstrip('/'), - package)) - raise e - -def doap(request, package, **kwargs): - kwargs.setdefault('template_name', 'djangopypi/package_doap.xml') - kwargs.setdefault('mimetype', 'text/xml') - kwargs.pop('username') - return details(request, package, **kwargs) +from django.utils.decorators import method_decorator +from django.views.generic import ListView, DetailView, UpdateView, create_update + +from userpypi.decorators import user_owns_package, user_maintains_package +from userpypi.models import Package, Release +from userpypi.forms import SimplePackageSearchForm, PackageForm +from userpypi.settings import PROXY_MISSING, PROXY_BASE_URL + + +class OwnerObjectMixin(object): + def get_context_data(self, **kwargs): + context = super(OwnerObjectMixin, self).get_context_data(**kwargs) + context['owner'] = self.kwargs.get('owner', None) + context['is_owner'] = self.owner == self.request.user.owner + return context + + def get_queryset(self): + """ + Filter the queryset based on whether or not the requesting user is + the owner of the requested objects + """ + self.owner = self.kwargs['owner'] + + if self.request.user.owner != self.owner: + params = dict(owner__owner=self.owner, private=False) + else: + params = dict(owner=self.request.user) + return self.model.objects.filter(**params) + + +class PackageListView(OwnerObjectMixin, ListView): + model = Package + context_object_name = 'package_list' + simple = False + owner = None + + def get_template_names(self): + """ + Returns a list of template names to be used for the request. Must + return a list. May not be called if render_to_response is overridden. + """ + if self.simple: + return ['userpypi/package_list_simple.html'] + else: + return ['userpypi/package_list.html'] + + +class PackageDetailView(OwnerObjectMixin, DetailView): + model = Package + context_object_name = 'package' + simple = False + doap = False + owner = None + + def render_to_response(self, context, **response_kwargs): + """ + Returns a response with a template rendered with the given context. + """ + self.doap = 'doap' in self.kwargs and self.kwargs['doap'] + + if self.doap: + response_kwargs['mimetype'] = 'text/xml' + + return super(PackageDetailView, self).render_to_response( + context, **response_kwargs) + + def get_object(self): + package = self.kwargs.get('package', None) + try: + queryset = self.get_queryset().filter(name=package) + obj = queryset.get() + except ObjectDoesNotExist: + if PROXY_MISSING: + return HttpResponseRedirect('%s/%s/' % + (PROXY_BASE_URL.rstrip('/'), + package)) + raise Http404(u"No %(verbose_name)s found matching the query" % + {'verbose_name': queryset.model._meta.verbose_name}) + return obj + + def get_template_names(self): + """ + Returns a list of template names to be used for the request. Must + return a list. May not be called if render_to_response is overridden. + """ + self.doap = 'doap' in self.kwargs and self.kwargs['doap'] + self.simple = 'simple' in self.kwargs and self.kwargs['simple'] + + if self.simple: + return ['userpypi/package_detail_simple.html'] + elif self.doap: + return ['userpypi/package_doap.xml'] + else: + return ['userpypi/package_detail.html'] + + +class PackageManageView(OwnerObjectMixin, UpdateView): + model = Package + form_class = PackageForm + context_object_name = 'package' + template_name = 'userpypi/package_manage.html' + + @method_decorator(user_owns_package) + def dispatch(self, *args, **kwargs): + return super(PackageManageView, self).dispatch(*args, **kwargs) + + def get_object(self): + package = self.kwargs.get('package', None) + try: + queryset = self.get_queryset().filter(name=package) + obj = queryset.get() + except ObjectDoesNotExist: + raise Http404(u"No %(verbose_name)s found matching the query" % + {'verbose_name': queryset.model._meta.verbose_name}) + return obj def search(request, **kwargs): if request.method == 'POST': form = SimplePackageSearchForm(request.POST) else: form = SimplePackageSearchForm(request.GET) - kwargs.pop('username') + kwargs.pop('owner') if form.is_valid(): q = form.cleaned_data['query'] - kwargs['queryset'] = Package.objects.filter(owner=request.user).filter(Q(name__contains=q) | - Q(releases__package_info__contains=q)).distinct() - return index(request, **kwargs) - -@user_owns_package() -def manage(request, package, **kwargs): - kwargs.pop('username') - kwargs['object_id'] = package - kwargs.setdefault('form_class', PackageForm) - kwargs.setdefault('template_name', 'djangopypi/package_manage.html') - kwargs.setdefault('template_object_name', 'package') + kwargs['queryset'] = Package.objects.filter(owner=request.user).filter( + Q(name__contains=q) | Q(releases__package_info__contains=q)).distinct() + return PackageListView(request, **kwargs) - return create_update.update_object(request, **kwargs) @user_maintains_package() def manage_versions(request, package, **kwargs): - kwargs.pop('username') + kwargs.pop('owner') package = get_object_or_404(Package, owner=request.user, name=package) kwargs.setdefault('formset_factory_kwargs', {}) kwargs['formset_factory_kwargs'].setdefault('fields', ('hidden',)) kwargs['formset_factory_kwargs']['extra'] = 0 kwargs.setdefault('formset_factory', inlineformset_factory(Package, Release, **kwargs['formset_factory_kwargs'])) - kwargs.setdefault('template_name', 'djangopypi/package_manage_versions.html') + kwargs.setdefault('template_name', 'userpypi/package_manage_versions.html') kwargs.setdefault('template_object_name', 'package') - kwargs.setdefault('extra_context',{}) - kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) + kwargs.setdefault('extra_context', {}) + kwargs.setdefault('mimetype', settings.DEFAULT_CONTENT_TYPE) kwargs['extra_context'][kwargs['template_object_name']] = package - kwargs.setdefault('formset_kwargs',{}) + kwargs.setdefault('formset_kwargs', {}) kwargs['formset_kwargs']['instance'] = package if request.method == 'POST': diff --git a/userpypi/views/releases.py b/userpypi/views/releases.py index 3b15f65..c057f7a 100644 --- a/userpypi/views/releases.py +++ b/userpypi/views/releases.py @@ -6,45 +6,71 @@ from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext -from djangopypi.decorators import user_owns_package, user_maintains_package -from djangopypi.models import Package, Release, Distribution -from djangopypi.forms import ReleaseForm, DistributionUploadForm -from djangopypi.settings import METADATA_FORMS -from djangopypi.utils import get_class +from userpypi.decorators import user_owns_package, user_maintains_package +from userpypi.models import Package, Release, Distribution +from userpypi.forms import ReleaseForm, DistributionUploadForm +from userpypi.settings import METADATA_FORMS +from userpypi.utils import get_class +from userpypi.views.packages import OwnerObjectMixin -def index(request, **kwargs): - kwargs.setdefault('template_object_name','release') - params = dict(package__owner=request.user, hidden=False) - kwargs.setdefault('queryset', Release.objects.filter(**params)) - kwargs.pop('username') - return list_detail.object_list(request, **kwargs) +class ReleaseListView(OwnerObjectMixin, ListView): + model = Release + context_object_name = 'release_list' + simple = False + owner = None + + def get_template_names(self): + """ + Returns a list of template names to be used for the request. Must return + a list. May not be called if render_to_response is overridden. + """ + if self.simple: + return ['userpypi/release_list_simple.html'] + else: + return ['userpypi/release_list.html'] + -def details(request, package, version, **kwargs): - release = get_object_or_404(Package, owner=request.user, name=package).get_release(version) - - if not release: - raise Http404('Version %s does not exist for %s' % (version, - package,)) - - kwargs.setdefault('template_object_name', 'release') - kwargs.setdefault('template_name', 'djangopypi/release_detail.html') - kwargs.setdefault('extra_context', {}) - kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) - - kwargs['extra_context'][kwargs['template_object_name']] = release +class ReleaseDetailView(OwnerObjectMixin, DetailView): + model = Release + context_object_name = 'release' + doap = False + owner = None + + def render_to_response(self, context, **response_kwargs): + """ + Returns a response with a template rendered with the given context. + """ + if self.doap: + response_kwargs['mimetype'] = 'text/xml' - return render_to_response(kwargs['template_name'], kwargs['extra_context'], - context_instance=RequestContext(request), - mimetype=kwargs['mimetype']) + return super(ReleaseDetailView, self).render_to_response(context, **response_kwargs) + + def get_object(self): + package = self.kwargs.get('package', None) + try: + queryset = self.get_queryset().filter(package__name=package) + obj = queryset.get() + except ObjectDoesNotExist: + raise Http404(_(u"No %(verbose_name)s found matching the query") % + {'verbose_name': queryset.model._meta.verbose_name}) + return obj + + def get_template_names(self): + """ + Returns a list of template names to be used for the request. Must return + a list. May not be called if render_to_response is overridden. + """ + self.doap = 'doap' in self.kwargs and self.kwargs['doap'] + + if self.doap: + return ['userpypi/release_doap.xml'] + else: + return ['userpypi/release_detail.html'] -def doap(request, package, version, **kwargs): - kwargs.setdefault('template_name','djangopypi/release_doap.xml') - kwargs.setdefault('mimetype', 'text/xml') - return details(request, package, version, **kwargs) @user_maintains_package() def manage(request, package, version, **kwargs): - kwargs.pop('username') + kwargs.pop('owner') release = get_object_or_404(Package, owner=request.user, name=package).get_release(version) if not release: @@ -54,15 +80,15 @@ def manage(request, package, version, **kwargs): kwargs['object_id'] = release.pk kwargs.setdefault('form_class', ReleaseForm) - kwargs.setdefault('template_name', 'djangopypi/release_manage.html') + kwargs.setdefault('template_name', 'userpypi/release_manage.html') kwargs.setdefault('template_object_name', 'release') return create_update.update_object(request, **kwargs) @user_maintains_package() def manage_metadata(request, package, version, **kwargs): - kwargs.pop('username') - kwargs.setdefault('template_name', 'djangopypi/release_manage.html') + kwargs.pop('owner') + kwargs.setdefault('template_name', 'userpypi/release_manage.html') kwargs.setdefault('template_object_name', 'release') kwargs.setdefault('extra_context', {}) kwargs.setdefault('mimetype', settings.DEFAULT_CONTENT_TYPE) @@ -125,7 +151,7 @@ def manage_files(request, package, version, **kwargs): kwargs['formset_factory_kwargs']['extra'] = 0 kwargs.setdefault('formset_factory', inlineformset_factory(Release, Distribution, **kwargs['formset_factory_kwargs'])) - kwargs.setdefault('template_name', 'djangopypi/release_manage_files.html') + kwargs.setdefault('template_name', 'userpypi/release_manage_files.html') kwargs.setdefault('template_object_name', 'release') kwargs.setdefault('extra_context',{}) kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) @@ -161,10 +187,10 @@ def upload_file(request, package, version, **kwargs): package,)) kwargs.setdefault('form_factory', DistributionUploadForm) - kwargs.setdefault('post_save_redirect', reverse('djangopypi-release-manage-files', + kwargs.setdefault('post_save_redirect', reverse('userpypi-release-manage-files', kwargs={'package': package, 'version': version})) - kwargs.setdefault('template_name', 'djangopypi/release_upload_file.html') + kwargs.setdefault('template_name', 'userpypi/release_upload_file.html') kwargs.setdefault('template_object_name', 'release') kwargs.setdefault('extra_context',{}) kwargs.setdefault('mimetype',settings.DEFAULT_CONTENT_TYPE) From 5a9d70d924fc3d8e6cfa64ba9494e5613c68344a Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 15 Apr 2012 17:25:02 -0400 Subject: [PATCH 119/146] Moved requirements file out of the package --- userpypi/requirements.txt => requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename userpypi/requirements.txt => requirements.txt (100%) diff --git a/userpypi/requirements.txt b/requirements.txt similarity index 100% rename from userpypi/requirements.txt rename to requirements.txt From 34930747d9e60a18e1b37f3e92c9fe8b899311fe Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 15 Apr 2012 17:27:35 -0400 Subject: [PATCH 120/146] Renamed package from djangopypi to userpypi --- Changelog.rst | 2 +- README.rst | 14 +- setup.py | 2 +- userpypi/__init__.py | 4 +- userpypi/admin.py | 5 +- userpypi/decorators.py | 12 +- userpypi/feeds.py | 2 +- userpypi/forms.py | 8 +- .../management/commands/loadclassifiers.py | 2 +- userpypi/management/commands/ppadd.py | 4 +- .../management/commands/update_mirrors.py | 2 +- userpypi/migrations/0001_initial.py | 46 +++--- userpypi/migrations/0002_refactoring.py | 142 +++++++++--------- .../0003_add_masterindex_mirrorlog.py | 52 +++---- .../0004_allow_anonymous_distributions.py | 32 ++-- .../0005_allow_null_distribution_uploader.py | 30 ++-- userpypi/models.py | 8 +- userpypi/search_indexes.py | 4 +- userpypi/settings.py | 22 +-- userpypi/signals.py | 2 +- .../templates/djangopypi/package_detail.html | 2 +- .../templates/djangopypi/package_doap.xml | 4 +- .../djangopypi/package_list_simple.html | 2 +- .../djangopypi/package_manage_versions.html | 6 +- .../templates/djangopypi/release_detail.html | 2 +- .../templates/djangopypi/release_doap.xml | 2 +- .../djangopypi/release_manage_files.html | 2 +- userpypi/tests/__init__.py | 6 +- userpypi/urls.py | 5 +- userpypi/views/__init__.py | 12 +- userpypi/views/distutils.py | 8 +- userpypi/views/packages.py | 6 +- userpypi/views/releases.py | 5 +- userpypi/views/xmlrpc.py | 6 +- 34 files changed, 235 insertions(+), 228 deletions(-) diff --git a/Changelog.rst b/Changelog.rst index ef14054..fc506d6 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -42,7 +42,7 @@ History ---------------- * Added DOAP views of packages and releases -* Splitting djangopypi off of chishop +* Splitting userpypi off of chishop * Switched most views to using django generic views Backwards incompatible changes diff --git a/README.rst b/README.rst index 2cbf1c7..3ce48be 100644 --- a/README.rst +++ b/README.rst @@ -10,18 +10,18 @@ Installation Path ____ -The first step is to get ``djangopypi`` into your Python path. +The first step is to get ``userpypi`` into your Python path. Buildout ++++++++ -Simply add ``djangopypi`` to your list of ``eggs`` and run buildout again it +Simply add ``userpypi`` to your list of ``eggs`` and run buildout again it should downloaded and installed properly. EasyInstall/Setuptools ++++++++++++++++++++++ -If you have setuptools installed, you can use ``easy_install djangopypi`` +If you have setuptools installed, you can use ``easy_install userpypi`` Manual ++++++ @@ -33,14 +33,14 @@ Download and unpack the source then run:: Django Settings _______________ -Add ``djangopypi`` to your ``INSTALLED_APPS`` setting and run ``syncdb`` again +Add ``userpypi`` to your ``INSTALLED_APPS`` setting and run ``syncdb`` again to get the database tables [#]_. -Then add an include in your url config for ``djangopypi.urls``:: +Then add an include in your url config for ``userpypi.urls``:: urlpatterns = patterns("", ... - url(r'', include("djangopypi.urls")) + url(r'', include("userpypi.urls")) ) This will make the repository interface be accessible at ``/pypi/``. @@ -91,5 +91,5 @@ To push the package to the local pypi:: $ python setup.py mregister -r local sdist mupload -r local -.. [#] ``djangopypi`` is South enabled, if you are using South then you will need +.. [#] ``userpypi`` is South enabled, if you are using South then you will need to run the South ``migrate`` command to get the tables. \ No newline at end of file diff --git a/setup.py b/setup.py index 3d34757..a08b1bb 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ def fread(fname): reqs = '' setup( - name='djangopypi', + name='userpypi', version=userpypi.get_version(), description="A Django application that emulates the Python Package Index.", long_description=fread("README.rst")+"\n\n"+fread('Changelog.rst')+"\n\n"+fread('AUTHORS.rst'), diff --git a/userpypi/__init__.py b/userpypi/__init__.py index d6c0338..333cfd1 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -21,7 +21,7 @@ def get_version(short=False): __version__ = get_version() try: - from djangopypi import settings - from djangopypi import signals + from userpypi import settings + from userpypi import signals except ImportError: pass diff --git a/userpypi/admin.py b/userpypi/admin.py index 8bb668f..63618be 100644 --- a/userpypi/admin.py +++ b/userpypi/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin -from djangopypi.models import * -from djangopypi.settings import MIRRORING +from userpypi.models import * +from userpypi.settings import MIRRORING @@ -9,7 +9,6 @@ admin.site.register(Release) admin.site.register(Classifier) admin.site.register(Distribution) -admin.site.register(Review) if MIRRORING: admin.site.register(MasterIndex) diff --git a/userpypi/decorators.py b/userpypi/decorators.py index b7aa377..1df3cf4 100644 --- a/userpypi/decorators.py +++ b/userpypi/decorators.py @@ -13,7 +13,7 @@ def available_attrs(fn): return tuple(a for a in WRAPPER_ASSIGNMENTS if hasattr(fn, a)) -from djangopypi.http import HttpResponseUnauthorized, login_basic_auth +from userpypi.http import HttpResponseUnauthorized, login_basic_auth # Find us a csrf exempt decorator that'll work with Django 1.0+ try: @@ -53,9 +53,11 @@ def user_owns_package(login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): login_url = settings.LOGIN_URL def decorator(view_func): - def _wrapped_view(request, package, *args, **kwargs): + def _wrapped_view(request, owner, package, *args, **kwargs): + if request.user.username != owner: + return HttpResponseForbidden() if request.user.packages_owned.filter(name=package).count() > 0: - return view_func(request, package=package, *args, **kwargs) + return view_func(request, owner=owner, package=package, *args, **kwargs) path = urlquote(request.get_full_path()) tup = login_url, redirect_field_name, path @@ -73,11 +75,11 @@ def user_maintains_package(login_url=None, redirect_field_name=REDIRECT_FIELD_NA login_url = settings.LOGIN_URL def decorator(view_func): - def _wrapped_view(request, package, *args, **kwargs): + def _wrapped_view(request, owner, package, *args, **kwargs): if (request.user.is_authenticated() and (request.user.packages_owned.filter(name=package).count() > 0 or request.user.packages_maintained.filter(name=package).count() > 0)): - return view_func(request, package=package, *args, **kwargs) + return view_func(request, owner, package=package, *args, **kwargs) path = urlquote(request.get_full_path()) tup = login_url, redirect_field_name, path diff --git a/userpypi/feeds.py b/userpypi/feeds.py index 9f9da4c..646f7d6 100644 --- a/userpypi/feeds.py +++ b/userpypi/feeds.py @@ -17,7 +17,7 @@ def __call__(self, request, *args, **kwargs): feedgen.write(response, 'utf-8') return response -from djangopypi.models import Package, Release +from userpypi.models import Package, Release diff --git a/userpypi/forms.py b/userpypi/forms.py index 9f15438..ba1fb26 100644 --- a/userpypi/forms.py +++ b/userpypi/forms.py @@ -4,8 +4,8 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ -from djangopypi.settings import (ALLOW_VERSION_OVERWRITE, METADATA_FIELDS, ) -from djangopypi.models import Package, Classifier, Release, Distribution +from userpypi.settings import (ALLOW_VERSION_OVERWRITE, METADATA_FIELDS, ) +from userpypi.models import Package, Classifier, Release, Distribution @@ -15,12 +15,12 @@ class SimplePackageSearchForm(forms.Form): class PackageForm(forms.ModelForm): class Meta: model = Package - exclude = ['name'] + exclude = ['name', 'owner', 'private'] class DistributionUploadForm(forms.ModelForm): class Meta: model = Distribution - fields = ('content','comment','filetype','pyversion',) + fields = ('content', 'comment', 'filetype', 'pyversion',) def clean_content(self): content = self.cleaned_data['content'] diff --git a/userpypi/management/commands/loadclassifiers.py b/userpypi/management/commands/loadclassifiers.py index 4dc90f6..1fc9c71 100644 --- a/userpypi/management/commands/loadclassifiers.py +++ b/userpypi/management/commands/loadclassifiers.py @@ -12,7 +12,7 @@ import os.path from django.core.management.base import BaseCommand -from djangopypi.models import Classifier +from userpypi.models import Classifier CLASSIFIERS_URL = "http://pypi.python.org/pypi?%3Aaction=list_classifiers" diff --git a/userpypi/management/commands/ppadd.py b/userpypi/management/commands/ppadd.py index 2f193b8..3d78d7c 100644 --- a/userpypi/management/commands/ppadd.py +++ b/userpypi/management/commands/ppadd.py @@ -18,7 +18,7 @@ from urlparse import urlsplit from setuptools.package_index import PackageIndex from django.contrib.auth.models import User -from djangopypi.models import Package, Release, Classifier +from userpypi.models import Package, Release, Classifier @@ -28,7 +28,7 @@ def tempdir(): """Simple context that provides a temporary directory that is deleted when the context is exited.""" - d = tempfile.mkdtemp(".tmp", "djangopypi.") + d = tempfile.mkdtemp(".tmp", "userpypi.") yield d shutil.rmtree(d) diff --git a/userpypi/management/commands/update_mirrors.py b/userpypi/management/commands/update_mirrors.py index dc670a9..b3cdd9a 100644 --- a/userpypi/management/commands/update_mirrors.py +++ b/userpypi/management/commands/update_mirrors.py @@ -10,7 +10,7 @@ from django.core.files import File from django.core.management.base import BaseCommand -from djangopypi.models import * +from userpypi.models import * diff --git a/userpypi/migrations/0001_initial.py b/userpypi/migrations/0001_initial.py index 2180a43..bbfeeda 100644 --- a/userpypi/migrations/0001_initial.py +++ b/userpypi/migrations/0001_initial.py @@ -9,14 +9,14 @@ class Migration(SchemaMigration): def forwards(self, orm): # Adding model 'Classifier' - db.create_table('djangopypi_classifier', ( + db.create_table('userpypi_classifier', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), )) - db.send_create_signal('djangopypi', ['Classifier']) + db.send_create_signal('userpypi', ['Classifier']) # Adding model 'Project' - db.create_table('djangopypi_project', ( + db.create_table('userpypi_project', ( ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), ('license', self.gf('django.db.models.fields.TextField')(blank=True)), ('metadata_version', self.gf('django.db.models.fields.CharField')(default=1.0, max_length=64)), @@ -30,51 +30,51 @@ def forwards(self, orm): ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), )) - db.send_create_signal('djangopypi', ['Project']) + db.send_create_signal('userpypi', ['Project']) # Adding M2M table for field classifiers on 'Project' - db.create_table('djangopypi_project_classifiers', ( + db.create_table('userpypi_project_classifiers', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), - ('classifier', models.ForeignKey(orm['djangopypi.classifier'], null=False)) + ('project', models.ForeignKey(orm['userpypi.project'], null=False)), + ('classifier', models.ForeignKey(orm['userpypi.classifier'], null=False)) )) - db.create_unique('djangopypi_project_classifiers', ['project_id', 'classifier_id']) + db.create_unique('userpypi_project_classifiers', ['project_id', 'classifier_id']) # Adding model 'Release' - db.create_table('djangopypi_release', ( + db.create_table('userpypi_release', ( ('upload_time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), ('md5_digest', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), ('filetype', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), ('pyversion', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), - ('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='releases', to=orm['djangopypi.Project'])), + ('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='releases', to=orm['userpypi.Project'])), ('platform', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), ('version', self.gf('django.db.models.fields.CharField')(max_length=128)), ('signature', self.gf('django.db.models.fields.CharField')(max_length=128, blank=True)), ('distribution', self.gf('django.db.models.fields.files.FileField')(max_length=100)), ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), )) - db.send_create_signal('djangopypi', ['Release']) + db.send_create_signal('userpypi', ['Release']) # Adding unique constraint on 'Release', fields ['project', 'version', 'platform', 'distribution', 'pyversion'] - db.create_unique('djangopypi_release', ['project_id', 'version', 'platform', 'distribution', 'pyversion']) + db.create_unique('userpypi_release', ['project_id', 'version', 'platform', 'distribution', 'pyversion']) def backwards(self, orm): # Deleting model 'Classifier' - db.delete_table('djangopypi_classifier') + db.delete_table('userpypi_classifier') # Deleting model 'Project' - db.delete_table('djangopypi_project') + db.delete_table('userpypi_project') # Removing M2M table for field classifiers on 'Project' - db.delete_table('djangopypi_project_classifiers') + db.delete_table('userpypi_project_classifiers') # Deleting model 'Release' - db.delete_table('djangopypi_release') + db.delete_table('userpypi_release') # Removing unique constraint on 'Release', fields ['project', 'version', 'platform', 'distribution', 'pyversion'] - db.delete_unique('djangopypi_release', ['project_id', 'version', 'platform', 'distribution', 'pyversion']) + db.delete_unique('userpypi_release', ['project_id', 'version', 'platform', 'distribution', 'pyversion']) models = { @@ -114,16 +114,16 @@ def backwards(self, orm): 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, - 'djangopypi.classifier': { + 'userpypi.classifier': { 'Meta': {'object_name': 'Classifier'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}) }, - 'djangopypi.project': { + 'userpypi.project': { 'Meta': {'object_name': 'Project'}, 'author': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), 'author_email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'classifiers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['djangopypi.Classifier']"}), + 'classifiers': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['userpypi.Classifier']"}), 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'download_url': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), 'home_page': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}), @@ -135,14 +135,14 @@ def backwards(self, orm): 'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) }, - 'djangopypi.release': { + 'userpypi.release': { 'Meta': {'unique_together': "(('project', 'version', 'platform', 'distribution', 'pyversion'),)", 'object_name': 'Release'}, 'distribution': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), 'filetype': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'platform': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), - 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Project']"}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['userpypi.Project']"}), 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'signature': ('django.db.models.fields.CharField', [], {'max_length': '128', 'blank': 'True'}), 'upload_time': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}), @@ -150,4 +150,4 @@ def backwards(self, orm): } } - complete_apps = ['djangopypi'] + complete_apps = ['userpypi'] diff --git a/userpypi/migrations/0002_refactoring.py b/userpypi/migrations/0002_refactoring.py index eccb058..524cfd0 100644 --- a/userpypi/migrations/0002_refactoring.py +++ b/userpypi/migrations/0002_refactoring.py @@ -9,15 +9,15 @@ class Migration(SchemaMigration): def forwards(self, orm): # Deleting model 'Project' - db.delete_table('djangopypi_project') + db.delete_table('userpypi_project') # Removing M2M table for field classifiers on 'Project' - db.delete_table('djangopypi_project_classifiers') + db.delete_table('userpypi_project_classifiers') # Adding model 'Distribution' - db.create_table('djangopypi_distribution', ( + db.create_table('userpypi_distribution', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='distributions', to=orm['djangopypi.Release'])), + ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='distributions', to=orm['userpypi.Release'])), ('content', self.gf('django.db.models.fields.files.FileField')(max_length=100)), ('md5_digest', self.gf('django.db.models.fields.CharField')(max_length=32, blank=True)), ('filetype', self.gf('django.db.models.fields.CharField')(max_length=32)), @@ -27,97 +27,97 @@ def forwards(self, orm): ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), ('uploader', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), )) - db.send_create_signal('djangopypi', ['Distribution']) + db.send_create_signal('userpypi', ['Distribution']) # Adding model 'Review' - db.create_table('djangopypi_review', ( + db.create_table('userpypi_review', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reviews', to=orm['djangopypi.Release'])), + ('release', self.gf('django.db.models.fields.related.ForeignKey')(related_name='reviews', to=orm['userpypi.Release'])), ('rating', self.gf('django.db.models.fields.PositiveSmallIntegerField')(blank=True)), ('comment', self.gf('django.db.models.fields.TextField')(blank=True)), )) - db.send_create_signal('djangopypi', ['Review']) + db.send_create_signal('userpypi', ['Review']) # Adding model 'Package' - db.create_table('djangopypi_package', ( + db.create_table('userpypi_package', ( ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, primary_key=True)), ('auto_hide', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), ('allow_comments', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)), )) - db.send_create_signal('djangopypi', ['Package']) + db.send_create_signal('userpypi', ['Package']) # Adding M2M table for field owners on 'Package' - db.create_table('djangopypi_package_owners', ( + db.create_table('userpypi_package_owners', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('package', models.ForeignKey(orm['djangopypi.package'], null=False)), + ('package', models.ForeignKey(orm['userpypi.package'], null=False)), ('user', models.ForeignKey(orm['auth.user'], null=False)) )) - db.create_unique('djangopypi_package_owners', ['package_id', 'user_id']) + db.create_unique('userpypi_package_owners', ['package_id', 'user_id']) # Adding M2M table for field maintainers on 'Package' - db.create_table('djangopypi_package_maintainers', ( + db.create_table('userpypi_package_maintainers', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('package', models.ForeignKey(orm['djangopypi.package'], null=False)), + ('package', models.ForeignKey(orm['userpypi.package'], null=False)), ('user', models.ForeignKey(orm['auth.user'], null=False)) )) - db.create_unique('djangopypi_package_maintainers', ['package_id', 'user_id']) + db.create_unique('userpypi_package_maintainers', ['package_id', 'user_id']) # Deleting field 'Classifier.id' - db.delete_column('djangopypi_classifier', 'id') + db.delete_column('userpypi_classifier', 'id') # Changing field 'Classifier.name' - db.alter_column('djangopypi_classifier', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, primary_key=True)) + db.alter_column('userpypi_classifier', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, primary_key=True)) # Deleting field 'Release.md5_digest' - db.delete_column('djangopypi_release', 'md5_digest') + db.delete_column('userpypi_release', 'md5_digest') # Deleting field 'Release.filetype' - db.delete_column('djangopypi_release', 'filetype') + db.delete_column('userpypi_release', 'filetype') # Deleting field 'Release.upload_time' - db.delete_column('djangopypi_release', 'upload_time') + db.delete_column('userpypi_release', 'upload_time') # Deleting field 'Release.pyversion' - db.delete_column('djangopypi_release', 'pyversion') + db.delete_column('userpypi_release', 'pyversion') # Deleting field 'Release.project' - db.delete_column('djangopypi_release', 'project_id') + db.delete_column('userpypi_release', 'project_id') # Deleting field 'Release.platform' - db.delete_column('djangopypi_release', 'platform') + db.delete_column('userpypi_release', 'platform') # Deleting field 'Release.signature' - db.delete_column('djangopypi_release', 'signature') + db.delete_column('userpypi_release', 'signature') # Deleting field 'Release.distribution' - db.delete_column('djangopypi_release', 'distribution') + db.delete_column('userpypi_release', 'distribution') # Adding field 'Release.package' - db.add_column('djangopypi_release', 'package', self.gf('django.db.models.fields.related.ForeignKey')(default='', related_name='releases', to=orm['djangopypi.Package']), keep_default=False) + db.add_column('userpypi_release', 'package', self.gf('django.db.models.fields.related.ForeignKey')(default='', related_name='releases', to=orm['userpypi.Package']), keep_default=False) # Adding field 'Release.metadata_version' - db.add_column('djangopypi_release', 'metadata_version', self.gf('django.db.models.fields.CharField')(default='1.0', max_length=64), keep_default=False) + db.add_column('userpypi_release', 'metadata_version', self.gf('django.db.models.fields.CharField')(default='1.0', max_length=64), keep_default=False) # Adding field 'Release.package_info' - db.add_column('djangopypi_release', 'package_info', self.gf('djangopypi.models.PackageInfoField')(default=''), keep_default=False) + db.add_column('userpypi_release', 'package_info', self.gf('userpypi.models.PackageInfoField')(default=''), keep_default=False) # Adding field 'Release.hidden' - db.add_column('djangopypi_release', 'hidden', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False) + db.add_column('userpypi_release', 'hidden', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True), keep_default=False) # Adding field 'Release.created' - db.add_column('djangopypi_release', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default='', blank=True), keep_default=False) + db.add_column('userpypi_release', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default='', blank=True), keep_default=False) # Removing unique constraint on 'Release', fields ['project', 'platform', 'distribution', 'version', 'pyversion'] - db.delete_unique('djangopypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) + db.delete_unique('userpypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) # Adding unique constraint on 'Release', fields ['version', 'package'] - db.create_unique('djangopypi_release', ['version', 'package_id']) + db.create_unique('userpypi_release', ['version', 'package_id']) def backwards(self, orm): # Adding model 'Project' - db.create_table('djangopypi_project', ( + db.create_table('userpypi_project', ( ('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), ('description', self.gf('django.db.models.fields.TextField')(blank=True)), ('metadata_version', self.gf('django.db.models.fields.CharField')(default=1.0, max_length=64)), @@ -131,81 +131,81 @@ def backwards(self, orm): ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('author_email', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)), )) - db.send_create_signal('djangopypi', ['Project']) + db.send_create_signal('userpypi', ['Project']) # Adding M2M table for field classifiers on 'Project' - db.create_table('djangopypi_project_classifiers', ( + db.create_table('userpypi_project_classifiers', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('project', models.ForeignKey(orm['djangopypi.project'], null=False)), - ('classifier', models.ForeignKey(orm['djangopypi.classifier'], null=False)) + ('project', models.ForeignKey(orm['userpypi.project'], null=False)), + ('classifier', models.ForeignKey(orm['userpypi.classifier'], null=False)) )) - db.create_unique('djangopypi_project_classifiers', ['project_id', 'classifier_id']) + db.create_unique('userpypi_project_classifiers', ['project_id', 'classifier_id']) # Deleting model 'Distribution' - db.delete_table('djangopypi_distribution') + db.delete_table('userpypi_distribution') # Deleting model 'Review' - db.delete_table('djangopypi_review') + db.delete_table('userpypi_review') # Deleting model 'Package' - db.delete_table('djangopypi_package') + db.delete_table('userpypi_package') # Removing M2M table for field owners on 'Package' - db.delete_table('djangopypi_package_owners') + db.delete_table('userpypi_package_owners') # Removing M2M table for field maintainers on 'Package' - db.delete_table('djangopypi_package_maintainers') + db.delete_table('userpypi_package_maintainers') # Adding field 'Classifier.id' - db.add_column('djangopypi_classifier', 'id', self.gf('django.db.models.fields.AutoField')(default='', primary_key=True), keep_default=False) + db.add_column('userpypi_classifier', 'id', self.gf('django.db.models.fields.AutoField')(default='', primary_key=True), keep_default=False) # Changing field 'Classifier.name' - db.alter_column('djangopypi_classifier', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True)) + db.alter_column('userpypi_classifier', 'name', self.gf('django.db.models.fields.CharField')(max_length=255, unique=True)) # Adding field 'Release.md5_digest' - db.add_column('djangopypi_release', 'md5_digest', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + db.add_column('userpypi_release', 'md5_digest', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) # Adding field 'Release.filetype' - db.add_column('djangopypi_release', 'filetype', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + db.add_column('userpypi_release', 'filetype', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) # Adding field 'Release.upload_time' - db.add_column('djangopypi_release', 'upload_time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default='', blank=True), keep_default=False) + db.add_column('userpypi_release', 'upload_time', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default='', blank=True), keep_default=False) # Adding field 'Release.pyversion' - db.add_column('djangopypi_release', 'pyversion', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + db.add_column('userpypi_release', 'pyversion', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) # Adding field 'Release.project' - db.add_column('djangopypi_release', 'project', self.gf('django.db.models.fields.related.ForeignKey')(default='', related_name='releases', to=orm['djangopypi.Project']), keep_default=False) + db.add_column('userpypi_release', 'project', self.gf('django.db.models.fields.related.ForeignKey')(default='', related_name='releases', to=orm['userpypi.Project']), keep_default=False) # Adding field 'Release.platform' - db.add_column('djangopypi_release', 'platform', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) + db.add_column('userpypi_release', 'platform', self.gf('django.db.models.fields.CharField')(default='', max_length=255, blank=True), keep_default=False) # Adding field 'Release.signature' - db.add_column('djangopypi_release', 'signature', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), keep_default=False) + db.add_column('userpypi_release', 'signature', self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True), keep_default=False) # Adding field 'Release.distribution' - db.add_column('djangopypi_release', 'distribution', self.gf('django.db.models.fields.files.FileField')(default='', max_length=100), keep_default=False) + db.add_column('userpypi_release', 'distribution', self.gf('django.db.models.fields.files.FileField')(default='', max_length=100), keep_default=False) # Deleting field 'Release.package' - db.delete_column('djangopypi_release', 'package_id') + db.delete_column('userpypi_release', 'package_id') # Deleting field 'Release.metadata_version' - db.delete_column('djangopypi_release', 'metadata_version') + db.delete_column('userpypi_release', 'metadata_version') # Deleting field 'Release.package_info' - db.delete_column('djangopypi_release', 'package_info') + db.delete_column('userpypi_release', 'package_info') # Deleting field 'Release.hidden' - db.delete_column('djangopypi_release', 'hidden') + db.delete_column('userpypi_release', 'hidden') # Deleting field 'Release.created' - db.delete_column('djangopypi_release', 'created') + db.delete_column('userpypi_release', 'created') # Adding unique constraint on 'Release', fields ['project', 'platform', 'distribution', 'version', 'pyversion'] - db.create_unique('djangopypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) + db.create_unique('userpypi_release', ['project_id', 'platform', 'distribution', 'version', 'pyversion']) # Removing unique constraint on 'Release', fields ['version', 'package'] - db.delete_unique('djangopypi_release', ['version', 'package_id']) + db.delete_unique('userpypi_release', ['version', 'package_id']) models = { @@ -245,11 +245,11 @@ def backwards(self, orm): 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, - 'djangopypi.classifier': { + 'userpypi.classifier': { 'Meta': {'object_name': 'Classifier'}, 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}) }, - 'djangopypi.distribution': { + 'userpypi.distribution': { 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), @@ -258,11 +258,11 @@ def backwards(self, orm): 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['djangopypi.Release']"}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['userpypi.Release']"}), 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) }, - 'djangopypi.package': { + 'userpypi.package': { 'Meta': {'object_name': 'Package'}, 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}), @@ -270,23 +270,23 @@ def backwards(self, orm): 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) }, - 'djangopypi.release': { + 'userpypi.release': { 'Meta': {'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), - 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), - 'package_info': ('djangopypi.models.PackageInfoField', [], {}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['userpypi.Package']"}), + 'package_info': ('userpypi.models.PackageInfoField', [], {}), 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) }, - 'djangopypi.review': { + 'userpypi.review': { 'Meta': {'object_name': 'Review'}, 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['userpypi.Release']"}) } } - complete_apps = ['djangopypi'] + complete_apps = ['userpypi'] diff --git a/userpypi/migrations/0003_add_masterindex_mirrorlog.py b/userpypi/migrations/0003_add_masterindex_mirrorlog.py index 80bb25c..aa5ccf3 100644 --- a/userpypi/migrations/0003_add_masterindex_mirrorlog.py +++ b/userpypi/migrations/0003_add_masterindex_mirrorlog.py @@ -9,40 +9,40 @@ class Migration(SchemaMigration): def forwards(self, orm): # Adding model 'MirrorLog' - db.create_table('djangopypi_mirrorlog', ( + db.create_table('userpypi_mirrorlog', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('master', self.gf('django.db.models.fields.related.ForeignKey')(related_name='logs', to=orm['djangopypi.MasterIndex'])), + ('master', self.gf('django.db.models.fields.related.ForeignKey')(related_name='logs', to=orm['userpypi.MasterIndex'])), ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), )) - db.send_create_signal('djangopypi', ['MirrorLog']) + db.send_create_signal('userpypi', ['MirrorLog']) # Adding M2M table for field releases_added on 'MirrorLog' - db.create_table('djangopypi_mirrorlog_releases_added', ( + db.create_table('userpypi_mirrorlog_releases_added', ( ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('mirrorlog', models.ForeignKey(orm['djangopypi.mirrorlog'], null=False)), - ('release', models.ForeignKey(orm['djangopypi.release'], null=False)) + ('mirrorlog', models.ForeignKey(orm['userpypi.mirrorlog'], null=False)), + ('release', models.ForeignKey(orm['userpypi.release'], null=False)) )) - db.create_unique('djangopypi_mirrorlog_releases_added', ['mirrorlog_id', 'release_id']) + db.create_unique('userpypi_mirrorlog_releases_added', ['mirrorlog_id', 'release_id']) # Adding model 'MasterIndex' - db.create_table('djangopypi_masterindex', ( + db.create_table('userpypi_masterindex', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('title', self.gf('django.db.models.fields.CharField')(max_length=255)), ('url', self.gf('django.db.models.fields.CharField')(max_length=255)), )) - db.send_create_signal('djangopypi', ['MasterIndex']) + db.send_create_signal('userpypi', ['MasterIndex']) def backwards(self, orm): # Deleting model 'MirrorLog' - db.delete_table('djangopypi_mirrorlog') + db.delete_table('userpypi_mirrorlog') # Removing M2M table for field releases_added on 'MirrorLog' - db.delete_table('djangopypi_mirrorlog_releases_added') + db.delete_table('userpypi_mirrorlog_releases_added') # Deleting model 'MasterIndex' - db.delete_table('djangopypi_masterindex') + db.delete_table('userpypi_masterindex') models = { @@ -82,11 +82,11 @@ def backwards(self, orm): 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, - 'djangopypi.classifier': { + 'userpypi.classifier': { 'Meta': {'ordering': "('name',)", 'object_name': 'Classifier'}, 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}) }, - 'djangopypi.distribution': { + 'userpypi.distribution': { 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), @@ -95,24 +95,24 @@ def backwards(self, orm): 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['djangopypi.Release']"}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['userpypi.Release']"}), 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) }, - 'djangopypi.masterindex': { + 'userpypi.masterindex': { 'Meta': {'object_name': 'MasterIndex'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) }, - 'djangopypi.mirrorlog': { + 'userpypi.mirrorlog': { 'Meta': {'object_name': 'MirrorLog'}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['djangopypi.MasterIndex']"}), - 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'mirror_sources'", 'symmetrical': 'False', 'to': "orm['djangopypi.Release']"}) + 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['userpypi.MasterIndex']"}), + 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'mirror_sources'", 'symmetrical': 'False', 'to': "orm['userpypi.Release']"}) }, - 'djangopypi.package': { + 'userpypi.package': { 'Meta': {'ordering': "['name']", 'object_name': 'Package'}, 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), @@ -120,23 +120,23 @@ def backwards(self, orm): 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) }, - 'djangopypi.release': { + 'userpypi.release': { 'Meta': {'ordering': "['-created']", 'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), - 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), - 'package_info': ('djangopypi.models.PackageInfoField', [], {}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['userpypi.Package']"}), + 'package_info': ('userpypi.models.PackageInfoField', [], {}), 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) }, - 'djangopypi.review': { + 'userpypi.review': { 'Meta': {'object_name': 'Review'}, 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['userpypi.Release']"}) } } - complete_apps = ['djangopypi'] + complete_apps = ['userpypi'] diff --git a/userpypi/migrations/0004_allow_anonymous_distributions.py b/userpypi/migrations/0004_allow_anonymous_distributions.py index 581da47..bb9fe49 100644 --- a/userpypi/migrations/0004_allow_anonymous_distributions.py +++ b/userpypi/migrations/0004_allow_anonymous_distributions.py @@ -9,13 +9,13 @@ class Migration(SchemaMigration): def forwards(self, orm): # Changing field 'MirrorLog.created' - db.alter_column('djangopypi_mirrorlog', 'created', self.gf('django.db.models.fields.DateTimeField')()) + db.alter_column('userpypi_mirrorlog', 'created', self.gf('django.db.models.fields.DateTimeField')()) def backwards(self, orm): # Changing field 'MirrorLog.created' - db.alter_column('djangopypi_mirrorlog', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True)) + db.alter_column('userpypi_mirrorlog', 'created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True)) models = { @@ -55,11 +55,11 @@ def backwards(self, orm): 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, - 'djangopypi.classifier': { + 'userpypi.classifier': { 'Meta': {'ordering': "('name',)", 'object_name': 'Classifier'}, 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}) }, - 'djangopypi.distribution': { + 'userpypi.distribution': { 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), @@ -68,24 +68,24 @@ def backwards(self, orm): 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['djangopypi.Release']"}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['userpypi.Release']"}), 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'blank': 'True'}) }, - 'djangopypi.masterindex': { + 'userpypi.masterindex': { 'Meta': {'object_name': 'MasterIndex'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) }, - 'djangopypi.mirrorlog': { + 'userpypi.mirrorlog': { 'Meta': {'object_name': 'MirrorLog'}, 'created': ('django.db.models.fields.DateTimeField', [], {'default': "'now'"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['djangopypi.MasterIndex']"}), - 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'mirror_sources'", 'blank': 'True', 'to': "orm['djangopypi.Release']"}) + 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['userpypi.MasterIndex']"}), + 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'mirror_sources'", 'blank': 'True', 'to': "orm['userpypi.Release']"}) }, - 'djangopypi.package': { + 'userpypi.package': { 'Meta': {'ordering': "['name']", 'object_name': 'Package'}, 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), @@ -93,23 +93,23 @@ def backwards(self, orm): 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) }, - 'djangopypi.release': { + 'userpypi.release': { 'Meta': {'ordering': "['-created']", 'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), - 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), - 'package_info': ('djangopypi.models.PackageInfoField', [], {}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['userpypi.Package']"}), + 'package_info': ('userpypi.models.PackageInfoField', [], {}), 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) }, - 'djangopypi.review': { + 'userpypi.review': { 'Meta': {'object_name': 'Review'}, 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['userpypi.Release']"}) } } - complete_apps = ['djangopypi'] + complete_apps = ['userpypi'] diff --git a/userpypi/migrations/0005_allow_null_distribution_uploader.py b/userpypi/migrations/0005_allow_null_distribution_uploader.py index 99687e3..860145a 100644 --- a/userpypi/migrations/0005_allow_null_distribution_uploader.py +++ b/userpypi/migrations/0005_allow_null_distribution_uploader.py @@ -9,7 +9,7 @@ class Migration(SchemaMigration): def forwards(self, orm): # Changing field 'Distribution.uploader' - db.alter_column('djangopypi_distribution', 'uploader_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)) + db.alter_column('userpypi_distribution', 'uploader_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)) def backwards(self, orm): @@ -55,11 +55,11 @@ def backwards(self, orm): 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, - 'djangopypi.classifier': { + 'userpypi.classifier': { 'Meta': {'ordering': "('name',)", 'object_name': 'Classifier'}, 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'primary_key': 'True'}) }, - 'djangopypi.distribution': { + 'userpypi.distribution': { 'Meta': {'unique_together': "(('release', 'filetype', 'pyversion'),)", 'object_name': 'Distribution'}, 'comment': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}), 'content': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), @@ -68,24 +68,24 @@ def backwards(self, orm): 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'md5_digest': ('django.db.models.fields.CharField', [], {'max_length': '32', 'blank': 'True'}), 'pyversion': ('django.db.models.fields.CharField', [], {'max_length': '16', 'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['djangopypi.Release']"}), + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'distributions'", 'to': "orm['userpypi.Release']"}), 'signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'uploader': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}) }, - 'djangopypi.masterindex': { + 'userpypi.masterindex': { 'Meta': {'object_name': 'MasterIndex'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) }, - 'djangopypi.mirrorlog': { + 'userpypi.mirrorlog': { 'Meta': {'object_name': 'MirrorLog'}, 'created': ('django.db.models.fields.DateTimeField', [], {'default': "'now'"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['djangopypi.MasterIndex']"}), - 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'mirror_sources'", 'blank': 'True', 'to': "orm['djangopypi.Release']"}) + 'master': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'logs'", 'to': "orm['userpypi.MasterIndex']"}), + 'releases_added': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'mirror_sources'", 'blank': 'True', 'to': "orm['userpypi.Release']"}) }, - 'djangopypi.package': { + 'userpypi.package': { 'Meta': {'ordering': "['name']", 'object_name': 'Package'}, 'allow_comments': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'auto_hide': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), @@ -93,23 +93,23 @@ def backwards(self, orm): 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'primary_key': 'True'}), 'owners': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'packages_owned'", 'blank': 'True', 'to': "orm['auth.User']"}) }, - 'djangopypi.release': { + 'userpypi.release': { 'Meta': {'ordering': "['-created']", 'unique_together': "(('package', 'version'),)", 'object_name': 'Release'}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'metadata_version': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '64'}), - 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['djangopypi.Package']"}), - 'package_info': ('djangopypi.models.PackageInfoField', [], {}), + 'package': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'releases'", 'to': "orm['userpypi.Package']"}), + 'package_info': ('userpypi.models.PackageInfoField', [], {}), 'version': ('django.db.models.fields.CharField', [], {'max_length': '128'}) }, - 'djangopypi.review': { + 'userpypi.review': { 'Meta': {'object_name': 'Review'}, 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'rating': ('django.db.models.fields.PositiveSmallIntegerField', [], {'blank': 'True'}), - 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['djangopypi.Release']"}) + 'release': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reviews'", 'to': "orm['userpypi.Release']"}) } } - complete_apps = ['djangopypi'] + complete_apps = ['userpypi'] diff --git a/userpypi/models.py b/userpypi/models.py index ddc3d6f..12a7333 100644 --- a/userpypi/models.py +++ b/userpypi/models.py @@ -61,12 +61,16 @@ def __unicode__(self): class Package(models.Model): owner = models.ForeignKey(User, related_name="packages_owned") name = models.CharField(max_length=255) - auto_hide = models.BooleanField(default=True, blank=False) + auto_hide = models.BooleanField(_(u"Auto hide"), + default=True, + blank=False, + help_text="""Automatically hide previous releases when new releases + are created.""") maintainers = models.ManyToManyField( User, blank=True, related_name="packages_maintained", - through=Maintainer) + through='Maintainer') private = models.BooleanField(default=True) class Meta: diff --git a/userpypi/search_indexes.py b/userpypi/search_indexes.py index 4809922..8e2120b 100644 --- a/userpypi/search_indexes.py +++ b/userpypi/search_indexes.py @@ -1,6 +1,6 @@ from django.conf import settings -from djangopypi.models import Package +from userpypi.models import Package if 'haystack' in settings.INSTALLED_APPS: from haystack import site @@ -10,7 +10,7 @@ class PackageSearchIndex(SearchIndex): name = CharField(model_attr='name') text = CharField(document=True, use_template=True, null=True, stored=False, - template_name='djangopypi/haystack/package_text.txt') + template_name='userpypi/haystack/package_text.txt') author = MultiValueField(stored=False, null=True) classifier = MultiValueField(stored=False, null=True, model_attr='latest__classifiers') diff --git a/userpypi/settings.py b/userpypi/settings.py index 4f3ca6b..38909eb 100644 --- a/userpypi/settings.py +++ b/userpypi/settings.py @@ -68,21 +68,21 @@ 'requires_python','requires_external','project_url') }, 'METADATA_FORMS': { - '1.0': 'djangopypi.forms.Metadata10Form', - '1.1': 'djangopypi.forms.Metadata11Form', - '1.2': 'djangopypi.forms.Metadata12Form' + '1.0': 'userpypi.forms.Metadata10Form', + '1.1': 'userpypi.forms.Metadata11Form', + '1.2': 'userpypi.forms.Metadata12Form' }, - 'FALLBACK_VIEW': 'djangopypi.views.releases.index', + 'FALLBACK_VIEW': 'userpypi.views.releases.index', 'ACTION_VIEWS': { - "file_upload": 'djangopypi.views.distutils.register_or_upload', #``sdist`` command - "submit": 'djangopypi.views.distutils.register_or_upload', #``register`` command - "list_classifiers": 'djangopypi.views.distutils.list_classifiers', #``list_classifiers`` command + "file_upload": 'userpypi.views.distutils.register_or_upload', #``sdist`` command + "submit": 'userpypi.views.distutils.register_or_upload', #``register`` command + "list_classifiers": 'userpypi.views.distutils.list_classifiers', #``list_classifiers`` command }, 'XMLRPC_COMMANDS': { - 'list_packages': 'djangopypi.views.xmlrpc.list_packages', - 'package_releases': 'djangopypi.views.xmlrpc.package_releases', - 'release_urls': 'djangopypi.views.xmlrpc.release_urls', - 'release_data': 'djangopypi.views.xmlrpc.release_data', + 'list_packages': 'userpypi.views.xmlrpc.list_packages', + 'package_releases': 'userpypi.views.xmlrpc.package_releases', + 'release_urls': 'userpypi.views.xmlrpc.release_urls', + 'release_data': 'userpypi.views.xmlrpc.release_data', #'search': xmlrpc.search, Not done yet #'changelog': xmlrpc.changelog, Not done yet #'ratings': xmlrpc.ratings, Not done yet diff --git a/userpypi/signals.py b/userpypi/signals.py index 02ecfa3..55a06bf 100644 --- a/userpypi/signals.py +++ b/userpypi/signals.py @@ -1,7 +1,7 @@ from django.db.models import signals from django.utils.hashcompat import md5_constructor -from djangopypi.models import Package, Release, Distribution +from userpypi.models import Package, Release, Distribution def autohide_new_release_handler(sender, instance, created, *args, **kwargs): diff --git a/userpypi/templates/djangopypi/package_detail.html b/userpypi/templates/djangopypi/package_detail.html index 185f4e6..55014d8 100644 --- a/userpypi/templates/djangopypi/package_detail.html +++ b/userpypi/templates/djangopypi/package_detail.html @@ -2,7 +2,7 @@ {{ package.name }} + href="{% url userpypi-package-doap package=package.name %}"/>

    {{ package.name }}

    diff --git a/userpypi/templates/djangopypi/package_doap.xml b/userpypi/templates/djangopypi/package_doap.xml index 8f37419..b1601f1 100644 --- a/userpypi/templates/djangopypi/package_doap.xml +++ b/userpypi/templates/djangopypi/package_doap.xml @@ -50,10 +50,10 @@ {% endwith %} {% endif %} {% if release %} - {% include "djangopypi/release_doap_fragment.xml" %} + {% include "userpypi/release_doap_fragment.xml" %} {% else %} {% for release in package.releases.all %} - {% include "djangopypi/release_doap_fragment.xml" %} + {% include "userpypi/release_doap_fragment.xml" %} {% endfor %} {% endif %} diff --git a/userpypi/templates/djangopypi/package_list_simple.html b/userpypi/templates/djangopypi/package_list_simple.html index 9883f0d..233434d 100644 --- a/userpypi/templates/djangopypi/package_list_simple.html +++ b/userpypi/templates/djangopypi/package_list_simple.html @@ -4,7 +4,7 @@ {% for package in package_list %} -{{ package.name }}
    +{{ package.name }}
    {% endfor %} \ No newline at end of file diff --git a/userpypi/templates/djangopypi/package_manage_versions.html b/userpypi/templates/djangopypi/package_manage_versions.html index 44109af..4330af9 100644 --- a/userpypi/templates/djangopypi/package_manage_versions.html +++ b/userpypi/templates/djangopypi/package_manage_versions.html @@ -23,9 +23,9 @@

    Manage {{ package.name }} Versions

    {{ form.DELETE }} {{ release.version }} {{ form.hidden }} - Show - Edit - Files + Show + Edit + Files {% endwith %} {% endfor %} diff --git a/userpypi/templates/djangopypi/release_detail.html b/userpypi/templates/djangopypi/release_detail.html index 915da98..c34cf1b 100644 --- a/userpypi/templates/djangopypi/release_detail.html +++ b/userpypi/templates/djangopypi/release_detail.html @@ -2,7 +2,7 @@ {{ release }} + href="{% url userpypi-release-doap package=release.package.name version=release.version %}"/>

    {{ release }}

    diff --git a/userpypi/templates/djangopypi/release_doap.xml b/userpypi/templates/djangopypi/release_doap.xml index a30fa6e..24759c1 100644 --- a/userpypi/templates/djangopypi/release_doap.xml +++ b/userpypi/templates/djangopypi/release_doap.xml @@ -1,2 +1,2 @@ -{% with release.package as package %}{% include "djangopypi/package_doap.xml" %}{% endwith %} +{% with release.package as package %}{% include "userpypi/package_doap.xml" %}{% endwith %} diff --git a/userpypi/templates/djangopypi/release_manage_files.html b/userpypi/templates/djangopypi/release_manage_files.html index 4f7ce44..f629fc7 100644 --- a/userpypi/templates/djangopypi/release_manage_files.html +++ b/userpypi/templates/djangopypi/release_manage_files.html @@ -41,7 +41,7 @@

    Manage {{ release }} Files

    {% endif %} {% if upload_form %}

    Upload a Distribution

    -
    {{ upload_form.as_p }}
    diff --git a/userpypi/tests/__init__.py b/userpypi/tests/__init__.py index 81f507e..9498d8f 100644 --- a/userpypi/tests/__init__.py +++ b/userpypi/tests/__init__.py @@ -2,8 +2,8 @@ import unittest import xmlrpclib import StringIO -#from djangopypi.views import parse_distutils_request, simple -from djangopypi.models import Package, Classifier, Release, PackageInfoField, Distribution +#from userpypi.views import parse_distutils_request, simple +from userpypi.models import Package, Classifier, Release, PackageInfoField, Distribution from django.test.client import Client from django.core.urlresolvers import reverse from django.contrib.auth.models import User @@ -112,7 +112,7 @@ def tearDown(self): self.dummy_user.delete() def test_search_for_package(self): - response = client.post(reverse('djangopypi-search'), {'search_term': 'foo'}) + response = client.post(reverse('userpypi-search'), {'search_term': 'foo'}) self.assertTrue("The quick brown fox jumps over the lazy dog." in response.content) class TestSimpleView(unittest.TestCase): diff --git a/userpypi/urls.py b/userpypi/urls.py index d198071..00ad5c7 100644 --- a/userpypi/urls.py +++ b/userpypi/urls.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from django.conf.urls.defaults import patterns, url from userpypi.feeds import ReleaseFeed +from userpypi.decorators import user_owns_package from .views.packages import PackageListView, PackageDetailView, PackageManageView from .views.releases import ReleaseDetailView @@ -16,7 +17,7 @@ name='userpypi-package-index'), url(r'^(?P[^/]+)/search/$', - 'packages.search', + 'userpypi.views.packages.search', name='userpypi-search'), url(r'^(?P[^/]+)/rss/$', ReleaseFeed(), @@ -44,7 +45,7 @@ PackageDetailView.as_view(doap=True), name='userpypi-package-doap'), url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/manage/$', - PackageManageView.as_view(), + user_owns_package()(PackageManageView.as_view()), name='userpypi-package-manage'), url(r'^pypi/(?P[\w\d_\.\-]+)/manage/versions/$', 'userpypi.views.packages.manage_versions', diff --git a/userpypi/views/__init__.py b/userpypi/views/__init__.py index 9ddfc8e..19b83ca 100644 --- a/userpypi/views/__init__.py +++ b/userpypi/views/__init__.py @@ -1,11 +1,11 @@ from django.http import HttpResponseNotAllowed -from djangopypi.decorators import csrf_exempt -from djangopypi.http import parse_distutils_request -from djangopypi.models import Package, Release -from djangopypi.views.xmlrpc import parse_xmlrpc_request -from djangopypi.settings import (FALLBACK_VIEW, ACTION_VIEWS) -from djangopypi.utils import get_class +from userpypi.decorators import csrf_exempt +from userpypi.http import parse_distutils_request +from userpypi.models import Package, Release +from userpypi.views.xmlrpc import parse_xmlrpc_request +from userpypi.settings import (FALLBACK_VIEW, ACTION_VIEWS) +from userpypi.utils import get_class @csrf_exempt def root(request, fallback_view=None, **kwargs): diff --git a/userpypi/views/distutils.py b/userpypi/views/distutils.py index 1275da3..f922152 100644 --- a/userpypi/views/distutils.py +++ b/userpypi/views/distutils.py @@ -7,10 +7,10 @@ from django.utils.datastructures import MultiValueDict from django.contrib.auth import login -from djangopypi.decorators import basic_auth -from djangopypi.forms import PackageForm, ReleaseForm -from djangopypi.models import Package, Release, Distribution, Classifier -from djangopypi.settings import (ALLOW_VERSION_OVERWRITE, METADATA_FIELDS, RELEASE_UPLOAD_TO) +from userpypi.decorators import basic_auth +from userpypi.forms import PackageForm, ReleaseForm +from userpypi.models import Package, Release, Distribution, Classifier +from userpypi.settings import (ALLOW_VERSION_OVERWRITE, METADATA_FIELDS, RELEASE_UPLOAD_TO) ALREADY_EXISTS_FMT = _( diff --git a/userpypi/views/packages.py b/userpypi/views/packages.py index 111c277..838c16e 100644 --- a/userpypi/views/packages.py +++ b/userpypi/views/packages.py @@ -11,6 +11,7 @@ from userpypi.decorators import user_owns_package, user_maintains_package from userpypi.models import Package, Release from userpypi.forms import SimplePackageSearchForm, PackageForm +from django.views.generic import ListView, DetailView, UpdateView from userpypi.settings import PROXY_MISSING, PROXY_BASE_URL @@ -18,7 +19,7 @@ class OwnerObjectMixin(object): def get_context_data(self, **kwargs): context = super(OwnerObjectMixin, self).get_context_data(**kwargs) context['owner'] = self.kwargs.get('owner', None) - context['is_owner'] = self.owner == self.request.user.owner + context['is_owner'] = self.owner == self.request.user.username return context def get_queryset(self): @@ -28,7 +29,7 @@ def get_queryset(self): """ self.owner = self.kwargs['owner'] - if self.request.user.owner != self.owner: + if self.request.user.username != self.owner: params = dict(owner__owner=self.owner, private=False) else: params = dict(owner=self.request.user) @@ -107,7 +108,6 @@ class PackageManageView(OwnerObjectMixin, UpdateView): context_object_name = 'package' template_name = 'userpypi/package_manage.html' - @method_decorator(user_owns_package) def dispatch(self, *args, **kwargs): return super(PackageManageView, self).dispatch(*args, **kwargs) diff --git a/userpypi/views/releases.py b/userpypi/views/releases.py index c057f7a..ca2d9d8 100644 --- a/userpypi/views/releases.py +++ b/userpypi/views/releases.py @@ -2,6 +2,7 @@ from django.core.urlresolvers import reverse from django.forms.models import inlineformset_factory from django.http import Http404, HttpResponseRedirect +from django.views.generic import ListView, DetailView, UpdateView from django.views.generic import list_detail, create_update from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext @@ -69,7 +70,7 @@ def get_template_names(self): @user_maintains_package() -def manage(request, package, version, **kwargs): +def manage(request, owner, package, version, **kwargs): kwargs.pop('owner') release = get_object_or_404(Package, owner=request.user, name=package).get_release(version) @@ -86,7 +87,7 @@ def manage(request, package, version, **kwargs): return create_update.update_object(request, **kwargs) @user_maintains_package() -def manage_metadata(request, package, version, **kwargs): +def manage_metadata(request, owner, package, version, **kwargs): kwargs.pop('owner') kwargs.setdefault('template_name', 'userpypi/release_manage.html') kwargs.setdefault('template_object_name', 'release') diff --git a/userpypi/views/xmlrpc.py b/userpypi/views/xmlrpc.py index a65fb01..077e119 100644 --- a/userpypi/views/xmlrpc.py +++ b/userpypi/views/xmlrpc.py @@ -2,9 +2,9 @@ from django.http import HttpResponseNotAllowed, HttpResponse -from djangopypi.models import Package, Release -from djangopypi.settings import XMLRPC_COMMANDS -from djangopypi.utils import get_class +from userpypi.models import Package, Release +from userpypi.settings import XMLRPC_COMMANDS +from userpypi.utils import get_class class XMLRPCResponse(HttpResponse): """ A wrapper around the base HttpResponse that dumps the output for xmlrpc From 307873411641ecc8eb3b45ac808a6a9fffcfdf3c Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 15 Apr 2012 17:38:53 -0400 Subject: [PATCH 121/146] Altered the debug wrapper so it only debugs if DEBUG is true. --- userpypi/utils.py | 17 ++++++++++------- userpypi/views/__init__.py | 3 ++- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/userpypi/utils.py b/userpypi/utils.py index 5513c0d..48a58ec 100644 --- a/userpypi/utils.py +++ b/userpypi/utils.py @@ -1,16 +1,19 @@ import sys, traceback +from django.conf import settings from django.utils.importlib import import_module from django.core.exceptions import ImproperlyConfigured def debug(func): # @debug is handy when debugging distutils requests - def _wrapped(*args, **kwargs): - try: - return func(*args, **kwargs) - except: - traceback.print_exception(*sys.exc_info()) - return _wrapped - + if settings.DEBUG: + def _wrapped(*args, **kwargs): + try: + return func(*args, **kwargs) + except: + traceback.print_exception(*sys.exc_info()) + return _wrapped + else: + return func def get_class(import_path): try: diff --git a/userpypi/views/__init__.py b/userpypi/views/__init__.py index 19b83ca..8f7fa7f 100644 --- a/userpypi/views/__init__.py +++ b/userpypi/views/__init__.py @@ -5,9 +5,10 @@ from userpypi.models import Package, Release from userpypi.views.xmlrpc import parse_xmlrpc_request from userpypi.settings import (FALLBACK_VIEW, ACTION_VIEWS) -from userpypi.utils import get_class +from userpypi.utils import get_class, debug @csrf_exempt +@debug def root(request, fallback_view=None, **kwargs): """ Root view of the package index, handle incoming actions from distutils or redirect to a more user friendly view """ From b101d592f2c35e91014cea491ca73e71ad91baa9 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Sun, 15 Apr 2012 17:39:07 -0400 Subject: [PATCH 122/146] Changed the FALLBACK_VIEW default --- userpypi/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userpypi/settings.py b/userpypi/settings.py index 38909eb..9c7ca72 100644 --- a/userpypi/settings.py +++ b/userpypi/settings.py @@ -72,7 +72,7 @@ '1.1': 'userpypi.forms.Metadata11Form', '1.2': 'userpypi.forms.Metadata12Form' }, - 'FALLBACK_VIEW': 'userpypi.views.releases.index', + 'FALLBACK_VIEW': 'userpypi.views.releases.ReleaseListView', 'ACTION_VIEWS': { "file_upload": 'userpypi.views.distutils.register_or_upload', #``sdist`` command "submit": 'userpypi.views.distutils.register_or_upload', #``register`` command From 9eb557a4d0f035a90e27f10b0a0ccd78639793f6 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 20 Apr 2012 08:21:10 -0400 Subject: [PATCH 123/146] Working basic_auth --- userpypi/urls.py | 17 ++++++++++------- userpypi/views/__init__.py | 6 ++++-- userpypi/views/packages.py | 5 +---- userpypi/views/releases.py | 26 +++++++++++++++++++++++--- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/userpypi/urls.py b/userpypi/urls.py index 00ad5c7..ae009be 100644 --- a/userpypi/urls.py +++ b/userpypi/urls.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- from django.conf.urls.defaults import patterns, url from userpypi.feeds import ReleaseFeed -from userpypi.decorators import user_owns_package +from userpypi.decorators import user_owns_package, basic_auth from .views.packages import PackageListView, PackageDetailView, PackageManageView -from .views.releases import ReleaseDetailView +from .views.releases import ReleaseDetailView, ReleaseListView urlpatterns = patterns('', @@ -15,6 +15,9 @@ url(r'^(?P[^/]+)/packages/$', PackageListView.as_view(), name='userpypi-package-index'), + url(r'^(?P[^/]+)/pypi/releases/$', + ReleaseListView.as_view(), + name='userpypi-release-list'), url(r'^(?P[^/]+)/search/$', 'userpypi.views.packages.search', @@ -25,18 +28,18 @@ # Simple indexes url(r'^(?P[^/]+)/simple/$', - PackageListView.as_view(simple=True), + basic_auth(PackageListView.as_view(simple=True)), name='userpypi-package-index-simple'), - url(r'^(?P[^/]+)/simple/(?P[\w\d_\.\-]+)/$', - PackageDetailView.as_view(simple=True), + url(r'^(?P[^/]+)/simple/(?P[\w\d_\.\-]+)/?$', + basic_auth(PackageDetailView.as_view(simple=True)), name='userpypi-package-simple'), # Regular Package Indexes url(r'^(?P[^/]+)/pypi/$', 'userpypi.views.root', name="userpypi-root"), - url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/$', - PackageDetailView.as_view(), + url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/?$', + basic_auth(PackageDetailView.as_view()), name='userpypi-package'), url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/rss/$', ReleaseFeed(), diff --git a/userpypi/views/__init__.py b/userpypi/views/__init__.py index 8f7fa7f..6f78bae 100644 --- a/userpypi/views/__init__.py +++ b/userpypi/views/__init__.py @@ -1,6 +1,6 @@ from django.http import HttpResponseNotAllowed -from userpypi.decorators import csrf_exempt +from userpypi.decorators import csrf_exempt, basic_auth from userpypi.http import parse_distutils_request from userpypi.models import Package, Release from userpypi.views.xmlrpc import parse_xmlrpc_request @@ -9,6 +9,7 @@ @csrf_exempt @debug +@basic_auth def root(request, fallback_view=None, **kwargs): """ Root view of the package index, handle incoming actions from distutils or redirect to a more user friendly view """ @@ -20,10 +21,11 @@ def root(request, fallback_view=None, **kwargs): action = request.POST.get(':action','') else: action = request.GET.get(':action','') - if not action: if fallback_view is None: fallback_view = get_class(FALLBACK_VIEW) + if hasattr(fallback_view, 'as_view'): + return fallback_view.as_view()(request, **kwargs) return fallback_view(request, **kwargs) if not action in ACTION_VIEWS: diff --git a/userpypi/views/packages.py b/userpypi/views/packages.py index 838c16e..a831d38 100644 --- a/userpypi/views/packages.py +++ b/userpypi/views/packages.py @@ -30,7 +30,7 @@ def get_queryset(self): self.owner = self.kwargs['owner'] if self.request.user.username != self.owner: - params = dict(owner__owner=self.owner, private=False) + params = dict(owner__username=self.owner, private=False) else: params = dict(owner=self.request.user) return self.model.objects.filter(**params) @@ -91,9 +91,6 @@ def get_template_names(self): Returns a list of template names to be used for the request. Must return a list. May not be called if render_to_response is overridden. """ - self.doap = 'doap' in self.kwargs and self.kwargs['doap'] - self.simple = 'simple' in self.kwargs and self.kwargs['simple'] - if self.simple: return ['userpypi/package_detail_simple.html'] elif self.doap: diff --git a/userpypi/views/releases.py b/userpypi/views/releases.py index ca2d9d8..28ff5bb 100644 --- a/userpypi/views/releases.py +++ b/userpypi/views/releases.py @@ -12,9 +12,29 @@ from userpypi.forms import ReleaseForm, DistributionUploadForm from userpypi.settings import METADATA_FORMS from userpypi.utils import get_class -from userpypi.views.packages import OwnerObjectMixin -class ReleaseListView(OwnerObjectMixin, ListView): +class ReleaseOwnerObjectMixin(object): + def get_context_data(self, **kwargs): + context = super(ReleaseOwnerObjectMixin, self).get_context_data(**kwargs) + context['owner'] = self.kwargs.get('owner', None) + context['is_owner'] = self.owner == self.request.user.username + return context + + def get_queryset(self): + """ + Filter the queryset based on whether or not the requesting user is + the owner of the requested objects + """ + self.owner = self.kwargs['owner'] + + if self.request.user.username != self.owner: + params = dict(package__owner__username=self.owner, package__private=False) + else: + params = dict(package__owner=self.request.user) + return self.model.objects.filter(**params) + + +class ReleaseListView(ReleaseOwnerObjectMixin, ListView): model = Release context_object_name = 'release_list' simple = False @@ -31,7 +51,7 @@ def get_template_names(self): return ['userpypi/release_list.html'] -class ReleaseDetailView(OwnerObjectMixin, DetailView): +class ReleaseDetailView(ReleaseOwnerObjectMixin, DetailView): model = Release context_object_name = 'release' doap = False From e104db32bd681c8c761fe7fe49b503f74d47abb4 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 20 Apr 2012 09:50:31 -0400 Subject: [PATCH 124/146] Properly handling missing packages and proxy requests with a redirect within the class-based views. --- userpypi/views/packages.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/userpypi/views/packages.py b/userpypi/views/packages.py index a831d38..5b41a40 100644 --- a/userpypi/views/packages.py +++ b/userpypi/views/packages.py @@ -59,11 +59,15 @@ class PackageDetailView(OwnerObjectMixin, DetailView): simple = False doap = False owner = None + redirect = '' def render_to_response(self, context, **response_kwargs): """ Returns a response with a template rendered with the given context. """ + if self.redirect: + return HttpResponseRedirect(self.redirect) + self.doap = 'doap' in self.kwargs and self.kwargs['doap'] if self.doap: @@ -79,9 +83,8 @@ def get_object(self): obj = queryset.get() except ObjectDoesNotExist: if PROXY_MISSING: - return HttpResponseRedirect('%s/%s/' % - (PROXY_BASE_URL.rstrip('/'), - package)) + self.redirect = '%s/%s/' % (PROXY_BASE_URL.rstrip('/'), package) + return None raise Http404(u"No %(verbose_name)s found matching the query" % {'verbose_name': queryset.model._meta.verbose_name}) return obj From 6d865409c3261ca7220304af032cd9a6700dfb8a Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 25 Apr 2012 15:17:57 -0400 Subject: [PATCH 125/146] Version bump to 0.5 --- userpypi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index 333cfd1..3199ad0 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -5,7 +5,7 @@ 'major': 0, 'minor': 5, 'micro': 0, - 'releaselevel': 'beta', + 'releaselevel': 'final', 'serial': 1 } From dd464c69131c6786ad179000de288e271757fecc Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 4 May 2012 16:16:25 -0400 Subject: [PATCH 126/146] renamed username param to owner --- userpypi/__init__.py | 2 +- userpypi/views/distutils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index 3199ad0..56d684f 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -4,7 +4,7 @@ __version_info__ = { 'major': 0, 'minor': 5, - 'micro': 0, + 'micro': 1, 'releaselevel': 'final', 'serial': 1 } diff --git a/userpypi/views/distutils.py b/userpypi/views/distutils.py index f922152..642610d 100644 --- a/userpypi/views/distutils.py +++ b/userpypi/views/distutils.py @@ -65,7 +65,7 @@ def submit_package_or_release(user, post_data, files): @basic_auth @transaction.commit_manually -def register_or_upload(request, username=None): +def register_or_upload(request, owner=None): if request.method != 'POST': transaction.rollback() return HttpResponseBadRequest('Only post requests are supported') From 50ed773fb00617dbf4331dd3498d5ef11a774ccd Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 10 May 2012 20:08:06 -0400 Subject: [PATCH 127/146] Adding initial maintainer support --- userpypi/__init__.py | 6 ++--- userpypi/forms.py | 45 ++++++++++++++++++++++++++++++++------ userpypi/models.py | 2 +- userpypi/views/packages.py | 10 ++++++++- 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index 56d684f..893e088 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -4,9 +4,9 @@ __version_info__ = { 'major': 0, 'minor': 5, - 'micro': 1, - 'releaselevel': 'final', - 'serial': 1 + 'micro': 2, + 'releaselevel': 'beta', + 'serial': 3 } def get_version(short=False): diff --git a/userpypi/forms.py b/userpypi/forms.py index ba1fb26..f857210 100644 --- a/userpypi/forms.py +++ b/userpypi/forms.py @@ -3,19 +3,50 @@ from django import forms from django.conf import settings from django.utils.translation import ugettext_lazy as _ +from django.forms.models import inlineformset_factory -from userpypi.settings import (ALLOW_VERSION_OVERWRITE, METADATA_FIELDS, ) -from userpypi.models import Package, Classifier, Release, Distribution +from userpypi.settings import ALLOW_VERSION_OVERWRITE, METADATA_FIELDS +from userpypi.models import (Package, Classifier, Release, Distribution, + Maintainer) +from django.contrib.auth.models import User +from selectable.base import ModelLookup +from selectable.forms import AutoCompleteSelectField +from selectable.registry import registry +class UserLookup(ModelLookup): + model = User + search_fields = ( + 'username__icontains', + 'first_name__icontains', + 'last_name__icontains', + ) + filters = {'is_active': True, } + + def get_item_value(self, item): + # Display for currently selected item + return item.username + + def get_item_label(self, item): + # Display for choice listings + return u"%s (%s)" % (item.username, item.get_full_name()) +registry.register(UserLookup) class SimplePackageSearchForm(forms.Form): query = forms.CharField(max_length=255) +class MaintainerForm(forms.ModelForm): + user = AutoCompleteSelectField(lookup_class=UserLookup) + + class Meta: + model = Maintainer + +MaintainerFormSet = inlineformset_factory(Package, Maintainer, form=MaintainerForm) + class PackageForm(forms.ModelForm): class Meta: model = Package - exclude = ['name', 'owner', 'private'] + exclude = ['name', 'owner', 'private', 'maintainers'] class DistributionUploadForm(forms.ModelForm): class Meta: @@ -26,7 +57,6 @@ def clean_content(self): content = self.cleaned_data['content'] storage = self.instance.content.storage field = self.instance.content.field - name = field.generate_filename(instance=self.instance, filename=content.name) @@ -40,11 +70,12 @@ def clean_content(self): raise forms.ValidationError('That distribution already exists, please ' 'delete it first before uploading a new ' 'version.') - + class ReleaseForm(forms.ModelForm): - metadata_version = forms.CharField(widget=forms.Select(choices=zip(METADATA_FIELDS.keys(), - METADATA_FIELDS.keys()))) + metadata_version = forms.CharField( + widget=forms.Select(choices=zip(METADATA_FIELDS.keys(), + METADATA_FIELDS.keys()))) class Meta: model = Release diff --git a/userpypi/models.py b/userpypi/models.py index 12a7333..a789d1d 100644 --- a/userpypi/models.py +++ b/userpypi/models.py @@ -107,7 +107,7 @@ def get_release(self, version): class Maintainer(models.Model): package = models.ForeignKey(Package) user = models.ForeignKey(User) - permission = models.BigIntegerField(blank=True, null=True) + permission = models.BigIntegerField(choices=enumerate(['Read Only', 'Read and Write'])) class Release(models.Model): diff --git a/userpypi/views/packages.py b/userpypi/views/packages.py index 5b41a40..c2db06c 100644 --- a/userpypi/views/packages.py +++ b/userpypi/views/packages.py @@ -10,7 +10,7 @@ from userpypi.decorators import user_owns_package, user_maintains_package from userpypi.models import Package, Release -from userpypi.forms import SimplePackageSearchForm, PackageForm +from userpypi.forms import SimplePackageSearchForm, PackageForm, MaintainerFormSet from django.views.generic import ListView, DetailView, UpdateView from userpypi.settings import PROXY_MISSING, PROXY_BASE_URL @@ -111,6 +111,14 @@ class PackageManageView(OwnerObjectMixin, UpdateView): def dispatch(self, *args, **kwargs): return super(PackageManageView, self).dispatch(*args, **kwargs) + def get(self, request, *args, **kwargs): + self.object = self.get_object() + form_class = self.get_form_class() + form = self.get_form(form_class) + maintainer_formset = MaintainerFormSet(instance=self.object) + return self.render_to_response(self.get_context_data( + form=form, maintainer_formset=maintainer_formset)) + def get_object(self): package = self.kwargs.get('package', None) try: From fbe2ae2ebf7a27e98b2bbaf40ef6acde10e0c51e Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Thu, 10 May 2012 20:09:05 -0400 Subject: [PATCH 128/146] ignoring build directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 82aed01..6327f8f 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ downloads .installed.cfg chishop/media/dists/* chishop/haystack/* +build/ From b8375bce8597de51247b1b19ac03f4af976fc909 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 11 May 2012 13:39:04 -0400 Subject: [PATCH 129/146] Switched back to a normal function view for the manage view. It was tons easier with the inline checking. --- userpypi/forms.py | 11 +++++-- userpypi/urls.py | 5 ++-- userpypi/views/packages.py | 59 +++++++++++++++++++++----------------- 3 files changed, 43 insertions(+), 32 deletions(-) diff --git a/userpypi/forms.py b/userpypi/forms.py index f857210..2880b1e 100644 --- a/userpypi/forms.py +++ b/userpypi/forms.py @@ -40,13 +40,18 @@ class MaintainerForm(forms.ModelForm): class Meta: model = Maintainer - -MaintainerFormSet = inlineformset_factory(Package, Maintainer, form=MaintainerForm) +MaintainerFormSet = inlineformset_factory(Package, Maintainer, + form=MaintainerForm, extra=0) class PackageForm(forms.ModelForm): class Meta: model = Package - exclude = ['name', 'owner', 'private', 'maintainers'] + exclude = ['maintainers'] + widgets = { + 'owner': forms.HiddenInput, + 'name': forms.HiddenInput, + 'private': forms.HiddenInput + } class DistributionUploadForm(forms.ModelForm): class Meta: diff --git a/userpypi/urls.py b/userpypi/urls.py index ae009be..b405ebb 100644 --- a/userpypi/urls.py +++ b/userpypi/urls.py @@ -3,7 +3,8 @@ from userpypi.feeds import ReleaseFeed from userpypi.decorators import user_owns_package, basic_auth -from .views.packages import PackageListView, PackageDetailView, PackageManageView +from .views.packages import (PackageListView, PackageDetailView, + PackageManageView, manage) from .views.releases import ReleaseDetailView, ReleaseListView urlpatterns = patterns('', @@ -48,7 +49,7 @@ PackageDetailView.as_view(doap=True), name='userpypi-package-doap'), url(r'^(?P[^/]+)/pypi/(?P[\w\d_\.\-]+)/manage/$', - user_owns_package()(PackageManageView.as_view()), + manage, name='userpypi-package-manage'), url(r'^pypi/(?P[\w\d_\.\-]+)/manage/versions/$', 'userpypi.views.packages.manage_versions', diff --git a/userpypi/views/packages.py b/userpypi/views/packages.py index c2db06c..8b1e49e 100644 --- a/userpypi/views/packages.py +++ b/userpypi/views/packages.py @@ -1,12 +1,13 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db.models.query import Q -from django.http import Http404, HttpResponseRedirect +from django.http import Http404, HttpResponseRedirect, HttpResponse from django.forms.models import inlineformset_factory from django.shortcuts import get_object_or_404, render_to_response -from django.template import RequestContext +from django.template import RequestContext, loader from django.utils.decorators import method_decorator from django.views.generic import ListView, DetailView, UpdateView, create_update +from django.core.urlresolvers import reverse from userpypi.decorators import user_owns_package, user_maintains_package from userpypi.models import Package, Release @@ -101,33 +102,37 @@ def get_template_names(self): else: return ['userpypi/package_detail.html'] - -class PackageManageView(OwnerObjectMixin, UpdateView): - model = Package - form_class = PackageForm - context_object_name = 'package' - template_name = 'userpypi/package_manage.html' - - def dispatch(self, *args, **kwargs): - return super(PackageManageView, self).dispatch(*args, **kwargs) +@user_maintains_package() +def manage(request, owner, package): + """ + """ + template_name='userpypi/package_manage.html' + template_object_name='package' + form_class=PackageForm + obj = get_object_or_404(Package, owner__username=owner, name=package) - def get(self, request, *args, **kwargs): - self.object = self.get_object() - form_class = self.get_form_class() - form = self.get_form(form_class) - maintainer_formset = MaintainerFormSet(instance=self.object) - return self.render_to_response(self.get_context_data( - form=form, maintainer_formset=maintainer_formset)) + if request.method == 'POST': + form = PackageForm(request.POST, instance=obj) + if form.is_valid(): + obj = form.save(commit=False) + maintainer_formset = MaintainerFormSet(request.POST, instance=obj) + if maintainer_formset.is_valid(): + obj.save() + maintainer_formset.save() + return HttpResponseRedirect(reverse('userpypi-package-manage', kwargs={'owner': owner, 'package': obj})) + else: + form = PackageForm(instance=obj) + maintainer_formset = MaintainerFormSet(instance=obj) - def get_object(self): - package = self.kwargs.get('package', None) - try: - queryset = self.get_queryset().filter(name=package) - obj = queryset.get() - except ObjectDoesNotExist: - raise Http404(u"No %(verbose_name)s found matching the query" % - {'verbose_name': queryset.model._meta.verbose_name}) - return obj + t = loader.get_template(template_name) + c = RequestContext(request, { + 'form': form, + 'maintainer_formset': maintainer_formset, + 'package': obj, + }) + response = HttpResponse(t.render(c)) + return response + def search(request, **kwargs): if request.method == 'POST': From 76dc963b2f3729acd1188b2de7a2d0a7a8272341 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 11 May 2012 13:41:47 -0400 Subject: [PATCH 130/146] Forgot to not import the view I just deleted --- userpypi/urls.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/userpypi/urls.py b/userpypi/urls.py index b405ebb..1857080 100644 --- a/userpypi/urls.py +++ b/userpypi/urls.py @@ -3,8 +3,7 @@ from userpypi.feeds import ReleaseFeed from userpypi.decorators import user_owns_package, basic_auth -from .views.packages import (PackageListView, PackageDetailView, - PackageManageView, manage) +from .views.packages import PackageListView, PackageDetailView, manage from .views.releases import ReleaseDetailView, ReleaseListView urlpatterns = patterns('', From 9efd2e096b4ecf3c26c3e6f7e354ff5191146b89 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 11 May 2012 13:46:11 -0400 Subject: [PATCH 131/146] Need at least 1 extra inline for the dynamic adding to work. --- userpypi/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userpypi/forms.py b/userpypi/forms.py index 2880b1e..9df493c 100644 --- a/userpypi/forms.py +++ b/userpypi/forms.py @@ -41,7 +41,7 @@ class MaintainerForm(forms.ModelForm): class Meta: model = Maintainer MaintainerFormSet = inlineformset_factory(Package, Maintainer, - form=MaintainerForm, extra=0) + form=MaintainerForm, extra=1) class PackageForm(forms.ModelForm): class Meta: From bfd81aeb79430eccc736ed5de34015d1aa97e4d5 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 11 May 2012 13:46:34 -0400 Subject: [PATCH 132/146] Version bump to 0.5.2 --- userpypi/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index 893e088..a6fb0fe 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -5,8 +5,8 @@ 'major': 0, 'minor': 5, 'micro': 2, - 'releaselevel': 'beta', - 'serial': 3 + 'releaselevel': 'final', + 'serial': 1 } def get_version(short=False): From 39c07e99b9cd8f9ad64dd81e4b2a28775edaf99b Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 25 May 2012 06:58:56 -0400 Subject: [PATCH 133/146] Added permissions for fine tuning of maintainer and user permissions --- userpypi/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/userpypi/models.py b/userpypi/models.py index a789d1d..4d7b659 100644 --- a/userpypi/models.py +++ b/userpypi/models.py @@ -79,6 +79,12 @@ class Meta: get_latest_by = "releases__latest" ordering = ['name',] unique_together = ('owner', 'name',) + permissions = ( + ('read_packages', 'Read Packages'), + ('update_packages', 'Update Packages'), + ('create_packages', 'Create Packages'), + ('admin', 'Administrator'), + ) def __unicode__(self): return self.name From 3d61c1d91b8e151d9b1a4b5bc228cfee52b70146 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 25 May 2012 06:59:55 -0400 Subject: [PATCH 134/146] Changed the OwnerMixin to include "owner" in the context as a User object. --- userpypi/views/packages.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/userpypi/views/packages.py b/userpypi/views/packages.py index 8b1e49e..cc92b43 100644 --- a/userpypi/views/packages.py +++ b/userpypi/views/packages.py @@ -1,37 +1,50 @@ from django.conf import settings +from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist +from django.core.urlresolvers import reverse from django.db.models.query import Q -from django.http import Http404, HttpResponseRedirect, HttpResponse from django.forms.models import inlineformset_factory +from django.http import Http404, HttpResponseRedirect, HttpResponse from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext, loader from django.utils.decorators import method_decorator from django.views.generic import ListView, DetailView, UpdateView, create_update -from django.core.urlresolvers import reverse +from django.views.generic import ListView, DetailView, UpdateView from userpypi.decorators import user_owns_package, user_maintains_package from userpypi.models import Package, Release from userpypi.forms import SimplePackageSearchForm, PackageForm, MaintainerFormSet -from django.views.generic import ListView, DetailView, UpdateView from userpypi.settings import PROXY_MISSING, PROXY_BASE_URL class OwnerObjectMixin(object): def get_context_data(self, **kwargs): context = super(OwnerObjectMixin, self).get_context_data(**kwargs) - context['owner'] = self.kwargs.get('owner', None) + context['owner'] = self.get_owner() context['is_owner'] = self.owner == self.request.user.username return context + def get_owner(self): + """ + Set the owner user object + """ + owner = getattr(self, 'owner') + if owner and issubclass(owner.__class__, User): + return owner + owner = self.kwargs.get('owner', None) + if owner is not None: + self.owner = User.objects.get(username=owner) + else: + self.owner = None + return self.owner + def get_queryset(self): """ Filter the queryset based on whether or not the requesting user is the owner of the requested objects """ - self.owner = self.kwargs['owner'] - - if self.request.user.username != self.owner: - params = dict(owner__username=self.owner, private=False) + if self.request.user != self.get_owner(): + params = dict(owner=self.owner, private=False) else: params = dict(owner=self.request.user) return self.model.objects.filter(**params) From 03c05a688536227c228875b8e382ae36b59cde0a Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 25 May 2012 07:00:26 -0400 Subject: [PATCH 135/146] Version bump to 0.5.3 --- userpypi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index a6fb0fe..fc39493 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -4,7 +4,7 @@ __version_info__ = { 'major': 0, 'minor': 5, - 'micro': 2, + 'micro': 3, 'releaselevel': 'final', 'serial': 1 } From 2fa9e4fd68fc349ebb226f6ad2ff0559077779e4 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 28 May 2012 07:49:31 -0400 Subject: [PATCH 136/146] Added a get_owner method for releases --- userpypi/views/releases.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/userpypi/views/releases.py b/userpypi/views/releases.py index 28ff5bb..2fec849 100644 --- a/userpypi/views/releases.py +++ b/userpypi/views/releases.py @@ -1,5 +1,6 @@ from django.conf import settings from django.core.urlresolvers import reverse +from django.contrib.auth.models import User from django.forms.models import inlineformset_factory from django.http import Http404, HttpResponseRedirect from django.views.generic import ListView, DetailView, UpdateView @@ -16,18 +17,30 @@ class ReleaseOwnerObjectMixin(object): def get_context_data(self, **kwargs): context = super(ReleaseOwnerObjectMixin, self).get_context_data(**kwargs) - context['owner'] = self.kwargs.get('owner', None) + context['owner'] = self.get_owner() context['is_owner'] = self.owner == self.request.user.username return context + def get_owner(self): + """ + Set the owner user object + """ + owner = getattr(self, 'owner') + if owner and issubclass(owner.__class__, User): + return owner + owner = self.kwargs.get('owner', None) + if owner is not None: + self.owner = User.objects.get(username=owner) + else: + self.owner = None + return self.owner + def get_queryset(self): """ Filter the queryset based on whether or not the requesting user is the owner of the requested objects """ - self.owner = self.kwargs['owner'] - - if self.request.user.username != self.owner: + if self.request.user != self.get_owner(): params = dict(package__owner__username=self.owner, package__private=False) else: params = dict(package__owner=self.request.user) From 20aba3645230cba7afcd7430d1f29ff859753164 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 28 May 2012 07:50:04 -0400 Subject: [PATCH 137/146] Version bump to 0.5.4 --- userpypi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index fc39493..17d26b6 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -4,7 +4,7 @@ __version_info__ = { 'major': 0, 'minor': 5, - 'micro': 3, + 'micro': 4, 'releaselevel': 'final', 'serial': 1 } From 336536302113e6c7a91a5fe3172fecc31a4f78c1 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 1 Jun 2012 13:04:41 -0400 Subject: [PATCH 138/146] Fixing the authentication and creation of packages for teams. --- userpypi/views/distutils.py | 67 +++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/userpypi/views/distutils.py b/userpypi/views/distutils.py index 642610d..92ed991 100644 --- a/userpypi/views/distutils.py +++ b/userpypi/views/distutils.py @@ -2,10 +2,11 @@ from django.db import transaction from django.http import (HttpResponseForbidden, HttpResponseBadRequest, - HttpResponse) + HttpResponse, Http404) from django.utils.translation import ugettext_lazy as _ from django.utils.datastructures import MultiValueDict from django.contrib.auth import login +from django.contrib.auth.models import User from userpypi.decorators import basic_auth from userpypi.forms import PackageForm, ReleaseForm @@ -63,41 +64,79 @@ def submit_package_or_release(user, post_data, files): return HttpResponse() +def authorize(request_user, owner_user, package): + """ + Go through the checks to see if the user is authorized to perform any actions + + Returns: success, err_msg + """ + MUST_CREATE = package is not None + + if owner_user.profile.organization: + try: + membership = request_user.memberships.get(team=owner_obj) + except request_user.DoesNotExist: + return False, 'You are not a member of team %s' % owner.username + if MUST_CREATE and membership.permission < 3: # Can't create a new package + return False, 'You can not create packages' + if membership.permission == 1: + return False, 'You can not update packages' + return True, '' + + if request_user != owner_user: + if MUST_CREATE: + return False, "You can not create a package on someone else's account." + try: + maintainer = package.maintainers.get(user=request_user) + except request_user.DoesNotExist: + return False, 'You are not a maintainer of %s' % package.name + if membership.permission == 1: + return False, 'You can not update packages' + return True, '' + @basic_auth @transaction.commit_manually def register_or_upload(request, owner=None): if request.method != 'POST': transaction.rollback() return HttpResponseBadRequest('Only post requests are supported') - name = request.POST.get('name', None).strip() + name = request.POST.get('name', None).strip() if not name: transaction.rollback() return HttpResponseBadRequest('No package name specified') - try: - package = Package.objects.get(owner=request.user, name=name) - except Package.DoesNotExist: - package = Package.objects.create(owner=request.user, name=name) - - if (request.user != package.owner and - request.user not in package.maintainers.all()): - transaction.rollback() - return HttpResponseForbidden( - 'You are not an owner/maintainer of %s' % (package.name,)) version = request.POST.get('version', None).strip() metadata_version = request.POST.get('metadata_version', None).strip() - if not version or not metadata_version: transaction.rollback() return HttpResponseBadRequest( 'Release version and metadata version must be specified') - if not metadata_version in METADATA_FIELDS: transaction.rollback() return HttpResponseBadRequest('Metadata version must be one of: %s' (', '.join(METADATA_FIELDS.keys()),)) + try: + owner_obj = User.objects.get(username=owner) + except User.DoesNotExist: + transaction.rollback() + raise Http404 + + try: + package = Package.objects.get(owner=owner_obj, name=name) + except Package.DoesNotExist: + package = None + + authorized, err_msg = authorize(request.user, owner_obj, package) + + if not authorized: + transaction.rollback() + return HttpResponseForbidden(err_msg) + + if package is None: + package = Package.objects.create(owner=owner_obj, name=name) + release, created = Release.objects.get_or_create(package=package, version=version) From 2bfec0c53584c42913d469d3c64c8ced7d936efe Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Fri, 1 Jun 2012 13:05:38 -0400 Subject: [PATCH 139/146] Version bump to 0.5.5 --- userpypi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index 17d26b6..4479ed0 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -4,7 +4,7 @@ __version_info__ = { 'major': 0, 'minor': 5, - 'micro': 4, + 'micro': 5, 'releaselevel': 'final', 'serial': 1 } From e25c67ad87e1de97dfdc3cd5de8b1cffe38b009a Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Wed, 6 Jun 2012 17:39:29 -0400 Subject: [PATCH 140/146] Updated a mis-typed variable in the authorize method --- userpypi/__init__.py | 2 +- userpypi/views/distutils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index 4479ed0..1a74f2b 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -4,7 +4,7 @@ __version_info__ = { 'major': 0, 'minor': 5, - 'micro': 5, + 'micro': 6, 'releaselevel': 'final', 'serial': 1 } diff --git a/userpypi/views/distutils.py b/userpypi/views/distutils.py index 92ed991..47e1e9f 100644 --- a/userpypi/views/distutils.py +++ b/userpypi/views/distutils.py @@ -74,7 +74,7 @@ def authorize(request_user, owner_user, package): if owner_user.profile.organization: try: - membership = request_user.memberships.get(team=owner_obj) + membership = request_user.memberships.get(team=owner_user) except request_user.DoesNotExist: return False, 'You are not a member of team %s' % owner.username if MUST_CREATE and membership.permission < 3: # Can't create a new package From c4345722b811158130b749ceb5511c9bd8e64ecf Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 11 Jun 2012 08:07:51 -0400 Subject: [PATCH 141/146] Added the owner into the package admin --- userpypi/admin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/userpypi/admin.py b/userpypi/admin.py index 63618be..d6e8d22 100644 --- a/userpypi/admin.py +++ b/userpypi/admin.py @@ -3,9 +3,11 @@ from userpypi.models import * from userpypi.settings import MIRRORING +class PackageAdmin(admin.ModelAdmin): + list_display = ('name', 'owner',) + search_fields = ('name',) - -admin.site.register(Package) +admin.site.register(Package, PackageAdmin) admin.site.register(Release) admin.site.register(Classifier) admin.site.register(Distribution) From 18971e65257c0cd119bdca09145218abfb043be4 Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 11 Jun 2012 08:08:25 -0400 Subject: [PATCH 142/146] Changed the way in which the owner is retrieved to support organizations --- userpypi/views/packages.py | 7 +++++-- userpypi/views/releases.py | 14 +++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/userpypi/views/packages.py b/userpypi/views/packages.py index cc92b43..d2e7f46 100644 --- a/userpypi/views/packages.py +++ b/userpypi/views/packages.py @@ -44,9 +44,12 @@ def get_queryset(self): the owner of the requested objects """ if self.request.user != self.get_owner(): - params = dict(owner=self.owner, private=False) + if self.owner.profile.organization: + params = dict(owner=self.owner) + else: + params = dict(owner=self.owner, private=False) else: - params = dict(owner=self.request.user) + params = dict(owner=self.owner) return self.model.objects.filter(**params) diff --git a/userpypi/views/releases.py b/userpypi/views/releases.py index 2fec849..0d1e999 100644 --- a/userpypi/views/releases.py +++ b/userpypi/views/releases.py @@ -30,9 +30,12 @@ def get_owner(self): return owner owner = self.kwargs.get('owner', None) if owner is not None: - self.owner = User.objects.get(username=owner) + try: + self.owner = User.objects.get(username=owner) + except User.DoesNotExist: + raise Http404 else: - self.owner = None + raise Http404 return self.owner def get_queryset(self): @@ -41,9 +44,10 @@ def get_queryset(self): the owner of the requested objects """ if self.request.user != self.get_owner(): - params = dict(package__owner__username=self.owner, package__private=False) - else: - params = dict(package__owner=self.request.user) + if self.owner.profile.organization: + params = dict(package__owner__username=self.owner) + else: + params = dict(package__owner=self.request.user, private=False) return self.model.objects.filter(**params) From 480fd5f5e78373c14283cb9c516297054c774a5f Mon Sep 17 00:00:00 2001 From: Corey Oordt Date: Mon, 11 Jun 2012 08:09:37 -0400 Subject: [PATCH 143/146] Version bump to 0.5.7 --- userpypi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userpypi/__init__.py b/userpypi/__init__.py index 1a74f2b..c3e8bea 100644 --- a/userpypi/__init__.py +++ b/userpypi/__init__.py @@ -4,7 +4,7 @@ __version_info__ = { 'major': 0, 'minor': 5, - 'micro': 6, + 'micro': 7, 'releaselevel': 'final', 'serial': 1 } From 297cca88d3fe18675aefdc6ca436e3e0cee24873 Mon Sep 17 00:00:00 2001 From: Matt Caldwell Date: Wed, 13 Jun 2012 10:44:26 -0400 Subject: [PATCH 144/146] Fix NameError due to missing ObjectDoesNotExist import, clean up unused imports --- userpypi/views/releases.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/userpypi/views/releases.py b/userpypi/views/releases.py index 0d1e999..9e15cd2 100644 --- a/userpypi/views/releases.py +++ b/userpypi/views/releases.py @@ -1,14 +1,15 @@ from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist from django.core.urlresolvers import reverse from django.contrib.auth.models import User from django.forms.models import inlineformset_factory -from django.http import Http404, HttpResponseRedirect -from django.views.generic import ListView, DetailView, UpdateView -from django.views.generic import list_detail, create_update +from django.http import Http404 +from django.views.generic import ListView, DetailView +from django.views.generic import create_update from django.shortcuts import get_object_or_404, render_to_response from django.template import RequestContext -from userpypi.decorators import user_owns_package, user_maintains_package +from userpypi.decorators import user_maintains_package from userpypi.models import Package, Release, Distribution from userpypi.forms import ReleaseForm, DistributionUploadForm from userpypi.settings import METADATA_FORMS From ba48d8ec48b0cfe2dc8af4ca5c32c9304d34b43c Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 8 Aug 2012 18:56:43 +0700 Subject: [PATCH 145/146] updated buildout.cfg and created a Makefile --- Makefile | 20 ++++++++++++++++++++ buildout.cfg | 1 - 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..31f6cd0 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: run +run: bin/django syncdb + ./bin/django runserver 0.0.0.0:10000 + +.PHONY: shell +shell: bin/django + ./bin/django shell + +bin/django: bin/buildout buildout.cfg + ./bin/buildout + touch bin/django + +bin/buildout: + python bootstrap.py --distribute + +syncdb: bin/django + ./bin/django syncdb + touch syncdb + +buildout.cfg: diff --git a/buildout.cfg b/buildout.cfg index 160db4c..33463b9 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -7,7 +7,6 @@ eggs = pkginfo [django] recipe = djangorecipe -version = 1.1.1 settings = settings eggs = ${buildout:eggs} test = djangopypi From 46e196a7a1ffc4aef83d1b32e7cf60e06cd9c99b Mon Sep 17 00:00:00 2001 From: Developer Date: Wed, 8 Aug 2012 18:57:29 +0700 Subject: [PATCH 146/146] fixed settings to run Django 1.4.1 --- chishop/conf/default.py | 8 ++++---- chishop/settings.py | 16 ++++++++++------ chishop/urls.py | 2 +- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/chishop/conf/default.py b/chishop/conf/default.py index ce43d48..366aaa8 100644 --- a/chishop/conf/default.py +++ b/chishop/conf/default.py @@ -70,9 +70,9 @@ # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.load_template_source', - 'django.template.loaders.app_directories.load_template_source', -# 'django.template.loaders.eggs.load_template_source', + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + #'django.template.loaders.eggs.load_template_source', ) MIDDLEWARE_CLASSES = ( @@ -84,7 +84,7 @@ ROOT_URLCONF = 'chishop.urls' TEMPLATE_CONTEXT_PROCESSORS = ( - "django.core.context_processors.auth", + "django.contrib.auth.context_processors.auth", "django.core.context_processors.debug", "django.core.context_processors.i18n", "django.core.context_processors.media", diff --git a/chishop/settings.py b/chishop/settings.py index e68a4e5..5948446 100644 --- a/chishop/settings.py +++ b/chishop/settings.py @@ -15,9 +15,13 @@ MANAGERS = ADMINS -DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = os.path.join(here, 'devdatabase.db') -DATABASE_USER = '' -DATABASE_PASSWORD = '' -DATABASE_HOST = '' -DATABASE_PORT = '' +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': 'dev.sqlite3', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} diff --git a/chishop/urls.py b/chishop/urls.py index 5a5dd77..7046fcb 100644 --- a/chishop/urls.py +++ b/chishop/urls.py @@ -16,7 +16,7 @@ urlpatterns += patterns("", # Admin interface url(r'^admin/doc/', include("django.contrib.admindocs.urls")), - url(r'^admin/(.*)', admin.site.root), + url(r'^admin/', include(admin.site.urls)), # Registration url(r'^accounts/', include('registration.backends.default.urls')),