Merge lp:~salgado/offspring/private-projects-views into lp:offspring

Proposed by Guilherme Salgado
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
Reviewer Review Type Date Requested Status
Kevin McDermott Approve
Review via email: mp+84153@code.launchpad.net

Description of the change

Make 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.
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

Revision history for this message
Kevin McDermott (bigkevmcd) wrote :
Download full text (4.2 KiB)

=== added file 'lib/offspring/web/queuemanager/tests/test_views.py'
#1

+from django.contrib.auth.models import (
+ AnonymousUser,
+ Group,
+ Permission,
+ )
These all fit on one line...

+from offspring.web.queuemanager.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):
+ raise NotImplementedError()

+
+ def grant_necessary_permission_to(self, user):
+ 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):
+ """
+ Check that this view of a private project is not accessible to those
+ who lack the rights to see the project.
+ """
+ project = factory.make_project(is_private=True)
+ user = factory.make_user()
+ self.grant_necessary_permission_to(user)
+ self.assertTrue(
+ self.client.login(username=user.username, password=user.username))
+ response = self.client.get(
+ reverse(self.view_path, args=[project.name]))
+ self.assertEqual(404, response.status_code)

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_permission_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/queuemanager/views.py'

 from django.shortcuts import (
     render_to_response,
- 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):
+ """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_or_404(klass, *args, **kwargs)
+ if not obj.is_visible_to(user):
+ 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(
+ request.user)
+ b = get_possibly_private_object(
+ request.user,
+ user_visible_builds.select_related('builder', 'project', 'requestor'),

"Use the function naming rules: lowercase with words separated by underscores ...

Read more...

review: Needs Fixing
Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (5.4 KiB)

On Wed, 2011-12-07 at 08:24 +0000, Kevin McDermott wrote:
> Review: Needs Fixing
>
> === added file 'lib/offspring/web/queuemanager/tests/test_views.py'
> #1
>
> +from django.contrib.auth.models import (
> + AnonymousUser,
> + Group,
> + Permission,
> + )
> These all fit on one line...
>
> +from offspring.web.queuemanager.models import Project
> + )
>
> This too...

Oops, fixed both.

>
> #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."

This was because I wanted to put the method name verbatin in there. Is
TestGetPossiblyPrivateObject any better?

>
> #3
>
> + def get_expected_page_heading(self, project):
> + raise NotImplementedError()
>
> +
> + def grant_necessary_permission_to(self, user):
> + 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):
> + """
> + Check that this view of a private project is not accessible to those
> + who lack the rights to see the project.
> + """
> + project = factory.make_project(is_private=True)
> + user = factory.make_user()
> + self.grant_necessary_permission_to(user)
> + self.assertTrue(
> + self.client.login(username=user.username, password=user.username))
> + response = self.client.get(
> + reverse(self.view_path, args=[project.name]))
> + self.assertEqual(404, response.status_code)
>
> 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(TestCase):
>
> 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_permission_to_user(user, permission):
>
> No docstring, and the parameter is misleading, it doesn't accept a permission, it accepts a Permission codename...

Fixed both.

>
> === modified file 'lib/offspring/web/queuemanager/views.py'
>
> 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_private_object(user, klass, *args, **kwargs):
> + """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_or_404(klass, *args, **kwargs)
> + if not obj.is_visible_to(user):
> + 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 yo...

Read more...

98. By Guilherme Salgado

A few tweaks and improvements suggested by Kevin

Revision history for this message
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.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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,

Subscribers

People subscribed via source and target branches