Merge lp:~adiroiban/launchpad/bug-340662-take-2 into lp:launchpad/db-devel

Proposed by Adi Roiban
Status: Merged
Approved by: Brad Crittenden
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~adiroiban/launchpad/bug-340662-take-2
Merge into: lp:launchpad/db-devel
Diff against target: 275 lines (+72/-52)
5 files modified
lib/canonical/launchpad/security.py (+3/-28)
lib/lp/translations/browser/configure.zcml (+2/-2)
lib/lp/translations/browser/potemplate.py (+9/-8)
lib/lp/translations/configure.zcml (+2/-2)
lib/lp/translations/stories/standalone/xx-potemplate-edit.txt (+56/-12)
To merge this branch: bzr merge lp:~adiroiban/launchpad/bug-340662-take-2
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+17598@code.launchpad.net

Commit message

Remove "name" field from POTemplate edit view. On POTemplateSubset url traversal, check permission on POFile. Remove EditPOTemplateSubset from security.py

To post a comment you must log in.
Revision history for this message
Adi Roiban (adiroiban) wrote :

= Bug #340662 and #507498=
This branch should fix the issues raised in this MP
https://code.edge.launchpad.net/~adiroiban/launchpad/bug-340662/+merge/16629

== Proposed fix ==
Remove "name" field from PoTemplate edit page.

In the PoTemplateSubset URL traversal code, check permissions for the POTemplate and not for the context.

== Pre-implementation notes ==
Danilo asked to remove the "name" field from the edit page since it is to fragile to be edited by project owners.

Henning suggest that permission checking for POTemplateSubset URL traversal can be done for POTemplate and in this way we can remove the EditPOTemplateSubSet from security.py

== Implementation details ==
Nothing special here.

== Tests ==
lp-test -t templates

== Demo and Q/A ==
As a product owner (ex. <email address hidden>) for a project (ex. evolution) go to a template page for that project
https://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/

You should see the „Change details” that links to the +edit page

From this page you should be able to change template name, domain name, path, details, priority, owner and „accept translation”.

Change some values and then save them.

From the series +template page you will see „Edit” links for each template, including disabled templates:
https://translations.launchpad.dev/evolution/trunk/+templates

Disabling a template, it should still be in the list (with a red background) and you can still edit it.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/security.py
  lib/lp/translations/configure.zcml
  lib/lp/translations/browser/potemplate.py
  lib/lp/translations/stories/standalone/xx-potemplate-edit.txt

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

Hi Adi,

Thanks for this branch.

As noted in IRC, there is an import violation that is clearly not your fault but needs to be fixed before landing this branch. Thanks for agreeing to look into it.

Also, when attempting the demo you describe in the MP I get an unauthorized error changing any field b/c date_last_updated requires launchpad.TranslationsAdmin. We need to beef up the tests to ensure the permissions are set as we need.

