aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsk Solem <askh@opera.com>2009-12-21 17:52:15 +0100
committerAsk Solem <askh@opera.com>2009-12-21 17:52:26 +0100
commit1e2fdc99abff24259e1c47651ca34373a2dcf9d0 (patch)
tree80075d011fbd6d02a5b67c666efb10e00f65636d
parenta2ac0298341b9cb00049f34b1e1d1b5eba41efd2 (diff)
downloadchishop-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.py51
-rw-r--r--djangopypi/views/__init__.py195
-rw-r--r--djangopypi/views/dists.py79
-rw-r--r--djangopypi/views/search.py24
-rw-r--r--djangopypi/views/users.py24
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 @@
1from django.http import HttpResponse 1from django.http import HttpResponse
2from django.core.files.uploadedfile import SimpleUploadedFile
3from django.utils.datastructures import MultiValueDict
4from django.contrib.auth import authenticate
5
2 6
3class HttpResponseNotImplemented(HttpResponse): 7class 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
19def 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
57def 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 @@
1import cgi 1from django.http import Http404
2import os
3
4try:
5 from cStringIO import StringIO
6except ImportError:
7 from StringIO import StringIO
8
9from django.conf import settings
10from django.http import Http404, HttpResponse, HttpResponseBadRequest
11from django.http import QueryDict, HttpResponseForbidden
12from django.shortcuts import render_to_response 2from django.shortcuts import render_to_response
13from django.template import RequestContext 3from django.template import RequestContext
14from django.utils.datastructures import MultiValueDict
15from django.utils.translation import ugettext_lazy as _
16from django.core.files.uploadedfile import SimpleUploadedFile
17from django.contrib.auth import authenticate, login
18from django.db.models import Q
19
20from registration.backends import get_backend
21from registration.forms import RegistrationForm
22 4
23from djangopypi.models import Project, Classifier, Release, UPLOAD_TO 5from djangopypi.models import Project, Release
24from djangopypi.forms import ProjectForm, ReleaseForm
25from djangopypi.http import HttpResponseUnauthorized
26from djangopypi.http import HttpResponseNotImplemented 6from djangopypi.http import HttpResponseNotImplemented
27from djangopypi.utils import decode_fs 7from djangopypi.http import parse_distutils_request
8from djangopypi.views.dists import register_or_upload
9from djangopypi.views.users import create_user
28from djangopypi.views.search import search 10from djangopypi.views.search import search
29 11
30 12
31ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """
32 + "problems with that you should create a new release.")
33
34
35def 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
74def 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
86def 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
138def 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
150def 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
168ACTIONS = { 13ACTIONS = {
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,
216def show_version(request, dist_name, version, 61def 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
233def 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 @@
1import os
2
3from django.conf import settings
4from django.http import (HttpResponse, HttpResponseForbidden,
5 HttpResponseBadRequest)
6from django.utils.translation import ugettext_lazy as _
7from django.contrib.auth import login
8
9from djangopypi.http import login_basic_auth, HttpResponseUnauthorized
10from djangopypi.forms import ProjectForm, ReleaseForm
11from djangopypi.models import Project, Release, Classifier, UPLOAD_TO
12
13ALREADY_EXISTS_FMT = _(
14 "A file named '%s' already exists for %s. Please create a new release.")
15
16
17def 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
69def 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 @@
1from django.template import RequestContext
2from django.shortcuts import render_to_response
3from django.db.models.query import Q
4
5from djangopypi.models import Project
6
7
8def _search_query(q):
9 return Q(name__contains=q) | Q(summary__contains=q)
10
11
12def 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 @@
1from django.http import HttpResponse, HttpResponseBadRequest
2
3from registration.forms import RegistrationForm
4from registration.backends import get_backend
5
6DEFAULT_BACKEND = "registration.backends.default.DefaultBackend"
7
8
9def 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')