Merge lp:~salgado/offspring/private-projects-views into lp:offspring
- private-projects-views
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Kevin McDermott |
Approved revision: | 98 |
Merged at revision: | 98 |
Proposed branch: | lp:~salgado/offspring/private-projects-views |
Merge into: | lp:offspring |
Diff against target: |
478 lines (+336/-31) 5 files modified
Makefile (+6/-0) lib/offspring/web/queuemanager/tests/__init__.py (+1/-0) lib/offspring/web/queuemanager/tests/factory.py (+5/-2) lib/offspring/web/queuemanager/tests/test_views.py (+259/-0) lib/offspring/web/queuemanager/views.py (+65/-29) |
To merge this branch: | bzr merge lp:~salgado/offspring/private-projects-views |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Kevin McDermott | Approve | ||
Review via email: mp+84153@code.launchpad.net |
Commit message
Description of the change
Make project and build views return a 404 if the user is not allowed to see that private project/build.
- 92. By Kevin McDermott
-
Merge add-path-
independence [r=salgado] [f=897203] This changes the default file used in Offspring, to use a file sourced from the
root of the offspring Python source files.The path to the configuration file can still be explicitly set either through
the environment or explicitly on the command-line. - 93. By Kevin McDermott
-
Merge slave-testing [r=jedimike] [f=898118]
This adds tests for the Offspring LexBuilderSlave functionality.
- 94. By Guilherme Salgado
-
Make project and build views return a 404 if the user is not allowed to see that private project/build.
- 95. By Guilherme Salgado
-
Update callsites that were still using the factory methods with old names
- 96. By Guilherme Salgado
-
Fix two leftover occurrences of config.web
- 97. By Guilherme Salgado
-
Add docstrings to test methods
Guilherme Salgado (salgado) wrote : | # |
On Wed, 2011-12-07 at 08:24 +0000, Kevin McDermott wrote:
> Review: Needs Fixing
>
> === added file 'lib/offspring/
> #1
>
> +from django.
> + AnonymousUser,
> + Group,
> + Permission,
> + )
> These all fit on one line...
>
> +from offspring.
> + )
>
> This too...
Oops, fixed both.
>
> #2
>
> +class Test_get_
>
> " Almost without exception, class names use the CapWords convention. Classes for internal use have a leading underscore in addition."
This was because I wanted to put the method name verbatin in there. Is
TestGetPossibly
>
> #3
>
> + def get_expected_
> + raise NotImplementedE
>
> +
> + def grant_necessary
> + pass
>
> No docstring, what is expected of subclasses?
>
> For extra points, in the case of "grant_
>
> #4
>
> + def test_private_
> + """
> + Check that this view of a private project is not accessible to those
> + who lack the rights to see the project.
> + """
> + project = factory.
> + user = factory.make_user()
> + self.grant_
> + self.assertTrue(
> + self.client.
> + response = self.client.get(
> + reverse(
> + self.assertEqua
>
> Should this really return a 404? How about a 403? There may be a good reason for returning a 404 btw :-)
Yes, we return a 404 because we don't want people to know that there is a
project they're not allowed to see there.
>
> #5
>
> +class BuildViewTests(
>
> This should refer to BuildResult to make it clearer (and in the subsequent tests), given that there are BuildResults and BuildRequests...
Right, fixed.
>
> #6
>
> +def grant_permissio
>
> No docstring, and the parameter is misleading, it doesn't accept a permission, it accepts a Permission codename...
Fixed both.
>
> === modified file 'lib/offspring/
>
> from django.shortcuts import (
> render_to_response,
> - get_object_or_404
> + get_object_or_404,
> )
>
> This import would fit all on one line...
Changed
> #7
>
> +def get_possibly_
> + """Use get_object_or_404() to return a possibly private object.
> +
> + If the object does not exist or the user doesn't have permission to see
> + it, raise Http404.
> + """
> + obj = get_object_
> + if not obj.is_
> + raise Http404('No matches for the given query')
> + return obj
>
> Could this reasonably be named better? get_possibly_
>
> That's also not great, but given that yo...
- 98. By Guilherme Salgado
-
A few tweaks and improvements suggested by Kevin
Kevin McDermott (bigkevmcd) wrote : | # |
>
> You mean that just because of the extra hack we need or because there are
> other problems with generic view?
>
> Also, do you think it's important enough to block this merge or would it be ok
> do land it this way, unblock the queue, and then file a bug to have it fixed
> later?
Sure, can you file a bug for this tho'?
My concern is that some untested-for behaviour will break due to the global being changed...maybe not today, or tomorrow... :-)
+1 from me.
Preview Diff
1 | === modified file 'Makefile' |
2 | --- Makefile 2011-12-01 16:02:08 +0000 |
3 | +++ Makefile 2011-12-07 13:26:26 +0000 |
4 | @@ -40,6 +40,12 @@ |
5 | tar --directory=lib/offspring/web/media/js/ -zxf .download-cache/SmartClientRuntime-7.0.tgz |
6 | make |
7 | |
8 | +web-run: web |
9 | + ./bin/offspring-web runserver --settings=offspring.web.settings_devel |
10 | + |
11 | +web-shell: web |
12 | + ./bin/offspring-web shell --settings=offspring.web.settings_devel |
13 | + |
14 | install-test-runner: .virtualenv .download-cache |
15 | $(PIP_INSTALL) nose |
16 | |
17 | |
18 | === modified file 'lib/offspring/web/queuemanager/tests/__init__.py' |
19 | --- lib/offspring/web/queuemanager/tests/__init__.py 2011-12-01 15:09:51 +0000 |
20 | +++ lib/offspring/web/queuemanager/tests/__init__.py 2011-12-07 13:26:26 +0000 |
21 | @@ -1,1 +1,2 @@ |
22 | from offspring.web.queuemanager.tests.test_models import * |
23 | +from offspring.web.queuemanager.tests.test_views import * |
24 | |
25 | === modified file 'lib/offspring/web/queuemanager/tests/factory.py' |
26 | --- lib/offspring/web/queuemanager/tests/factory.py 2011-11-30 14:29:13 +0000 |
27 | +++ lib/offspring/web/queuemanager/tests/factory.py 2011-12-07 13:26:26 +0000 |
28 | @@ -73,14 +73,17 @@ |
29 | title = self.get_unique_string() |
30 | return ProjectGroup.objects.create(name=name, title=title) |
31 | |
32 | - def make_build_result(self, project=None): |
33 | + def make_build_result(self, project=None, name=None, result=None): |
34 | """ |
35 | Create a BuildResult with the given project or a newly created one if |
36 | none is given. |
37 | """ |
38 | + if name is None: |
39 | + name = self.get_unique_string() |
40 | if project is None: |
41 | project = self.make_project() |
42 | - return BuildResult.objects.create(project=project) |
43 | + return BuildResult.objects.create( |
44 | + project=project, name=name, result=result) |
45 | |
46 | def make_release(self, build=None, name=None, creator=None): |
47 | """ |
48 | |
49 | === added file 'lib/offspring/web/queuemanager/tests/test_views.py' |
50 | --- lib/offspring/web/queuemanager/tests/test_views.py 1970-01-01 00:00:00 +0000 |
51 | +++ lib/offspring/web/queuemanager/tests/test_views.py 2011-12-07 13:26:26 +0000 |
52 | @@ -0,0 +1,259 @@ |
53 | +from django.core.urlresolvers import reverse |
54 | +from django.contrib.auth.models import AnonymousUser, Group, Permission |
55 | +from django.http import Http404 |
56 | +from django.test import TestCase |
57 | + |
58 | +from offspring.enums import ProjectBuildStates |
59 | +from offspring.web.queuemanager.models import Project |
60 | +from offspring.web.queuemanager.views import get_possibly_private_object |
61 | +from offspring.web.queuemanager.tests.factory import factory |
62 | + |
63 | + |
64 | +class TestGetPossiblyPrivateObject(TestCase): |
65 | + |
66 | + def test_public_object(self): |
67 | + """ |
68 | + Check that get_possibly_private_object() returns the public project |
69 | + with the given key. |
70 | + """ |
71 | + obj = factory.make_project(is_private=False) |
72 | + obj2 = get_possibly_private_object( |
73 | + AnonymousUser(), Project, pk=obj.pk) |
74 | + self.assertEqual(obj.pk, obj2.pk) |
75 | + |
76 | + def test_private_object_for_owner(self): |
77 | + """ |
78 | + Check that get_possibly_private_object() returns the private project |
79 | + with the given key when the user is the project's owner. |
80 | + """ |
81 | + obj = factory.make_project(is_private=True) |
82 | + obj2 = get_possibly_private_object(obj.owner, Project, pk=obj.pk) |
83 | + self.assertEqual(obj.pk, obj2.pk) |
84 | + |
85 | + def test_private_object_for_user_with_no_access(self): |
86 | + """ |
87 | + Check that get_possibly_private_object() raises a 404 error when a |
88 | + user tries to get a project they're not allowed to see. |
89 | + """ |
90 | + obj = factory.make_project(is_private=True) |
91 | + user = factory.make_user() |
92 | + self.assertRaises( |
93 | + Http404, get_possibly_private_object, user, Project, pk=obj.pk) |
94 | + |
95 | + |
96 | +class ProjectViewTestsMixin(object): |
97 | + view_path = None |
98 | + |
99 | + def get_expected_page_heading(self, project): |
100 | + """ |
101 | + Return the heading that should be present on the page tested here. |
102 | + |
103 | + Must be overwritten in subclasses. |
104 | + """ |
105 | + raise NotImplementedError() |
106 | + |
107 | + def grant_necessary_permission_to(self, user): |
108 | + """ |
109 | + Grant the permission necessary for the given user to see the page |
110 | + tested here. |
111 | + |
112 | + Only override it in subclasses if the page you're testing requires |
113 | + an extra permission to be seen. |
114 | + """ |
115 | + |
116 | + def test_private_project_visible(self): |
117 | + """ |
118 | + Check that this view of a private project is accessible to those who |
119 | + have the rights to see the project. |
120 | + """ |
121 | + project = factory.make_project(is_private=True) |
122 | + user = project.owner |
123 | + self.grant_necessary_permission_to(user) |
124 | + self.assertTrue( |
125 | + self.client.login(username=user.username, password=user.username)) |
126 | + response = self.client.get( |
127 | + reverse(self.view_path, args=[project.name])) |
128 | + self.assertContains( |
129 | + response, self.get_expected_page_heading(project), |
130 | + status_code=200, msg_prefix=response.content) |
131 | + |
132 | + def test_private_project_invisible(self): |
133 | + """ |
134 | + Check that this view of a private project is not accessible to those |
135 | + who lack the rights to see the project. |
136 | + """ |
137 | + project = factory.make_project(is_private=True) |
138 | + user = factory.make_user() |
139 | + self.grant_necessary_permission_to(user) |
140 | + self.assertTrue( |
141 | + self.client.login(username=user.username, password=user.username)) |
142 | + response = self.client.get( |
143 | + reverse(self.view_path, args=[project.name])) |
144 | + self.assertEqual(404, response.status_code) |
145 | + |
146 | + |
147 | +class ProjectDetailsViewTests(TestCase, ProjectViewTestsMixin): |
148 | + view_path = 'offspring.web.queuemanager.views.project_details' |
149 | + |
150 | + def get_expected_page_heading(self, project): |
151 | + return 'Project Details for %s' % project.title.capitalize() |
152 | + |
153 | + def test_public_project(self): |
154 | + """ |
155 | + Check that the details view of a public project is accessible to |
156 | + anyone. |
157 | + """ |
158 | + project = factory.make_project(is_private=False) |
159 | + response = self.client.get( |
160 | + reverse(self.view_path, args=[project.name])) |
161 | + self.assertContains( |
162 | + response, self.get_expected_page_heading(project), |
163 | + status_code=200, msg_prefix=response.content) |
164 | + |
165 | + |
166 | +class ProjectEditViewTests(TestCase, ProjectViewTestsMixin): |
167 | + view_path = 'offspring.web.queuemanager.views.project_edit' |
168 | + |
169 | + def get_expected_page_heading(self, project): |
170 | + return 'Update details for %s' % project.title.capitalize() |
171 | + |
172 | + def grant_necessary_permission_to(self, user): |
173 | + grant_permission_to_user(user, 'change_project') |
174 | + |
175 | + def test_public_project(self): |
176 | + """ |
177 | + Check that the edit view of a public project is accessible to those |
178 | + who have the change_project permission. |
179 | + """ |
180 | + project = factory.make_project(is_private=False) |
181 | + user = factory.make_user() |
182 | + self.grant_necessary_permission_to(user) |
183 | + self.assertTrue( |
184 | + self.client.login(username=user.username, password=user.username)) |
185 | + response = self.client.get( |
186 | + reverse(self.view_path, args=[project.name])) |
187 | + self.assertContains( |
188 | + response, self.get_expected_page_heading(project), |
189 | + status_code=200, msg_prefix=response.content) |
190 | + |
191 | + |
192 | +class BuildResultViewTests(TestCase): |
193 | + |
194 | + def test_public_build(self): |
195 | + """ |
196 | + Check that the main view of a public build is accessible to anyone. |
197 | + """ |
198 | + project = factory.make_project(is_private=False) |
199 | + build = factory.make_build_result(project=project) |
200 | + response = self.client.get( |
201 | + reverse('offspring.web.queuemanager.views.build_details', |
202 | + args=[project.name, build.name])) |
203 | + self.assertContains( |
204 | + response, 'Build Details', status_code=200, |
205 | + msg_prefix=response.content) |
206 | + |
207 | + def test_private_build_visible(self): |
208 | + """ |
209 | + Check that the main view of a private build is accessible to those who |
210 | + have the rights to see the build's project. |
211 | + """ |
212 | + project = factory.make_project(is_private=True) |
213 | + build = factory.make_build_result(project=project) |
214 | + user = project.owner |
215 | + self.assertTrue( |
216 | + self.client.login(username=user.username, password=user.username)) |
217 | + response = self.client.get( |
218 | + reverse('offspring.web.queuemanager.views.build_details', |
219 | + args=[project.name, build.name])) |
220 | + self.assertContains( |
221 | + response, 'Build Details', status_code=200, |
222 | + msg_prefix=response.content) |
223 | + |
224 | + def test_private_build_invisible(self): |
225 | + """ |
226 | + Check that the main view of a private build is not accessible to those |
227 | + who lack the rights to see the build's project. |
228 | + """ |
229 | + project = factory.make_project(is_private=True) |
230 | + build = factory.make_build_result(project=project) |
231 | + self.assertTrue(make_user_and_login(self.client)) |
232 | + response = self.client.get( |
233 | + reverse('offspring.web.queuemanager.views.build_details', |
234 | + args=[project.name, build.name])) |
235 | + self.assertEqual(404, response.status_code) |
236 | + |
237 | + |
238 | +class BuildResultPublishViewTests(TestCase): |
239 | + |
240 | + def test_public_build(self): |
241 | + """ |
242 | + Check that the publish view of a public build is accessible to anyone. |
243 | + """ |
244 | + project = factory.make_project(is_private=False) |
245 | + build = factory.make_build_result( |
246 | + project=project, result=ProjectBuildStates.SUCCESS) |
247 | + user = factory.make_user() |
248 | + grant_permission_to_user(user, 'add_release') |
249 | + self.assertTrue( |
250 | + self.client.login(username=user.username, password=user.username)) |
251 | + response = self.client.get( |
252 | + reverse('offspring.web.queuemanager.views.build_publish', |
253 | + args=[project.name, build.name])) |
254 | + self.assertContains( |
255 | + response, 'Release %s build' % project.title.capitalize(), |
256 | + status_code=200, msg_prefix=response.content) |
257 | + |
258 | + def test_private_build_visible(self): |
259 | + """ |
260 | + Check that the publish view of a private build is accessible to those |
261 | + who have the rights to see the build's project and the add_release |
262 | + permission. |
263 | + """ |
264 | + project = factory.make_project(is_private=True) |
265 | + build = factory.make_build_result( |
266 | + project=project, result=ProjectBuildStates.SUCCESS) |
267 | + user = build.project.owner |
268 | + grant_permission_to_user(user, 'add_release') |
269 | + self.assertTrue( |
270 | + self.client.login(username=user.username, password=user.username)) |
271 | + response = self.client.get( |
272 | + reverse('offspring.web.queuemanager.views.build_publish', |
273 | + args=[project.name, build.name])) |
274 | + self.assertContains( |
275 | + response, 'Release %s build' % project.title.capitalize(), |
276 | + status_code=200, msg_prefix=response.content) |
277 | + |
278 | + def test_private_build_invisible(self): |
279 | + """ |
280 | + Check that the publish view of a private build is not accessible to |
281 | + those who lack the rights to see the build's project. |
282 | + """ |
283 | + project = factory.make_project(is_private=True) |
284 | + build = factory.make_build_result( |
285 | + project=project, result=ProjectBuildStates.SUCCESS) |
286 | + user = factory.make_user() |
287 | + grant_permission_to_user(user, 'add_release') |
288 | + self.assertTrue( |
289 | + self.client.login(username=user.username, password=user.username)) |
290 | + response = self.client.get( |
291 | + reverse('offspring.web.queuemanager.views.build_publish', |
292 | + args=[project.name, build.name])) |
293 | + self.assertEqual(404, response.status_code) |
294 | + |
295 | + |
296 | +def make_user_and_login(client): |
297 | + user = factory.make_user() |
298 | + return client.login(username=user.username, password=user.username) |
299 | + |
300 | + |
301 | +def grant_permission_to_user(user, permission_codename): |
302 | + """Grant the permission with the given codename to the given user.""" |
303 | + group = Group(name=user.username) |
304 | + group.save() |
305 | + group.permissions.add( |
306 | + Permission.objects.get(codename=permission_codename)) |
307 | + group.save() |
308 | + user.groups.add(group) |
309 | + user.save() |
310 | + return user |
311 | + |
312 | |
313 | === modified file 'lib/offspring/web/queuemanager/views.py' |
314 | --- lib/offspring/web/queuemanager/views.py 2011-11-18 15:45:06 +0000 |
315 | +++ lib/offspring/web/queuemanager/views.py 2011-12-07 13:26:26 +0000 |
316 | @@ -16,13 +16,11 @@ |
317 | from django.http import ( |
318 | HttpResponse, |
319 | HttpResponseRedirect, |
320 | - HttpResponseForbidden |
321 | + HttpResponseForbidden, |
322 | + Http404, |
323 | ) |
324 | from django.middleware import csrf |
325 | -from django.shortcuts import ( |
326 | - render_to_response, |
327 | - get_object_or_404 |
328 | -) |
329 | +from django.shortcuts import render_to_response, get_object_or_404 |
330 | from django.template import ( |
331 | Context, |
332 | RequestContext, |
333 | @@ -40,7 +38,7 @@ |
334 | |
335 | from pygooglechart import PieChart3D |
336 | |
337 | -from offspring import config |
338 | +from offspring.config import get_configuration |
339 | from offspring.enums import ProjectBuildStates |
340 | from offspring.web.queuemanager.forms import ( |
341 | CreateProjectForm, |
342 | @@ -57,6 +55,22 @@ |
343 | Release |
344 | ) |
345 | |
346 | + |
347 | +config = get_configuration() |
348 | + |
349 | + |
350 | +def get_possibly_private_object(user, klass, *args, **kwargs): |
351 | + """Use get_object_or_404() to return a possibly private object. |
352 | + |
353 | + If the object does not exist or the user doesn't have permission to see |
354 | + it, raise Http404. |
355 | + """ |
356 | + obj = get_object_or_404(klass, *args, **kwargs) |
357 | + if not obj.is_visible_to(user): |
358 | + raise Http404('No matches for the given query') |
359 | + return obj |
360 | + |
361 | + |
362 | #XXX: Reduce horrible duplication that occurs in these functions (mostly done). |
363 | #TODO: Thanks to reduced duplication, lets start using more generic views. |
364 | |
365 | @@ -118,15 +132,18 @@ |
366 | |
367 | |
368 | def build_details(request, projectName, buildName): |
369 | - b = get_object_or_404( |
370 | - BuildResult.objects.select_related('builder', 'project', 'requestor'), |
371 | + user_visible_builds = BuildResult.all_objects.accessible_by_user( |
372 | + request.user) |
373 | + build = get_possibly_private_object( |
374 | + request.user, |
375 | + user_visible_builds.select_related('builder', 'project', 'requestor'), |
376 | project__pk=projectName, name=buildName) |
377 | pageData = { |
378 | 'pillar' : 'builds', |
379 | 'upcoming_milestones' : LaunchpadProjectMilestone.upcoming_milestones(), |
380 | 'recent_milestones' : LaunchpadProjectMilestone.recent_milestones(), |
381 | - 'project' : b.project, |
382 | - 'build' : b |
383 | + 'project' : build.project, |
384 | + 'build' : build |
385 | } |
386 | return render_to_response( |
387 | 'queuemanager/build_details.html', pageData, |
388 | @@ -134,14 +151,18 @@ |
389 | |
390 | @login_required |
391 | def build_publish(request, projectName, buildName): |
392 | - p = get_object_or_404(Project, pk=projectName) |
393 | - b = get_object_or_404( |
394 | - BuildResult, project=p, name=buildName, |
395 | + user = request.user |
396 | + p = get_possibly_private_object(user, Project, pk=projectName) |
397 | + # We use get_possibly_private_object() here just for consistency as it's |
398 | + # not really needed because the above will fail if the user doesn't have |
399 | + # the rights to see this build. |
400 | + b = get_possibly_private_object( |
401 | + user, BuildResult, project=p, name=buildName, |
402 | result=ProjectBuildStates.SUCCESS, release=None) |
403 | - if request.user.has_perm('queuemanager.add_release'): |
404 | + if user.has_perm('queuemanager.add_release'): |
405 | r = Release() |
406 | r.build = b |
407 | - r.creator = request.user |
408 | + r.creator = user |
409 | if request.method == 'POST': |
410 | f = ReleaseForm(request.POST, instance=r) |
411 | if f.is_valid(): |
412 | @@ -175,21 +196,35 @@ |
413 | project_create = permission_required('queuemanager.add_project')(project_create) |
414 | |
415 | def project_edit(request, projectName): |
416 | - return update_object( |
417 | - request, |
418 | - form_class=EditProjectForm, |
419 | - login_required=True, |
420 | - template_name='queuemanager/project_edit.html', |
421 | - template_object_name='project', |
422 | - extra_context={ 'pillar' : 'projects' }, |
423 | - object_id=projectName, |
424 | - ) |
425 | + # XXX: This nasty hack is because update_object uses (via lookup_object) |
426 | + # .objects directly when it should insted use the default manager |
427 | + # (https://code.djangoproject.com/ticket/17021). |
428 | + from django.views.generic import create_update |
429 | + orig_lookup = create_update.lookup_object |
430 | + def lookup(model, obj_id, slug, slug_field): |
431 | + return get_possibly_private_object( |
432 | + request.user, Project, pk=projectName) |
433 | + create_update.lookup_object = lookup |
434 | + try: |
435 | + response = update_object( |
436 | + request, |
437 | + form_class=EditProjectForm, |
438 | + login_required=True, |
439 | + template_name='queuemanager/project_edit.html', |
440 | + template_object_name='project', |
441 | + extra_context={ 'pillar' : 'projects' }, |
442 | + object_id=projectName, |
443 | + ) |
444 | + finally: |
445 | + create_update.lookup_object = orig_lookup |
446 | + return response |
447 | project_edit = permission_required('queuemanager.change_project')(project_edit) |
448 | |
449 | def project_details(request, projectName): |
450 | - p = get_object_or_404( |
451 | - Project.objects.select_related('project_group', 'launchpad_project'), |
452 | - pk=projectName) |
453 | + user_visible_objects = Project.all_objects.accessible_by_user( |
454 | + request.user) |
455 | + p = get_possibly_private_object( |
456 | + request.user, user_visible_objects, pk=projectName) |
457 | project_build_results = BuildResult.objects.filter( |
458 | project = p).exclude(result = None) |
459 | build_stats = { |
460 | @@ -204,15 +239,16 @@ |
461 | chart.set_pie_labels(["Failed Builds", "Successful Builds"]) |
462 | build_stats['pie_chart'] = chart.get_url() |
463 | |
464 | - releases_uri_base = config.web('releases_uri_base') |
465 | + releases_uri_base = config.get('web', 'releases_uri_base') |
466 | if not releases_uri_base.endswith('/'): |
467 | releases_uri_base += '/' |
468 | releases_url = urlparse.urljoin(releases_uri_base, p.name + '/images/') |
469 | |
470 | + archive_uri = config.get('web', 'archive_uri', None) |
471 | pageData = { |
472 | 'build_stats' : build_stats, |
473 | 'csrf_token' : csrf.get_token(request), |
474 | - 'include_sources_list': config.web('archive_uri', None) is not None, |
475 | + 'include_sources_list': archive_uri is not None, |
476 | 'pillar' : 'projects', |
477 | 'project' : p, |
478 | 'project_build_results' : project_build_results, |
=== added file 'lib/offspring/ web/queuemanage r/tests/ test_views. py'
#1
+from django. contrib. auth.models import (
+ AnonymousUser,
+ Group,
+ Permission,
+ )
These all fit on one line...
+from offspring. web.queuemanage r.models import Project
+ )
This too...
#2
+class Test_get_ possibly_ private_ object( TestCase) :
" Almost without exception, class names use the CapWords convention. Classes for internal use have a leading underscore in addition."
#3
+ def get_expected_ page_heading( self, project): rror()
+ raise NotImplementedE
+ _permission_ to(self, user):
+ def grant_necessary
+ pass
No docstring, what is expected of subclasses?
For extra points, in the case of "grant_ necessary_ permission_ to", you can drop the pass if you write a docstring, as the docstring counts as the body of the method ;-)
#4
+ def test_private_ project_ invisible( self): make_project( is_private= True) necessary_ permission_ to(user) login(username= user.username, password= user.username) ) self.view_ path, args=[project. name])) l(404, response. status_ code)
+ """
+ Check that this view of a private project is not accessible to those
+ who lack the rights to see the project.
+ """
+ project = factory.
+ user = factory.make_user()
+ self.grant_
+ self.assertTrue(
+ self.client.
+ response = self.client.get(
+ reverse(
+ self.assertEqua
Should this really return a 404? How about a 403? There may be a good reason for returning a 404 btw :-)
#5
+class BuildViewTests( TestCase) :
This should refer to BuildResult to make it clearer (and in the subsequent tests), given that there are BuildResults and BuildRequests...
#6
+def grant_permissio n_to_user( user, permission):
No docstring, and the parameter is misleading, it doesn't accept a permission, it accepts a Permission codename...
=== modified file 'lib/offspring/ web/queuemanage r/views. py'
from django.shortcuts import ( to_response,
render_
- get_object_or_404
+ get_object_or_404,
)
This import would fit all on one line...
#7
+def get_possibly_ private_ object( user, klass, *args, **kwargs): or_404( klass, *args, **kwargs) visible_ to(user) :
+ """Use get_object_or_404() to return a possibly private object.
+
+ If the object does not exist or the user doesn't have permission to see
+ it, raise Http404.
+ """
+ obj = get_object_
+ if not obj.is_
+ raise Http404('No matches for the given query')
+ return obj
Could this reasonably be named better? get_possibly_ private_ object_ or_404 ?
That's also not great, but given that your function is intended to be a replacement the standard get_object_or_404, it would make reading the code easier...
How about get_protected_ object_ or_404 ? Not sure it's much of an improvement, but "possibly_private" sounds odd...
#8
+ user_visible_builds = BuildResult. all_objects. accessible_ by_user( private_ object( builds. select_ related( 'builder' , 'project', 'requestor'),
+ request.user)
+ b = get_possibly_
+ request.user,
+ user_visible_
"Use the function naming rules: lowercase with words separated by underscores ...