launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02161
[Merge] lp:~bac/launchpad/bug-662994 into lp:launchpad
Brad Crittenden has proposed merging lp:~bac/launchpad/bug-662994 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#662994 ~registry cannot rename a project group
https://bugs.launchpad.net/bugs/662994
= Summary =
A task for CHR and Curtis is to change project group names. The
registry team has never had the ability to do so.
== Proposed fix ==
Move the permission of name from launchpad.Edit to launchpad.Moderate
and from the +edit to the +review page.
== Pre-implementation notes ==
Talk with Curtis
== Implementation details ==
The change described gives ~registry the ability to change a PG name but
removes it from the PG owner. This effect is necessary due to the way
our permissions are partitioned between owners, admins, and registry.
On a positive note, it brings behavior of PG editing in-line with projects.
I also changed the matchers to expose 'Contains' in __all__ which had
been inadvertently left out.
== Tests ==
bin/test -vvm lp.registry -t xx-project-edit.txt -t test_projectgroup
== Demo and Q/A ==
= Launchpad lint =
I will fix these lint issues.
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/registry/stories/project/xx-project-edit.txt
lib/lp/registry/tests/test_projectgroup.py
lib/lp/registry/stories/object/xx-nameblacklist.txt
lib/lp/registry/interfaces/projectgroup.py
lib/lp/registry/browser/tests/test_projectgroup.py
lib/lp/testing/matchers.py
lib/lp/registry/browser/project.py
./lib/lp/registry/stories/object/xx-nameblacklist.txt
7: source has bad indentation.
14: source exceeds 78 characters.
20: source has bad indentation.
24: source exceeds 78 characters.
30: source has bad indentation.
39: source has bad indentation.
56: source has bad indentation.
61: source has bad indentation.
./lib/lp/testing/matchers.py
144: E231 missing whitespace after ':'
236: Line exceeds 78 characters.
--
https://code.launchpad.net/~bac/launchpad/bug-662994/+merge/43647
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~bac/launchpad/bug-662994 into lp:launchpad.
=== modified file 'lib/lp/registry/browser/project.py'
--- lib/lp/registry/browser/project.py 2010-12-01 04:27:44 +0000
+++ lib/lp/registry/browser/project.py 2010-12-14 14:41:27 +0000
@@ -350,11 +350,11 @@
class ProjectEditView(LaunchpadEditFormView):
"""View class that lets you edit a Project object."""
implements(IProjectGroupEditMenu)
-
label = "Change project group details"
+ page_title = label
schema = IProjectGroup
field_names = [
- 'name', 'displayname', 'title', 'summary', 'description',
+ 'displayname', 'title', 'summary', 'description',
'bug_reporting_guidelines', 'bug_reported_acknowledgement',
'homepageurl', 'bugtracker', 'sourceforgeproject',
'freshmeatproject', 'wikiurl']
@@ -388,8 +388,10 @@
self.field_names = self.default_field_names[:]
admin = check_permission('launchpad.Admin', self.context)
if not admin:
+ self.field_names.remove('owner')
+ moderator = check_permission('launchpad.Moderate', self.context)
+ if not moderator:
self.field_names.remove('name')
- self.field_names.remove('owner')
super(ProjectReviewView, self).setUpFields()
self.form_fields = self._createAliasesField() + self.form_fields
if admin:
=== added file 'lib/lp/registry/browser/tests/test_projectgroup.py'
--- lib/lp/registry/browser/tests/test_projectgroup.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/tests/test_projectgroup.py 2010-12-14 14:41:27 +0000
@@ -0,0 +1,84 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for project group views."""
+
+__metaclass__ = type
+
+from zope.component import getUtility
+from zope.security.interfaces import Unauthorized
+from testtools.matchers import Not
+from canonical.launchpad.webapp import canonical_url
+from canonical.launchpad.webapp.interfaces import ILaunchBag
+from canonical.testing.layers import DatabaseFunctionalLayer
+from lp.registry.interfaces.person import IPersonSet
+from lp.testing import (
+ celebrity_logged_in,
+ person_logged_in,
+ TestCaseWithFactory,
+ )
+from lp.testing.matchers import (
+ Contains,
+ )
+from lp.testing.sampledata import ADMIN_EMAIL
+from lp.testing.views import create_initialized_view
+
+
+class TestProjectGroupEditView(TestCaseWithFactory):
+ """Tests the edit view."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestProjectGroupEditView, self).setUp()
+ self.project_group = self.factory.makeProject(name='grupo')
+
+ def test_links_admin(self):
+ # An admin can change details and administer a project group.
+ with celebrity_logged_in('admin'):
+ user = getUtility(ILaunchBag).user
+ view = create_initialized_view(self.project_group, '+index',
+ principal=user)
+ contents = view.render()
+ self.assertThat(contents, Contains("Change details"))
+ self.assertThat(contents, Contains("Administer"))
+
+ def test_links_registry_expert(self):
+ # A registry expert cannot change details but can administer a project
+ # group.
+ with celebrity_logged_in('registry_experts'):
+ user = getUtility(ILaunchBag).user
+ view = create_initialized_view(self.project_group, '+index',
+ principal=user)
+ contents = view.render()
+ self.assertThat(contents, Not(Contains("Change details")))
+ self.assertThat(contents, Contains("Administer"))
+
+ def test_links_owner(self):
+ # An owner can change details but not administer a project group.
+ with person_logged_in(self.project_group.owner):
+ user = getUtility(ILaunchBag).user
+ view = create_initialized_view(self.project_group, '+index',
+ principal=user)
+ contents = view.render()
+ self.assertThat(contents, Contains("Change details"))
+ self.assertThat(contents, Not(Contains("Administer")))
+
+ def test_edit_view_admin(self):
+ admin = getUtility(IPersonSet).getByEmail(ADMIN_EMAIL)
+ browser = self.getUserBrowser(user=admin)
+ browser.open(canonical_url(self.project_group, view_name='+edit'))
+ browser.open(canonical_url(self.project_group, view_name='+review'))
+
+ def test_edit_view_registry_expert(self):
+ registry_expert = self.factory.makeRegistryExpert()
+ browser = self.getUserBrowser(user=registry_expert)
+ url = canonical_url(self.project_group, view_name='+edit')
+ self.assertRaises(Unauthorized, browser.open, url)
+ browser.open(canonical_url(self.project_group, view_name='+review'))
+
+ def test_edit_view_owner(self):
+ browser = self.getUserBrowser(user=self.project_group.owner)
+ browser.open(canonical_url(self.project_group, view_name='+edit'))
+ url = canonical_url(self.project_group, view_name='+review')
+ self.assertRaises(Unauthorized, browser.open, url)
=== modified file 'lib/lp/registry/interfaces/projectgroup.py'
--- lib/lp/registry/interfaces/projectgroup.py 2010-12-01 20:53:19 +0000
+++ lib/lp/registry/interfaces/projectgroup.py 2010-12-14 14:41:27 +0000
@@ -104,6 +104,15 @@
title=_('Reviewed'), required=False,
description=_("Whether or not this project group has been "
"reviewed.")))
+ name = exported(
+ ProjectNameField(
+ title=_('Name'),
+ required=True,
+ description=_(
+ "A unique name, used in URLs, identifying the project "
+ "group. All lowercase, no special characters. "
+ "Examples: apache, mozilla, gimp."),
+ constraint=name_validator))
class IProjectGroupPublic(
@@ -133,16 +142,6 @@
description=_("Project group registrant. Must be a valid "
"Launchpad Person.")))
- name = exported(
- ProjectNameField(
- title=_('Name'),
- required=True,
- description=_(
- "A unique name, used in URLs, identifying the project "
- "group. All lowercase, no special characters. "
- "Examples: apache, mozilla, gimp."),
- constraint=name_validator))
-
displayname = exported(
TextLine(
title=_('Display Name'),
=== modified file 'lib/lp/registry/stories/object/xx-nameblacklist.txt'
--- lib/lp/registry/stories/object/xx-nameblacklist.txt 2010-11-03 20:38:32 +0000
+++ lib/lp/registry/stories/object/xx-nameblacklist.txt 2010-12-14 14:41:27 +0000
@@ -18,7 +18,7 @@
Try renaming a project to a blacklisted name:
>>> admin_browser.open('http://launchpad.dev/mozilla')
->>> admin_browser.getLink('Change details').click()
+>>> admin_browser.getLink('Administer').click()
>>> admin_browser.getControl('Name', index=0).value = 'blacklisted'
>>> admin_browser.getControl('Change Details').click()
>>> "The name 'blacklisted' has been blocked by the Launchpad administrators" \
@@ -51,7 +51,7 @@
be a Launchpad Celebrity.
We can edit the details of an object with a blacklisted name quite
-happily without generating
+happily without generating
>>> admin_browser.open('http://launchpad.dev/~admins')
>>> admin_browser.getLink('Change details').click()
@@ -62,4 +62,3 @@
[]
>>> "has been blocked" in admin_browser.contents
False
-
=== modified file 'lib/lp/registry/stories/project/xx-project-edit.txt'
--- lib/lp/registry/stories/project/xx-project-edit.txt 2010-08-20 18:13:20 +0000
+++ lib/lp/registry/stories/project/xx-project-edit.txt 2010-12-14 14:41:27 +0000
@@ -10,7 +10,6 @@
Change project group details...
>>> soup = find_main_content(browser.contents)
- >>> browser.getControl(name='field.name').value = 'new-name'
>>> browser.getControl('Display Name').value = 'New Name'
>>> browser.getControl('Title').value = 'New Title'
>>> browser.getControl('Project Group Summary').value = 'New Summary.'
@@ -20,7 +19,7 @@
>>> browser.getControl('Change Details').click()
>>> print browser.url
- http://launchpad.dev/new-name
+ http://launchpad.dev/gnome
Regular users can't access the +review page.
@@ -31,18 +30,19 @@
But administrators can access the page:
- >>> admin_browser.open('http://launchpad.dev/new-name')
+ >>> admin_browser.open('http://launchpad.dev/gnome')
>>> admin_browser.getLink('Administer').click()
>>> print admin_browser.url
- http://launchpad.dev/new-name/+review
+ http://launchpad.dev/gnome/+review
>>> print admin_browser.title
Review upstream project group...
-Mark the project as reviewed.
+Mark the project as reviewed and change the name.
>>> admin_browser.getControl('Reviewed').selected = True
+ >>> admin_browser.getControl(name='field.name').value = 'new-name'
>>> admin_browser.getControl('Change').click()
>>> print admin_browser.url
@@ -148,17 +148,14 @@
Traceback (most recent call last):
...
LookupError: label 'Registrant'
- >>> expert_browser.getControl('Name')
- Traceback (most recent call last):
- ...
- LookupError: label 'Name'
+ >>> expert_browser.getControl('Name').value = 'newer-name'
>>> expert_browser.getControl('Aliases').value = 'sleepy'
>>> expert_browser.getControl('Active').selected = False
>>> expert_browser.getControl('Reviewed').selected = False
>>> expert_browser.getControl('Change').click()
- >>> expert_browser.open('http://launchpad.dev/new-name')
+ >>> expert_browser.open('http://launchpad.dev/newer-name')
>>> expert_browser.getLink('Administer').click()
>>> print expert_browser.getControl('Aliases').value
sleepy
=== renamed file 'lib/lp/registry/tests/test_project.py' => 'lib/lp/registry/tests/test_projectgroup.py'
--- lib/lp/registry/tests/test_project.py 2010-12-08 17:22:23 +0000
+++ lib/lp/registry/tests/test_projectgroup.py 2010-12-14 14:41:27 +0000
@@ -7,8 +7,8 @@
from lazr.restfulclient.errors import ClientError
from zope.component import getUtility
+from zope.security.interfaces import Unauthorized
-from canonical.launchpad.ftests import login
from canonical.launchpad.webapp.errorlog import globalErrorUtility
from canonical.testing.layers import (
DatabaseFunctionalLayer,
@@ -17,6 +17,7 @@
from lp.registry.interfaces.projectgroup import IProjectGroupSet
from lp.testing import (
launchpadlib_for,
+ login_celebrity,
login_person,
TestCaseWithFactory,
)
@@ -108,6 +109,33 @@
self.assertEqual(self.project3, results[0])
+class TestProjectGroupPermissions(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestProjectGroupPermissions, self).setUp()
+ self.pg = self.factory.makeProject(name='my-project-group')
+
+ def test_attribute_changes_by_admin(self):
+ login_celebrity('admin')
+ self.pg.name = 'new-name'
+ self.pg.owner = self.factory.makePerson(name='project-group-owner')
+
+ def test_attribute_changes_by_registry_admin(self):
+ login_celebrity('registry_experts')
+ new_owner = self.factory.makePerson(name='project-group-owner')
+ self.pg.name = 'new-name'
+ self.assertRaises(Unauthorized,
+ setattr, self.pg, 'owner', new_owner)
+
+ def test_attribute_changes_by_owner(self):
+ login_person(self.pg.owner)
+ self.assertRaises(Unauthorized,
+ setattr, self.pg, 'name', 'new-name')
+ self.pg.owner = self.factory.makePerson(name='project-group-owner')
+
+
class TestLaunchpadlibAPI(TestCaseWithFactory):
layer = DatabaseFunctionalLayer
@@ -117,8 +145,8 @@
# is raised when trying to deactivate a project that has source
# releases.
last_oops = globalErrorUtility.getLastOopsReport()
+
launchpad = launchpadlib_for("test", "salgado", "WRITE_PUBLIC")
-
project = launchpad.projects['evolution']
project.active = False
e = self.assertRaises(ClientError, project.lp_save)
=== modified file 'lib/lp/testing/matchers.py'
--- lib/lp/testing/matchers.py 2010-10-19 22:28:51 +0000
+++ lib/lp/testing/matchers.py 2010-12-14 14:41:27 +0000
@@ -3,6 +3,7 @@
__metaclass__ = type
__all__ = [
+ 'Contains',
'DoesNotCorrectlyProvide',
'DoesNotProvide',
'HasQueryCount',
@@ -141,7 +142,7 @@
for query in self.query_collector.queries:
result.append(unicode(query).encode('utf8'))
return {'queries': Content(UTF8_TEXT, lambda:['\n'.join(result)])}
-
+
class IsNotProxied(Mismatch):
"""An object is not proxied."""