review: Needs Fixing (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :

Hi,

Thanks for the review.

The permission problem should be fixed now and test extended to check all fields.

Not sure if there will be branch for fixing the import problem. I will leave the import problem as a final commit.

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

Hi Adi,

Thanks again for your changes. Your use of removeSecurityProxy was a bit flawed, though, so date_last_updated was not being changed. This flaw also exposes a hole in the testing as there is not test to ensure the value actually got changed.

If you apply the following change it will work:

=== modified file 'lib/lp/translations/browser/potemplate.py'
--- lib/lp/translations/browser/potemplate.py 2010-01-19 16:48:51 +0000
+++ lib/lp/translations/browser/potemplate.py 2010-01-20 14:45:58 +0000
@@ -533,9 +533,8 @@
             # We only update date_last_updated when translation_domain field
             # is changed because is the only significative change that,
             # somehow, affects the content of the potemplate.
- UTC = pytz.timezone('UTC')
- date_last_updated = removeSecurityProxy(context.date_last_updated)
- date_last_updated = datetime.datetime.now(UTC)
+ naked_context = removeSecurityProxy(context)
+ naked_context.date_last_updated = datetime.datetime.now(pytz.UTC)

Also, while you're there could you fix the preceding comment so that it reads:

We only change date_last_updated when the translation_domain field is changed because it is the only relevant field we care about regarding the date of last update.

In your test it would be great to grab the value of date_last_updated before and after the change and show that new > old. It's kind of messy in a story test but should be ok.

Please post a diff when you're done and I'll have a final look at it.

review: Needs Fixing (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :

Hi,
Here is the diff. Hope everything is OK now.

=== modified file 'lib/lp/translations/browser/potemplate.py'
--- lib/lp/translations/browser/potemplate.py 2010-01-19 16:48:51 +0000
+++ lib/lp/translations/browser/potemplate.py 2010-01-20 18:12:49 +0000
@@ -530,12 +530,12 @@
                 product=context.product, distribution=context.distribution,
                 sourcepackagename=context.sourcepackagename)
         if old_translation_domain != context.translation_domain:
- # We only update date_last_updated when translation_domain field
- # is changed because is the only significative change that,
- # somehow, affects the content of the potemplate.
+ # We only change date_last_updated when the translation_domain
+ # field is changed because it is the only relevant field we
+ # care about regarding the date of last update.
             UTC = pytz.timezone('UTC')
- date_last_updated = removeSecurityProxy(context.date_last_updated)
- date_last_updated = datetime.datetime.now(UTC)
+ naked_context = removeSecurityProxy(context)
+ naked_context.date_last_updated = datetime.datetime.now(UTC)

     @property
     def cancel_url(self):

=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-edit.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-edit.txt 2010-01-19 16:48:51 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-edit.txt 2010-01-20 19:10:54 +0000
@@ -90,6 +90,22 @@
   >>> browser.getControl(name='field.translation_domain').value
   'evolution-2.2'

+We remember the 'last_update_date' in order to check if it was changed
+after updating the template.
+
+ >>> from zope.component import getUtility
+ >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
+ >>> from lp.translations.model.potemplate import POTemplateSubset
+ >>> from lp.registry.interfaces.product import IProductSet
+ >>> login('<email address hidden>')
+ >>> evolution = getUtility(IProductSet).getByName('evolution')
+ >>> evolution_trunk = evolution.getSeries('trunk')
+ >>> hoary_subset = POTemplateSubset(productseries=evolution_trunk)
+ >>> evolution_template = hoary_subset.getPOTemplateByName(
+ ... 'evolution-2.2')
+ >>> previous_date_last_updated = evolution_template.date_last_updated
+ >>> logout()
+
 The visible fields can be changed and saved.

   >>> browser.getControl(name='field.translation_domain').value = u'evo'
@@ -120,3 +136,6 @@
   'name12'
   >>> browser.getControl(name='field.description').value
   'foo'
+ >>> previous_date_last_updated != evolution_template.date_last_updated
+ True
+

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

Adi it looks very good now. Thanks for the fixes.

One tiny thing: remove the UTC = line and just use 'pytz.UTC' instead, as shown in my previous comment. pytz is nice enough to export a pre-defined UTC constant so we should use it rather than looking it up again.

review: Approve (code)
Revision history for this message
Adi Roiban (adiroiban) wrote :
Download full text (4.3 KiB)

Here is the diff after running the syntactic check for modified files.

=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-01-18 16:54:07 +0000
+++ lib/canonical/launchpad/security.py 2010-01-20 20:29:00 +0000
@@ -469,7 +469,7 @@
         return (user.inTeam(self.obj.person) or
                 user.isOneOf(
                     self.obj.specification,
- ['owner','drafter', 'assignee', 'approver']) or
+ ['owner', 'drafter', 'assignee', 'approver']) or
                 user.in_admin)

@@ -530,6 +530,7 @@
             return True
         return user.isOwner(self.obj.target)

+
 class AdminMilestoneByLaunchpadAdmins(AuthorizationBase):
     permission = 'launchpad.Admin'
     usedfor = IMilestone
@@ -574,7 +575,7 @@

     def checkAuthenticated(self, user):
         """Is the user a privileged team member or Launchpad staff?
-
+
         Return true when the user is a member of Launchpad admins,
         registry experts, team admins, or the team owners.
         """
