diff options
Diffstat (limited to 'djangopypi/views/__init__.py')
-rw-r--r-- | djangopypi/views/__init__.py | 257 |
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 @@ | |||
1 | import cgi | ||
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 | ||
13 | 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 | |||
23 | from djangopypi.models import Project, Classifier, Release, UPLOAD_TO | ||
24 | from djangopypi.forms import ProjectForm, ReleaseForm | ||
25 | from djangopypi.http import HttpResponseUnauthorized | ||
26 | from djangopypi.http import HttpResponseNotImplemented | ||
27 | from djangopypi.utils import decode_fs | ||
28 | from djangopypi.views.search import search | ||
29 | |||
30 | |||
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 = { | ||
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 | |||
180 | def 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 | |||
198 | def 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 | |||
216 | def 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 | |||
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 | ) | ||