Merge lp:~salgado/offspring/private-projects-views into lp:~linaro-automation/offspring/linaro
- private-projects-views
- Merge into linaro
Proposed by
Guilherme Salgado
Status: | Merged |
---|---|
Approved by: | James Tunnicliffe |
Approved revision: | no longer in the source branch. |
Merge reported by: | Guilherme Salgado |
Merged at revision: | not available |
Proposed branch: | lp:~salgado/offspring/private-projects-views |
Merge into: | lp:~linaro-automation/offspring/linaro |
Prerequisite: | lp:~salgado/offspring/private-projects |
Diff against target: |
387 lines (+269/-26) 5 files modified
Makefile (+6/-0) lib/offspring/web/queuemanager/tests/__init__.py (+1/-0) lib/offspring/web/queuemanager/tests/factory.py (+5/-5) lib/offspring/web/queuemanager/tests/test_views.py (+201/-0) lib/offspring/web/queuemanager/views.py (+56/-21) |
To merge this branch: | bzr merge lp:~salgado/offspring/private-projects-views |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Tunnicliffe (community) | Approve | ||
Review via email: mp+78850@code.launchpad.net |
Commit message
Description of the change
Make all project and build views return a 404 if the user is not allowed to see that private project/build.
To post a comment you must log in.
Revision history for this message
Guilherme Salgado (salgado) wrote : | # |
In fact this was merged into our integration branch (lp:~linaro-infrastructure/offspring/private-builds)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2011-10-10 14:33:25 +0000 | |||
3 | +++ Makefile 2011-10-10 14:33:25 +0000 | |||
4 | @@ -36,6 +36,12 @@ | |||
5 | 36 | tar --directory=lib/offspring/web/media/js/ -zxf .download-cache/SmartClientRuntime-7.0.tgz | 36 | tar --directory=lib/offspring/web/media/js/ -zxf .download-cache/SmartClientRuntime-7.0.tgz |
6 | 37 | make | 37 | make |
7 | 38 | 38 | ||
8 | 39 | web-run: web | ||
9 | 40 | ./bin/offspring-web runserver --settings=offspring.web.settings_devel | ||
10 | 41 | |||
11 | 42 | web-shell: web | ||
12 | 43 | ./bin/offspring-web shell --settings=offspring.web.settings_devel | ||
13 | 44 | |||
14 | 39 | install-test-runner: .virtualenv .download-cache | 45 | install-test-runner: .virtualenv .download-cache |
15 | 40 | $(PIP_INSTALL) nose | 46 | $(PIP_INSTALL) nose |
16 | 41 | 47 | ||
17 | 42 | 48 | ||
18 | === modified file 'lib/offspring/web/queuemanager/tests/__init__.py' | |||
19 | --- lib/offspring/web/queuemanager/tests/__init__.py 2011-10-10 14:33:25 +0000 | |||
20 | +++ lib/offspring/web/queuemanager/tests/__init__.py 2011-10-10 14:33:25 +0000 | |||
21 | @@ -1,1 +1,2 @@ | |||
22 | 1 | from .test_models import * | 1 | from .test_models import * |
23 | 2 | from .test_views import * | ||
24 | 2 | 3 | ||
25 | === modified file 'lib/offspring/web/queuemanager/tests/factory.py' | |||
26 | --- lib/offspring/web/queuemanager/tests/factory.py 2011-10-10 14:33:25 +0000 | |||
27 | +++ lib/offspring/web/queuemanager/tests/factory.py 2011-10-10 14:33:25 +0000 | |||
28 | @@ -37,10 +37,8 @@ | |||
29 | 37 | 37 | ||
30 | 38 | def makeUser(self): | 38 | def makeUser(self): |
31 | 39 | userid = password = self.getUniqueString() | 39 | userid = password = self.getUniqueString() |
33 | 40 | user = User.objects.create_user( | 40 | return User.objects.create_user( |
34 | 41 | userid, self.getUniqueEmailAddress(), password) | 41 | userid, self.getUniqueEmailAddress(), password) |
35 | 42 | user.save() | ||
36 | 43 | return user | ||
37 | 44 | 42 | ||
38 | 45 | def makeProject(self, name=None, title=None, is_private=False, | 43 | def makeProject(self, name=None, title=None, is_private=False, |
39 | 46 | project_group=None, owner=None, access_groups=[]): | 44 | project_group=None, owner=None, access_groups=[]): |
40 | @@ -68,11 +66,13 @@ | |||
41 | 68 | group.save() | 66 | group.save() |
42 | 69 | return group | 67 | return group |
43 | 70 | 68 | ||
45 | 71 | def makeBuildResult(self, project=None): | 69 | def makeBuildResult(self, project=None, name=None, result=None): |
46 | 70 | if name is None: | ||
47 | 71 | name = self.getUniqueString() | ||
48 | 72 | if project is None: | 72 | if project is None: |
49 | 73 | project = self.makeProject() | 73 | project = self.makeProject() |
50 | 74 | project.save() | 74 | project.save() |
52 | 75 | result = BuildResult(project=project) | 75 | result = BuildResult(name=name, project=project, result=result) |
53 | 76 | result.save() | 76 | result.save() |
54 | 77 | return result | 77 | return result |
55 | 78 | 78 | ||
56 | 79 | 79 | ||
57 | === added file 'lib/offspring/web/queuemanager/tests/test_views.py' | |||
58 | --- lib/offspring/web/queuemanager/tests/test_views.py 1970-01-01 00:00:00 +0000 | |||
59 | +++ lib/offspring/web/queuemanager/tests/test_views.py 2011-10-10 14:33:25 +0000 | |||
60 | @@ -0,0 +1,201 @@ | |||
61 | 1 | from django.core.urlresolvers import reverse | ||
62 | 2 | from django.contrib.auth.models import ( | ||
63 | 3 | AnonymousUser, | ||
64 | 4 | Group, | ||
65 | 5 | Permission, | ||
66 | 6 | ) | ||
67 | 7 | from django.http import Http404 | ||
68 | 8 | from django.test import TestCase | ||
69 | 9 | |||
70 | 10 | from offspring.enums import ProjectBuildStates | ||
71 | 11 | from offspring.web.queuemanager.models import ( | ||
72 | 12 | Project, | ||
73 | 13 | ) | ||
74 | 14 | from offspring.web.queuemanager.views import get_possibly_private_object | ||
75 | 15 | from offspring.web.queuemanager.tests.factory import factory | ||
76 | 16 | |||
77 | 17 | |||
78 | 18 | class Test_get_possibly_private_object(TestCase): | ||
79 | 19 | |||
80 | 20 | def test_public_object(self): | ||
81 | 21 | obj = factory.makeProject(is_private=False) | ||
82 | 22 | obj2 = get_possibly_private_object( | ||
83 | 23 | AnonymousUser(), Project, pk=obj.pk) | ||
84 | 24 | self.assertEqual(obj.pk, obj2.pk) | ||
85 | 25 | |||
86 | 26 | def test_private_object_for_owner(self): | ||
87 | 27 | obj = factory.makeProject(is_private=True) | ||
88 | 28 | obj2 = get_possibly_private_object(obj.owner, Project, pk=obj.pk) | ||
89 | 29 | self.assertEqual(obj.pk, obj2.pk) | ||
90 | 30 | |||
91 | 31 | def test_private_object_for_user_with_no_access(self): | ||
92 | 32 | obj = factory.makeProject(is_private=True) | ||
93 | 33 | user = factory.makeUser() | ||
94 | 34 | self.assertRaises( | ||
95 | 35 | Http404, get_possibly_private_object, user, Project, pk=obj.pk) | ||
96 | 36 | |||
97 | 37 | |||
98 | 38 | class ProjectViewTestsMixin(object): | ||
99 | 39 | view_path = None | ||
100 | 40 | |||
101 | 41 | def get_expected_page_heading(self, project): | ||
102 | 42 | raise NotImplementedError() | ||
103 | 43 | |||
104 | 44 | def grant_necessary_permission_to(self, user): | ||
105 | 45 | pass | ||
106 | 46 | |||
107 | 47 | def test_private_project_visible(self): | ||
108 | 48 | project = factory.makeProject(is_private=True) | ||
109 | 49 | user = project.owner | ||
110 | 50 | self.grant_necessary_permission_to(user) | ||
111 | 51 | self.assertTrue( | ||
112 | 52 | self.client.login(username=user.username, password=user.username)) | ||
113 | 53 | response = self.client.get( | ||
114 | 54 | reverse(self.view_path, args=[project.name])) | ||
115 | 55 | self.assertContains( | ||
116 | 56 | response, self.get_expected_page_heading(project), | ||
117 | 57 | status_code=200, msg_prefix=response.content) | ||
118 | 58 | |||
119 | 59 | def test_private_project_invisible(self): | ||
120 | 60 | project = factory.makeProject(is_private=True) | ||
121 | 61 | user = factory.makeUser() | ||
122 | 62 | self.grant_necessary_permission_to(user) | ||
123 | 63 | self.assertTrue( | ||
124 | 64 | self.client.login(username=user.username, password=user.username)) | ||
125 | 65 | response = self.client.get( | ||
126 | 66 | reverse(self.view_path, args=[project.name])) | ||
127 | 67 | self.assertEqual(404, response.status_code) | ||
128 | 68 | |||
129 | 69 | |||
130 | 70 | class ProjectDetailsViewTests(TestCase, ProjectViewTestsMixin): | ||
131 | 71 | view_path = 'offspring.web.queuemanager.views.project_details' | ||
132 | 72 | |||
133 | 73 | def get_expected_page_heading(self, project): | ||
134 | 74 | return 'Project Details for %s' % project.title.capitalize() | ||
135 | 75 | |||
136 | 76 | def test_public_project(self): | ||
137 | 77 | project = factory.makeProject(is_private=False) | ||
138 | 78 | response = self.client.get( | ||
139 | 79 | reverse(self.view_path, args=[project.name])) | ||
140 | 80 | self.assertContains( | ||
141 | 81 | response, self.get_expected_page_heading(project), | ||
142 | 82 | status_code=200, msg_prefix=response.content) | ||
143 | 83 | |||
144 | 84 | |||
145 | 85 | class ProjectEditViewTests(TestCase, ProjectViewTestsMixin): | ||
146 | 86 | view_path = 'offspring.web.queuemanager.views.project_edit' | ||
147 | 87 | |||
148 | 88 | def get_expected_page_heading(self, project): | ||
149 | 89 | return 'Update details for %s' % project.title.capitalize() | ||
150 | 90 | |||
151 | 91 | def grant_necessary_permission_to(self, user): | ||
152 | 92 | grant_permission_to_user(user, 'change_project') | ||
153 | 93 | |||
154 | 94 | def test_public_project(self): | ||
155 | 95 | project = factory.makeProject(is_private=False) | ||
156 | 96 | user = factory.makeUser() | ||
157 | 97 | self.grant_necessary_permission_to(user) | ||
158 | 98 | self.assertTrue( | ||
159 | 99 | self.client.login(username=user.username, password=user.username)) | ||
160 | 100 | response = self.client.get( | ||
161 | 101 | reverse(self.view_path, args=[project.name])) | ||
162 | 102 | self.assertContains( | ||
163 | 103 | response, self.get_expected_page_heading(project), | ||
164 | 104 | status_code=200, msg_prefix=response.content) | ||
165 | 105 | |||
166 | 106 | |||
167 | 107 | class BuildViewTests(TestCase): | ||
168 | 108 | |||
169 | 109 | def test_public_build(self): | ||
170 | 110 | project = factory.makeProject(is_private=False) | ||
171 | 111 | build = factory.makeBuildResult(project=project) | ||
172 | 112 | response = self.client.get( | ||
173 | 113 | reverse('offspring.web.queuemanager.views.build_details', | ||
174 | 114 | args=[project.name, build.name])) | ||
175 | 115 | self.assertContains( | ||
176 | 116 | response, 'Build Details', status_code=200, | ||
177 | 117 | msg_prefix=response.content) | ||
178 | 118 | |||
179 | 119 | def test_private_build_visible(self): | ||
180 | 120 | project = factory.makeProject(is_private=True) | ||
181 | 121 | build = factory.makeBuildResult(project=project) | ||
182 | 122 | user = project.owner | ||
183 | 123 | self.assertTrue( | ||
184 | 124 | self.client.login(username=user.username, password=user.username)) | ||
185 | 125 | response = self.client.get( | ||
186 | 126 | reverse('offspring.web.queuemanager.views.build_details', | ||
187 | 127 | args=[project.name, build.name])) | ||
188 | 128 | self.assertContains( | ||
189 | 129 | response, 'Build Details', status_code=200, | ||
190 | 130 | msg_prefix=response.content) | ||
191 | 131 | |||
192 | 132 | def test_private_build_invisible(self): | ||
193 | 133 | project = factory.makeProject(is_private=True) | ||
194 | 134 | build = factory.makeBuildResult(project=project) | ||
195 | 135 | self.assertTrue(make_user_and_login(self.client)) | ||
196 | 136 | response = self.client.get( | ||
197 | 137 | reverse('offspring.web.queuemanager.views.build_details', | ||
198 | 138 | args=[project.name, build.name])) | ||
199 | 139 | self.assertEqual(404, response.status_code) | ||
200 | 140 | |||
201 | 141 | |||
202 | 142 | class BuildPublishViewTests(TestCase): | ||
203 | 143 | |||
204 | 144 | def test_public_build(self): | ||
205 | 145 | project = factory.makeProject(is_private=False) | ||
206 | 146 | build = factory.makeBuildResult( | ||
207 | 147 | project=project, result=ProjectBuildStates.SUCCESS) | ||
208 | 148 | user = factory.makeUser() | ||
209 | 149 | grant_permission_to_user(user, 'add_release') | ||
210 | 150 | self.assertTrue( | ||
211 | 151 | self.client.login(username=user.username, password=user.username)) | ||
212 | 152 | response = self.client.get( | ||
213 | 153 | reverse('offspring.web.queuemanager.views.build_publish', | ||
214 | 154 | args=[project.name, build.name])) | ||
215 | 155 | self.assertContains( | ||
216 | 156 | response, 'Release %s build' % project.title.capitalize(), | ||
217 | 157 | status_code=200, msg_prefix=response.content) | ||
218 | 158 | |||
219 | 159 | def test_private_build_visible(self): | ||
220 | 160 | project = factory.makeProject(is_private=True) | ||
221 | 161 | build = factory.makeBuildResult( | ||
222 | 162 | project=project, result=ProjectBuildStates.SUCCESS) | ||
223 | 163 | user = build.project.owner | ||
224 | 164 | grant_permission_to_user(user, 'add_release') | ||
225 | 165 | self.assertTrue( | ||
226 | 166 | self.client.login(username=user.username, password=user.username)) | ||
227 | 167 | response = self.client.get( | ||
228 | 168 | reverse('offspring.web.queuemanager.views.build_publish', | ||
229 | 169 | args=[project.name, build.name])) | ||
230 | 170 | self.assertContains( | ||
231 | 171 | response, 'Release %s build' % project.title.capitalize(), | ||
232 | 172 | status_code=200, msg_prefix=response.content) | ||
233 | 173 | |||
234 | 174 | def test_private_build_invisible(self): | ||
235 | 175 | project = factory.makeProject(is_private=True) | ||
236 | 176 | build = factory.makeBuildResult( | ||
237 | 177 | project=project, result=ProjectBuildStates.SUCCESS) | ||
238 | 178 | user = factory.makeUser() | ||
239 | 179 | grant_permission_to_user(user, 'add_release') | ||
240 | 180 | self.assertTrue( | ||
241 | 181 | self.client.login(username=user.username, password=user.username)) | ||
242 | 182 | response = self.client.get( | ||
243 | 183 | reverse('offspring.web.queuemanager.views.build_publish', | ||
244 | 184 | args=[project.name, build.name])) | ||
245 | 185 | self.assertEqual(404, response.status_code) | ||
246 | 186 | |||
247 | 187 | |||
248 | 188 | def make_user_and_login(client): | ||
249 | 189 | user = factory.makeUser() | ||
250 | 190 | return client.login(username=user.username, password=user.username) | ||
251 | 191 | |||
252 | 192 | |||
253 | 193 | def grant_permission_to_user(user, permission): | ||
254 | 194 | group = Group(name=user.username) | ||
255 | 195 | group.save() | ||
256 | 196 | group.permissions.add(Permission.objects.get(codename=permission)) | ||
257 | 197 | group.save() | ||
258 | 198 | user.groups.add(group) | ||
259 | 199 | user.save() | ||
260 | 200 | return user | ||
261 | 201 | |||
262 | 0 | 202 | ||
263 | === modified file 'lib/offspring/web/queuemanager/views.py' | |||
264 | --- lib/offspring/web/queuemanager/views.py 2011-10-10 14:33:25 +0000 | |||
265 | +++ lib/offspring/web/queuemanager/views.py 2011-10-10 14:33:25 +0000 | |||
266 | @@ -16,12 +16,13 @@ | |||
267 | 16 | from django.http import ( | 16 | from django.http import ( |
268 | 17 | HttpResponse, | 17 | HttpResponse, |
269 | 18 | HttpResponseRedirect, | 18 | HttpResponseRedirect, |
271 | 19 | HttpResponseForbidden | 19 | HttpResponseForbidden, |
272 | 20 | Http404, | ||
273 | 20 | ) | 21 | ) |
274 | 21 | from django.middleware import csrf | 22 | from django.middleware import csrf |
275 | 22 | from django.shortcuts import ( | 23 | from django.shortcuts import ( |
276 | 23 | render_to_response, | 24 | render_to_response, |
278 | 24 | get_object_or_404 | 25 | get_object_or_404, |
279 | 25 | ) | 26 | ) |
280 | 26 | from django.template import ( | 27 | from django.template import ( |
281 | 27 | Context, | 28 | Context, |
282 | @@ -57,6 +58,19 @@ | |||
283 | 57 | Release | 58 | Release |
284 | 58 | ) | 59 | ) |
285 | 59 | 60 | ||
286 | 61 | |||
287 | 62 | def get_possibly_private_object(user, klass, *args, **kwargs): | ||
288 | 63 | """Use get_object_or_404() to return a possibly private object. | ||
289 | 64 | |||
290 | 65 | If the object does not exist or the user doesn't have permission to see | ||
291 | 66 | it, raise Http404. | ||
292 | 67 | """ | ||
293 | 68 | obj = get_object_or_404(klass, *args, **kwargs) | ||
294 | 69 | if not obj.is_visible_to(user): | ||
295 | 70 | raise Http404('No matches for the given query') | ||
296 | 71 | return obj | ||
297 | 72 | |||
298 | 73 | |||
299 | 60 | #XXX: Reduce horrible duplication that occurs in these functions (mostly done). | 74 | #XXX: Reduce horrible duplication that occurs in these functions (mostly done). |
300 | 61 | #TODO: Thanks to reduced duplication, lets start using more generic views. | 75 | #TODO: Thanks to reduced duplication, lets start using more generic views. |
301 | 62 | 76 | ||
302 | @@ -118,8 +132,11 @@ | |||
303 | 118 | 132 | ||
304 | 119 | 133 | ||
305 | 120 | def build_details(request, projectName, buildName): | 134 | def build_details(request, projectName, buildName): |
308 | 121 | b = get_object_or_404( | 135 | user_visible_builds = BuildResult.all_objects.accessible_by_user( |
309 | 122 | BuildResult.objects.select_related('builder', 'project', 'requestor'), | 136 | request.user) |
310 | 137 | b = get_possibly_private_object( | ||
311 | 138 | request.user, | ||
312 | 139 | user_visible_builds.select_related('builder', 'project', 'requestor'), | ||
313 | 123 | project__pk=projectName, name=buildName) | 140 | project__pk=projectName, name=buildName) |
314 | 124 | pageData = { | 141 | pageData = { |
315 | 125 | 'pillar' : 'builds', | 142 | 'pillar' : 'builds', |
316 | @@ -134,14 +151,18 @@ | |||
317 | 134 | 151 | ||
318 | 135 | @login_required | 152 | @login_required |
319 | 136 | def build_publish(request, projectName, buildName): | 153 | def build_publish(request, projectName, buildName): |
323 | 137 | p = get_object_or_404(Project, pk=projectName) | 154 | user = request.user |
324 | 138 | b = get_object_or_404( | 155 | p = get_possibly_private_object(user, Project, pk=projectName) |
325 | 139 | BuildResult, project=p, name=buildName, | 156 | # We use get_possibly_private_object() here just for consistency as it's |
326 | 157 | # not really needed because the above will fail if the user doesn't have | ||
327 | 158 | # the rights to see this build. | ||
328 | 159 | b = get_possibly_private_object( | ||
329 | 160 | user, BuildResult, project=p, name=buildName, | ||
330 | 140 | result=ProjectBuildStates.SUCCESS, release=None) | 161 | result=ProjectBuildStates.SUCCESS, release=None) |
332 | 141 | if request.user.has_perm('queuemanager.add_release'): | 162 | if user.has_perm('queuemanager.add_release'): |
333 | 142 | r = Release() | 163 | r = Release() |
334 | 143 | r.build = b | 164 | r.build = b |
336 | 144 | r.creator = request.user | 165 | r.creator = user |
337 | 145 | if request.method == 'POST': | 166 | if request.method == 'POST': |
338 | 146 | f = ReleaseForm(request.POST, instance=r) | 167 | f = ReleaseForm(request.POST, instance=r) |
339 | 147 | if f.is_valid(): | 168 | if f.is_valid(): |
340 | @@ -175,21 +196,35 @@ | |||
341 | 175 | project_create = permission_required('queuemanager.add_project')(project_create) | 196 | project_create = permission_required('queuemanager.add_project')(project_create) |
342 | 176 | 197 | ||
343 | 177 | def project_edit(request, projectName): | 198 | def project_edit(request, projectName): |
353 | 178 | return update_object( | 199 | # XXX: This nasty hack is because update_object uses (via lookup_object) |
354 | 179 | request, | 200 | # .objects directly when it should insted use the default manager |
355 | 180 | form_class=EditProjectForm, | 201 | # (https://code.djangoproject.com/ticket/17021). |
356 | 181 | login_required=True, | 202 | from django.views.generic import create_update |
357 | 182 | template_name='queuemanager/project_edit.html', | 203 | orig_lookup = create_update.lookup_object |
358 | 183 | template_object_name='project', | 204 | def lookup(model, obj_id, slug, slug_field): |
359 | 184 | extra_context={ 'pillar' : 'projects' }, | 205 | return get_possibly_private_object( |
360 | 185 | object_id=projectName, | 206 | request.user, Project, pk=projectName) |
361 | 186 | ) | 207 | create_update.lookup_object = lookup |
362 | 208 | try: | ||
363 | 209 | response = update_object( | ||
364 | 210 | request, | ||
365 | 211 | form_class=EditProjectForm, | ||
366 | 212 | login_required=True, | ||
367 | 213 | template_name='queuemanager/project_edit.html', | ||
368 | 214 | template_object_name='project', | ||
369 | 215 | extra_context={ 'pillar' : 'projects' }, | ||
370 | 216 | object_id=projectName, | ||
371 | 217 | ) | ||
372 | 218 | finally: | ||
373 | 219 | create_update.lookup_object = orig_lookup | ||
374 | 220 | return response | ||
375 | 187 | project_edit = permission_required('queuemanager.change_project')(project_edit) | 221 | project_edit = permission_required('queuemanager.change_project')(project_edit) |
376 | 188 | 222 | ||
377 | 189 | def project_details(request, projectName): | 223 | def project_details(request, projectName): |
381 | 190 | p = get_object_or_404( | 224 | user_visible_objects = Project.all_objects.accessible_by_user( |
382 | 191 | Project.objects.select_related('project_group', 'launchpad_project'), | 225 | request.user) |
383 | 192 | pk=projectName) | 226 | p = get_possibly_private_object( |
384 | 227 | request.user, user_visible_objects, pk=projectName) | ||
385 | 193 | project_build_results = BuildResult.objects.filter( | 228 | project_build_results = BuildResult.objects.filter( |
386 | 194 | project = p).exclude(result = None) | 229 | project = p).exclude(result = None) |
387 | 195 | build_stats = { | 230 | build_stats = { |
Looks good.