@@ -1431,7 +1432,6 @@

     # This code MUST match the logic in IBuildSet.getBuildsForBuilder()
     # otherwise users are likely to get 403 errors, or worse.
-
     def checkAuthenticated(self, user):
         """Private restricts to admins and archive members."""
         if not self.obj.archive.private:

=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2009-12-12 05:47:41 +0000
+++ lib/lp/translations/browser/configure.zcml 2010-01-20 20:31:52 +0000
@@ -391,9 +391,9 @@
             <browser:page
                 name="+chart"
                 template="../templates/potemplate-chart.pt"/>
-
+
             <!-- Potemplate Portlets -->
-
+
             <browser:page
                 name="+portlet-details"
                 template="../templates/potemplate-portlet-details.pt"/>

=== modified file 'lib/lp/translations/browser/potemplate.py'
--- lib/lp/translations/browser/potemplate.py 2010-01-20 20:16:29 +0000
+++ lib/lp/translations/browser/potemplate.py 2010-01-20 20:33:54 +0000
@@ -533,9 +533,8 @@
             # We only change date_last_updated when the translation_domain
             # field is changed because it is the only relevant field we
             # care about regarding the date of last update.
- UTC = pytz.timezone('UTC')
             naked_context = removeSecurityProxy(context)
- naked_context.date_last_updated = datetime.datetime.now(UTC)
+ naked_context.date_last_updated = datetime.datetime.now(pytz.UTC)

     @property
     def cancel_url(self):

=== modified file 'lib/lp/translations/configure.zcml'
--- lib/lp/translations/configure.zcml 2010-01-17 02:07:16 +0000
+++ lib/lp/translations/configure.zcml 2010-01-20 20:32:41 +0000
@@ -415,7 +415,7 @@
             <require
                 permission="launchpad.TranslationsAdmin"
                 set_attributes="
- name productseries distroseries sourcepackagename
+ name productseries distroseries sourcepackagename
                     sourcepackageversion binaryp...

