diff options
author | Ask Solem <askh@opera.com> | 2009-12-21 17:52:15 +0100 |
---|---|---|
committer | Ask Solem <askh@opera.com> | 2009-12-21 17:52:26 +0100 |
commit | 1e2fdc99abff24259e1c47651ca34373a2dcf9d0 (patch) | |
tree | 80075d011fbd6d02a5b67c666efb10e00f65636d | |
parent | a2ac0298341b9cb00049f34b1e1d1b5eba41efd2 (diff) | |
download | chishop-1e2fdc99abff24259e1c47651ca34373a2dcf9d0.tar.bz2 chishop-1e2fdc99abff24259e1c47651ca34373a2dcf9d0.tar.xz chishop-1e2fdc99abff24259e1c47651ca34373a2dcf9d0.zip |
Re-organize views into views.dists, views.users, views.search
-rw-r--r-- | djangopypi/http.py | 51 | ||||
-rw-r--r-- | djangopypi/views/__init__.py | 195 | ||||
-rw-r--r-- | djangopypi/views/dists.py | 79 | ||||
-rw-r--r-- | djangopypi/views/search.py | 24 | ||||
-rw-r--r-- | djangopypi/views/users.py | 24 |
5 files changed, 185 insertions, 188 deletions
diff --git a/djangopypi/http.py b/djangopypi/http.py index 01081f7..7be5959 100644 --- a/djangopypi/http.py +++ b/djangopypi/http.py | |||
@@ -1,4 +1,8 @@ | |||
1 | from django.http import HttpResponse | 1 | from django.http import HttpResponse |
2 | from django.core.files.uploadedfile import SimpleUploadedFile | ||
3 | from django.utils.datastructures import MultiValueDict | ||
4 | from django.contrib.auth import authenticate | ||
5 | |||
2 | 6 | ||
3 | class HttpResponseNotImplemented(HttpResponse): | 7 | class HttpResponseNotImplemented(HttpResponse): |
4 | status_code = 501 | 8 | status_code = 501 |
@@ -12,4 +16,51 @@ class HttpResponseUnauthorized(HttpResponse): | |||
12 | self['WWW-Authenticate'] = 'Basic realm="%s"' % realm | 16 | self['WWW-Authenticate'] = 'Basic realm="%s"' % realm |
13 | 17 | ||
14 | 18 | ||
19 | def parse_distutils_request(request): | ||
20 | raw_post_data = request.raw_post_data | ||
21 | sep = raw_post_data.splitlines()[1] | ||
22 | items = raw_post_data.split(sep) | ||
23 | post_data = {} | ||
24 | files = {} | ||
25 | for part in filter(lambda e: not e.isspace(), items): | ||
26 | item = part.splitlines() | ||
27 | if len(item) < 2: | ||
28 | continue | ||
29 | header = item[1].replace("Content-Disposition: form-data; ", "") | ||
30 | kvpairs = header.split(";") | ||
31 | headers = {} | ||
32 | for kvpair in kvpairs: | ||
33 | if not kvpair: | ||
34 | continue | ||
35 | key, value = kvpair.split("=") | ||
36 | headers[key] = value.strip('"') | ||
37 | if "name" not in headers: | ||
38 | continue | ||
39 | content = part[len("\n".join(item[0:2]))+2:len(part)-1] | ||
40 | if "filename" in headers: | ||
41 | file = SimpleUploadedFile(headers["filename"], content, | ||
42 | content_type="application/gzip") | ||
43 | files["distribution"] = [file] | ||
44 | elif headers["name"] in post_data: | ||
45 | post_data[headers["name"]].append(content) | ||
46 | else: | ||
47 | # Distutils sends UNKNOWN for empty fields (e.g platform) | ||
48 | # [russell.sim@gmail.com] | ||
49 | if content == 'UNKNOWN': | ||
50 | post_data[headers["name"]] = [None] | ||
51 | else: | ||
52 | post_data[headers["name"]] = [content] | ||
53 | |||
54 | return MultiValueDict(post_data), MultiValueDict(files) | ||
55 | |||
15 | 56 | ||
57 | def login_basic_auth(request): | ||
58 | authentication = request.META.get("HTTP_AUTHORIZATION") | ||
59 | if not authentication: | ||
60 | return | ||
61 | (authmeth, auth) = authentication.split(' ', 1) | ||
62 | if authmeth.lower() != "basic": | ||
63 | return | ||
64 | auth = auth.strip().decode("base64") | ||
65 | username, password = auth.split(":", 1) | ||
66 | return authenticate(username=username, password=password) | ||
diff --git a/djangopypi/views/__init__.py b/djangopypi/views/__init__.py index f8b7fac..1438c54 100644 --- a/djangopypi/views/__init__.py +++ b/djangopypi/views/__init__.py | |||
@@ -1,170 +1,15 @@ | |||
1 | import cgi | 1 | from django.http import Http404 |
2 | import os | ||
3 | |||
4 | try: | ||
5 | from cStringIO import StringIO | ||
6 | except ImportError: | ||
7 | from StringIO import StringIO | ||
8 | |||
9 | from django.conf import settings | ||
10 | from django.http import Http404, HttpResponse, HttpResponseBadRequest | ||
11 | from django.http import QueryDict, HttpResponseForbidden | ||
12 | from django.shortcuts import render_to_response | 2 | from django.shortcuts import render_to_response |
13 | from django.template import RequestContext | 3 | from django.template import RequestContext |
14 | from django.utils.datastructures import MultiValueDict | ||
15 | from django.utils.translation import ugettext_lazy as _ | ||
16 | from django.core.files.uploadedfile import SimpleUploadedFile | ||
17 | from django.contrib.auth import authenticate, login | ||
18 | from django.db.models import Q | ||
19 | |||
20 | from registration.backends import get_backend | ||
21 | from registration.forms import RegistrationForm | ||
22 | 4 | ||
23 | from djangopypi.models import Project, Classifier, Release, UPLOAD_TO | 5 | from djangopypi.models import Project, Release |
24 | from djangopypi.forms import ProjectForm, ReleaseForm | ||
25 | from djangopypi.http import HttpResponseUnauthorized | ||
26 | from djangopypi.http import HttpResponseNotImplemented | 6 | from djangopypi.http import HttpResponseNotImplemented |
27 | from djangopypi.utils import decode_fs | 7 | from djangopypi.http import parse_distutils_request |
8 | from djangopypi.views.dists import register_or_upload | ||
9 | from djangopypi.views.users import create_user | ||
28 | from djangopypi.views.search import search | 10 | from djangopypi.views.search import search |
29 | 11 | ||
30 | 12 | ||
31 | ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ | ||
32 | + "problems with that you should create a new release.") | ||
33 | |||
34 | |||
35 | def parse_distutils_request(request): | ||
36 | raw_post_data = request.raw_post_data | ||
37 | sep = raw_post_data.splitlines()[1] | ||
38 | items = raw_post_data.split(sep) | ||
39 | post_data = {} | ||
40 | files = {} | ||
41 | for part in filter(lambda e: not e.isspace(), items): | ||
42 | item = part.splitlines() | ||
43 | if len(item) < 2: | ||
44 | continue | ||
45 | header = item[1].replace("Content-Disposition: form-data; ", "") | ||
46 | kvpairs = header.split(";") | ||
47 | headers = {} | ||
48 | for kvpair in kvpairs: | ||
49 | if not kvpair: | ||
50 | continue | ||
51 | key, value = kvpair.split("=") | ||
52 | headers[key] = value.strip('"') | ||
53 | if "name" not in headers: | ||
54 | continue | ||
55 | content = part[len("\n".join(item[0:2]))+2:len(part)-1] | ||
56 | if "filename" in headers: | ||
57 | file = SimpleUploadedFile(headers["filename"], content, | ||
58 | content_type="application/gzip") | ||
59 | files["distribution"] = [file] | ||
60 | elif headers["name"] in post_data: | ||
61 | post_data[headers["name"]].append(content) | ||
62 | else: | ||
63 | # Distutils sends UNKNOWN for empty fields (e.g platform) | ||
64 | # [russell.sim@gmail.com] | ||
65 | if content == 'UNKNOWN': | ||
66 | post_data[headers["name"]] = [None] | ||
67 | else: | ||
68 | post_data[headers["name"]] = [content] | ||
69 | |||
70 | return MultiValueDict(post_data), MultiValueDict(files) | ||
71 | |||
72 | |||
73 | |||
74 | def login_basic_auth(request): | ||
75 | authentication = request.META.get("HTTP_AUTHORIZATION") | ||
76 | if not authentication: | ||
77 | return | ||
78 | (authmeth, auth) = authentication.split(' ', 1) | ||
79 | if authmeth.lower() != "basic": | ||
80 | return | ||
81 | auth = auth.strip().decode("base64") | ||
82 | username, password = auth.split(":", 1) | ||
83 | return authenticate(username=username, password=password) | ||
84 | |||
85 | |||
86 | def submit_project_or_release(user, post_data, files): | ||
87 | """Registers/updates a project or release""" | ||
88 | try: | ||
89 | project = Project.objects.get(name=post_data['name']) | ||
90 | if project.owner != user: | ||
91 | return HttpResponseForbidden( | ||
92 | "That project is owned by someone else!") | ||
93 | except Project.DoesNotExist: | ||
94 | project = None | ||
95 | |||
96 | project_form = ProjectForm(post_data, instance=project) | ||
97 | if project_form.is_valid(): | ||
98 | project = project_form.save(commit=False) | ||
99 | project.owner = user | ||
100 | project.save() | ||
101 | for c in post_data.getlist('classifiers'): | ||
102 | classifier, created = Classifier.objects.get_or_create(name=c) | ||
103 | project.classifiers.add(classifier) | ||
104 | if files: | ||
105 | allow_overwrite = getattr(settings, | ||
106 | "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False) | ||
107 | try: | ||
108 | release = Release.objects.get(version=post_data['version'], | ||
109 | project=project, | ||
110 | distribution=UPLOAD_TO + '/' + | ||
111 | files['distribution']._name) | ||
112 | if not allow_overwrite: | ||
113 | return HttpResponseForbidden(ALREADY_EXISTS_FMT % ( | ||
114 | release.filename, release)) | ||
115 | except Release.DoesNotExist: | ||
116 | release = None | ||
117 | |||
118 | # If the old file already exists, django will append a _ after the | ||
119 | # filename, however with .tar.gz files django does the "wrong" | ||
120 | # thing and saves it as project-0.1.2.tar_.gz. So remove it before | ||
121 | # django sees anything. | ||
122 | release_form = ReleaseForm(post_data, files, instance=release) | ||
123 | if release_form.is_valid(): | ||
124 | if release and os.path.exists(release.distribution.path): | ||
125 | os.remove(release.distribution.path) | ||
126 | release = release_form.save(commit=False) | ||
127 | release.project = project | ||
128 | release.save() | ||
129 | else: | ||
130 | return HttpResponseBadRequest( | ||
131 | "ERRORS: %s" % release_form.errors) | ||
132 | else: | ||
133 | return HttpResponseBadRequest("ERRORS: %s" % project_form.errors) | ||
134 | |||
135 | return HttpResponse() | ||
136 | |||
137 | |||
138 | def register_or_upload(request, post_data, files): | ||
139 | user = login_basic_auth(request) | ||
140 | if not user: | ||
141 | return HttpResponseUnauthorized('pypi') | ||
142 | |||
143 | login(request, user) | ||
144 | if not request.user.is_authenticated(): | ||
145 | return HttpResponseForbidden( | ||
146 | "Not logged in, or invalid username/password.") | ||
147 | |||
148 | return submit_project_or_release(user, post_data, files) | ||
149 | |||
150 | def create_user(request, post_data, files): | ||
151 | """Create new user from a distutil client request""" | ||
152 | form = RegistrationForm({"username": post_data["name"], | ||
153 | "email": post_data["email"], | ||
154 | "password1": post_data["password"], | ||
155 | "password2": post_data["password"]}) | ||
156 | if not form.is_valid(): | ||
157 | # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg" | ||
158 | # Which is HTTP/WSGI incompatible, so we're just returning a empty 400. | ||
159 | return HttpResponseBadRequest() | ||
160 | |||
161 | backend = get_backend("registration.backends.default.DefaultBackend") | ||
162 | if not backend.registration_allowed(request): | ||
163 | return HttpResponseBadRequest() | ||
164 | new_user = backend.register(request, **form.cleaned_data) | ||
165 | return HttpResponse("OK\n", status=200, mimetype='text/plain') | ||
166 | |||
167 | |||
168 | ACTIONS = { | 13 | ACTIONS = { |
169 | # file_upload is the action used with distutils ``sdist`` command. | 14 | # file_upload is the action used with distutils ``sdist`` command. |
170 | "file_upload": register_or_upload, | 15 | "file_upload": register_or_upload, |
@@ -216,8 +61,8 @@ def show_links(request, dist_name, | |||
216 | def show_version(request, dist_name, version, | 61 | def show_version(request, dist_name, version, |
217 | template_name="djangopypi/show_version.html"): | 62 | template_name="djangopypi/show_version.html"): |
218 | try: | 63 | try: |
219 | release = Project.objects.get(name=dist_name).releases \ | 64 | project = Project.objects.get(name=dist_name) |
220 | .get(version=version) | 65 | release = project.releases.get(version=version) |
221 | except (Project.DoesNotExist, Release.DoesNotExist): | 66 | except (Project.DoesNotExist, Release.DoesNotExist): |
222 | raise Http404() | 67 | raise Http404() |
223 | 68 | ||
@@ -229,29 +74,3 @@ def show_version(request, dist_name, version, | |||
229 | }) | 74 | }) |
230 | 75 | ||
231 | return render_to_response(template_name, context_instance=context) | 76 | return render_to_response(template_name, context_instance=context) |
232 | |||
233 | def search(request): | ||
234 | search_term = '' | ||
235 | if request.method == 'POST': | ||
236 | search_term = request.POST.get('search_term') | ||
237 | if search_term != '': | ||
238 | dists = Project.objects.filter(Q(name__contains=search_term) | Q(summary__contains=search_term)) | ||
239 | return render_to_response( | ||
240 | 'djangopypi/search_results.html', | ||
241 | {'dists':dists,'search_term':search_term}, | ||
242 | context_instance = RequestContext(request) | ||
243 | ) | ||
244 | else: | ||
245 | dists = Project.objects.all() | ||
246 | return render_to_response( | ||
247 | 'djangopypi/search_results.html', | ||
248 | {'search_term':search_term}, | ||
249 | context_instance = RequestContext(request) | ||
250 | ) | ||
251 | else: | ||
252 | dists = Project.objects.all() | ||
253 | return render_to_response( | ||
254 | 'djangopypi/search_results.html', | ||
255 | {'search_term':search_term}, | ||
256 | context_instance = RequestContext(request) | ||
257 | ) | ||
diff --git a/djangopypi/views/dists.py b/djangopypi/views/dists.py new file mode 100644 index 0000000..9e4a146 --- /dev/null +++ b/djangopypi/views/dists.py | |||
@@ -0,0 +1,79 @@ | |||
1 | import os | ||
2 | |||
3 | from django.conf import settings | ||
4 | from django.http import (HttpResponse, HttpResponseForbidden, | ||
5 | HttpResponseBadRequest) | ||
6 | from django.utils.translation import ugettext_lazy as _ | ||
7 | from django.contrib.auth import login | ||
8 | |||
9 | from djangopypi.http import login_basic_auth, HttpResponseUnauthorized | ||
10 | from djangopypi.forms import ProjectForm, ReleaseForm | ||
11 | from djangopypi.models import Project, Release, Classifier, UPLOAD_TO | ||
12 | |||
13 | ALREADY_EXISTS_FMT = _( | ||
14 | "A file named '%s' already exists for %s. Please create a new release.") | ||
15 | |||
16 | |||
17 | def submit_project_or_release(user, post_data, files): | ||
18 | """Registers/updates a project or release""" | ||
19 | try: | ||
20 | project = Project.objects.get(name=post_data['name']) | ||
21 | if project.owner != user: | ||
22 | return HttpResponseForbidden( | ||
23 | "That project is owned by someone else!") | ||
24 | except Project.DoesNotExist: | ||
25 | project = None | ||
26 | |||
27 | project_form = ProjectForm(post_data, instance=project) | ||
28 | if project_form.is_valid(): | ||
29 | project = project_form.save(commit=False) | ||
30 | project.owner = user | ||
31 | project.save() | ||
32 | for c in post_data.getlist('classifiers'): | ||
33 | classifier, created = Classifier.objects.get_or_create(name=c) | ||
34 | project.classifiers.add(classifier) | ||
35 | if files: | ||
36 | allow_overwrite = getattr(settings, | ||
37 | "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False) | ||
38 | try: | ||
39 | release = Release.objects.get(version=post_data['version'], | ||
40 | project=project, | ||
41 | distribution=UPLOAD_TO + '/' + | ||
42 | files['distribution']._name) | ||
43 | if not allow_overwrite: | ||
44 | return HttpResponseForbidden(ALREADY_EXISTS_FMT % ( | ||
45 | release.filename, release)) | ||
46 | except Release.DoesNotExist: | ||
47 | release = None | ||
48 | |||
49 | # If the old file already exists, django will append a _ after the | ||
50 | # filename, however with .tar.gz files django does the "wrong" | ||
51 | # thing and saves it as project-0.1.2.tar_.gz. So remove it before | ||
52 | # django sees anything. | ||
53 | release_form = ReleaseForm(post_data, files, instance=release) | ||
54 | if release_form.is_valid(): | ||
55 | if release and os.path.exists(release.distribution.path): | ||
56 | os.remove(release.distribution.path) | ||
57 | release = release_form.save(commit=False) | ||
58 | release.project = project | ||
59 | release.save() | ||
60 | else: | ||
61 | return HttpResponseBadRequest( | ||
62 | "ERRORS: %s" % release_form.errors) | ||
63 | else: | ||
64 | return HttpResponseBadRequest("ERRORS: %s" % project_form.errors) | ||
65 | |||
66 | return HttpResponse() | ||
67 | |||
68 | |||
69 | def register_or_upload(request, post_data, files): | ||
70 | user = login_basic_auth(request) | ||
71 | if not user: | ||
72 | return HttpResponseUnauthorized('pypi') | ||
73 | |||
74 | login(request, user) | ||
75 | if not request.user.is_authenticated(): | ||
76 | return HttpResponseForbidden( | ||
77 | "Not logged in, or invalid username/password.") | ||
78 | |||
79 | return submit_project_or_release(user, post_data, files) | ||
diff --git a/djangopypi/views/search.py b/djangopypi/views/search.py new file mode 100644 index 0000000..5d6a76b --- /dev/null +++ b/djangopypi/views/search.py | |||
@@ -0,0 +1,24 @@ | |||
1 | from django.template import RequestContext | ||
2 | from django.shortcuts import render_to_response | ||
3 | from django.db.models.query import Q | ||
4 | |||
5 | from djangopypi.models import Project | ||
6 | |||
7 | |||
8 | def _search_query(q): | ||
9 | return Q(name__contains=q) | Q(summary__contains=q) | ||
10 | |||
11 | |||
12 | def search(request, template="djangopypi/search_results.html"): | ||
13 | context = RequestContext(request, {"dists": None, "search_term": ""}) | ||
14 | |||
15 | if request.method == "POST": | ||
16 | search_term = context["search_term"] = request.POST.get("search_term") | ||
17 | if search_term: | ||
18 | query = _search_query(search_term) | ||
19 | context["dists"] = Project.objects.filter(query) | ||
20 | |||
21 | if context["dists"] is None: | ||
22 | context["dists"] = Project.objects.all() | ||
23 | |||
24 | return render_to_response(template, context_instance=context) | ||
diff --git a/djangopypi/views/users.py b/djangopypi/views/users.py new file mode 100644 index 0000000..a58ac3e --- /dev/null +++ b/djangopypi/views/users.py | |||
@@ -0,0 +1,24 @@ | |||
1 | from django.http import HttpResponse, HttpResponseBadRequest | ||
2 | |||
3 | from registration.forms import RegistrationForm | ||
4 | from registration.backends import get_backend | ||
5 | |||
6 | DEFAULT_BACKEND = "registration.backends.default.DefaultBackend" | ||
7 | |||
8 | |||
9 | def create_user(request, post_data, files, backend_name=DEFAULT_BACKEND): | ||
10 | """Create new user from a distutil client request""" | ||
11 | form = RegistrationForm({"username": post_data["name"], | ||
12 | "email": post_data["email"], | ||
13 | "password1": post_data["password"], | ||
14 | "password2": post_data["password"]}) | ||
15 | if not form.is_valid(): | ||
16 | # Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg" | ||
17 | # Which is HTTP/WSGI incompatible, so we're just returning a empty 400. | ||
18 | return HttpResponseBadRequest() | ||
19 | |||
20 | backend = get_backend(backend_name) | ||
21 | if not backend.registration_allowed(request): | ||
22 | return HttpResponseBadRequest() | ||
23 | new_user = backend.register(request, **form.cleaned_data) | ||
24 | return HttpResponse("OK\n", status=200, mimetype='text/plain') | ||