Merge lp:~jtv/launchpad/bug-427263 into lp:launchpad/db-devel

Proposed by Jeroen T. Vermeulen
Status: Rejected
Rejected by: Jeroen T. Vermeulen
Proposed branch: lp:~jtv/launchpad/bug-427263
Merge into: lp:launchpad/db-devel
Diff against target: 348 lines (+111/-48)
9 files modified
lib/canonical/launchpad/doc/lazr-js-widgets.txt (+1/-1)
lib/canonical/launchpad/emailtemplates/person-merged.txt (+15/-0)
lib/canonical/widgets/lazrjs.py (+1/-1)
lib/lp/archiveuploader/poppyinterface.py (+0/-4)
lib/lp/registry/doc/person-merge.txt (+26/-2)
lib/lp/registry/model/person.py (+14/-15)
lib/lp/registry/tests/test_personset.py (+52/-11)
lib/lp/testing/menu.py (+2/-4)
lib/lp/translations/scripts/message_sharing_migration.py (+0/-10)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-427263
Reviewer Review Type Date Requested Status
Michael Nelson (community) code Approve
Review via email: mp+14851@code.launchpad.net

Commit message

Make the "edit" icons for multi-line editor widgets show up in Konqueror as well.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

= Bug 427263 =

In Konqueror, the little "edit" buttons for bug descriptions and other multi-line edit fields do not show up. The reason, as Michael Nelson found, was that this browser does not render <a> tags with no text in them.

This branch works around that by inserting a zero-width non-joiner (as well as the sprite) in the anchor tag for the button. There are no visible changes in other browsers that I can see; as the name says, it's a zero-width character.

Tests:
{{{
./bin/test -t lazr-js-widgets.txt
}}}

Unsurprisingly, there's not a whole lot of test impact. This is purely a visual thing.

Demo and Q/A: Log in. Visit anything with an editable description that would normally use the Ajax inline editor—such a bug or a blueprint that you have edit rights on. (On a blueprint the "edit" icon will appear for the Whiteboard). Visit the same page in Konqueror, while logged into the same account. Without this fix, the edit icon is not visible. With this fix, it is. The inline editor doesn't work in konqueror, so it'll still take you to the pre-ajax edit form. But at least you've got a way to reach it!

Jeroen

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Thanks Jeroen. Sorry it took so long to do (hotel internet :/ ).

So I'm approving your changes `bzr diff -r 9878..9880`, which is exactly what I see locally doing `bzr diff -r ancestor:../devel`, not sure why the MP has other changes displayed.

Thanks!

review: Approve (code)
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

> Thanks Jeroen. Sorry it took so long to do (hotel internet :/ ).
>
> So I'm approving your changes `bzr diff -r 9878..9880`, which is exactly what
> I see locally doing `bzr diff -r ancestor:../devel`, not sure why the MP has
> other changes displayed.

Thanks for the review—feels better to finish the sprint without WIP branches. The diff was just messed up from branching off devel and then proposing for merge in db-devel (which is the default).