Read more...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-01-14 11:36:51 +0000
+++ lib/canonical/launchpad/security.py 2010-01-20 20:38:18 +0000
@@ -469,7 +469,7 @@
469 return (user.inTeam(self.obj.person) or469 return (user.inTeam(self.obj.person) or
470 user.isOneOf(470 user.isOneOf(
471 self.obj.specification,471 self.obj.specification,
472 ['owner','drafter', 'assignee', 'approver']) or472 ['owner', 'drafter', 'assignee', 'approver']) or
473 user.in_admin)473 user.in_admin)
474474
475475
@@ -530,6 +530,7 @@
530 return True530 return True
531 return user.isOwner(self.obj.target)531 return user.isOwner(self.obj.target)
532532
533
533class AdminMilestoneByLaunchpadAdmins(AuthorizationBase):534class AdminMilestoneByLaunchpadAdmins(AuthorizationBase):
534 permission = 'launchpad.Admin'535 permission = 'launchpad.Admin'
535 usedfor = IMilestone536 usedfor = IMilestone
@@ -574,7 +575,7 @@
574575
575 def checkAuthenticated(self, user):576 def checkAuthenticated(self, user):
576 """Is the user a privileged team member or Launchpad staff?577 """Is the user a privileged team member or Launchpad staff?
577 578
578 Return true when the user is a member of Launchpad admins,579 Return true when the user is a member of Launchpad admins,
579 registry experts, team admins, or the team owners.580 registry experts, team admins, or the team owners.
580 """581 """
@@ -1144,8 +1145,6 @@
1144 self, user)1145 self, user)
11451146
11461147
1147# Please keep EditPOTemplateSubset in sync with this, unless you
1148# know exactly what you are doing.
1149class EditPOTemplateDetails(AdminPOTemplateDetails, EditByOwnersOrAdmins):1148class EditPOTemplateDetails(AdminPOTemplateDetails, EditByOwnersOrAdmins):
1150 permission = 'launchpad.Edit'1149 permission = 'launchpad.Edit'
1151 usedfor = IPOTemplate1150 usedfor = IPOTemplate
@@ -1433,7 +1432,6 @@
14331432
1434 # This code MUST match the logic in IBuildSet.getBuildsForBuilder()1433 # This code MUST match the logic in IBuildSet.getBuildsForBuilder()
1435 # otherwise users are likely to get 403 errors, or worse.1434 # otherwise users are likely to get 403 errors, or worse.
1436
1437 def checkAuthenticated(self, user):1435 def checkAuthenticated(self, user):
1438 """Private restricts to admins and archive members."""1436 """Private restricts to admins and archive members."""
1439 if not self.obj.archive.private:1437 if not self.obj.archive.private:
@@ -1648,29 +1646,6 @@
1648 return OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user)1646 return OnlyRosettaExpertsAndAdmins.checkAuthenticated(self, user)
16491647
16501648
1651# Please keep EditPOTemplate in sync with this, unless you
1652# know exactly what you are doing. Note that this permission controls
1653# access to browsing into individual potemplates, but it's on a different
1654# object (POTemplateSubset) from EditPOTemplateDetails,
1655# even though it looks almost identical
1656class EditPOTemplateSubset(AuthorizationBase):
1657 permission = 'launchpad.Edit'
1658 usedfor = IPOTemplateSubset
1659
1660 def checkAuthenticated(self, user):
1661 """Allow anyone with admin rights; owners, product owners and
1662 distribution owners; and for distros, translation group owners.
1663 """
1664 if (self.obj.productseries is not None and
1665 user.inTeam(self.obj.productseries.product.owner)):
1666 # The user is the owner of the product.
1667 return True
1668
1669 return (
1670 AdminPOTemplateSubset(self.obj).checkAuthenticated(user) or
1671 EditByOwnersOrAdmins(self.obj).checkAuthenticated(user))
1672
1673
1674class AdminDistroSeriesTranslations(AuthorizationBase):1649class AdminDistroSeriesTranslations(AuthorizationBase):
1675 permission = 'launchpad.TranslationsAdmin'1650 permission = 'launchpad.TranslationsAdmin'
1676 usedfor = IDistroSeries1651 usedfor = IDistroSeries
16771652
=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2009-12-12 05:47:41 +0000
+++ lib/lp/translations/browser/configure.zcml 2010-01-20 20:38:18 +0000
@@ -391,9 +391,9 @@
391 <browser:page391 <browser:page
392 name="+chart"392 name="+chart"
393 template="../templates/potemplate-chart.pt"/>393 template="../templates/potemplate-chart.pt"/>
394 394
395 <!-- Potemplate Portlets -->395 <!-- Potemplate Portlets -->
396 396
397 <browser:page397 <browser:page
398 name="+portlet-details"398 name="+portlet-details"
399 template="../templates/potemplate-portlet-details.pt"/>399 template="../templates/potemplate-portlet-details.pt"/>
400400
=== modified file 'lib/lp/translations/browser/potemplate.py'
--- lib/lp/translations/browser/potemplate.py 2009-12-28 22:58:18 +0000
+++ lib/lp/translations/browser/potemplate.py 2010-01-20 20:38:18 +0000
@@ -31,6 +31,7 @@
31from zope.component import getUtility31from zope.component import getUtility
32from zope.interface import implements32from zope.interface import implements
33from zope.publisher.browser import FileUpload33from zope.publisher.browser import FileUpload
34from zope.security.proxy import removeSecurityProxy
3435
35from canonical.lazr.utils import smartquote36from canonical.lazr.utils import smartquote
3637
@@ -291,7 +292,7 @@
291 # SourcePackageName is needed to avoid hardcoding this URL.292 # SourcePackageName is needed to avoid hardcoding this URL.
292 url = (canonical_url(293 url = (canonical_url(
293 self.context.distroseries, rootsite="translations") +294 self.context.distroseries, rootsite="translations") +
294 "/+source/" + self.context.sourcepackagename.name + 295 "/+source/" + self.context.sourcepackagename.name +
295 "/+translations")296 "/+translations")
296 else:297 else:
297 url = canonical_url(298 url = canonical_url(
@@ -512,7 +513,7 @@
512 """View class that lets you edit a POTemplate object."""513 """View class that lets you edit a POTemplate object."""
513514
514 schema = IPOTemplate515 schema = IPOTemplate
515 field_names = ['name', 'translation_domain', 'description', 'priority',516 field_names = ['translation_domain', 'description', 'priority',
516 'path', 'owner', 'iscurrent']517 'path', 'owner', 'iscurrent']
517 label = 'Edit translation template details'518 label = 'Edit translation template details'
518 page_title = 'Edit details'519 page_title = 'Edit details'
@@ -529,11 +530,11 @@
529 product=context.product, distribution=context.distribution,530 product=context.product, distribution=context.distribution,
530 sourcepackagename=context.sourcepackagename)531 sourcepackagename=context.sourcepackagename)
531 if old_translation_domain != context.translation_domain:532 if old_translation_domain != context.translation_domain:
532 # We only update date_last_updated when translation_domain field533 # We only change date_last_updated when the translation_domain
533 # is changed because is the only significative change that,534 # field is changed because it is the only relevant field we
534 # somehow, affects the content of the potemplate.535 # care about regarding the date of last update.
535 UTC = pytz.timezone('UTC')536 naked_context = removeSecurityProxy(context)
536 context.date_last_updated = datetime.datetime.now(UTC)537 naked_context.date_last_updated = datetime.datetime.now(pytz.UTC)
537538
538 @property539 @property
539 def cancel_url(self):540 def cancel_url(self):
@@ -713,7 +714,7 @@
713 raise AssertionError('Unknown context for %s' % potemplate.title)714 raise AssertionError('Unknown context for %s' % potemplate.title)
714715
715 if ((official_rosetta and potemplate.iscurrent) or716 if ((official_rosetta and potemplate.iscurrent) or
716 check_permission('launchpad.Edit', self.context)):717 check_permission('launchpad.Edit', potemplate)):
717 # The target is using officially Launchpad Translations and the718 # The target is using officially Launchpad Translations and the
718 # template is available to be translated, or the user is a is a719 # template is available to be translated, or the user is a is a
719 # Launchpad administrator in which case we show everything.720 # Launchpad administrator in which case we show everything.
720721
=== modified file 'lib/lp/translations/configure.zcml'
--- lib/lp/translations/configure.zcml 2010-01-12 23:25:16 +0000
+++ lib/lp/translations/configure.zcml 2010-01-20 20:38:18 +0000
@@ -411,11 +411,11 @@
411 permission="launchpad.Edit"411 permission="launchpad.Edit"
412 set_attributes="412 set_attributes="
413 owner priority description translation_domain path413 owner priority description translation_domain path
414 iscurrent name"/>414 iscurrent"/>
415 <require415 <require
416 permission="launchpad.TranslationsAdmin"416 permission="launchpad.TranslationsAdmin"
417 set_attributes="417 set_attributes="
418 productseries distroseries sourcepackagename 418 name productseries distroseries sourcepackagename
419 sourcepackageversion binarypackagename languagepack419 sourcepackageversion binarypackagename languagepack
420 source_file_format source_file date_last_updated420 source_file_format source_file date_last_updated
421 from_sourcepackagename header"/>421 from_sourcepackagename header"/>
422422
=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-edit.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-edit.txt 2009-12-29 18:21:37 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-edit.txt 2010-01-20 20:38:18 +0000
@@ -33,6 +33,8 @@
33 >>> print browser.url33 >>> print browser.url
34 http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/+edit34 http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/+edit
3535
36The owner of a product has access to edit page for PO templates.
37
36 >>> browser = setupBrowser(auth='Basic test@canonical.com:test')38 >>> browser = setupBrowser(auth='Basic test@canonical.com:test')
37 >>> browser.open(39 >>> browser.open(
38 ... 'http://translations.launchpad.dev/evolution/trunk/+pots/'40 ... 'http://translations.launchpad.dev/evolution/trunk/+pots/'
@@ -40,20 +42,17 @@
40 >>> print browser.url42 >>> print browser.url
41 http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/+edit43 http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2/+edit
4244
43We should be sure that the edit form get only the non admin fields.45Owner will not see admin fields, only those fields designated for the
46edit page.
4447
45 >>> browser.getControl(name='field.name').value48 >>> browser.getControl(name='field.name').value
46 'evolution-2.2'49 Traceback (most recent call last):
47 >>> browser.getControl(name='field.description').value50 ...
48 'Template for evolution in hoary'51 LookupError:...
49 >>> browser.getControl(name='field.header').value52 >>> browser.getControl(name='field.header').value
50 Traceback (most recent call last):53 Traceback (most recent call last):
51 ...54 ...
52 LookupError:...55 LookupError:...
53 >>> browser.getControl(name='field.iscurrent').value
54 True
55 >>> browser.getControl(name='field.owner').value
56 'rosetta-admins'
57 >>> browser.getControl(name='field.productseries').value56 >>> browser.getControl(name='field.productseries').value
58 Traceback (most recent call last):57 Traceback (most recent call last):
59 ...58 ...
@@ -78,20 +77,65 @@
78 Traceback (most recent call last):77 Traceback (most recent call last):
79 ...78 ...
80 LookupError:...79 LookupError:...
80 >>> browser.getControl(name='field.description').value
81 'Template for evolution in hoary'
81 >>> browser.getControl(name='field.path').value82 >>> browser.getControl(name='field.path').value
82 'po/evolution-2.2.pot'83 'po/evolution-2.2.pot'
8384 >>> browser.getControl(name='field.iscurrent').value
84And that we are able to POST it.85 True
8586 >>> browser.getControl(name='field.owner').value
87 'rosetta-admins'
88 >>> browser.getControl(name='field.priority').value
89 '0'
90 >>> browser.getControl(name='field.translation_domain').value
91 'evolution-2.2'
92
93We remember the 'last_update_date' in order to check if it was changed
94after updating the template.
95
96 >>> from zope.component import getUtility
97 >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
98 >>> from lp.translations.model.potemplate import POTemplateSubset
99 >>> from lp.registry.interfaces.product import IProductSet
100 >>> login('foo.bar@canonical.com')
101 >>> evolution = getUtility(IProductSet).getByName('evolution')
102 >>> evolution_trunk = evolution.getSeries('trunk')
103 >>> hoary_subset = POTemplateSubset(productseries=evolution_trunk)
104 >>> evolution_template = hoary_subset.getPOTemplateByName(
105 ... 'evolution-2.2')
106 >>> previous_date_last_updated = evolution_template.date_last_updated
107 >>> logout()
108
109The visible fields can be changed and saved.
110
111 >>> browser.getControl(name='field.translation_domain').value = u'evo'
112 >>> browser.getControl(name='field.priority').value = '100'
113 >>> browser.getControl(name='field.iscurrent').value = False
114 >>> browser.getControl(name='field.path').value = 'po/evolution.pot'
115 >>> browser.getControl(name='field.owner').value = u'name12'
86 >>> browser.getControl(name='field.description').value = u'foo'116 >>> browser.getControl(name='field.description').value = u'foo'
87 >>> browser.getControl('Change').click()117 >>> browser.getControl('Change').click()
88 >>> print browser.url118 >>> print browser.url
89 http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2119 http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2
90120
91And the changed value is stored.121The changed values will be stored and visible by accesing again the
122edit page.
92123
93 >>> browser.open(124 >>> browser.open(
94 ... 'http://translations.launchpad.dev/evolution/trunk/+pots/'125 ... 'http://translations.launchpad.dev/evolution/trunk/+pots/'
95 ... 'evolution-2.2/+edit')126 ... 'evolution-2.2/+edit')
127 >>> browser.getControl(name='field.translation_domain').value
128 'evo'
129 >>> browser.getControl(name='field.priority').value
130 '100'
131 >>> browser.getControl(name='field.iscurrent').value
132 False
133 >>> browser.getControl(name='field.path').value
134 'po/evolution.pot'
135 >>> browser.getControl(name='field.owner').value
136 'name12'
96 >>> browser.getControl(name='field.description').value137 >>> browser.getControl(name='field.description').value
97 'foo'138 'foo'
139 >>> previous_date_last_updated != evolution_template.date_last_updated
140 True
141

Subscribers

People subscribed via source and target branches

to status/vote changes: