launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04781
[Merge] lp:~danilo/launchpad/bug-321467 into lp:launchpad
Данило Шеган has proposed merging lp:~danilo/launchpad/bug-321467 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #321467 in Launchpad itself: "Template name is not validated on admin form."
https://bugs.launchpad.net/launchpad/+bug/321467
For more details, see:
https://code.launchpad.net/~danilo/launchpad/bug-321467/+merge/72553
= Bug 321467: validate POTemplate name =
We recently exposed POTemplate:+admin page to a wider group (namely, project maintainers). However, +admin page was not sufficiently well doing the validation before trying to save the data which caused old OOPSes to become more frequent.
== Proposed fix ==
While just adding a valid_name validation for POTemplate name is a simple affair, I've went a bit further: I've decided to move only the relevant fields to the +edit page and still prohibit access to the +admin page for anyone but admins.
== Pre-implementation notes ==
I discussed Ubuntu-needed fields with David Planella, an Ubuntu Translations Coordinator.
== Implementation details ==
This meant a few things:
1. We use launchpad.TranslationsAdmin where we used to use launchpad.Edit, and launchpad.Admin for launchpad.TranslationsAdmin privilege
2. potemplate name is added to the POTemplateEditView.field_names and sourcepackagename and languagepack fields are added if the template is for a distro
3. most of the validation code is moved from admin view into the edit view
4. newly added check to the validation code is a valid_name(name) check
5. POTemplateAdminView now derives from POTemplateEditView and only implements _validateTargetAndGetTemplates method which validates the specific bits it needs to
6. test cases are expanded so they test all of the above
7. we move the "Change details" (potemplate:+edit) link to be in the "Administration" section next to the "Administer template" (potemplate:+admin) link so it makes more sense
8. _return_url never worked properly when template was moved around (it only worked when just "name" was changed), so I fix that along the way
Note: @@ -320,12 +321,11 @@ in browser/potemplate.py is a drive-by fix to remove the stale XXX and use proper code.
== Tests ==
bin/test -cvvt potemplate_views
== Demo and Q/A ==
Log-in as one of (anonymous|regular user|project maintainer|rosetta expert|ubuntu translations coordinator team member) and try POTemplate:+edit and POTemplate:+admin pages from eg:
https://translations.qastaging.launchpad.net/ubuntu/natty/+templates
https://translations.qastaging.launchpad.net/openobject-addons/6.0/+templates
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/translations/browser/tests/test_potemplate_views.py
lib/lp/translations/templates/potemplate-index.pt
lib/lp/translations/browser/configure.zcml
lib/lp/translations/interfaces/potemplate.py
lib/canonical/launchpad/security.py
lib/lp/translations/configure.zcml
lib/lp/translations/browser/potemplate.py
--
https://code.launchpad.net/~danilo/launchpad/bug-321467/+merge/72553
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~danilo/launchpad/bug-321467 into lp:launchpad.
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2011-08-24 00:47:25 +0000
+++ lib/canonical/launchpad/security.py 2011-08-25 19:53:52 +0000
@@ -1262,6 +1262,23 @@
Product owners does not have administrative privileges.
"""
+ permission = 'launchpad.Admin'
+ usedfor = IPOTemplate
+
+ def checkAuthenticated(self, user):
+ template = self.obj
+ if user.in_rosetta_experts or user.in_admin:
+ return True
+ if template.distroseries is not None:
+ # Template is on a distribution.
+ return (
+ self.forwardCheckAuthenticated(user, template.distroseries,
+ 'launchpad.TranslationsAdmin'))
+ else:
+ # Template is on a product.
+ return False
+
+class EditPOTemplateDetails(AuthorizationBase):
permission = 'launchpad.TranslationsAdmin'
usedfor = IPOTemplate
@@ -1269,30 +1286,14 @@
template = self.obj
if template.distroseries is not None:
# Template is on a distribution.
- return AdminDistroSeriesTranslations(
- template.distroseries).checkAuthenticated(user)
+ return (
+ user.isOwner(template) or
+ self.forwardCheckAuthenticated(user, template.distroseries))
else:
# Template is on a product.
- return AdminProductSeriesTranslations(
- template.productseries).checkAuthenticated(user)
-
-
-class EditPOTemplateDetails(AdminPOTemplateDetails, EditByOwnersOrAdmins):
- permission = 'launchpad.Edit'
- usedfor = IPOTemplate
-
- def checkAuthenticated(self, user):
- """Allow anyone with admin rights; owners, product owners and
- distribution owners; and for distros, translation group owners.
- """
- if (self.obj.productseries is not None and
- user.inTeam(self.obj.productseries.product.owner)):
- # The user is the owner of the product.
- return True
-
- return (
- AdminPOTemplateDetails.checkAuthenticated(self, user) or
- EditByOwnersOrAdmins.checkAuthenticated(self, user))
+ return (
+ user.isOwner(template) or
+ self.forwardCheckAuthenticated(user, template.productseries))
class AddPOTemplate(OnlyRosettaExpertsAndAdmins):
=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2011-08-19 15:01:48 +0000
+++ lib/lp/translations/browser/configure.zcml 2011-08-25 19:53:52 +0000
@@ -400,7 +400,7 @@
<browser:page
name="+upload"
for="lp.translations.interfaces.potemplate.IPOTemplate"
- permission="launchpad.Edit"
+ permission="launchpad.TranslationsAdmin"
template="../templates/potemplate-upload.pt"
class="lp.translations.browser.potemplate.POTemplateUploadView"
layer="lp.translations.publisher.TranslationsLayer"/>
@@ -408,14 +408,14 @@
name="+edit"
for="lp.translations.interfaces.potemplate.IPOTemplate"
class="lp.translations.browser.potemplate.POTemplateEditView"
- permission="launchpad.Edit"
+ permission="launchpad.TranslationsAdmin"
template="../../app/templates/generic-edit.pt"
layer="lp.translations.publisher.TranslationsLayer"/>
<browser:page
name="+admin"
for="lp.translations.interfaces.potemplate.IPOTemplate"
class="lp.translations.browser.potemplate.POTemplateAdminView"
- permission="launchpad.TranslationsAdmin"
+ permission="launchpad.Admin"
template="../../app/templates/generic-edit.pt"
layer="lp.translations.publisher.TranslationsLayer"/>
<browser:page
=== modified file 'lib/lp/translations/browser/potemplate.py'
--- lib/lp/translations/browser/potemplate.py 2011-07-29 17:01:31 +0000
+++ lib/lp/translations/browser/potemplate.py 2011-08-25 19:53:52 +0000
@@ -63,15 +63,16 @@
ICanonicalUrlData,
ILaunchBag,
)
-from canonical.launchpad.webapp.launchpadform import ReturnToReferrerMixin
from canonical.launchpad.webapp.menu import structured
from canonical.lazr.utils import smartquote
+from lp.app.browser.launchpadform import ReturnToReferrerMixin
from lp.app.browser.tales import DateTimeFormatterAPI
from lp.app.enums import (
service_uses_launchpad,
ServiceUsage,
)
from lp.app.errors import NotFoundError
+from lp.app.validators.name import valid_name
from lp.registry.browser.productseries import ProductSeriesFacets
from lp.registry.browser.sourcepackage import SourcePackageFacets
from lp.registry.interfaces.productseries import IProductSeries
@@ -320,12 +321,11 @@
if (by_source_count > self.SHOW_RELATED_TEMPLATES):
other = by_source_count - self.SHOW_RELATED_TEMPLATES
if (self.context.distroseries):
- # XXX adiroiban 2009-12-21 bug=499058: A canonical_url for
- # SourcePackageName is needed to avoid hardcoding this URL.
- url = (canonical_url(
- self.context.distroseries, rootsite="translations") +
- "/+source/" + self.context.sourcepackagename.name +
- "/+translations")
+ sourcepackage = self.context.distroseries.getSourcePackage(
+ self.context.sourcepackagename)
+ url = canonical_url(
+ sourcepackage, rootsite="translations",
+ view_name='+translations')
else:
url = canonical_url(
self.context.productseries,
@@ -554,13 +554,51 @@
"""View class that lets you edit a POTemplate object."""
schema = IPOTemplate
- field_names = ['translation_domain', 'description', 'priority',
- 'path', 'owner', 'iscurrent']
label = 'Edit translation template details'
page_title = 'Edit details'
PRIORITY_MIN_VALUE = 0
PRIORITY_MAX_VALUE = 100000
+ @property
+ def field_names(self):
+ field_names = [
+ 'name', 'translation_domain', 'description', 'priority',
+ 'path', 'iscurrent']
+ if self.context.distroseries:
+ field_names.extend([
+ 'sourcepackagename',
+ 'languagepack',
+ ])
+ else:
+ field_names.append('owner')
+ return field_names
+
+ @property
+ def _return_url(self):
+ # We override the ReturnToReferrerMixin _return_url because it might
+ # change when any of the productseries, distroseries,
+ # sourcepackagename or name attributes change, and the basic version
+ # only supports watching changes to a single attribute.
+
+ # The referer header is a hidden input in the form.
+ referrer = self.request.form.get('_return_url')
+ returnChanged = False
+ if referrer is None:
+ # "referer" is misspelled in the HTTP specification.
+ referrer = self.request.getHeader('referer')
+ # If we were looking at the actual template, we want a new
+ # URL constructed.
+ if referrer is not None and '/+pots/' in referrer:
+ returnChanged = True
+
+ if (referrer is not None
+ and not returnChanged
+ and referrer.startswith(self.request.getApplicationURL())
+ and referrer != self.request.getHeader('location')):
+ return referrer
+ else:
+ return canonical_url(self.context)
+
@action(_('Change'), name='change')
def change_action(self, action, data):
context = self.context
@@ -581,7 +619,43 @@
naked_context = removeSecurityProxy(context)
naked_context.date_last_updated = datetime.datetime.now(pytz.UTC)
+ def _validateTargetAndGetTemplates(self, data):
+ """Return a POTemplateSubset corresponding to the chosen target."""
+ sourcepackagename = data.get('sourcepackagename',
+ self.context.sourcepackagename)
+ return getUtility(IPOTemplateSet).getSubset(
+ distroseries=self.context.distroseries,
+ sourcepackagename=sourcepackagename,
+ productseries=self.context.productseries)
+
def validate(self, data):
+ name = data.get('name', None)
+ if name is None or not valid_name(name):
+ self.setFieldError(
+ 'name',
+ 'Template name can only start with lowercase letters a-z '
+ 'or digits 0-9, and other than those characters, can only '
+ 'contain "-", "+" and "." characters.')
+
+ distroseries = data.get('distroseries', self.context.distroseries)
+ sourcepackagename = data.get(
+ 'sourcepackagename', self.context.sourcepackagename)
+ productseries = data.get('productseries', None)
+ sourcepackage_changed = (
+ distroseries is not None and
+ (distroseries != self.context.distroseries or
+ sourcepackagename != self.context.sourcepackagename))
+ productseries_changed = (productseries is not None and
+ productseries != self.context.productseries)
+ similar_templates = self._validateTargetAndGetTemplates(data)
+ if similar_templates is not None:
+ self.validateName(
+ name, similar_templates, sourcepackage_changed,
+ productseries_changed)
+ self.validateDomain(
+ data.get('translation_domain'), similar_templates,
+ sourcepackage_changed, productseries_changed)
+
priority = data.get('priority')
if priority is None:
return
@@ -592,24 +666,6 @@
'priority',
'The priority value must be between %s and %s.' % (
self.PRIORITY_MIN_VALUE, self.PRIORITY_MAX_VALUE))
- return
-
- @property
- def _return_attribute_name(self):
- """See 'ReturnToReferrerMixin'."""
- return "name"
-
-
-class POTemplateAdminView(POTemplateEditView):
- """View class that lets you admin a POTemplate object."""
- field_names = [
- 'name', 'translation_domain', 'description', 'header', 'iscurrent',
- 'owner', 'productseries', 'distroseries', 'sourcepackagename',
- 'from_sourcepackagename', 'sourcepackageversion', 'binarypackagename',
- 'languagepack', 'path', 'source_file_format', 'priority',
- 'date_last_updated']
- label = 'Administer translation template'
- page_title = "Administer"
def validateName(self, name, similar_templates,
sourcepackage_changed, productseries_changed):
@@ -645,8 +701,25 @@
self.setFieldError(
'translation_domain', "Domain is already in use.")
- def validate(self, data):
- super(POTemplateAdminView, self).validate(data)
+ @property
+ def _return_attribute_name(self):
+ """See 'ReturnToReferrerMixin'."""
+ return "name"
+
+
+class POTemplateAdminView(POTemplateEditView):
+ """View class that lets you admin a POTemplate object."""
+ field_names = [
+ 'name', 'translation_domain', 'description', 'header', 'iscurrent',
+ 'owner', 'productseries', 'distroseries', 'sourcepackagename',
+ 'from_sourcepackagename', 'sourcepackageversion', 'binarypackagename',
+ 'languagepack', 'path', 'source_file_format', 'priority',
+ 'date_last_updated']
+ label = 'Administer translation template'
+ page_title = "Administer"
+
+ def _validateTargetAndGetTemplates(self, data):
+ """Return a POTemplateSubset corresponding to the chosen target."""
distroseries = data.get('distroseries')
sourcepackagename = data.get('sourcepackagename')
productseries = data.get('productseries')
@@ -662,27 +735,10 @@
if message is not None:
self.addError(message)
- return
-
- # Validate name and domain; they should be unique within the
- # template's translation target (productseries or source
- # package). Validate against the target selected by the form,
- # not the template's existing target; the form may change the
- # template's target as well.
- similar_templates = getUtility(IPOTemplateSet).getSubset(
+ return None
+ return getUtility(IPOTemplateSet).getSubset(
distroseries=distroseries, sourcepackagename=sourcepackagename,
productseries=productseries)
- sourcepackage_changed = (
- distroseries != self.context.distroseries or
- sourcepackagename != self.context.sourcepackagename)
- productseries_changed = productseries != self.context.productseries
-
- self.validateName(
- data.get('name'), similar_templates,
- sourcepackage_changed, productseries_changed)
- self.validateDomain(
- data.get('translation_domain'), similar_templates,
- sourcepackage_changed, productseries_changed)
class POTemplateExportView(BaseExportView):
@@ -838,7 +894,7 @@
potemplate.iscurrent):
# This template is available for translation.
return potemplate
- elif check_permission('launchpad.Edit', potemplate):
+ elif check_permission('launchpad.TranslationsAdmin', potemplate):
# User has Edit privileges for this template and can access it.
return potemplate
else:
@@ -872,10 +928,10 @@
self.distroseries = series
else:
self.productseries = series
- self.can_admin = check_permission(
- 'launchpad.TranslationsAdmin', series)
+ self.can_admin = check_permission('launchpad.Admin', series)
self.can_edit = (
- self.can_admin or check_permission('launchpad.Edit', series))
+ self.can_admin or
+ check_permission('launchpad.TranslationsAdmin', series))
self.user_is_logged_in = (self.user is not None)
=== modified file 'lib/lp/translations/browser/tests/test_potemplate_views.py'
--- lib/lp/translations/browser/tests/test_potemplate_views.py 2011-06-07 12:59:28 +0000
+++ lib/lp/translations/browser/tests/test_potemplate_views.py 2011-08-25 19:53:52 +0000
@@ -9,10 +9,13 @@
from canonical.launchpad.webapp.servers import LaunchpadTestRequest
from canonical.testing.layers import DatabaseFunctionalLayer
from lp.testing import TestCaseWithFactory
-from lp.translations.browser.potemplate import POTemplateAdminView
-
-
-class TestPOTemplateAdminViewValidation(TestCaseWithFactory):
+from lp.translations.browser.potemplate import (
+ POTemplateAdminView,
+ POTemplateEditView,
+ )
+
+
+class TestPOTemplateEditViewValidation(TestCaseWithFactory):
layer = DatabaseFunctionalLayer
@@ -29,6 +32,42 @@
data.update(**kwargs)
return data
+ def test_field_names_productseries(self):
+ # A product series template has one set of field names that include
+ # the template name.
+ potemplate = self.factory.makePOTemplate()
+ view = POTemplateEditView(potemplate, LaunchpadTestRequest())
+ self.assertContentEqual(
+ ['name', 'translation_domain', 'description', 'priority',
+ 'path', 'iscurrent', 'owner'],
+ view.field_names)
+
+ def test_field_names_sourcepackage(self):
+ # A sourcepackage template has two more fields compared to the
+ # product series templates.
+ sourcepackage = self.factory.makeSourcePackage()
+ potemplate = self.factory.makePOTemplate(
+ distroseries=sourcepackage.distroseries,
+ sourcepackagename=sourcepackage.sourcepackagename)
+ view = POTemplateEditView(potemplate, LaunchpadTestRequest())
+ self.assertContentEqual(
+ ['name', 'translation_domain', 'description', 'priority',
+ 'path', 'iscurrent', 'sourcepackagename', 'languagepack'],
+ view.field_names)
+
+ def test_detects_invalid_names(self):
+ # A template name must be satisfying the valid_name constraint.
+ invalid_name = 'name!'
+ potemplate = self.factory.makePOTemplate()
+ data = self._makeData(potemplate, name=invalid_name)
+ view = POTemplateEditView(potemplate, LaunchpadTestRequest())
+ view.validate(data)
+ self.assertEqual(
+ [u'Template name can only start with lowercase letters a-z '
+ u'or digits 0-9, and other than those characters, can only '
+ u'contain "-", "+" and "." characters.'],
+ view.errors)
+
def test_detects_name_clash_on_name_change(self):
# A template name may not already be used.
existing_name = self.factory.getUniqueString()
@@ -36,7 +75,7 @@
series = existing_potemplate.productseries
potemplate = self.factory.makePOTemplate(productseries=series)
- view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+ view = POTemplateEditView(potemplate, LaunchpadTestRequest())
data = self._makeData(potemplate, name=existing_name)
view.validate(data)
self.assertEqual([u'Name is already in use.'], view.errors)
@@ -49,11 +88,50 @@
series = existing_potemplate.productseries
potemplate = self.factory.makePOTemplate(productseries=series)
- view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
+ view = POTemplateEditView(potemplate, LaunchpadTestRequest())
data = self._makeData(potemplate, translation_domain=existing_domain)
view.validate(data)
self.assertEqual([u'Domain is already in use.'], view.errors)
+ def test_detects_name_clash_on_sourcepackage_change(self):
+ # Detect changing to a source package that already has a template of
+ # the same name.
+ sourcepackage = self.factory.makeSourcePackage()
+ existing_potemplate = self.factory.makePOTemplate(
+ sourcepackage=sourcepackage)
+ potemplate = self.factory.makePOTemplate(
+ distroseries=sourcepackage.distroseries,
+ name=existing_potemplate.name)
+
+ view = POTemplateEditView(potemplate, LaunchpadTestRequest())
+ data = self._makeData(
+ potemplate, sourcepackagename=sourcepackage.sourcepackagename)
+ view.validate(data)
+ self.assertEqual(
+ [u'Source package already has a template with that same name.'],
+ view.errors)
+
+ def test_detects_domain_clash_on_sourcepackage_change(self):
+ # Detect changing to a source package that already has a template with
+ # the same translation domain.
+ sourcepackage = self.factory.makeSourcePackage()
+ existing_potemplate = self.factory.makePOTemplate(
+ sourcepackage=sourcepackage)
+ potemplate = self.factory.makePOTemplate(
+ distroseries=sourcepackage.distroseries,
+ translation_domain=existing_potemplate.translation_domain)
+
+ view = POTemplateEditView(potemplate, LaunchpadTestRequest())
+ data = self._makeData(
+ potemplate, sourcepackagename=sourcepackage.sourcepackagename)
+ view.validate(data)
+ self.assertEqual(
+ [u'Source package already has a template with that same domain.'],
+ view.errors)
+
+
+class TestPOTemplateAdminViewValidation(TestPOTemplateEditViewValidation):
+
def test_detects_name_clash_on_productseries_change(self):
# Detect changing to a productseries that already has a template of
# the same name.
@@ -86,42 +164,6 @@
[u'Series already has a template with that same domain.'],
view.errors)
- def test_detects_name_clash_on_sourcepackage_change(self):
- # Detect changing to a source package that already has a template of
- # the same name.
- sourcepackage = self.factory.makeSourcePackage()
- existing_potemplate = self.factory.makePOTemplate(
- sourcepackage=sourcepackage)
- potemplate = self.factory.makePOTemplate(
- distroseries=sourcepackage.distroseries,
- name=existing_potemplate.name)
-
- view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
- data = self._makeData(
- potemplate, sourcepackagename=sourcepackage.sourcepackagename)
- view.validate(data)
- self.assertEqual(
- [u'Source package already has a template with that same name.'],
- view.errors)
-
- def test_detects_domain_clash_on_sourcepackage_change(self):
- # Detect changing to a productseries that already has a template with
- # the same translation domain.
- sourcepackage = self.factory.makeSourcePackage()
- existing_potemplate = self.factory.makePOTemplate(
- sourcepackage=sourcepackage)
- potemplate = self.factory.makePOTemplate(
- distroseries=sourcepackage.distroseries,
- translation_domain=existing_potemplate.translation_domain)
-
- view = POTemplateAdminView(potemplate, LaunchpadTestRequest())
- data = self._makeData(
- potemplate, sourcepackagename=sourcepackage.sourcepackagename)
- view.validate(data)
- self.assertEqual(
- [u'Source package already has a template with that same domain.'],
- view.errors)
-
def test_detects_no_sourcepackage_or_productseries(self):
# Detect if no source package or productseries was selected.
potemplate = self.factory.makePOTemplate()
=== modified file 'lib/lp/translations/configure.zcml'
--- lib/lp/translations/configure.zcml 2011-07-30 14:21:23 +0000
+++ lib/lp/translations/configure.zcml 2011-08-25 19:53:52 +0000
@@ -431,15 +431,15 @@
<allow
interface="lp.translations.interfaces.potemplate.IPOTemplate"/>
<require
- permission="launchpad.Edit"
- set_attributes="
- owner priority description translation_domain path
- iscurrent"/>
- <require
permission="launchpad.TranslationsAdmin"
set_attributes="
- name productseries distroseries sourcepackagename
- sourcepackageversion binarypackagename languagepack
+ name owner priority description translation_domain path
+ iscurrent languagepack sourcepackagename"/>
+ <require
+ permission="launchpad.Admin"
+ set_attributes="
+ productseries distroseries
+ sourcepackageversion binarypackagename
source_file_format source_file date_last_updated
from_sourcepackagename header"/>
</class>
=== modified file 'lib/lp/translations/doc/rosetta-karma.txt'
--- lib/lp/translations/doc/rosetta-karma.txt 2011-05-27 19:53:20 +0000
+++ lib/lp/translations/doc/rosetta-karma.txt 2011-08-25 19:53:52 +0000
@@ -366,6 +366,7 @@
>>> login('test@xxxxxxxxxxxxx')
>>> form = {
... u'field.owner': u'test@xxxxxxxxxxxxx',
+ ... u'field.name': u'test',
... u'field.priority': u'0',
... u'field.description': u'This is a new description',
... u'field.actions.change': u'Change'}
=== modified file 'lib/lp/translations/interfaces/potemplate.py'
--- lib/lp/translations/interfaces/potemplate.py 2011-07-28 12:59:26 +0000
+++ lib/lp/translations/interfaces/potemplate.py 2011-08-25 19:53:52 +0000
@@ -33,6 +33,7 @@
from canonical.launchpad import _
from canonical.launchpad.interfaces.librarian import ILibraryFileAlias
from lp.app.errors import NotFoundError
+from lp.app.validators.name import valid_name
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.sourcepackage import ISourcePackage
from lp.registry.interfaces.sourcepackagename import ISourcePackageName
@@ -112,7 +113,7 @@
"unique name in its package. It's important to get this "
"correct, because Launchpad will recommend alternative "
"translations based on the name."),
- required=True))
+ constraint=valid_name, required=True))
translation_domain = exported(TextLine(
title=_("Translation domain"),
=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-edit.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-edit.txt 2010-10-18 22:24:59 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-edit.txt 2011-08-25 19:53:52 +0000
@@ -48,11 +48,6 @@
Owner will not see admin fields, only those fields designated for the
edit page.
- >>> browser.getControl(name='field.name').value
- Traceback (most recent call last):
- ...
- LookupError:...
-
>>> browser.getControl(name='field.header').value
Traceback (most recent call last):
...
@@ -123,6 +118,7 @@
The visible fields can be changed and saved.
+ >>> browser.getControl(name='field.name').value = u'evo'
>>> browser.getControl(name='field.translation_domain').value = u'evo'
>>> browser.getControl(name='field.priority').value = '100'
>>> browser.getControl(name='field.iscurrent').value = False
@@ -131,14 +127,17 @@
>>> browser.getControl(name='field.description').value = u'foo'
>>> browser.getControl('Change').click()
>>> print browser.url
- http://translations.launchpad.dev/evolution/trunk/+pots/evolution-2.2
+ http://translations.launchpad.dev/evolution/trunk/+pots/evo
The changed values will be stored and visible by accesing again the edit
page.
>>> browser.open(
... 'http://translations.launchpad.dev/evolution/trunk/+pots/'
- ... 'evolution-2.2/+edit')
+ ... 'evo/+edit')
+ >>> browser.getControl(name='field.name').value
+ 'evo'
+
>>> browser.getControl(name='field.translation_domain').value
'evo'
@@ -160,6 +159,11 @@
>>> previous_date_last_updated != evolution_template.date_last_updated
True
+Restore the template name for further tests.
+
+ >>> browser.getControl(name='field.name').value = u'evolution-2.2'
+ >>> browser.getControl('Change').click()
+
Priority range
--------------
@@ -173,7 +177,7 @@
... 'evolution-2.2/+edit')
>>> admin_browser.getControl(name='field.priority').value = '-1'
>>> admin_browser.getControl('Change').click()
- >>> print browser.url
+ >>> print admin_browser.url
http://translations.../evolution/trunk/+pots/evolution-2.2/+edit
>>> print_feedback_messages(admin_browser.contents)
@@ -185,7 +189,7 @@
... 'evolution-2.2/+edit')
>>> admin_browser.getControl(name='field.priority').value = '100001'
>>> admin_browser.getControl('Change').click()
- >>> print browser.url
+ >>> print admin_browser.url
http://translations.../evolution/trunk/+pots/evolution-2.2/+edit
>>> print_feedback_messages(admin_browser.contents)
=== modified file 'lib/lp/translations/stories/standalone/xx-series-templates.txt'
--- lib/lp/translations/stories/standalone/xx-series-templates.txt 2011-07-29 15:37:48 +0000
+++ lib/lp/translations/stories/standalone/xx-series-templates.txt 2011-08-25 19:53:52 +0000
@@ -90,15 +90,17 @@
>>> print utc_browser.url
http://.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+edit
+Administration page is inaccessible.
+
>>> utc_browser.open(
... 'http://translations.launchpad.dev/ubuntu/hoary/+templates')
>>> utc_browser.getLink(
- ... url='+source/evolution/+pots/evolution-2.2/+admin').click()
- >>> print utc_browser.url
- http://.../ubuntu/hoary/+source/evolution/+pots/evolution-2.2/+admin
+ ... url='+source/evolution/+pots/evolution-2.2/+admin')
+ Traceback (most recent call last):
+ ...
+ LinkNotFoundError
-Trying to edit/administer disabled templates brings them to the
-appropriate page.
+Trying to edit disabled templates brings them to the appropriate page.
>>> utc_browser.open(
... 'http://translations.launchpad.dev/ubuntu/hoary/+templates')
@@ -107,12 +109,15 @@
>>> print utc_browser.url
http://.../ubuntu/hoary/+source/evolution/+pots/disabled-template/+edit
+Administration page is inaccessible.
+
>>> utc_browser.open(
... 'http://translations.launchpad.dev/ubuntu/hoary/+templates')
>>> utc_browser.getLink(
- ... url='+source/evolution/+pots/disabled-template/+admin').click()
- >>> print utc_browser.url
- http://.../ubuntu/hoary/+source/evolution/+pots/disabled-template/+admin
+ ... url='+source/evolution/+pots/disabled-template/+admin')
+ Traceback (most recent call last):
+ ...
+ LinkNotFoundError
Links to the templates
=== modified file 'lib/lp/translations/templates/potemplate-index.pt'
--- lib/lp/translations/templates/potemplate-index.pt 2011-03-08 09:59:36 +0000
+++ lib/lp/translations/templates/potemplate-index.pt 2011-08-25 19:53:52 +0000
@@ -28,10 +28,6 @@
<strong>Owner:</strong>
<a tal:replace="structure context/owner/fmt:link">owner name</a>
</p>
- <div>
- <a href="+edit" class="edit sprite"
- tal:condition="context/required:launchpad.Edit">Change details</a>
- </div>
</div>
<div class="yui-g">
@@ -109,7 +105,7 @@
<h3>Administration</h3>
<p>If you want to synchronize these translations manually, you
can
- <tal:both condition="context/required:launchpad.Edit">
+ <tal:both condition="context/required:launchpad.TranslationsAdmin">
<a tal:attributes="href context/menu:navigation/upload/url"
class="add sprite">upload</a>
or
@@ -119,11 +115,16 @@
translation tarballs.
</p>
<div tal:condition="context/required:launchpad.TranslationsAdmin">
- <a tal:attributes="href
- context/menu:navigation/administer/url"
+ <a href="+edit" class="edit sprite"
+ tal:condition="context/required:launchpad.TranslationsAdmin">
+ Change details</a>
+ <tal:admin condition="context/required:launchpad.Admin">
+ â <a tal:attributes="href
+ context/menu:navigation/administer/url"
class="edit sprite">
- Administer this template
- </a>
+ Administer this template
+ </a>
+ </tal:admin>
</div>
</div>
</div>