Jeroen

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Stumbled across a better way to fix this at the last moment. Uses CSS only. Abandoning this MP.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/doc/lazr-js-widgets.txt'
2--- lib/canonical/launchpad/doc/lazr-js-widgets.txt 2009-07-14 13:16:50 +0000
3+++ lib/canonical/launchpad/doc/lazr-js-widgets.txt 2009-11-13 22:40:28 +0000
4@@ -39,7 +39,7 @@
5 important</span>
6 <a href="http://bugs.launchpad.dev/.../+edit"
7 class="yui-editable_text-trigger sprite edit"
8- ></a>
9+ >...</a>
10 </h1>
11 <script>
12 ...
13
14=== added file 'lib/canonical/launchpad/emailtemplates/person-merged.txt'
15--- lib/canonical/launchpad/emailtemplates/person-merged.txt 1970-01-01 00:00:00 +0000
16+++ lib/canonical/launchpad/emailtemplates/person-merged.txt 2009-11-13 22:40:28 +0000
17@@ -0,0 +1,15 @@
18+
19+The Launchpad account named '%(dupename)s' was merged into the account
20+named '%(person)s'. You can confirm the merged email addresses to
21+associate them with your account. Merged email addresses are unsubscribed
22+from all Launchpad mailing lists during the merge. All team memberships
23+for the merged account were also transferred to your account, and
24+those team may have mailing lists.
25+
26+You can review and update your email and subscription settings at:
27+
28+ https://launchpad.net/~%(person)s/+editemails
29+
30+Thank you,
31+
32+The Launchpad team
33
34=== modified file 'lib/canonical/widgets/lazrjs.py'
35--- lib/canonical/widgets/lazrjs.py 2009-10-28 08:03:36 +0000
36+++ lib/canonical/widgets/lazrjs.py 2009-11-13 22:40:28 +0000
37@@ -55,7 +55,7 @@
38 # Template for the trigger button.
39 TRIGGER_TEMPLATE = dedent(u"""\
40 <a href="%(edit_url)s" class="yui-editable_text-trigger sprite edit"
41- ></a>
42+ >&zwnj;</a>
43 """)
44
45 # Template for the activation script.
46
47=== modified file 'lib/lp/archiveuploader/poppyinterface.py'
48--- lib/lp/archiveuploader/poppyinterface.py 2009-06-24 23:33:29 +0000
49+++ lib/lp/archiveuploader/poppyinterface.py 2009-11-13 22:40:28 +0000
50@@ -9,8 +9,6 @@
51 import stat
52 import time
53
54-from canonical.launchpad.scripts import execute_zcml_for_scripts
55-from canonical.lp import initZopeless
56 from contrib.glock import GlobalLock
57
58 class PoppyInterfaceFailure(Exception):
59@@ -22,8 +20,6 @@
60
61 def __init__(self, targetpath, logger, allow_user, cmd=None,
62 targetstart=0, perms=None):
63- execute_zcml_for_scripts()
64- self.tm = initZopeless(dbuser='ro')
65 self.targetpath = targetpath
66 self.logger = logging.getLogger("%s.PoppyInterface" % logger.name)
67 self.cmd = cmd
68
69=== renamed file 'lib/lp/registry/doc/xx-coc-views.txt' => 'lib/lp/registry/browser/tests/coc-views.txt'
70=== renamed file 'lib/lp/registry/doc/person-edit-views.txt' => 'lib/lp/registry/browser/tests/person-edit-views.txt'
71=== renamed file 'lib/lp/registry/doc/person-karma-views.txt' => 'lib/lp/registry/browser/tests/person-karma-views.txt'
72=== renamed file 'lib/lp/registry/doc/team-hierarchy-views.txt' => 'lib/lp/registry/browser/tests/team-hierarchy-views.txt'
73=== renamed file 'lib/lp/registry/doc/teammembership-views.txt' => 'lib/lp/registry/browser/tests/teammembership-views.txt'
74=== modified file 'lib/lp/registry/doc/person-merge.txt'
75--- lib/lp/registry/doc/person-merge.txt 2009-08-13 19:03:36 +0000
76+++ lib/lp/registry/doc/person-merge.txt 2009-11-13 22:40:28 +0000
77@@ -199,8 +199,32 @@
78 >>> results.get_one()[0] is None
79 True
80
81-
82-== Person decoration ==
83+An email is sent to the user informing him that he should review his email
84+and mailing list subscription settings.
85+
86+ >>> from canonical.launchpad.interfaces.personnotification import (
87+ ... IPersonNotificationSet)
88+
89+ >>> notification_set = getUtility(IPersonNotificationSet)
90+ >>> notifications = notification_set.getNotificationsToSend()
91+ >>> notifications.count()
92+ 1
93+ >>> notification = notifications[0]
94+ >>> print notification.person.name
95+ name12
96+ >>> print notification.subject
97+ Launchpad accounts merged
98+ >>> print notification.body
99+ The Launchpad account named 'marilize-merged' was merged into the account
100+ named 'name12'. ...
101+
102+ You can review and update your email and subscription settings at:
103+
104+ https://launchpad.net/name12/+editemails ...
105+
106+
107+Person decoration
108+-----------------
109
110 Several tables "extend" the Person table by having additional information
111 that is UNIQUEly keyed to Person.id. We have a utility function that merges
112
113=== modified file 'lib/lp/registry/model/person.py'
114--- lib/lp/registry/model/person.py 2009-10-16 09:25:45 +0000
115+++ lib/lp/registry/model/person.py 2009-11-13 22:40:28 +0000
116@@ -2861,21 +2861,9 @@
117 name=name, new_name=new_name, product=product))
118
119 def _mergeMailingListSubscriptions(self, cur, from_id, to_id):
120- # Update MailingListSubscription. Note that no remaining records
121- # will have email_address set, as we assert earlier that the
122- # from_person has no email addresses.
123- # Update records that don't conflict.
124- cur.execute('''
125- UPDATE MailingListSubscription
126- SET person=%(to_id)d
127- WHERE person=%(from_id)d
128- AND mailing_list NOT IN (
129- SELECT mailing_list
130- FROM MailingListSubscription
131- WHERE person=%(to_id)d
132- )
133- ''' % vars())
134- # Then trash the remainders.
135+ # Update MailingListSubscription. Note that since all the from_id
136+ # email addresses are set to NEW, all the subscriptions must be
137+ # removed because the user must confirm them.
138 cur.execute('''
139 DELETE FROM MailingListSubscription WHERE person=%(from_id)d
140 ''' % vars())
141@@ -3472,6 +3460,17 @@
142 # flush its caches.
143 store.invalidate()
144
145+ # Inform the user of the merge changes.
146+ if not to_person.isTeam():
147+ mail_text = get_email_template('person-merged.txt')
148+ mail_text = mail_text % {
149+ 'dupename': from_person.name,
150+ 'person': to_person.name,
151+ }
152+ subject = 'Launchpad accounts merged'
153+ getUtility(IPersonNotificationSet).addNotification(
154+ to_person, subject, mail_text)
155+
156 def getValidPersons(self, persons):
157 """See `IPersonSet.`"""
158 person_ids = [person.id for person in persons]
159
160=== modified file 'lib/lp/registry/tests/test_personset.py'
161--- lib/lp/registry/tests/test_personset.py 2009-09-29 19:00:28 +0000
162+++ lib/lp/registry/tests/test_personset.py 2009-11-13 22:40:28 +0000
163@@ -7,46 +7,56 @@
164
165 from unittest import TestLoader
166
167+import transaction
168 from zope.component import getUtility
169 from zope.security.proxy import removeSecurityProxy
170
171 from lp.registry.model.person import PersonSet
172+from lp.registry.interfaces.mailinglistsubscription import (
173+ MailingListAutoSubscribePolicy)
174 from lp.registry.interfaces.person import (
175 PersonCreationRationale, IPersonSet)
176-from lp.testing import TestCaseWithFactory
177+from lp.testing import TestCaseWithFactory, login_person, logout
178+
179+from canonical.database.sqlbase import cursor
180 from canonical.launchpad.testing.databasehelpers import (
181 remove_all_sample_data_branches)
182-from canonical.testing import LaunchpadFunctionalLayer
183+from canonical.testing import DatabaseFunctionalLayer
184
185
186 class TestPersonSetBranchCounts(TestCaseWithFactory):
187
188- layer = LaunchpadFunctionalLayer
189+ layer = DatabaseFunctionalLayer
190
191 def setUp(self):
192 TestCaseWithFactory.setUp(self)
193 remove_all_sample_data_branches()
194+ self.person_set = getUtility(IPersonSet)
195
196 def test_no_branches(self):
197 """Initially there should be no branches."""
198- self.assertEqual(0, PersonSet().getPeopleWithBranches().count())
199+ self.assertEqual(0, self.person_set.getPeopleWithBranches().count())
200
201 def test_five_branches(self):
202 branches = [self.factory.makeAnyBranch() for x in range(5)]
203 # Each branch has a different product, so any individual product
204 # will return one branch.
205- self.assertEqual(5, PersonSet().getPeopleWithBranches().count())
206- self.assertEqual(1, PersonSet().getPeopleWithBranches(
207+ self.assertEqual(5, self.person_set.getPeopleWithBranches().count())
208+ self.assertEqual(1, self.person_set.getPeopleWithBranches(
209 branches[0].product).count())
210
211
212 class TestPersonSetEnsurePerson(TestCaseWithFactory):
213
214- layer = LaunchpadFunctionalLayer
215+ layer = DatabaseFunctionalLayer
216 email_address = 'testing.ensure.person@example.com'
217 displayname = 'Testing ensurePerson'
218 rationale = PersonCreationRationale.SOURCEPACKAGEUPLOAD
219
220+ def setUp(self):
221+ TestCaseWithFactory.setUp(self)
222+ self.person_set = getUtility(IPersonSet)
223+
224 def test_ensurePerson_returns_existing_person(self):
225 # IPerson.ensurePerson returns existing person and does not
226 # override its details.
227@@ -54,7 +64,7 @@
228 testing_person = self.factory.makePerson(
229 email=self.email_address, displayname=testing_displayname)
230
231- ensured_person = getUtility(IPersonSet).ensurePerson(
232+ ensured_person = self.person_set.ensurePerson(
233 self.email_address, self.displayname, self.rationale)
234 self.assertEquals(testing_person.id, ensured_person.id)
235 self.assertIsNot(
236@@ -67,7 +77,7 @@
237 def test_ensurePerson_hides_new_person_email(self):
238 # IPersonSet.ensurePerson creates new person with
239 # 'hide_email_addresses' set.
240- ensured_person = getUtility(IPersonSet).ensurePerson(
241+ ensured_person = self.person_set.ensurePerson(
242 self.email_address, self.displayname, self.rationale)
243 self.assertTrue(ensured_person.hide_email_addresses)
244
245@@ -78,7 +88,7 @@
246 self.displayname, email=self.email_address)
247 self.assertIs(None, test_account.preferredemail.person)
248
249- ensured_person = getUtility(IPersonSet).ensurePerson(
250+ ensured_person = self.person_set.ensurePerson(
251 self.email_address, self.displayname, self.rationale)
252 self.assertEquals(test_account.id, ensured_person.account.id)
253 self.assertTrue(ensured_person.hide_email_addresses)
254@@ -98,7 +108,7 @@
255 self.assertIs(None, testing_account.preferredemail.person)
256 self.assertIs(None, testing_person.preferredemail)
257
258- ensured_person = getUtility(IPersonSet).ensurePerson(
259+ ensured_person = self.person_set.ensurePerson(
260 self.email_address, self.displayname, self.rationale)
261
262 # The existing Person was retrieved and the Account
263@@ -108,5 +118,36 @@
264 ensured_person.preferredemail.id)
265
266
267+class TestPersonSetMerge(TestCaseWithFactory):
268+
269+ layer = DatabaseFunctionalLayer
270+
271+ def setUp(self):
272+ TestCaseWithFactory.setUp(self)
273+ # Use the unsecured PersonSet so that private methods can be tested.
274+ self.person_set = PersonSet()
275+ self.from_person = self.factory.makePerson()
276+ self.to_person = self.factory.makePerson()
277+ self.cur = cursor()
278+
279+ def test__mergeMailingListSubscriptions_no_subscriptions(self):
280+ self.person_set._mergeMailingListSubscriptions(
281+ self.cur, self.from_person.id, self.to_person.id)
282+ self.assertEqual(0, self.cur.rowcount)
283+
284+ def test__mergeMailingListSubscriptions_with_subscriptions(self):
285+ self.from_person.mailing_list_auto_subscribe_policy = (
286+ MailingListAutoSubscribePolicy.ALWAYS)
287+ self.team, self.mailing_list = self.factory.makeTeamAndMailingList(
288+ 'test-mailinglist', 'team-owner')
289+ login_person(self.team.teamowner)
290+ self.team.addMember(self.from_person, reviewer=self.team.teamowner)
291+ logout()
292+ transaction.commit()
293+ self.person_set._mergeMailingListSubscriptions(
294+ self.cur, self.from_person.id, self.to_person.id)
295+ self.assertEqual(1, self.cur.rowcount)
296+
297+
298 def test_suite():
299 return TestLoader().loadTestsFromName(__name__)
300
301=== modified file 'lib/lp/testing/menu.py'
302--- lib/lp/testing/menu.py 2009-09-10 20:35:24 +0000
303+++ lib/lp/testing/menu.py 2009-11-13 22:40:28 +0000
304@@ -13,7 +13,6 @@
305
306 def check_menu_links(menu):
307 context = menu.context
308- is_sane_menu = True
309 for link in menu.iterlinks():
310 if '?' in link.target:
311 view_name, _args = link.target.split('?')
312@@ -24,6 +23,5 @@
313 try:
314 view = getMultiAdapter((context, request), name=view_name)
315 except:
316- is_sane_menu = False
317- print 'Bad link %s: %s' % (link.name, url)
318- print is_sane_menu
319+ return 'Bad link %s: %s' % (link.name, url)
320+ return True
321
322=== modified file 'lib/lp/translations/scripts/message_sharing_migration.py'
323--- lib/lp/translations/scripts/message_sharing_migration.py 2009-10-30 11:18:59 +0000
324+++ lib/lp/translations/scripts/message_sharing_migration.py 2009-11-13 22:40:28 +0000
325@@ -7,7 +7,6 @@
326 ]
327
328
329-import gc
330 import os
331
332 from zope.component import getUtility
333@@ -291,15 +290,6 @@
334 if self.txn is None:
335 return
336
337- if self.commit_count % 100 == 0 or not intermediate:
338- freed = gc.collect()
339- objcount = len(gc.get_objects())
340- garbage = len(gc.garbage)
341- memsize = open("/proc/%s/statm" % os.getpid()).read().split()[5]
342- log.debug(
343- "Freed: %d. Object count: %d. Garbage: %d. Memory size: %s"
344- % (freed, objcount, garbage, memsize))
345-
346 self.commit_count += 1
347
348 if intermediate and self.commit_count % 10 != 0:

Subscribers

People subscribed via source and target branches

to status/vote changes: