diff options
author | Hugo Lopes Tavares <hltbra@gmail.com> | 2009-11-10 20:41:58 -0200 |
---|---|---|
committer | Hugo Lopes Tavares <hltbra@gmail.com> | 2009-11-10 20:41:58 -0200 |
commit | 16e6cd2549bca5f89b73b4f670ca9b19dcb175c6 (patch) | |
tree | 2429de061aa33aa47f60bf431d1a6d43c2e9ac64 | |
parent | 346aaaf4821a6bc3403c48ee905be25dde2a6518 (diff) | |
parent | d064f1cafcb90241725ddecf8aabb10b479a1f76 (diff) | |
download | chishop-16e6cd2549bca5f89b73b4f670ca9b19dcb175c6.tar.bz2 chishop-16e6cd2549bca5f89b73b4f670ca9b19dcb175c6.tar.xz chishop-16e6cd2549bca5f89b73b4f670ca9b19dcb175c6.zip |
Merge branch 'master' of git://github.com/ask/chishop
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | TODO | 58 | ||||
-rw-r--r-- | chishop/settings.py | 1 | ||||
-rw-r--r-- | djangopypi/templates/djangopypi/pypi_show_links.html | 2 | ||||
-rw-r--r-- | djangopypi/utils.py | 38 | ||||
-rw-r--r-- | djangopypi/views.py | 54 |
6 files changed, 102 insertions, 52 deletions
@@ -1,3 +1,4 @@ | |||
1 | Ask Solem <askh@opera.com> | 1 | Ask Solem <askh@opera.com> |
2 | Rune Halvorsen <runeh@opera.com> | 2 | Rune Halvorsen <runeh@opera.com> |
3 | Russell Sim <russell.sim@gmail.com> | 3 | Russell Sim <russell.sim@gmail.com> |
4 | Brian Rosner <brosner@gmail.com> | ||
@@ -1,12 +1,52 @@ | |||
1 | * Write a tutorial on how to set up the server, registering projects, and | 1 | PyPI 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 | |||
20 | Post-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 | ||
34 | on 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 | |||
46 | Documentation | ||
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. |
13 | DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False | 13 | DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False |
14 | DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' | 14 | DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' |
15 | LOCAL_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 |
17 | LOCAL_DEVELOPMENT = True | 18 | LOCAL_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 @@ | |||
1 | import sys | ||
2 | import traceback | ||
3 | |||
4 | from django.core.files.uploadedfile import SimpleUploadedFile | ||
5 | from django.utils.datastructures import MultiValueDict | ||
6 | |||
7 | |||
8 | def 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 | |||
16 | def 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 | |||
31 | def 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 | ||
33 | import cgi | ||
33 | import os | 34 | import os |
34 | 35 | ||
36 | try: | ||
37 | from cStringIO import StringIO | ||
38 | except ImportError: | ||
39 | from StringIO import StringIO | ||
40 | |||
35 | from django.conf import settings | 41 | from django.conf import settings |
36 | from django.http import Http404, HttpResponse, HttpResponseBadRequest | 42 | from django.http import Http404, HttpResponse, HttpResponseBadRequest |
37 | from django.http import QueryDict, HttpResponseForbidden | 43 | from django.http import QueryDict, HttpResponseForbidden |
@@ -45,53 +51,17 @@ from django.core.files.uploadedfile import SimpleUploadedFile | |||
45 | from django.contrib.auth import authenticate, login | 51 | from django.contrib.auth import authenticate, login |
46 | from djangopypi.http import HttpResponseNotImplemented | 52 | from djangopypi.http import HttpResponseNotImplemented |
47 | from djangopypi.http import HttpResponseUnauthorized | 53 | from djangopypi.http import HttpResponseUnauthorized |
54 | from djangopypi.utils import decode_fs | ||
48 | 55 | ||
49 | 56 | ||
50 | ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ | 57 | ALREADY_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 | ||
54 | def parse_weird_post_data(raw_post_data): | 61 | def 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 | ||
97 | def login_basic_auth(request): | 67 | def login_basic_auth(request): |
@@ -181,7 +151,7 @@ ACTIONS = { | |||
181 | 151 | ||
182 | def simple(request, template_name="djangopypi/simple.html"): | 152 | def 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( |