mirror of
https://github.com/kennethreitz-archive/chishop.git
synced 2026-06-05 23:40:18 +00:00
Merge branch 'master' of git://github.com/ask/chishop
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
Ask Solem <askh@opera.com>
|
||||
Rune Halvorsen <runeh@opera.com>
|
||||
Russel Sim <russel.sim@jcu.edu.au>
|
||||
Russell Sim <russell.sim@gmail.com>
|
||||
|
||||
@@ -15,6 +15,8 @@ First you have to install the dependencies::
|
||||
|
||||
Initial configuration
|
||||
---------------------
|
||||
::
|
||||
|
||||
$ cd chipshop/
|
||||
|
||||
$ $EDITOR settings.py
|
||||
@@ -23,6 +25,7 @@ Initial configuration
|
||||
|
||||
Run the PyPI server
|
||||
-------------------
|
||||
::
|
||||
|
||||
$ python manage.py runserver
|
||||
|
||||
@@ -30,8 +33,46 @@ Run the PyPI server
|
||||
Please note that ``chishop/media/dists`` has to be writable by the
|
||||
user the web-server is running as.
|
||||
|
||||
Contact Information
|
||||
====================
|
||||
askh@opera.com
|
||||
Using Setuptools
|
||||
================
|
||||
|
||||
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
|
||||
|
||||
Uploading a package: Python >=2.6
|
||||
--------------------------------------------
|
||||
|
||||
To push the package to the local pypi::
|
||||
|
||||
$ python setup.py register sdist upload -r local
|
||||
|
||||
|
||||
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::
|
||||
|
||||
$ 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.
|
||||
|
||||
To push the package to the local pypi::
|
||||
|
||||
$ python setup.py mregister sdist mupload -r local
|
||||
|
||||
.. # vim: syntax=rst expandtab tabstop=4 shiftwidth=4 shiftround
|
||||
|
||||
@@ -7,3 +7,7 @@
|
||||
* Maybe add a permission "can upload new release", so more than one
|
||||
user can change the same project.
|
||||
* Should a project have co-owners?
|
||||
- One possible solution:
|
||||
http://github.com/initcrash/django-object-permissions/tree
|
||||
* Script to populate classifiers from
|
||||
http://pypi.python.org/pypi?%3Aaction=list_classifiers
|
||||
|
||||
@@ -11,6 +11,7 @@ ADMINS = (
|
||||
# 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'
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
||||
|
||||
+8
-77
@@ -37,83 +37,14 @@ from djangopypi.models import Project, Classifier, Release
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class PermissionDeniedError(Exception):
|
||||
"""The user did not have the privileges to execute an action."""
|
||||
class ProjectForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Project
|
||||
exclude = ['owner', 'classifiers']
|
||||
|
||||
|
||||
class AlreadyExistsError(Exception):
|
||||
"""Filename already exists."""
|
||||
class ReleaseForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Release
|
||||
exclude = ['project']
|
||||
|
||||
ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """
|
||||
+ "problems with that you should create a new release.")
|
||||
|
||||
|
||||
class ProjectRegisterForm(forms.Form):
|
||||
name = forms.CharField()
|
||||
license = forms.CharField(required=False)
|
||||
metadata_version = forms.CharField(initial="1.0")
|
||||
author = forms.CharField(required=False)
|
||||
home_page = forms.CharField(required=False)
|
||||
download_url = forms.CharField(required=False)
|
||||
summary = forms.CharField(required=False)
|
||||
description = forms.CharField(required=False)
|
||||
author_email = forms.CharField(required=False)
|
||||
version = forms.CharField()
|
||||
platform = forms.CharField(required=False)
|
||||
|
||||
PermissionDeniedError = PermissionDeniedError
|
||||
AlreadyExistsError = AlreadyExistsError
|
||||
|
||||
def save(self, classifiers, user, file=None):
|
||||
values = dict(self.cleaned_data)
|
||||
name = values["name"]
|
||||
version = values.pop("version")
|
||||
platform = values.pop("platform", "UNKNOWN")
|
||||
values["owner"] = user
|
||||
|
||||
try:
|
||||
project = Project.objects.get(name=name)
|
||||
except Project.DoesNotExist:
|
||||
project = Project.objects.create(**values)
|
||||
else:
|
||||
# If the project already exists,
|
||||
# be sure that the current user owns this object.
|
||||
if project.owner != user:
|
||||
raise self.PermissionDeniedError(
|
||||
"%s doesn't own that project." % user.username)
|
||||
[setattr(project, field_name, field_value)
|
||||
for field_name, field_value in values.items()]
|
||||
project.save()
|
||||
|
||||
|
||||
for classifier in classifiers:
|
||||
project.classifiers.add(
|
||||
Classifier.objects.get_or_create(name=classifier)[0])
|
||||
|
||||
# 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
|
||||
# django sees anything.
|
||||
allow_overwrite = getattr(settings,
|
||||
"DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False)
|
||||
|
||||
if file:
|
||||
try:
|
||||
release = Release.objects.get(version=version,
|
||||
platform=platform, project=project)
|
||||
if os.path.exists(release.distribution.path):
|
||||
if not allow_overwrite:
|
||||
raise self.AlreadyExistsError(ALREADY_EXISTS_FMT % (
|
||||
release.filename, release))
|
||||
os.remove(release.distribution.path)
|
||||
|
||||
release.delete()
|
||||
except (Release.DoesNotExist, ValueError):
|
||||
pass
|
||||
|
||||
release, created = Release.objects.get_or_create(version=version,
|
||||
platform=platform,
|
||||
project=project)
|
||||
if file:
|
||||
release.distribution.save(file.name, file, save=True)
|
||||
release.save()
|
||||
|
||||
@@ -31,6 +31,7 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
import os
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import User
|
||||
@@ -63,6 +64,8 @@ ARCHITECTURES = (
|
||||
("ultrasparc", "UltraSparc"),
|
||||
)
|
||||
|
||||
UPLOAD_TO = getattr(settings,
|
||||
"DJANGOPYPI_RELEASE_UPLOAD_TO", 'dist')
|
||||
|
||||
class Classifier(models.Model):
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
@@ -77,7 +80,7 @@ class Classifier(models.Model):
|
||||
|
||||
class Project(models.Model):
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
license = models.CharField(max_length=255, blank=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)
|
||||
@@ -113,7 +116,7 @@ class Project(models.Model):
|
||||
|
||||
class Release(models.Model):
|
||||
version = models.CharField(max_length=128)
|
||||
distribution = models.FileField(upload_to="dists")
|
||||
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)
|
||||
|
||||
+79
-25
@@ -30,19 +30,27 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest
|
||||
from django.http import QueryDict, HttpResponseForbidden
|
||||
from django.shortcuts import render_to_response
|
||||
from djangopypi.models import Project
|
||||
from djangopypi.forms import ProjectRegisterForm
|
||||
from djangopypi.models import Project, Classifier, Release, UPLOAD_TO
|
||||
from djangopypi.forms import ProjectForm, ReleaseForm
|
||||
from django.template import RequestContext
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.contrib.auth import authenticate, login
|
||||
from djangopypi.http import HttpResponseNotImplemented
|
||||
from djangopypi.http import HttpResponseUnauthorized
|
||||
|
||||
|
||||
ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """
|
||||
+ "problems with that you should create a new release.")
|
||||
|
||||
|
||||
def parse_weird_post_data(raw_post_data):
|
||||
""" For some reason Django can't parse the HTTP POST data
|
||||
sent by ``distutils`` register/upload commands.
|
||||
@@ -72,18 +80,18 @@ def parse_weird_post_data(raw_post_data):
|
||||
if "filename" in headers:
|
||||
file = SimpleUploadedFile(headers["filename"], content,
|
||||
content_type="application/gzip")
|
||||
files[headers["name"]] = file
|
||||
files["distribution"] = [file]
|
||||
elif headers["name"] in post_data:
|
||||
post_data[headers["name"]].append(content)
|
||||
else:
|
||||
# Distutils sends UNKNOWN for empty fields (e.g platform)
|
||||
# [russel.sim@jcu.edu.au]
|
||||
# [russell.sim@gmail.com]
|
||||
if content == 'UNKNOWN':
|
||||
post_data[headers["name"]] = [None]
|
||||
else:
|
||||
post_data[headers["name"]] = [content]
|
||||
|
||||
return MultiValueDict(post_data), files
|
||||
return MultiValueDict(post_data), MultiValueDict(files)
|
||||
|
||||
|
||||
def login_basic_auth(request):
|
||||
@@ -98,30 +106,76 @@ def login_basic_auth(request):
|
||||
return authenticate(username=username, password=password)
|
||||
|
||||
|
||||
def submit_project_or_release(user, post_data, files):
|
||||
"""Registers/updates a project or release"""
|
||||
try:
|
||||
project = Project.objects.get(name=post_data['name'])
|
||||
if project.owner != user:
|
||||
return HttpResponseForbidden(
|
||||
"That project is owned by someone else!")
|
||||
except Project.DoesNotExist:
|
||||
project = None
|
||||
|
||||
project_form = ProjectForm(post_data, instance=project)
|
||||
if project_form.is_valid():
|
||||
project = project_form.save(commit=False)
|
||||
project.owner = user
|
||||
project.save()
|
||||
for c in post_data.getlist('classifiers'):
|
||||
classifier, created = Classifier.objects.get_or_create(name=c)
|
||||
project.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,
|
||||
distribution=UPLOAD_TO + '/' +
|
||||
files['distribution']._name)
|
||||
if not allow_overwrite:
|
||||
return HttpResponseForbidden(ALREADY_EXISTS_FMT % (
|
||||
release.filename, release))
|
||||
except Release.DoesNotExist:
|
||||
release = None
|
||||
|
||||
# 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
|
||||
# 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.save()
|
||||
else:
|
||||
return HttpResponseBadRequest(
|
||||
"ERRORS: %s" % release_form.errors)
|
||||
else:
|
||||
return HttpResponseBadRequest("ERRORS: %s" % project_form.errors)
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
def simple(request, template_name="djangopypi/simple.html"):
|
||||
if request.method == "POST":
|
||||
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.")
|
||||
post_data, files = parse_weird_post_data(request.raw_post_data)
|
||||
action = post_data.get(":action")
|
||||
classifiers = post_data.getlist("classifiers")
|
||||
register_form = ProjectRegisterForm(post_data.copy())
|
||||
if register_form.is_valid():
|
||||
try:
|
||||
register_form.save(classifiers, request.user,
|
||||
file=files.get("content"))
|
||||
except register_form.PermissionDeniedError, e:
|
||||
return HttpResonseForbidden(
|
||||
"That project is owned by someone else!")
|
||||
except register_form.AlreadyExistsError, e:
|
||||
return HttpResponseForbidden(e)
|
||||
return HttpResponse("Successfully registered.")
|
||||
return HttpResponse("ERRORS: %s" % register_form.errors)
|
||||
if action == 'file_upload':
|
||||
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 submit_project_or_release(user, post_data, files)
|
||||
|
||||
return HttpResponseNotImplemented(
|
||||
"The :action %s is not implemented" % action)
|
||||
|
||||
dists = Project.objects.all().order_by("name")
|
||||
context = RequestContext(request, {
|
||||
|
||||
Reference in New Issue
Block a user