Merge lp:~sinzui/launchpad/project-set-owner-0 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Merged at revision: 11632
Proposed branch: lp:~sinzui/launchpad/project-set-owner-0
Merge into: lp:launchpad
Diff against target: 409 lines (+127/-141)
7 files modified
lib/lp/registry/browser/tests/product-edit-people-view.txt (+5/-51)
lib/lp/registry/doc/product.txt (+2/-53)
lib/lp/registry/model/product.py (+21/-22)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+0/-10)
lib/lp/translations/configure.zcml (+5/-0)
lib/lp/translations/subscribers.py (+44/-0)
lib/lp/translations/tests/test_translationimportqueue.py (+50/-5)
To merge this branch: bzr merge lp:~sinzui/launchpad/project-set-owner-0
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Jeroen T. Vermeulen Pending
Review via email: mp+36055@code.launchpad.net

Description of the change

This is my branch to remove the cascading reassignment when a project
owner is reassigned and avoid oopses with translation queue entries.

  reviewer jtv

    lp:~sinzui/launchpad/project-set-owner-0
    Diff size: 430
    Launchpad bug:
          https://bugs.launchpad.net/bugs/635438
    Test command: ./bin/test -vv \
          -t product-edit-people-view.txt -t registry.*doc/product.txt
          -t test_translationimportqueue
    Pre-implementation: danilos, jtv
    Target release: 10.10

Remove the cascading reassignment when a project owner reassigned
-----------------------------------------------------------------

