Merge ~ilasc/launchpad:add-person-livefs-index-view into launchpad:master

Proposed by Ioana Lasc
Status: Merged
Approved by: Ioana Lasc
Approved revision: 2af12266cf4747d1c1b31fec08cd5d4c6ea79f28
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~ilasc/launchpad:add-person-livefs-index-view
Merge into: launchpad:master
Diff against target: 303 lines (+225/-1)
4 files modified
lib/lp/registry/browser/configure.zcml (+7/-0)
lib/lp/registry/browser/person.py (+23/-0)
lib/lp/registry/browser/tests/test_person.py (+149/-1)
lib/lp/registry/templates/person-livefses.pt (+46/-0)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+385042@code.launchpad.net

Commit message

Add Person:+livefs index view

Description of the change

Added livefs index view to provide the UI list of all the live filesystems owned by a given person.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

I have various nitpicks below, but I think you can go ahead and land this once you've fixed those. Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
2index b8dc1bb..3991dd5 100644
3--- a/lib/lp/registry/browser/configure.zcml
4+++ b/lib/lp/registry/browser/configure.zcml
5@@ -1231,6 +1231,13 @@
6 template="../templates/person-oauth-tokens.pt"
7 />
8 <browser:page
9+ name="+livefs"
10+ for="lp.registry.interfaces.person.IPerson"
11+ class="lp.registry.browser.person.PersonLiveFSView"
12+ permission="launchpad.View"
13+ template="../templates/person-livefses.pt"
14+ />
15+ <browser:page
16 name="+index"
17 for="lp.registry.interfaces.person.ITeam"
18 class="lp.registry.browser.team.TeamIndexView"
19diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
20index 8677ecd..d512b7b 100644
21--- a/lib/lp/registry/browser/person.py
22+++ b/lib/lp/registry/browser/person.py
23@@ -27,6 +27,7 @@ __all__ = [
24 'PersonIndexView',
25 'PersonKarmaView',
26 'PersonLanguagesView',
27+ 'PersonLiveFSView',
28 'PersonNavigation',
29 'PersonOAuthTokensView',
30 'PersonOverviewMenu',
31@@ -3620,6 +3621,28 @@ class PersonOAuthTokensView(LaunchpadView):
32 canonical_url(self.context, view_name='+oauth-tokens'))
33
34
35+class PersonLiveFSView(LaunchpadView):
36+ """Default view for the list of live filesystems owned by a person."""
37+ page_title = 'LiveFS'
38+
39+ @property
40+ def label(self):
41+ return 'Live filesystems for %s' % self.context.display_name
42+
43+ @property
44+ def livefses(self):
45+ livefses = getUtility(ILiveFSSet).getByPerson(self.context)
46+ return livefses.order_by('name')
47+
48+ @property
49+ def livefses_navigator(self):
50+ return BatchNavigator(self.livefses, self.request)
51+
52+ @cachedproperty
53+ def count(self):
54+ return self.livefses_navigator.batch.total()
55+
56+
57 class PersonTimeZoneForm(Interface):
58
59 time_zone = Choice(
60diff --git a/lib/lp/registry/browser/tests/test_person.py b/lib/lp/registry/browser/tests/test_person.py
61index 626cab4..10bbba7 100644
62--- a/lib/lp/registry/browser/tests/test_person.py
63+++ b/lib/lp/registry/browser/tests/test_person.py
64@@ -1,10 +1,14 @@
65-# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
66+# -*- coding: utf-8 -*-
67+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
68 # GNU Affero General Public License version 3 (see the file LICENSE).
69
70+from __future__ import unicode_literals
71+
72 __metaclass__ = type
73
74 import doctest
75 import email
76+from operator import attrgetter
77 import re
78 from textwrap import dedent
79
80@@ -24,6 +28,7 @@ from zope.publisher.interfaces import NotFound
81 from zope.security.proxy import removeSecurityProxy
82
83 from lp.app.browser.lazrjs import TextAreaEditorWidget
84+from lp.app.browser.tales import DateTimeFormatterAPI
85 from lp.app.enums import InformationType
86 from lp.app.errors import NotFoundError
87 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
88@@ -43,6 +48,7 @@ from lp.registry.model.karma import KarmaCategory
89 from lp.registry.model.milestone import milestone_sort_key
90 from lp.scripts.garbo import PopulateLatestPersonSourcePackageReleaseCache
91 from lp.services.config import config
92+from lp.services.features.testing import FeatureFixture
93 from lp.services.identity.interfaces.account import AccountStatus
94 from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
95 from lp.services.log.logger import DevNullLogger
96@@ -61,6 +67,7 @@ from lp.soyuz.enums import (
97 ArchiveStatus,
98 PackagePublishingStatus,
99 )
100+from lp.soyuz.interfaces.livefs import LIVEFS_FEATURE_FLAG
101 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
102 from lp.testing import (
103 ANONYMOUS,
104@@ -1274,6 +1281,147 @@ class TestPersonRelatedProjectsView(TestCaseWithFactory):
105 self.assertThat(view(), next_match)
106
107
108+class TestPersonLiveFSView(BrowserTestCase):
109+ layer = DatabaseFunctionalLayer
110+
111+ def setUp(self):
112+ super(TestPersonLiveFSView, self).setUp()
113+ self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: "on"}))
114+ self.person = self.factory.makePerson(
115+ name="test-person", displayname="Test Person")
116+
117+ def makeLiveFS(self, count=1):
118+ with person_logged_in(self.person):
119+ return [
120+ self.factory.makeLiveFS(
121+ registrant=self.person, owner=self.person)
122+ for _ in range(count)]
123+
124+ def test_displays_livefs(self):
125+ livefs = self.factory.makeLiveFS(
126+ registrant=self.person, owner=self.person)
127+ view = create_initialized_view(
128+ self.person, "+livefs", principal=self.person)
129+
130+ expected_url = "/~%s/+livefs/%s/%s/%s" % (
131+ livefs.owner.name, livefs.distro_series.distribution.name,
132+ livefs.distro_series.name, livefs.name)
133+ link_match = soupmatchers.HTMLContains(
134+ soupmatchers.Tag(
135+ 'Livefs name link', 'a',
136+ attrs={'href': expected_url},
137+ text=livefs.name))
138+ date_formatter = DateTimeFormatterAPI(livefs.date_created)
139+ date_created_match = soupmatchers.HTMLContains(
140+ soupmatchers.Tag(
141+ 'Livefs date created', 'td',
142+ text='%s' % date_formatter.displaydate()))
143+ with person_logged_in(self.person):
144+ self.assertThat(view.render(), link_match)
145+ self.assertThat(view.render(), date_created_match)
146+
147+ def test_displays_no_livefs(self):
148+ view = create_initialized_view(
149+ self.person, "+livefs", principal=self.person)
150+ no_livefs_match = soupmatchers.HTMLContains(
151+ soupmatchers.Tag(
152+ 'No livefs', 'p',
153+ text='There are no live filesystems for %s'
154+ % self.person.display_name))
155+ with person_logged_in(self.person):
156+ self.assertThat(view.render(), no_livefs_match)
157+
158+ def test_paginates_livefs(self):
159+ batch_size = 5
160+ self.pushConfig("launchpad", default_batch_size=batch_size)
161+ livefs = self.makeLiveFS(10)
162+ view = create_initialized_view(
163+ self.person, "+livefs", principal=self.person)
164+ no_livefs_match = soupmatchers.HTMLContains(
165+ soupmatchers.Tag(
166+ 'Top livefs paragraph', 'strong',
167+ text="10"))
168+ first_match = soupmatchers.HTMLContains(
169+ soupmatchers.Tag(
170+ 'Navigation first', 'span',
171+ attrs={'class': 'first inactive'},
172+ text="First"))
173+ previous_match = soupmatchers.HTMLContains(
174+ soupmatchers.Tag(
175+ 'Navigation previous', 'span',
176+ attrs={'class': 'previous inactive'},
177+ text="Previous"))
178+ with person_logged_in(self.person):
179+ self.assertThat(view.render(), no_livefs_match)
180+ self.assertThat(view.render(), first_match)
181+ self.assertThat(view.render(), previous_match)
182+ self.assertThat(view.render(), soupmatchers.HTMLContains(
183+ soupmatchers.Within(
184+ soupmatchers.Tag(
185+ "next element", "a",
186+ attrs={"id": "lower-batch-nav-batchnav-next"}),
187+ soupmatchers.Tag(
188+ "next link", "strong",
189+ text='Next'))))
190+ self.assertThat(view.render(), soupmatchers.HTMLContains(
191+ soupmatchers.Tag(
192+ "last element", "a",
193+ attrs={"id": "lower-batch-nav-batchnav-last"},
194+ text='Last')))
195+
196+ # Assert we're listing the first set of live filesystems
197+ items = sorted(livefs, key=attrgetter('name'))
198+ for lfs in items[:batch_size]:
199+ expected_url = "/~%s/+livefs/%s/%s/%s" % (
200+ lfs.owner.name, lfs.distro_series.distribution.name,
201+ lfs.distro_series.name, lfs.name)
202+ link_match = soupmatchers.HTMLContains(
203+ soupmatchers.Tag(
204+ 'Livefs name link', 'a',
205+ attrs={'href': expected_url},
206+ text=lfs.name))
207+ self.assertThat(view.render(), link_match)
208+
209+ def test_displays_livefs_only_for_owner(self):
210+ livefs = self.factory.makeLiveFS(
211+ registrant=self.person, owner=self.person)
212+ different_owner = self.factory.makePerson(
213+ name="different-person", displayname="Different Person")
214+ livefs_different_owner = self.factory.makeLiveFS(
215+ registrant=different_owner, owner=different_owner)
216+ view = create_initialized_view(
217+ self.person, "+livefs", principal=self.person)
218+ expected_url = "/~%s/+livefs/%s/%s/%s" % (
219+ livefs.owner.name, livefs.distro_series.distribution.name,
220+ livefs.distro_series.name, livefs.name)
221+ link_match = soupmatchers.HTMLContains(
222+ soupmatchers.Tag(
223+ 'Livefs name link', 'a',
224+ attrs={'href': expected_url},
225+ text=livefs.name))
226+ date_formatter = DateTimeFormatterAPI(livefs.date_created)
227+ date_created_match = soupmatchers.HTMLContains(
228+ soupmatchers.Tag(
229+ 'Livefs date created', 'td',
230+ text='%s' % date_formatter.displaydate()))
231+
232+ different_owner_url = "/~%s/+livefs/%s/%s/%s" % (
233+ livefs_different_owner.owner.name,
234+ livefs_different_owner.distro_series.distribution.name,
235+ livefs_different_owner.distro_series.name,
236+ livefs_different_owner.name)
237+ different_owner_match = soupmatchers.HTMLContains(
238+ soupmatchers.Tag(
239+ 'Livefs name link', 'a',
240+ attrs={'href': different_owner_url},
241+ text=livefs_different_owner.name))
242+
243+ with person_logged_in(self.person):
244+ self.assertThat(view.render(), link_match)
245+ self.assertThat(view.render(), date_created_match)
246+ self.assertNotIn(different_owner_match, view.render())
247+
248+
249 class TestPersonRelatedPackagesFailedBuild(TestCaseWithFactory):
250 """The related packages views display links to failed builds."""
251
252diff --git a/lib/lp/registry/templates/person-livefses.pt b/lib/lp/registry/templates/person-livefses.pt
253new file mode 100644
254index 0000000..b292801
255--- /dev/null
256+++ b/lib/lp/registry/templates/person-livefses.pt
257@@ -0,0 +1,46 @@
258+<html
259+ xmlns="http://www.w3.org/1999/xhtml"
260+ xmlns:tal="http://xml.zope.org/namespaces/tal"
261+ xmlns:metal="http://xml.zope.org/namespaces/metal"
262+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
263+ metal:use-macro="view/macro:page/main_side"
264+ i18n:domain="launchpad"
265+>
266+
267+<body>
268+ <metal:side fill-slot="side">
269+ <div tal:replace="structure context/@@+global-actions"/>
270+ </metal:side>
271+
272+ <div metal:fill-slot="main">
273+ <div class="main-portlet">
274+ <p tal:define="count view/count" tal:condition="count">
275+ <span tal:condition="python: count == 1">There is <strong>1</strong> live filesystem</span>
276+ <span tal:condition="python: count != 1">There are <strong tal:content="count"/> live filesystems</span>
277+ registered for <tal:owner replace="context/display_name" />.
278+ </p>
279+ <p tal:condition="not: view/count">There are no live filesystems for <tal:owner replace="context/display_name"/></p>
280+ </div>
281+
282+ <table class="listing" id="livefses_list" tal:condition="view/count">
283+ <tbody>
284+ <tr class="head">
285+ <th>Name</th>
286+ <th>Date created</th>
287+ </tr>
288+
289+ <tr tal:repeat="filesystem view/livefses">
290+ <td>
291+ <a tal:content="filesystem/name"
292+ tal:attributes="href filesystem/fmt:url" />
293+ </td>
294+ <td tal:content="filesystem/date_created/fmt:displaydate" />
295+ </tr>
296+ </tbody>
297+ </table>
298+
299+ <tal:navigation replace="structure view/livefses_navigator/@@+navigation-links-lower" />
300+ </div>
301+
302+</body>
303+</html>

Subscribers

People subscribed via source and target branches

to status/vote changes: