← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/launchpad/dd-addseries-bug-745793 into lp:launchpad

 

Gavin Panella has proposed merging lp:~allenap/launchpad/dd-addseries-bug-745793 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #745793 in Launchpad itself: "Simplify Distribution:+addseries"
  https://bugs.launchpad.net/launchpad/+bug/745793

For more details, see:
https://code.launchpad.net/~allenap/launchpad/dd-addseries-bug-745793/+merge/56253

Simplify the Distribution:+addseries page by removing the title,
description and parent_series fields (a mock-up can be found on
https://dev.launchpad.net/LEP/DerivativeDistributions). It also adds
some pop-up help and more descriptive text.

I haven't followed the mock-up exactly. For example, where the mock-up
uses the verb "Register" I have used "Add". Links to the page are all
"Add a series", the page itself is named +addseries, and it's less
fancy too.

In the process of adding the help links I created another feature for
LaunchpadFormView. If help_links is set, these are applied to the
widgets in the form, and launchpad-form.pt then includes them in the
generated widget rows. The help_link widget attribute can also be set
with custom_widget():

    custom_widget("name", TextWidget, help_link=u"/+help/...")

-- 
https://code.launchpad.net/~allenap/launchpad/dd-addseries-bug-745793/+merge/56253
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/dd-addseries-bug-745793 into lp:launchpad.
=== modified file 'lib/lp/app/browser/launchpadform.py'
--- lib/lp/app/browser/launchpadform.py	2010-12-13 08:23:15 +0000
+++ lib/lp/app/browser/launchpadform.py	2011-04-04 19:56:15 +0000
@@ -38,8 +38,10 @@
     providedBy,
     )
 from zope.interface.advice import addClassAdvisor
-from zope.traversing.interfaces import ITraversable, TraversalError
-
+from zope.traversing.interfaces import (
+    ITraversable,
+    TraversalError,
+    )
 
 from canonical.launchpad.webapp.interfaces import (
     IAlwaysSubmittedWidget,
@@ -189,6 +191,13 @@
             self.form_fields, self.prefix, context, self.request,
             data=self.initial_values, adapters=self.adapters,
             ignore_request=False)
+        for field_name, help_link in self.help_links.iteritems():
+            self.widgets[field_name].help_link = help_link
+
+    @property
+    def help_links(self):
+        """Dictionary mapping field names to help links."""
+        return {}
 
     @property
     def adapters(self):

=== modified file 'lib/lp/app/browser/tests/test_launchpadform.py'
--- lib/lp/app/browser/tests/test_launchpadform.py	2010-11-25 03:21:16 +0000
+++ lib/lp/app/browser/tests/test_launchpadform.py	2011-04-04 19:56:15 +0000
@@ -5,16 +5,23 @@
 
 __metaclass__ = type
 
+from lxml import html
+from z3c.ptcompat import ViewPageTemplateFile
 from zope.interface import Interface
 from zope.schema import Text
 
+from canonical.config import config
 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
 from canonical.testing.layers import DatabaseFunctionalLayer
 from lp.app.browser.launchpadform import (
     has_structured_doc,
     LaunchpadFormView,
     )
-from lp.testing import TestCase, test_tales
+from lp.testing import (
+    test_tales,
+    TestCase,
+    TestCaseWithFactory,
+    )
 
 
 class TestInterface(Interface):
@@ -64,3 +71,65 @@
                 'widget/query:has-structured-doc', widget=normal_widget))
         self.assertTrue(test_tales(
                 'widget/query:has-structured-doc', widget=structured_widget))
+
+
+class TestHelpLinksInterface(Interface):
+    """Test interface for the view below."""
+
+    nickname = Text(title=u'name')
+
+    displayname = Text(title=u'name')
+
+
+class TestHelpLinksView(LaunchpadFormView):
+    """A trivial view that contains help links."""
+
+    schema = TestHelpLinksInterface
+
+    page_title = u"TestHelpLinksView"
+    template = ViewPageTemplateFile(
+        config.root + '/lib/lp/app/templates/generic-edit.pt')
+
+    help_links = {
+        "nickname": u"http://widget.example.com/name";,
+        "displayname": u"http://widget.example.com/displayname";,
+        }
+
+
+class TestHelpLinks(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_help_links_on_widget(self):
+        # The values in a view's help_links dictionary gets copied into the
+        # corresponding widgets' help_link attributes.
+        request = LaunchpadTestRequest()
+        view = TestHelpLinksView(None, request)
+        view.initialize()
+        nickname_widget, displayname_widget = list(view.widgets)
+        self.assertEqual(
+            u"http://widget.example.com/name";,
+            nickname_widget.help_link)
+        self.assertEqual(
+            u"http://widget.example.com/displayname";,
+            displayname_widget.help_link)
+
+    def test_help_links_render(self):
+        # The values in a view's help_links dictionary are rendered in the
+        # default generic-edit template.
+        user = self.factory.makePerson()
+        request = LaunchpadTestRequest(PATH_INFO="/")
+        request.setPrincipal(user)
+        view = TestHelpLinksView(user, request)
+        view.initialize()
+        root = html.fromstring(view.render())
+        [nickname_help_link] = root.cssselect(
+            "label[for$=nickname] ~ a[target=help]")
+        self.assertEqual(
+            u"http://widget.example.com/name";,
+            nickname_help_link.get("href"))
+        [displayname_help_link] = root.cssselect(
+            "label[for$=displayname] ~ a[target=help]")
+        self.assertEqual(
+            u"http://widget.example.com/displayname";,
+            displayname_help_link.get("href"))

=== modified file 'lib/lp/app/templates/launchpad-form.pt'
--- lib/lp/app/templates/launchpad-form.pt	2011-02-14 01:48:57 +0000
+++ lib/lp/app/templates/launchpad-form.pt	2011-04-04 19:56:15 +0000
@@ -105,7 +105,7 @@
                   error_class python:error and 'error' or None;
                   show_optional python:view.showOptionalMarker(field_name);
                   widget_class widget/widget_class|nothing;
-                  widget_help_link widget_help_link|nothing">
+                  widget_help_link widget_help_link|widget/help_link|nothing">
     <tal:is-visible condition="widget/visible">
       <tr
         tal:condition="python: view.isSingleLineLayout(field_name)"
@@ -124,7 +124,7 @@
             <tal:help-link condition="widget_help_link">
               <a tal:attributes="href widget_help_link"
                  target="help" class="sprite maybe">
-                &nbsp;<span class="invisible-link">Tag help</span>
+                &nbsp;<span class="invisible-link">(?)</span>
               </a>
             </tal:help-link>
           </div>
@@ -148,6 +148,12 @@
             <span tal:condition="show_optional"
                   class="fieldRequired">(Optional)</span>
             </tal:showlabel>
+            <tal:help-link condition="widget_help_link">
+              <a tal:attributes="href widget_help_link"
+                 target="help" class="sprite maybe">
+                &nbsp;<span class="invisible-link">(?)</span>
+              </a>
+            </tal:help-link>
             <div
               tal:condition="error"
               tal:content="structure error"
@@ -170,7 +176,7 @@
           <tal:help-link condition="widget_help_link">
             <a tal:attributes="href widget_help_link"
                target="help" class="sprite maybe">
-              &nbsp;<span class="invisible-link">Tag help</span>
+              &nbsp;<span class="invisible-link">(?)</span>
             </a>
           </tal:help-link>
           <div

=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml	2011-03-31 12:53:27 +0000
+++ lib/lp/registry/browser/configure.zcml	2011-04-04 19:56:15 +0000
@@ -1957,7 +1957,7 @@
         class="lp.registry.browser.distroseries.DistroSeriesAddView"
         facet="overview"
         permission="launchpad.Admin"
-        template="../../app/templates/generic-edit.pt">
+        template="../templates/distroseries-add.pt">
     </browser:page>
     <browser:page
         name="+addseries"
@@ -1965,7 +1965,7 @@
         class="lp.registry.browser.distroseries.DistroSeriesAddView"
         facet="overview"
         permission="launchpad.Append"
-        template="../../app/templates/generic-edit.pt">
+        template="../templates/distroseries-add.pt">
     </browser:page>
     <browser:page
         name="+initseries"

=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py	2011-03-31 12:53:27 +0000
+++ lib/lp/registry/browser/distroseries.py	2011-04-04 19:56:15 +0000
@@ -19,6 +19,7 @@
     'DistroSeriesView',
     ]
 
+from lazr.restful.interface import copy_field
 from zope.component import getUtility
 from zope.event import notify
 from zope.formlib import form
@@ -39,7 +40,6 @@
     _,
     helpers,
     )
-from canonical.launchpad.interfaces.launchpad import ILaunchBag
 from canonical.launchpad.webapp import (
     action,
     custom_widget,
@@ -504,31 +504,68 @@
         self.next_url = canonical_url(self.context)
 
 
+class IDistroSeriesAddForm(Interface):
+
+    name = copy_field(
+        IDistroSeries["name"], description=_(
+            "The name of this series as used for URLs."))
+
+    version = copy_field(
+        IDistroSeries["version"], description=_(
+            "The version of the new series."))
+
+    displayname = copy_field(
+        IDistroSeries["displayname"], description=_(
+            "The name of the new series as it would "
+            "appear in a paragraph."))
+
+    summary = copy_field(IDistroSeries["summary"])
+
+
 class DistroSeriesAddView(LaunchpadFormView):
     """A view to create an `IDistroSeries`."""
-    schema = IDistroSeries
+    schema = IDistroSeriesAddForm
     field_names = [
-        'name', 'displayname', 'title', 'summary', 'description', 'version',
-        'parent_series']
-
-    label = 'Register a series'
+        'name',
+        'version',
+        'displayname',
+        'summary',
+        ]
+
+    help_links = {
+        "name": u"/+help/distribution-add-series.html#codename",
+        }
+
+    label = 'Add a series'
     page_title = label
 
-    @action(_('Create Series'), name='create')
+    def validate(self, data):
+        # XXX: Rip this out before landing.
+        deprecated_field_names = [
+            'field.title',
+            'field.description',
+            'field.parent_series',
+            ]
+        deprecated_field_names_found = set(
+            self.request).intersection(deprecated_field_names)
+        if len(deprecated_field_names_found) > 0:
+            raise AssertionError(
+                "Deprecated fields: %s" % " ".join(
+                    deprecated_field_names_found))
+        super(DistroSeriesAddView, self).validate(data)
+
+    @action(_('Add Series'), name='create')
     def createAndAdd(self, action, data):
         """Create and add a new Distribution Series"""
-        registrant = getUtility(ILaunchBag).user
-
-        assert registrant is not None
         distroseries = self.context.newSeries(
             name=data['name'],
             displayname=data['displayname'],
-            title=data['title'],
+            title=data['displayname'],
             summary=data['summary'],
-            description=data['description'],
+            description=u"",
             version=data['version'],
-            parent_series=data['parent_series'],
-            registrant=registrant)
+            parent_series=None,
+            registrant=self.user)
         notify(ObjectCreatedEvent(distroseries))
         self.next_url = canonical_url(distroseries)
         return distroseries

=== modified file 'lib/lp/registry/browser/tests/distroseries-views.txt'
--- lib/lp/registry/browser/tests/distroseries-views.txt	2010-10-09 16:36:22 +0000
+++ lib/lp/registry/browser/tests/distroseries-views.txt	2011-04-04 19:56:15 +0000
@@ -86,6 +86,7 @@
     Changeslist: foo@xxxxxxx
     Status: DEVELOPMENT
 
+
 When the distroseries is released, i.e. when it goes from an unstable
 status (FUTURE, EXPERIMENTAL, DEVELOPMENT, FROZEN) to CURRENT, its
 'datereleased' field is set.
@@ -209,9 +210,9 @@
 
     >>> view = create_view(ubuntu, '+addseries')
     >>> print view.page_title
-    Register a series
+    Add a series
     >>> print view.label
-    Register a series
+    Add a series
 
     >>> print view.cancel_url
     http://launchpad.dev/ubuntu
@@ -219,19 +220,15 @@
     None
 
     >>> view.field_names
-    ['name', 'displayname', 'title', 'summary', 'description', 'version',
-     'parent_series']
+    ['name', 'version', 'displayname', 'summary']
 
 A distroseries is created whent the required field are submitted.
 
     >>> form = {
     ...     'field.name': 'sane',
     ...     'field.displayname': 'Sane Name',
-    ...     'field.title': 'Ubuntu Sane Name',
     ...     'field.summary': 'A stable series to introduce fnord.',
-    ...     'field.description': 'I am board filling this out',
     ...     'field.version': '2009.06',
-    ...     'field.parent_series': 'ubuntu/hoary',
     ...     'field.actions.create': 'Create Series',
     ...     }
     >>> view = create_initialized_view(ubuntu, '+addseries', form=form)