OOPS-1714L464 show that IntegrityError: duplicate key value violates unique
constraint "translationimportqueueentry__entry_per_importer__unq" was cause
when the user tried to set the project owner to the same team as the project
driver (elementaryart

The product-edit-people-view.txt test indicates that the
translationimportqueue.importer is reassigned. After investigation we
discovered that reassigning a project owner causes many subordinate
object to also be reassigned. The rules for releases, series, and entries
are pointless, because Launchpad does not use the owner field for permissions.
The project owner or driver is always used. Launchpad does not need to do
this work.

Rules
-----

    * Remove the reassignment rules from Product._setOwner()
    * Add a subscriber that reassigns import entries only when there
      is no conflict.

QA
--

We can verify this bug is fixed on edge by setting /nautilus-elementary owner
to ~elementaryart. I will ask the project owner when we think the fix is
ready.

Lint
----

Linting changed files:
  lib/lp/registry/browser/tests/product-edit-people-view.txt
  lib/lp/registry/doc/product.txt
  lib/lp/registry/model/product.py
  lib/lp/services/database/tests/test_prejoin.py
  lib/lp/translations/configure.zcml
  lib/lp/translations/subscribers.py
  lib/lp/translations/tests/test_translationimportqueue.py

Test
----

    * lib/lp/registry/browser/tests/product-edit-people-view.txt
      * Remove the tests that verify reassignment...the view never did that.
    * lib/lp/registry/doc/product.txt
      * Removed the tests for reassignment because the rules were moved
        out of Product.
    * lib/lp/translations/tests/test_translationimportqueue.py
      * Added a test to verify when an entry's importer is reassigned, and
        when it is not.

Implementation
--------------

    * lib/lp/registry/model/product.py
      * Replaced the mutator with a simple object.
    * lib/lp/services/database/tests/test_prejoin.py
      * Updated the test to use the real object attr.
    * lib/lp/translations/configure.zcml
      * Registered a subscriber to handle product changes.
    * lib/lp/translations/subscribers.py
      * Added a subscriber function that updated the entry importer attr
        if the product owner changes and there is no conflict.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :
Download full text (6.0 KiB)

Hi Curtis,

Interesting branch. So, all along, the effort of changing series and
releases was for nothing? I think we discussed it a *long* time ago
and decided it was unnecessary but harmless. Glad you simplified
things by getting rid of it.

I've got a couple of questions on the tests, shown in-line below.

> === added file 'lib/lp/translations/subscribers.py'
> --- lib/lp/translations/subscribers.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/translations/subscribers.py 2010-09-24 14:17:10 +0000

> +def product_modified(product, event):
> + """Update translations objects when the product changes."""
> + old_owner = event.object_before_modification.owner
> + if old_owner != product.owner:
> + import_queue = getUtility(ITranslationImportQueue)
> + # The change in ownership may cause permission issues for
> + # entries in the translation import queue *if* the old owner has
> + # less permission now. This issue can be avoided by updating the
> + # entries's importer to the new owner. This can only be done if the
> + # new owner does not already have an entry in the queue.
> + # See bug 635438 for more details.
> + old_owner_entries = []
> + new_owner_entries = []
> + for entry in import_queue.getAllEntries(target=product):
> + if entry.importer == old_owner:
> + old_owner_entries.append(entry)
> + elif entry.importer == product.owner:
> + new_owner_entries.append(entry)
> + else:
> + # There is not permission issue.

Please reword this comment because I'm not sure what you're saying.

> + pass
> + if len(old_owner_entries) > 0 and len(new_owner_entries) == 0:
> + # The new owner does not have any conflicting entries in the
> + # queue. Change the old owner's entries to the new owner to
> + # ensure there is not permissions issues during processing.

s/there is not permissions/there are no permission/ ???

> + for entry in old_owner_entries:
> + removeSecurityProxy(entry).importer = product.owner

> === modified file 'lib/lp/translations/tests/test_translationimportqueue.py'
> --- lib/lp/translations/tests/test_translationimportqueue.py 2010-08-20 20:31:18 +0000
> +++ lib/lp/translations/tests/test_translationimportqueue.py 2010-09-24 14:17:10 +0000
> @@ -1,7 +1,7 @@
> -# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
> # GNU Affero General Public License version 3 (see the file LICENSE).
>
> -# pylint: disable-msg=C0102
> +from __future__ import with_statement
>
> __metaclass__ = type
>
> @@ -11,9 +11,15 @@
> from zope.component import getUtility
>
> from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
> -from canonical.testing import LaunchpadZopelessLayer
> +from canonical.testing import (
> + LaunchpadFunctionalLayer,
> + LaunchpadZopelessLayer,
> + )
> from lp.services.worlddata.interfaces.language import ILanguageSet
> -from lp.testing import TestCaseWithFactory
> +from lp.t...

Read more...

review: Needs Fixing (code)
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (6.0 KiB)

On Fri, 2010-09-24 at 15:21 +0000, Brad Crittenden wrote:
...
> > === added file 'lib/lp/translations/subscribers.py'
> > --- lib/lp/translations/subscribers.py 1970-01-01 00:00:00 +0000
> > +++ lib/lp/translations/subscribers.py 2010-09-24 14:17:10 +0000
>
> > +def product_modified(product, event):
> > + """Update translations objects when the product changes."""
> > + old_owner = event.object_before_modification.owner
> > + if old_owner != product.owner:
> > + import_queue = getUtility(ITranslationImportQueue)
> > + # The change in ownership may cause permission issues for
> > + # entries in the translation import queue *if* the old owner has
> > + # less permission now. This issue can be avoided by updating the
> > + # entries's importer to the new owner. This can only be done if the
> > + # new owner does not already have an entry in the queue.
> > + # See bug 635438 for more details.
> > + old_owner_entries = []
> > + new_owner_entries = []
> > + for entry in import_queue.getAllEntries(target=product):
> > + if entry.importer == old_owner:
> > + old_owner_entries.append(entry)
> > + elif entry.importer == product.owner:
> > + new_owner_entries.append(entry)
> > + else:
> > + # There is not permission issue.
>
> Please reword this comment because I'm not sure what you're saying.

# The entry is not affected by the ownership change.

> > + pass
> > + if len(old_owner_entries) > 0 and len(new_owner_entries) == 0:
> > + # The new owner does not have any conflicting entries in the
> > + # queue. Change the old owner's entries to the new owner to
> > + # ensure there is not permissions issues during processing.
>
> s/there is not permissions/there are no permission/ ???

corrected.

> > === modified file 'lib/lp/translations/tests/test_translationimportqueue.py'
> > --- lib/lp/translations/tests/test_translationimportqueue.py 2010-08-20 20:31:18 +0000
> > +++ lib/lp/translations/tests/test_translationimportqueue.py 2010-09-24 14:17:10 +0000
...
> > +class TestProductOwnerEntryImporter(TestCaseWithFactory):
> > + """Test entries update when owners change."""
> > +
> > + layer = LaunchpadFunctionalLayer
> > +
> > + def setUp(self):
> > + super(TestProductOwnerEntryImporter, self).setUp()
> > + self.product = self.factory.makeProduct()
> > + self.old_owner = self.product.owner
> > + self.new_owner = self.factory.makePerson()
> > + with person_logged_in(self.old_owner):
> > + self.product.driver = self.new_owner
> > + self.import_queue = getUtility(ITranslationImportQueue)
> > +
> > + def test_product_change_owner_changes_entry_importer(self):
> > + # Changing the Product owner also updates the importer of the entry.
> > + with person_logged_in(self.old_owner):
> > + entry = self.import_queue.addOrUpdateEntry(
> > + u'po/sr.po', 'foo', True, self.old_owner,
> > + productseries=self.product.series[0])
> > + ...

Read more...

Revision history for this message
Brad Crittenden (bac) wrote :

typo: # This is being initialize. <-- initialized

I think this:

+ if self.owner is None:
+ # This is being initialize.
+ self._owner = new_owner
+ return
+ if self.owner != new_owner:
+ ...

would be more readable as:

+ if self.owner is None:
+ # This is being initialize.
+ self._owner = new_owner
+ elif self.owner != new_owner:
+ ...

review: Approve (code)
Revision history for this message
Curtis Hovey (sinzui) wrote :

On Fri, 2010-09-24 at 20:17 +0000, Brad Crittenden wrote:
> Review: Approve code
> typo: # This is being initialize. <-- initialized
>
> I think this:
>
> + if self.owner is None:
> + # This is being initialize.
> + self._owner = new_owner
> + return
> + if self.owner != new_owner:
> + ...
>
> would be more readable as:
>
> + if self.owner is None:
> + # This is being initialize.
> + self._owner = new_owner
> + elif self.owner != new_owner:
> + ...

=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2010-09-24 18:58:27 +0000
+++ lib/lp/registry/model/product.py 2010-09-24 20:21:30 +0000
@@ -749,15 +749,17 @@
         changed.
         """
         if self.owner is None:
- # This is being initialize.
+ # This is being initialized.
             self._owner = new_owner
- return
- if self.owner != new_owner:
+ elif self.owner != new_owner:
             old_product = Snapshot(self, providing=providedBy(self))
             self._owner = new_owner
             notify(ObjectModifiedEvent(
                 self, object_before_modification=old_product,
                 edited_fields=['_owner']))
+ else:
+ # The new owner is the same as the current owner.
+ pass

     owner = property(_getOwner, _setOwner)

--
__Curtis C. Hovey_________
http://launchpad.net/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/tests/product-edit-people-view.txt'
2--- lib/lp/registry/browser/tests/product-edit-people-view.txt 2010-06-23 18:41:02 +0000
3+++ lib/lp/registry/browser/tests/product-edit-people-view.txt 2010-09-25 02:36:04 +0000
4@@ -1,15 +1,11 @@
5 ProductEditPeopleView
6 =====================
7
8-Artifact reassignment
9----------------------
10-
11-When a product is re-assigned to another person, objects related to that
12-product (product series, product releases and translations in the import
13-queue) owned by the same owner/maintainer are also re-assigned to the new
14-owner/maintainer.
15-
16-Firefox is owned by Sample Person (name12)
17+Reassignment to an IPerson
18+--------------------------
19+
20+A product can be reassigned to another person. Firefox is owned by Sample
21+Person (name12).
22
23 >>> from zope.component import getUtility
24 >>> from canonical.launchpad.interfaces import IProductSet
25@@ -20,31 +16,6 @@
26 >>> print sample_person.name
27 name12
28
29-The series and releases are owned by Sample Person
30-
31- >>> [series.name for series in firefox.series]
32- [u'1.0', u'trunk']
33- >>> [series.owner.name for series in firefox.series]
34- [u'name12', u'name12']
35- >>> [release.version for release in firefox.releases]
36- [u'0.9', u'0.9.1', u'0.9.2', u'1.0.0']
37- >>> [release.owner.name for release in firefox.releases]
38- [u'name16', u'name16', u'name16', u'name12']
39-
40-Add a TranslationImportQueueEntry owned by Sample Person.
41-
42- >>> from lp.translations.interfaces.translationimportqueue import (
43- ... ITranslationImportQueue)
44-
45- >>> firefox_productseries = firefox.series[0]
46- >>> translationimportqueue = getUtility(ITranslationImportQueue)
47- >>> login_person(sample_person)
48- >>> entry = translationimportqueue.addOrUpdateEntry(
49- ... 'po/sr.po', 'foo', True, sample_person,
50- ... productseries=firefox_productseries)
51- >>> print entry.importer.name
52- name12
53-
54 No Privileges Person is taking over the project, but he cannot access the
55 view because he is not yet an owner/maintainer or admin.
56
57@@ -71,23 +42,6 @@
58 >>> firefox.owner.name
59 u'no-priv'
60
61-The dependencies are also correctly re-assigned.
62-
63- >>> [series.name for series in firefox.series]
64- [u'1.0', u'trunk']
65- >>> [series.owner.name for series in firefox.series]
66- [u'no-priv', u'no-priv']
67- >>> [release.version for release in firefox.releases]
68- [u'0.9', u'0.9.1', u'0.9.2', u'1.0.0']
69- >>> [release.owner.name for release in firefox.releases]
70- [u'name16', u'name16', u'name16', u'no-priv']
71-
72- >>> firefox_productseries = firefox.series[0]
73- >>> translationimportqueue = getUtility(ITranslationImportQueue)
74- >>> entry = translationimportqueue.getAllEntries(
75- ... target=firefox_productseries)
76- >>> print entry[0].importer.name
77- no-priv
78
79 Assigning to Registry Administrators
80 ------------------------------------
81
82=== modified file 'lib/lp/registry/doc/product.txt'
83--- lib/lp/registry/doc/product.txt 2010-09-15 13:25:01 +0000
84+++ lib/lp/registry/doc/product.txt 2010-09-25 02:36:04 +0000
85@@ -723,68 +723,17 @@
86 Changing ownership
87 ==================
88
89-If the owner of a project changes, all series and productreleases
90-owned by the old owner are transfered to the new owner.
91+A product owner can be changed by the current owner.
92
93 >>> print firefox.owner.name
94 name12
95
96- >>> for series in firefox.series:
97- ... print series.owner.name, series.name
98- name12 1.0
99- name12 trunk
100-
101- >>> for release in firefox.releases:
102- ... print release.owner.name, release.title
103- name16 Mozilla Firefox 0.9 "One Tree Hill"
104- name16 Mozilla Firefox 0.9.1 "One Tree Hill (v2)"
105- name16 Mozilla Firefox 0.9.2 "One (secure) Tree Hill"
106- name12 Mozilla Firefox 1.0.0 "First Stable Release"
107-
108- >>> from lp.translations.interfaces.translationimportqueue import (
109- ... ITranslationImportQueue)
110- >>> import_queue = getUtility(ITranslationImportQueue)
111- >>> entry = import_queue.addOrUpdateEntry(
112- ... u'po/sr.po', 'foo', True, firefox.owner,
113- ... productseries=firefox.series[0])
114- >>> foobar = getUtility(IPersonSet).getByEmail("foo.bar@canonical.com")
115- >>> entry = import_queue.addOrUpdateEntry(
116- ... u'po/sr.po', 'foo', True, foobar,
117- ... productseries=firefox.series[1])
118- >>> for entry in import_queue.getAllEntries(target=firefox):
119- ... print entry.importer.name
120- name12
121- name16
122-
123-The owner of firefox can be changed.
124-
125- >>> login("foo.bar@canonical.com")
126 >>> mark = getUtility(IPersonSet).getByEmail('mark@example.com')
127 >>> print mark.name
128 mark
129
130+ >>> login_person(firefox.owner)
131 >>> firefox.owner = mark
132
133-Now that the owner for firefox has changed the series and product
134-releases previously owned by name12 are now owned by mark. Those not
135-owned by name12 are unchanged.
136-
137 >>> print firefox.owner.name
138 mark
139-
140- >>> for series in firefox.series:
141- ... print series.owner.name, series.name
142- mark 1.0
143- mark trunk
144-
145- >>> for release in firefox.releases:
146- ... print release.owner.name, release.title
147- name16 Mozilla Firefox 0.9 "One Tree Hill"
148- name16 Mozilla Firefox 0.9.1 "One Tree Hill (v2)"
149- name16 Mozilla Firefox 0.9.2 "One (secure) Tree Hill"
150- mark Mozilla Firefox 1.0.0 "First Stable Release"
151-
152- >>> for entry in import_queue.getAllEntries(target=firefox):
153- ... print entry.importer.name
154- mark
155- name16
156
157=== modified file 'lib/lp/registry/model/product.py'
158--- lib/lp/registry/model/product.py 2010-09-14 15:04:06 +0000
159+++ lib/lp/registry/model/product.py 2010-09-25 02:36:04 +0000
160@@ -17,6 +17,8 @@
161 import operator
162
163 from lazr.delegates import delegates
164+from lazr.lifecycle.event import ObjectModifiedEvent
165+from lazr.lifecycle.snapshot import Snapshot
166 from lazr.restful.error import expose
167 import pytz
168 from sqlobject import (
169@@ -38,7 +40,11 @@
170 Unicode,
171 )
172 from zope.component import getUtility
173-from zope.interface import implements
174+from zope.event import notify
175+from zope.interface import (
176+ implements,
177+ providedBy,
178+ )
179 from zope.security.proxy import removeSecurityProxy
180
181 from canonical.database.constants import UTC_NOW
182@@ -49,9 +55,6 @@
183 SQLBase,
184 sqlvalues,
185 )
186-from canonical.launchpad.components.decoratedresultset import (
187- DecoratedResultSet,
188- )
189 from canonical.launchpad.interfaces.launchpad import (
190 IHasIcon,
191 IHasLogo,
192@@ -164,9 +167,6 @@
193 IHasCustomLanguageCodes,
194 )
195 from lp.translations.interfaces.translationgroup import TranslationPermission
196-from lp.translations.interfaces.translationimportqueue import (
197- ITranslationImportQueue,
198- )
199 from lp.translations.model.customlanguagecode import (
200 CustomLanguageCode,
201 HasCustomLanguageCodesMixin,
202@@ -745,22 +745,21 @@
203 def _setOwner(self, new_owner):
204 """Set the owner.
205
206- Change the owner and change the ownership of related artifacts.
207+ Send an IObjectModifiedEvent to notify subscribers that the owner
208+ changed.
209 """
210- old_owner = self._owner
211- self._owner = new_owner
212- if old_owner is not None:
213- import_queue = getUtility(ITranslationImportQueue)
214- for entry in import_queue.getAllEntries(target=self):
215- if entry.importer == old_owner:
216- removeSecurityProxy(entry).importer = new_owner
217- for series in self.series:
218- if series.owner == old_owner:
219- series.owner = new_owner
220- for release in self.releases:
221- if release.owner == old_owner:
222- release.owner = new_owner
223- Store.of(self).flush()
224+ if self.owner is None:
225+ # This is being initialized.
226+ self._owner = new_owner
227+ elif self.owner != new_owner:
228+ old_product = Snapshot(self, providing=providedBy(self))
229+ self._owner = new_owner
230+ notify(ObjectModifiedEvent(
231+ self, object_before_modification=old_product,
232+ edited_fields=['_owner']))
233+ else:
234+ # The new owner is the same as the current owner.
235+ pass
236
237 owner = property(_getOwner, _setOwner)
238
239
240=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
241--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-09-14 18:28:53 +0000
242+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-09-25 02:36:04 +0000
243@@ -428,16 +428,6 @@
244 >>> test_project['owner_link']
245 u'http://.../~mark'
246
247- >>> test_series = webservice.get('/test-project/test-series').jsonBody()
248- >>> test_series['owner_link']
249- u'http://.../~mark'
250-
251- >>> release_path = '/test-project/test-series/test-milestone'
252- >>> test_project_release = webservice.get(release_path).jsonBody()
253- >>> test_project_release['owner_link']
254- u'http://.../~mark'
255-
256-
257 Read-only attributes, like registrant, cannot be modified via the
258 webservice.patch() method.
259
260
261=== modified file 'lib/lp/translations/configure.zcml'
262--- lib/lp/translations/configure.zcml 2010-08-19 02:11:43 +0000
263+++ lib/lp/translations/configure.zcml 2010-09-25 02:36:04 +0000
264@@ -255,6 +255,11 @@
265 factory="lp.translations.browser.translations.TranslationsVHostBreadcrumb"
266 permission="zope.Public"/>
267
268+ <!-- Subscribers to registry objects -->
269+
270+ <subscriber
271+ for="canonical.launchpad.interfaces.IProduct zope.lifecycleevent.interfaces.IObjectModifiedEvent"
272+ handler="lp.translations.subscribers.product_modified"/>
273
274 <!-- DistroSeriesLanguage and Dummy -->
275
276
277=== added file 'lib/lp/translations/subscribers.py'
278--- lib/lp/translations/subscribers.py 1970-01-01 00:00:00 +0000
279+++ lib/lp/translations/subscribers.py 2010-09-25 02:36:04 +0000
280@@ -0,0 +1,44 @@
281+# Copyright 2010 Canonical Ltd. This software is licensed under the
282+# GNU Affero General Public License version 3 (see the file LICENSE).
283+
284+__metaclass__ = type
285+
286+__all__ = [
287+ 'product_modified',
288+ ]
289+
290+from zope.component import getUtility
291+from zope.security.proxy import removeSecurityProxy
292+
293+from lp.translations.interfaces.translationimportqueue import (
294+ ITranslationImportQueue,
295+ )
296+
297+
298+def product_modified(product, event):
299+ """Update translations objects when the product changes."""
300+ old_owner = event.object_before_modification.owner
301+ if old_owner != product.owner:
302+ import_queue = getUtility(ITranslationImportQueue)
303+ # The change in ownership may cause permission issues for
304+ # entries in the translation import queue *if* the old owner has
305+ # less permission now. This issue can be avoided by updating the
306+ # entries's importer to the new owner. This can only be done if the
307+ # new owner does not already have an entry in the queue.
308+ # See bug 635438 for more details.
309+ old_owner_entries = []
310+ new_owner_entries = []
311+ for entry in import_queue.getAllEntries(target=product):
312+ if entry.importer == old_owner:
313+ old_owner_entries.append(entry)
314+ elif entry.importer == product.owner:
315+ new_owner_entries.append(entry)
316+ else:
317+ # The entry is not affected by the ownership change.
318+ pass
319+ if len(old_owner_entries) > 0 and len(new_owner_entries) == 0:
320+ # The new owner does not have any conflicting entries in the
321+ # queue. Change the old owner's entries to the new owner to
322+ # ensure there is no permissions issues during processing.
323+ for entry in old_owner_entries:
324+ removeSecurityProxy(entry).importer = product.owner
325
326=== modified file 'lib/lp/translations/tests/test_translationimportqueue.py'
327--- lib/lp/translations/tests/test_translationimportqueue.py 2010-08-20 20:31:18 +0000
328+++ lib/lp/translations/tests/test_translationimportqueue.py 2010-09-25 02:36:04 +0000
329@@ -1,7 +1,7 @@
330-# Copyright 2009 Canonical Ltd. This software is licensed under the
331+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
332 # GNU Affero General Public License version 3 (see the file LICENSE).
333
334-# pylint: disable-msg=C0102
335+from __future__ import with_statement
336
337 __metaclass__ = type
338
339@@ -11,9 +11,15 @@
340 from zope.component import getUtility
341
342 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
343-from canonical.testing import LaunchpadZopelessLayer
344+from canonical.testing import (
345+ LaunchpadFunctionalLayer,
346+ LaunchpadZopelessLayer,
347+ )
348 from lp.services.worlddata.interfaces.language import ILanguageSet
349-from lp.testing import TestCaseWithFactory
350+from lp.testing import (
351+ person_logged_in,
352+ TestCaseWithFactory,
353+ )
354 from lp.testing.factory import LaunchpadObjectFactory
355 from lp.translations.interfaces.translationimportqueue import (
356 ITranslationImportQueue,
357@@ -291,6 +297,45 @@
358 self.assertEquals(catalan_valencia, pofile.language)
359
360
361+class TestProductOwnerEntryImporter(TestCaseWithFactory):
362+ """Test entries update when owners change."""
363+
364+ layer = LaunchpadFunctionalLayer
365+
366+ def setUp(self):
367+ super(TestProductOwnerEntryImporter, self).setUp()
368+ self.product = self.factory.makeProduct()
369+ self.old_owner = self.product.owner
370+ self.new_owner = self.factory.makePerson()
371+ with person_logged_in(self.old_owner):
372+ self.product.driver = self.new_owner
373+ self.import_queue = getUtility(ITranslationImportQueue)
374+
375+ def test_product_change_owner_changes_entry_importer(self):
376+ # Changing the Product owner also updates the importer of the entry.
377+ with person_logged_in(self.old_owner):
378+ entry = self.import_queue.addOrUpdateEntry(
379+ u'po/sr.po', 'foo', True, self.old_owner,
380+ productseries=self.product.series[0])
381+ self.product.owner = self.new_owner
382+ self.assertEqual(self.new_owner, entry.importer)
383+
384+ def test_product_change_owner_preserves_entry_importer(self):
385+ # When the new owner already has an entry in the product's import
386+ # queue, the entry importer is not updated because that would
387+ # cause an non-unique key for the entry.
388+ with person_logged_in(self.new_owner):
389+ new_entry = self.import_queue.addOrUpdateEntry(
390+ u'po/sr.po', 'foo', True, self.new_owner,
391+ productseries=self.product.series[0])
392+ with person_logged_in(self.old_owner):
393+ old_entry = self.import_queue.addOrUpdateEntry(
394+ u'po/sr.po', 'foo', True, self.old_owner,
395+ productseries=self.product.series[0])
396+ self.product.owner = self.new_owner
397+ self.assertEqual(self.old_owner, old_entry.importer)
398+
399+
400 def test_suite():
401 """Add only specific test cases and leave out the base case."""
402 suite = unittest.TestSuite()
403@@ -300,5 +345,5 @@
404 unittest.makeSuite(TestCanSetStatusPOTemplateWithQueuedUser))
405 suite.addTest(unittest.makeSuite(TestCanSetStatusPOFileWithQueuedUser))
406 suite.addTest(unittest.makeSuite(TestGetGuessedPOFile))
407+ suite.addTest(unittest.makeSuite(TestProductOwnerEntryImporter))
408 return suite
409-