aboutsummaryrefslogtreecommitdiff
path: root/djangopypi/views/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'djangopypi/views/__init__.py')
-rw-r--r--djangopypi/views/__init__.py257
1 files changed, 257 insertions, 0 deletions
diff --git a/djangopypi/views/__init__.py b/djangopypi/views/__init__.py
new file mode 100644
index 0000000..f8b7fac
--- /dev/null
+++ b/djangopypi/views/__init__.py
@@ -0,0 +1,257 @@
1import cgi
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
13from 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
23from djangopypi.models import Project, Classifier, Release, UPLOAD_TO
24from djangopypi.forms import ProjectForm, ReleaseForm
25from djangopypi.http import HttpResponseUnauthorized
26from djangopypi.http import HttpResponseNotImplemented
27from djangopypi.utils import decode_fs
28from djangopypi.views.search import search
29
30
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 = {
169 # file_upload is the action used with distutils ``sdist`` command.
170 "file_upload": register_or_upload,
171
172 # submit is the :action used with distutils ``register`` command.
173 "submit": register_or_upload,
174
175 # user is the action used when registering a new user
176 "user": create_user,
177}
178
179
180def simple(request, template_name="djangopypi/simple.html"):
181 if request.method == "POST":
182 post_data, files = parse_distutils_request(request)
183 action_name = post_data.get(":action")
184 if action_name not in ACTIONS:
185 return HttpResponseNotImplemented(
186 "The action %s is not implemented" % action_name)
187 return ACTIONS[action_name](request, post_data, files)
188
189 dists = Project.objects.all().order_by("name")
190 context = RequestContext(request, {
191 "dists": dists,
192 "title": 'Package Index',
193 })
194
195 return render_to_response(template_name, context_instance=context)
196
197
198def show_links(request, dist_name,
199 template_name="djangopypi/show_links.html"):
200 try:
201 project = Project.objects.get(name=dist_name)
202 releases = project.releases.all().order_by('-version')
203 except Project.DoesNotExist:
204 raise Http404
205
206 context = RequestContext(request, {
207 "dist_name": dist_name,
208 "releases": releases,
209 "project": project,
210 "title": project.name,
211 })
212
213 return render_to_response(template_name, context_instance=context)
214
215
216def show_version(request, dist_name, version,
217 template_name="djangopypi/show_version.html"):
218 try:
219 release = Project.objects.get(name=dist_name).releases \
220 .get(version=version)
221 except (Project.DoesNotExist, Release.DoesNotExist):
222 raise Http404()
223
224 context = RequestContext(request, {
225 "dist_name": dist_name,
226 "version": version,
227 "release": release,
228 "title": dist_name,
229 })
230
231 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 )