diff options
author | Russell Sim <russell.sim@vpac.org> | 2009-04-21 21:04:16 +1000 |
---|---|---|
committer | Russell Sim <russell.sim@vpac.org> | 2009-04-21 21:04:16 +1000 |
commit | 05af358f9cbeeb04216f649ffec6c828edac794c (patch) | |
tree | af4ae4eab467013ecb8cabaef66059a88461b409 | |
parent | b9acbab26cd672b3fe441ae2e111ce575ae6b8ef (diff) | |
download | chishop-05af358f9cbeeb04216f649ffec6c828edac794c.tar.bz2 chishop-05af358f9cbeeb04216f649ffec6c828edac794c.tar.xz chishop-05af358f9cbeeb04216f649ffec6c828edac794c.zip |
complete refactor to use ModelForm
-rw-r--r-- | chishop/settings.py | 1 | ||||
-rw-r--r-- | djangopypi/forms.py | 85 | ||||
-rw-r--r-- | djangopypi/models.py | 5 | ||||
-rw-r--r-- | djangopypi/views.py | 102 |
4 files changed, 91 insertions, 102 deletions
diff --git a/chishop/settings.py b/chishop/settings.py index 9d769f2..22dff36 100644 --- a/chishop/settings.py +++ b/chishop/settings.py | |||
@@ -15,6 +15,7 @@ ADMINS = ( | |||
15 | # The default on PyPI is to not allow this, but it can be real handy | 15 | # The default on PyPI is to not allow this, but it can be real handy |
16 | # if you're sloppy. | 16 | # if you're sloppy. |
17 | DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False | 17 | DJANGOPYPI_ALLOW_VERSION_OVERWRITE = False |
18 | DJANGOPYPI_RELEASE_UPLOAD_TO = 'dists' | ||
18 | 19 | ||
19 | MANAGERS = ADMINS | 20 | MANAGERS = ADMINS |
20 | 21 | ||
diff --git a/djangopypi/forms.py b/djangopypi/forms.py index 51b1498..7704753 100644 --- a/djangopypi/forms.py +++ b/djangopypi/forms.py | |||
@@ -37,83 +37,14 @@ from djangopypi.models import Project, Classifier, Release | |||
37 | from django.utils.translation import ugettext_lazy as _ | 37 | from django.utils.translation import ugettext_lazy as _ |
38 | 38 | ||
39 | 39 | ||
40 | class PermissionDeniedError(Exception): | 40 | class ProjectForm(forms.ModelForm): |
41 | """The user did not have the privileges to execute an action.""" | 41 | class Meta: |
42 | model = Project | ||
43 | exclude = ['owner', 'classifiers'] | ||
42 | 44 | ||
43 | 45 | ||
44 | class AlreadyExistsError(Exception): | 46 | class ReleaseForm(forms.ModelForm): |
45 | """Filename already exists.""" | 47 | class Meta: |
48 | model = Release | ||
49 | exclude = ['project'] | ||
46 | 50 | ||
47 | ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ | ||
48 | + "problems with that you should create a new release.") | ||
49 | |||
50 | |||
51 | class ProjectRegisterForm(forms.Form): | ||
52 | name = forms.CharField() | ||
53 | license = forms.CharField(required=False) | ||
54 | metadata_version = forms.CharField(initial="1.0") | ||
55 | author = forms.CharField(required=False) | ||
56 | home_page = forms.CharField(required=False) | ||
57 | download_url = forms.CharField(required=False) | ||
58 | summary = forms.CharField(required=False) | ||
59 | description = forms.CharField(required=False) | ||
60 | author_email = forms.CharField(required=False) | ||
61 | version = forms.CharField() | ||
62 | platform = forms.CharField(required=False) | ||
63 | |||
64 | PermissionDeniedError = PermissionDeniedError | ||
65 | AlreadyExistsError = AlreadyExistsError | ||
66 | |||
67 | def save(self, classifiers, user, file=None): | ||
68 | values = dict(self.cleaned_data) | ||
69 | name = values["name"] | ||
70 | version = values.pop("version") | ||
71 | platform = values.pop("platform", "UNKNOWN") | ||
72 | values["owner"] = user | ||
73 | |||
74 | try: | ||
75 | project = Project.objects.get(name=name) | ||
76 | except Project.DoesNotExist: | ||
77 | project = Project.objects.create(**values) | ||
78 | else: | ||
79 | # If the project already exists, | ||
80 | # be sure that the current user owns this object. | ||
81 | if project.owner != user: | ||
82 | raise self.PermissionDeniedError( | ||
83 | "%s doesn't own that project." % user.username) | ||
84 | [setattr(project, field_name, field_value) | ||
85 | for field_name, field_value in values.items()] | ||
86 | project.save() | ||
87 | |||
88 | |||
89 | for classifier in classifiers: | ||
90 | project.classifiers.add( | ||
91 | Classifier.objects.get_or_create(name=classifier)[0]) | ||
92 | |||
93 | # If the old file already exists, django will append a _ after the | ||
94 | # filename, however with .tar.gz files django does the "wrong" thing | ||
95 | # and saves it as project-0.1.2.tar_.gz. So remove it before | ||
96 | # django sees anything. | ||
97 | allow_overwrite = getattr(settings, | ||
98 | "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False) | ||
99 | |||
100 | if file: | ||
101 | try: | ||
102 | release = Release.objects.get(version=version, | ||
103 | platform=platform, project=project) | ||
104 | if os.path.exists(release.distribution.path): | ||
105 | if not allow_overwrite: | ||
106 | raise self.AlreadyExistsError(ALREADY_EXISTS_FMT % ( | ||
107 | release.filename, release)) | ||
108 | os.remove(release.distribution.path) | ||
109 | |||
110 | release.delete() | ||
111 | except (Release.DoesNotExist, ValueError): | ||
112 | pass | ||
113 | |||
114 | release, created = Release.objects.get_or_create(version=version, | ||
115 | platform=platform, | ||
116 | project=project) | ||
117 | if file: | ||
118 | release.distribution.save(file.name, file, save=True) | ||
119 | release.save() | ||
diff --git a/djangopypi/models.py b/djangopypi/models.py index d6df975..b4b1e34 100644 --- a/djangopypi/models.py +++ b/djangopypi/models.py | |||
@@ -31,6 +31,7 @@ POSSIBILITY OF SUCH DAMAGE. | |||
31 | """ | 31 | """ |
32 | 32 | ||
33 | import os | 33 | import os |
34 | from django.conf import settings | ||
34 | from django.db import models | 35 | from django.db import models |
35 | from django.utils.translation import ugettext_lazy as _ | 36 | from django.utils.translation import ugettext_lazy as _ |
36 | from django.contrib.auth.models import User | 37 | from django.contrib.auth.models import User |
@@ -63,6 +64,8 @@ ARCHITECTURES = ( | |||
63 | ("ultrasparc", "UltraSparc"), | 64 | ("ultrasparc", "UltraSparc"), |
64 | ) | 65 | ) |
65 | 66 | ||
67 | UPLOAD_TO = getattr(settings, | ||
68 | "DJANGOPYPI_RELEASE_UPLOAD_TO", 'dist') | ||
66 | 69 | ||
67 | class Classifier(models.Model): | 70 | class Classifier(models.Model): |
68 | name = models.CharField(max_length=255, unique=True) | 71 | name = models.CharField(max_length=255, unique=True) |
@@ -107,7 +110,7 @@ class Project(models.Model): | |||
107 | 110 | ||
108 | class Release(models.Model): | 111 | class Release(models.Model): |
109 | version = models.CharField(max_length=128) | 112 | version = models.CharField(max_length=128) |
110 | distribution = models.FileField(upload_to="dists") | 113 | distribution = models.FileField(upload_to=UPLOAD_TO) |
111 | md5_digest = models.CharField(max_length=255, blank=True) | 114 | md5_digest = models.CharField(max_length=255, blank=True) |
112 | platform = models.CharField(max_length=255, blank=True) | 115 | platform = models.CharField(max_length=255, blank=True) |
113 | signature = models.CharField(max_length=128, blank=True) | 116 | signature = models.CharField(max_length=128, blank=True) |
diff --git a/djangopypi/views.py b/djangopypi/views.py index 54d58dc..3c6d03d 100644 --- a/djangopypi/views.py +++ b/djangopypi/views.py | |||
@@ -30,19 +30,27 @@ POSSIBILITY OF SUCH DAMAGE. | |||
30 | 30 | ||
31 | """ | 31 | """ |
32 | 32 | ||
33 | import os | ||
34 | |||
35 | from django.conf import settings | ||
33 | from django.http import Http404, HttpResponse, HttpResponseBadRequest | 36 | from django.http import Http404, HttpResponse, HttpResponseBadRequest |
34 | from django.http import QueryDict, HttpResponseForbidden | 37 | from django.http import QueryDict, HttpResponseForbidden |
35 | from django.shortcuts import render_to_response | 38 | from django.shortcuts import render_to_response |
36 | from djangopypi.models import Project | 39 | from djangopypi.models import Project, Classifier, Release, UPLOAD_TO |
37 | from djangopypi.forms import ProjectRegisterForm | 40 | from djangopypi.forms import ProjectForm, ReleaseForm |
38 | from django.template import RequestContext | 41 | from django.template import RequestContext |
39 | from django.utils.datastructures import MultiValueDict | 42 | from django.utils.datastructures import MultiValueDict |
43 | from django.utils.translation import ugettext_lazy as _ | ||
40 | from django.core.files.uploadedfile import SimpleUploadedFile | 44 | from django.core.files.uploadedfile import SimpleUploadedFile |
41 | from django.contrib.auth import authenticate, login | 45 | from django.contrib.auth import authenticate, login |
42 | from djangopypi.http import HttpResponseNotImplemented | 46 | from djangopypi.http import HttpResponseNotImplemented |
43 | from djangopypi.http import HttpResponseUnauthorized | 47 | from djangopypi.http import HttpResponseUnauthorized |
44 | 48 | ||
45 | 49 | ||
50 | ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ | ||
51 | + "problems with that you should create a new release.") | ||
52 | |||
53 | |||
46 | def parse_weird_post_data(raw_post_data): | 54 | def parse_weird_post_data(raw_post_data): |
47 | """ For some reason Django can't parse the HTTP POST data | 55 | """ For some reason Django can't parse the HTTP POST data |
48 | sent by ``distutils`` register/upload commands. | 56 | sent by ``distutils`` register/upload commands. |
@@ -74,7 +82,7 @@ def parse_weird_post_data(raw_post_data): | |||
74 | if "filename" in headers: | 82 | if "filename" in headers: |
75 | file = SimpleUploadedFile(headers["filename"], content, | 83 | file = SimpleUploadedFile(headers["filename"], content, |
76 | content_type="application/gzip") | 84 | content_type="application/gzip") |
77 | files[headers["name"]] = file | 85 | files["distribution"] = [file] |
78 | elif headers["name"] in post_data: | 86 | elif headers["name"] in post_data: |
79 | post_data[headers["name"]].append(content) | 87 | post_data[headers["name"]].append(content) |
80 | else: | 88 | else: |
@@ -85,7 +93,7 @@ def parse_weird_post_data(raw_post_data): | |||
85 | else: | 93 | else: |
86 | post_data[headers["name"]] = [content] | 94 | post_data[headers["name"]] = [content] |
87 | 95 | ||
88 | return MultiValueDict(post_data), files | 96 | return MultiValueDict(post_data), MultiValueDict(files) |
89 | 97 | ||
90 | 98 | ||
91 | def login_basic_auth(request): | 99 | def login_basic_auth(request): |
@@ -100,30 +108,76 @@ def login_basic_auth(request): | |||
100 | return authenticate(username=username, password=password) | 108 | return authenticate(username=username, password=password) |
101 | 109 | ||
102 | 110 | ||
111 | def submit_project_or_release(user, post_data, files): | ||
112 | """Registers/updates a project or release""" | ||
113 | try: | ||
114 | project = Project.objects.get(name=post_data['name']) | ||
115 | if project.owner != user: | ||
116 | return HttpResponseForbidden( | ||
117 | "That project is owned by someone else!") | ||
118 | except Project.DoesNotExist: | ||
119 | project = None | ||
120 | |||
121 | project_form = ProjectForm(post_data, instance=project) | ||
122 | if project_form.is_valid(): | ||
123 | project = project_form.save(commit=False) | ||
124 | project.owner = user | ||
125 | project.save() | ||
126 | for c in post_data.getlist('classifiers'): | ||
127 | classifier, created = Classifier.objects.get_or_create(name=c) | ||
128 | project.classifiers.add(classifier) | ||
129 | if files: | ||
130 | allow_overwrite = getattr(settings, | ||
131 | "DJANGOPYPI_ALLOW_VERSION_OVERWRITE", False) | ||
132 | try: | ||
133 | release = Release.objects.get(version=post_data['version'], | ||
134 | project=project, | ||
135 | distribution=UPLOAD_TO + '/' + | ||
136 | files['distribution']._name) | ||
137 | if not allow_overwrite: | ||
138 | return HttpResponseForbidden(ALREADY_EXISTS_FMT % ( | ||
139 | release.filename, release)) | ||
140 | except Release.DoesNotExist: | ||
141 | release = None | ||
142 | |||
143 | # If the old file already exists, django will append a _ after the | ||
144 | # filename, however with .tar.gz files django does the "wrong" | ||
145 | # thing and saves it as project-0.1.2.tar_.gz. So remove it before | ||
146 | # django sees anything. | ||
147 | release_form = ReleaseForm(post_data, files, instance=release) | ||
148 | if release_form.is_valid(): | ||
149 | if release and os.path.exists(release.distribution.path): | ||
150 | os.remove(release.distribution.path) | ||
151 | release = release_form.save(commit=False) | ||
152 | release.project = project | ||
153 | release.save() | ||
154 | else: | ||
155 | return HttpResponseBadRequest( | ||
156 | "ERRORS: %s" % release_form.errors) | ||
157 | else: | ||
158 | return HttpResponseBadRequest("ERRORS: %s" % project_form.errors) | ||
159 | |||
160 | return HttpResponse() | ||
161 | |||
162 | |||
103 | def simple(request, template_name="djangopypi/simple.html"): | 163 | def simple(request, template_name="djangopypi/simple.html"): |
104 | if request.method == "POST": | 164 | if request.method == "POST": |
105 | user = login_basic_auth(request) | ||
106 | if not user: | ||
107 | return HttpResponseUnauthorized('PyPI') | ||
108 | login(request, user) | ||
109 | if not request.user.is_authenticated(): | ||
110 | return HttpResponseForbidden( | ||
111 | "Not logged in, or invalid username/password.") | ||
112 | post_data, files = parse_weird_post_data(request.raw_post_data) | 165 | post_data, files = parse_weird_post_data(request.raw_post_data) |
113 | action = post_data.get(":action") | 166 | action = post_data.get(":action") |
114 | classifiers = post_data.getlist("classifiers") | 167 | if action == 'file_upload': |
115 | register_form = ProjectRegisterForm(post_data.copy()) | 168 | user = login_basic_auth(request) |
116 | if register_form.is_valid(): | 169 | if not user: |
117 | try: | 170 | return HttpResponseUnauthorized('PyPI') |
118 | register_form.save(classifiers, request.user, | 171 | |
119 | file=files.get("content")) | 172 | login(request, user) |
120 | except register_form.PermissionDeniedError, e: | 173 | if not request.user.is_authenticated(): |
121 | return HttpResonseForbidden( | 174 | return HttpResponseForbidden( |
122 | "That project is owned by someone else!") | 175 | "Not logged in, or invalid username/password.") |
123 | except register_form.AlreadyExistsError, e: | 176 | |
124 | return HttpResponseForbidden(e) | 177 | return submit_project_or_release(user, post_data, files) |
125 | return HttpResponse("Successfully registered.") | 178 | |
126 | return HttpResponse("ERRORS: %s" % register_form.errors) | 179 | return HttpResponseNotImplemented( |
180 | "The :action %s is not implemented" % action) | ||
127 | 181 | ||
128 | dists = Project.objects.all().order_by("name") | 182 | dists = Project.objects.all().order_by("name") |
129 | context = RequestContext(request, { | 183 | context = RequestContext(request, { |