@@ -270,7 +267,6 @@
     >>> yo_form = dict(form)
     >>> yo_form['field.name'] = 'island'
     >>> yo_form['field.displayname'] = 'Island'
-    >>> yo_form['field.title'] = 'YouBuntu Island'
     >>> view = create_initialized_view(
     ...     youbuntu, name='+addseries', form=yo_form, principal=yo_driver)
     >>> view.errors
@@ -311,9 +307,7 @@
     >>> yo_form = dict(form)
     >>> del yo_form['field.actions.create']
     >>> yo_form['field.displayname'] = 'Mountain'
-    >>> yo_form['field.title'] = 'YouBuntu Mountain'
     >>> yo_form['field.summary'] = 'Mountain summary'
-    >>> yo_form['field.description'] = 'Mountain description'
     >>> yo_form['field.actions.change'] = 'Change'
     >>> view = create_initialized_view(
     ...     yo_series, name='+edit', form=yo_form, principal=yo_driver)

=== modified file 'lib/lp/registry/browser/tests/test_distroseries.py'
--- lib/lp/registry/browser/tests/test_distroseries.py	2011-03-29 19:05:34 +0000
+++ lib/lp/registry/browser/tests/test_distroseries.py	2011-04-04 19:56:15 +0000
@@ -10,12 +10,44 @@
 from canonical.testing.layers import DatabaseFunctionalLayer
 from lp.testing import (
     feature_flags,
+    person_logged_in,
     set_feature_flag,
     TestCaseWithFactory,
     )
 from lp.testing.views import create_initialized_view
 
 
+class TestDistroSeriesAddView(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_submit(self):
+        # When creating a new DistroSeries via DistroSeriesAddView, the title
+        # is set to the same as the displayname (title is, in any case,
+        # deprecated), the description is left empty, and parent_series is
+        # None (DistroSeriesInitializeView takes care of setting that).
+        user = self.factory.makePerson()
+        distribution = self.factory.makeDistribution(owner=user)
+        form = {
+            "field.name": u"polished",
+            "field.version": u"12.04",
+            "field.displayname": u"Polished Polecat",
+            "field.summary": u"Even The Register likes it.",
+            "field.actions.create": u"Add Series",
+            }
+        with person_logged_in(user):
+            create_initialized_view(distribution, "+addseries", form=form)
+        distroseries = distribution.getSeries(u"polished")
+        self.assertEqual(u"polished", distroseries.name)
+        self.assertEqual(u"12.04", distroseries.version)
+        self.assertEqual(u"Polished Polecat", distroseries.displayname)
+        self.assertEqual(u"Polished Polecat", distroseries.title)
+        self.assertEqual(u"Even The Register likes it.", distroseries.summary)
+        self.assertEqual(u"", distroseries.description)
+        self.assertIs(None, distroseries.parent_series)
+        self.assertEqual(user, distroseries.owner)
+
+
 class TestDistroSeriesInitializeView(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer

=== added file 'lib/lp/registry/templates/distroseries-add.pt'
--- lib/lp/registry/templates/distroseries-add.pt	1970-01-01 00:00:00 +0000
+++ lib/lp/registry/templates/distroseries-add.pt	2011-04-04 19:56:15 +0000
@@ -0,0 +1,21 @@
+<html
+  xmlns="http://www.w3.org/1999/xhtml";
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+  metal:use-macro="view/macro:page/main_only"
+  i18n:domain="launchpad">
+  <body>
+    <div metal:fill-slot="main">
+      <div metal:use-macro="context/@@launchpad_form/form">
+        <p metal:fill-slot="extra_info">
+          This page allows you to add a new distribution series.
+          <a href="/+help/distribution-add-series.html"
+             target="help" class="sprite maybe">
+            &nbsp;<span class="invisible-link">(?)</span>
+          </a>
+        </p>
+      </div>
+    </div>
+  </body>
+</html>