aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHugo Lopes Tavares <hltbra@gmail.com>2009-11-10 20:41:58 -0200
committerHugo Lopes Tavares <hltbra@gmail.com>2009-11-10 20:41:58 -0200
commit16e6cd2549bca5f89b73b4f670ca9b19dcb175c6 (patch)
tree2429de061aa33aa47f60bf431d1a6d43c2e9ac64
parent346aaaf4821a6bc3403c48ee905be25dde2a6518 (diff)
parentd064f1cafcb90241725ddecf8aabb10b479a1f76 (diff)
downloadchishop-16e6cd2549bca5f89b73b4f670ca9b19dcb175c6.tar.bz2
chishop-16e6cd2549bca5f89b73b4f670ca9b19dcb175c6.tar.xz
chishop-16e6cd2549bca5f89b73b4f670ca9b19dcb175c6.zip
Merge branch 'master' of git://github.com/ask/chishop
-rw-r--r--AUTHORS1
-rw-r--r--TODO58
-rw-r--r--chishop/settings.py1
-rw-r--r--djangopypi/templates/djangopypi/pypi_show_links.html2
-rw-r--r--djangopypi/utils.py38
-rw-r--r--djangopypi/views.py54
6 files changed, 102 insertions, 52 deletions
diff --git a/AUTHORS b/AUTHORS
index 71b6d81..9d005e6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,3 +1,4 @@
1Ask Solem <askh@opera.com> 1Ask Solem <askh@opera.com>
2Rune Halvorsen <runeh@opera.com> 2Rune Halvorsen <runeh@opera.com>
3Russell Sim <russell.sim@gmail.com> 3Russell Sim <russell.sim@gmail.com>
4Brian Rosner <brosner@gmail.com>
diff --git a/TODO b/TODO
index ae681f6..5d57eb7 100644
--- a/TODO
+++ b/TODO
@@ -1,12 +1,52 @@
1* Write a tutorial on how to set up the server, registering projects, and 1PyPI feature replication
2 how to upload releases. 2========================
3
3* Make it possible to register users via distutils. 4* Make it possible to register users via distutils.
4 (There should be a setting to turn this feature on/off). 5 There should be a setting to turn this feature on/off for private PyPIs.
5* Add a pretty, user-friendly web site interface in addition to 6 [taken-by: sverrej]
6 the API interface. 7
7* Maybe add a permission "can upload new release", so more than one 8* Roles (co-owners/maintainers)
8 user can change the same project.
9* Should a project have co-owners?
10 - One possible solution: 9 - One possible solution:
11 http://github.com/initcrash/django-object-permissions/tree 10 http://github.com/initcrash/django-object-permissions/tree
12 11 I'm not sure what the difference between a co-owner and maintainer is,
12 maybe it's just a label.
13* Package author admin interface (submit, edit, view)
14* Search
15* Documentation upload
16* Ratings
17* Random Monty Python quotes :-)
18* Comments :-)
19
20Post-PyPI
21=========
22
23* PEP-381: Mirroring infrastructure for PyPI
24 [taken-by: jezdez]
25
26* API to submit test reports for smoke test bots. Like CPAN Testers.
27 Platform/version/matrix etc.
28
29* Different listings: Author listings, classifier listings, etc.
30
31* Search metadata
32
33* Automatic generation of Sphinx for modules (so you can view them directly
34on pypi, like CPAN), Module listing etc.
35
36* Listing of special files: README, LICENSE, Changefile/Changes, TODO,
37 MANIFEST.
38
39* Dependency graphs.
40
41* Package file browser (like CPAN)
42
43
44
45
46Documentation
47=============
48
49* Write a tutorial on how to set up the server, registering projects, and
50 how to upload releases.
51
52
diff --git a/chishop/settings.py b/chishop/settings.py
index 00cf06d..180a652 100644
--- a/chishop/settings.py
+++ b/chishop/settings.py
@@ -12,6 +12,7 @@ ADMINS = (
12# if you're sloppy. 12# if you're sloppy.
13DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False 13DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False
14DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' 14DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists'
15LOCAL_DEVELOPMENT=True
15 16
16# change to False if you do not want Django's default server to serve static pages 17# change to False if you do not want Django's default server to serve static pages
17LOCAL_DEVELOPMENT = True 18LOCAL_DEVELOPMENT = True
diff --git a/djangopypi/templates/djangopypi/pypi_show_links.html b/djangopypi/templates/djangopypi/pypi_show_links.html
index 83142b7..6b239d1 100644
--- a/djangopypi/templates/djangopypi/pypi_show_links.html
+++ b/djangopypi/templates/djangopypi/pypi_show_links.html
@@ -12,7 +12,7 @@
12<tbody> 12<tbody>
13 {% for release in releases %} 13 {% for release in releases %}
14 <tr> 14 <tr>
15 <td><a href="/{{ release.get_dl_url }}">{{ release.filename }}</a></td> 15 <td><a href="{{ release.get_dl_url }}">{{ release.filename }}</a></td>
16 <td>{{ release.platform }}</td> 16 <td>{{ release.platform }}</td>
17 <td>{{ release.type }}</td> 17 <td>{{ release.type }}</td>
18 <td>{{ release.pyversion }}</td> 18 <td>{{ release.pyversion }}</td>
diff --git a/djangopypi/utils.py b/djangopypi/utils.py
new file mode 100644
index 0000000..ba2013e
--- /dev/null
+++ b/djangopypi/utils.py
@@ -0,0 +1,38 @@
1import sys
2import traceback
3
4from django.core.files.uploadedfile import SimpleUploadedFile
5from django.utils.datastructures import MultiValueDict
6
7
8def transmute(f):
9 if hasattr(f, "filename") and f.filename:
10 v = SimpleUploadedFile(f.filename, f.value, f.type)
11 else:
12 v = f.value.decode("utf-8")
13 return v
14
15
16def decode_fs(fs):
17 POST, FILES = {}, {}
18 for k in fs.keys():
19 v = transmute(fs[k])
20 if isinstance(v, SimpleUploadedFile):
21 FILES[k] = [v]
22 else:
23 # Distutils sends UNKNOWN for empty fields (e.g platform)
24 # [russell.sim@gmail.com]
25 if v == "UNKNOWN":
26 v = None
27 POST[k] = [v]
28 return MultiValueDict(POST), MultiValueDict(FILES)
29
30
31def debug(func):
32 # @debug is handy when debugging distutils requests
33 def _wrapped(*args, **kwargs):
34 try:
35 return func(*args, **kwargs)
36 except:
37 traceback.print_exception(*sys.exc_info())
38 return _wrapped \ No newline at end of file
diff --git a/djangopypi/views.py b/djangopypi/views.py
index 90929a1..784651b 100644
--- a/djangopypi/views.py
+++ b/djangopypi/views.py
@@ -30,8 +30,14 @@ POSSIBILITY OF SUCH DAMAGE.
30 30
31""" 31"""
32 32
33import cgi
33import os 34import os
34 35
36try:
37 from cStringIO import StringIO
38except ImportError:
39 from StringIO import StringIO
40
35from django.conf import settings 41from django.conf import settings
36from django.http import Http404, HttpResponse, HttpResponseBadRequest 42from django.http import Http404, HttpResponse, HttpResponseBadRequest
37from django.http import QueryDict, HttpResponseForbidden 43from django.http import QueryDict, HttpResponseForbidden
@@ -45,53 +51,17 @@ from django.core.files.uploadedfile import SimpleUploadedFile
45from django.contrib.auth import authenticate, login 51from django.contrib.auth import authenticate, login
46from djangopypi.http import HttpResponseNotImplemented 52from djangopypi.http import HttpResponseNotImplemented
47from djangopypi.http import HttpResponseUnauthorized 53from djangopypi.http import HttpResponseUnauthorized
54from djangopypi.utils import decode_fs
48 55
49 56
50ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ 57ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """
51 + "problems with that you should create a new release.") 58 + "problems with that you should create a new release.")
52 59
53 60
54def parse_weird_post_data(raw_post_data): 61def parse_distutils_request(request):
55 """ For some reason Django can't parse the HTTP POST data 62 fp = StringIO(request.raw_post_data)
56 sent by ``distutils`` register/upload commands. 63 fs = cgi.FieldStorage(fp=fp, environ=request.META)
57 64 return decode_fs(fs)
58 This parser should be able to so, and returns a
59 :class:`django.utils.datastructures.MultiValueDict`
60 as you would expect from a regular ``request.POST`` object.
61 """
62 # If anyone knows any better way to do this, you're welcome
63 # to give me a solid beating. [askh@opera.com]
64 sep = raw_post_data.splitlines()[1]
65 items = raw_post_data.split(sep)
66 post_data = {}
67 files = {}
68 for part in [e for e in items if not e.isspace()]:
69 item = part.splitlines()
70 if len(item) < 2: continue
71 header = item[1].replace("Content-Disposition: form-data; ", "")
72 kvpairs = header.split(";")
73 headers = {}
74 for kvpair in kvpairs:
75 if not kvpair: continue
76 key, value = kvpair.split("=")
77 headers[key] = value.strip('"')
78 if not "name" in headers: continue
79 content = part[len("\n".join(item[0:2]))+2:len(part)-1]
80 if "filename" in headers:
81 file = SimpleUploadedFile(headers["filename"], content,
82 content_type="application/gzip")
83 files["distribution"] = [file]
84 elif headers["name"] in post_data:
85 post_data[headers["name"]].append(content)
86 else:
87 # Distutils sends UNKNOWN for empty fields (e.g platform)
88 # [russell.sim@gmail.com]
89 if content == 'UNKNOWN':
90 post_data[headers["name"]] = [None]
91 else:
92 post_data[headers["name"]] = [content]
93
94 return MultiValueDict(post_data), MultiValueDict(files)
95 65
96 66
97def login_basic_auth(request): 67def login_basic_auth(request):
@@ -181,7 +151,7 @@ ACTIONS = {
181 151
182def simple(request, template_name="djangopypi/simple.html"): 152def simple(request, template_name="djangopypi/simple.html"):
183 if request.method == "POST": 153 if request.method == "POST":
184 post_data, files = parse_weird_post_data(request.raw_post_data) 154 post_data, files = parse_distutils_request(request.raw_post_data)
185 action_name = post_data.get(":action") 155 action_name = post_data.get(":action")
186 if action_name not in ACTIONS: 156 if action_name not in ACTIONS:
187 return HttpResponseNotImplemented( 157 return HttpResponseNotImplemented(