Merge lp:~thumper/launchpad/recipe-inline-edit-recipe-text into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Approved by: Leonard Richardson
Approved revision: no longer in the source branch.
Merged at revision: 12402
Proposed branch: lp:~thumper/launchpad/recipe-inline-edit-recipe-text
Merge into: lp:launchpad
Prerequisite: lp:~thumper/launchpad/daily-ajax
Diff against target: 436 lines (+131/-56)
10 files modified
lib/canonical/launchpad/icing/style-3-0.css.in (+7/-7)
lib/lp/bugs/interfaces/bugsupervisor.py (+0/-3)
lib/lp/code/browser/sourcepackagerecipe.py (+7/-0)
lib/lp/code/errors.py (+16/-7)
lib/lp/code/interfaces/sourcepackagerecipe.py (+10/-2)
lib/lp/code/model/sourcepackagerecipe.py (+13/-2)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+46/-33)
lib/lp/code/templates/sourcepackagerecipe-index.pt (+5/-1)
lib/lp/code/windmill/tests/test_recipe_index.py (+26/-0)
versions.cfg (+1/-1)
To merge this branch: bzr merge lp:~thumper/launchpad/recipe-inline-edit-recipe-text
Reviewer Review Type Date Requested Status
Leonard Richardson (community) Approve
Review via email: mp+49585@code.launchpad.net

Commit message

[r=leonardr][bug=673530,708265] Allow inline editing of the recipe text.

Description of the change

This branch adds the ability to edit the recipe text on the
main recipe page.

As part of this I've tweaked the styles so we don't get the
horrible overlap at the top of the multiline editors. I
believe this was missed during the yui 3.1 -> 3.2 transition
as the yui- prefix became yui3-.

lib/lp/code/errors.py
 - annotated the RecipeParseError to be a 400 Bad Request error.
 - also for the NoSuchBranch and PrivateBranchRecipe errors as
   they can also be raised when setting recipe text
 - pulled the webservice_error up to the base class instead of
   having it in each of the three derived classes.

lib/lp/code/interfaces/sourcepackagerecipe.py
 - change the setRecipeText from being exported as a writable
   operatioln to be a mutator for the recipe_text attribute.
   I talked with Leonard about the stack of adapters here.
   The operate from bottom to top.

lib/lp/code/model/sourcepackagerecipe.py
 - in order to get the full error returned to the client, we
   need to call the "expose" method from lazr.restful. I
   chatted with Leonard about this one too as it seemed a
   little strange. There was some reason at some stage, but
   he couldn't recall the underlying reason at the time I
   was asking. We may perhaps want to re-evaluate the need
   for this method at some time.

To post a comment you must log in.
Revision history for this message
Leonard Richardson (leonardr) wrote :

I don't understand why you got rid of the CSS styles for edit-description and edit-commit-message. Did you already generalize those widgets, and you're just cleaning up the CSS?

Your annotations create a situation where setRecipeText is published in version 1.0 of the web service but absent in version devel, and in which you can PATCH ISourcePackageRecipe['recipe_text'] in devel but not in 1.0. I'd like to see some tests of that using launchpadlib. Your windmill test only tests that you can PATCH ISourcePackageRecipe['recipe_test'] in devel.

I don't think this is that big a deal, but if you care enough to distinguish between 1.0 and devel in the first place, you should test the different versions. Let me know if you need help with the tests. I'll start you out by saying that you can us launchpadlib_for("test", person, version="1.0") to get a launchpadlib that operates against 1.0.

review: Needs Fixing
Revision history for this message
Tim Penhey (thumper) wrote :

As discussed over mumble and IRC, the CSS styles were changed to
be based more on the classes rather than the IDs.

Tests have been added, as you're aware.

I've also bumped the lazr.restful version here.

Revision history for this message
Leonard Richardson (leonardr) wrote :

<leonardr> thumper, i don't think test_recipe_text tests anything? to test that you can edit the recipe text, you would have to call lp_save()
<thumper> do we have to call lp_save for mutators?
<leonardr> thumper: you don't have to call lp_save() when you invoke a named operation
<leonardr> but the purpose of a mutator is to hide a named operation so that it looks like normal field access
<thumper> leonardr: but you do for mutators?
<leonardr> and in that case you do need to call lp_save()
* thumper adds a lp_save()

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/icing/style-3-0.css.in'
--- lib/canonical/launchpad/icing/style-3-0.css.in 2011-02-14 00:58:22 +0000
+++ lib/canonical/launchpad/icing/style-3-0.css.in 2011-02-17 22:48:58 +0000
@@ -947,18 +947,18 @@
947 top: -3px;947 top: -3px;
948 margin-left: 0.5em;948 margin-left: 0.5em;
949 }949 }
950.yui-ieditor {950.yui3-ieditor {
951 padding-right: 288px;951 padding-right: 288px;
952 }952 }
953.lazr-multiline-edit .yui-ieditor {953h1 .yui3-ieditor-errors {
954 font-size: 0.5em;
955 }
956.lazr-multiline-edit .yui3-ieditor {
954 padding-right: 0;957 padding-right: 0;
955 }958 }
956.lazr-multiline-edit textarea {959.lazr-multiline-edit textarea {
957 max-width: inherit;960 max-width: inherit;
958 }961 }
959.lazr-multiline-edit .yui-ieditor-input {
960 padding-right: 5px !important;
961 }
962.widget-hd.js-action {962.widget-hd.js-action {
963 /* The js-action class is also used for non-links, for example, with963 /* The js-action class is also used for non-links, for example, with
964 expand/collapse sections. */964 expand/collapse sections. */
@@ -1825,8 +1825,8 @@
1825 font-size: 93%;1825 font-size: 93%;
1826 margin: 1em 0;1826 margin: 1em 0;
1827 }1827 }
1828div#edit-description .yui-ieditor-input,1828
1829div#edit-commit_message .yui-ieditor-input {1829.yui3-ieditor-multiline .yui3-ieditor-input {
1830 top: 0;1830 top: 0;
1831 }1831 }
18321832
18331833
=== modified file 'lib/lp/bugs/interfaces/bugsupervisor.py'
--- lib/lp/bugs/interfaces/bugsupervisor.py 2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/interfaces/bugsupervisor.py 2011-02-17 22:48:58 +0000
@@ -36,9 +36,6 @@
36 "all bugs and will receive email about all activity on all bugs "36 "all bugs and will receive email about all activity on all bugs "
37 "for this project, so that should be a factor in your decision. "37 "for this project, so that should be a factor in your decision. "
38 "The bug supervisor will also have access to all private bugs."),38 "The bug supervisor will also have access to all private bugs."),
39
40
41
42 required=False, vocabulary='ValidPersonOrTeam', readonly=True))39 required=False, vocabulary='ValidPersonOrTeam', readonly=True))
4340
44 @mutator_for(bug_supervisor)41 @mutator_for(bug_supervisor)
4542
=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
--- lib/lp/code/browser/sourcepackagerecipe.py 2011-02-15 11:31:56 +0000
+++ lib/lp/code/browser/sourcepackagerecipe.py 2011-02-17 22:48:58 +0000
@@ -72,6 +72,7 @@
72from lp.app.browser.lazrjs import (72from lp.app.browser.lazrjs import (
73 BooleanChoiceWidget,73 BooleanChoiceWidget,
74 InlineEditPickerWidget,74 InlineEditPickerWidget,
75 TextAreaEditorWidget,
75 )76 )
76from lp.app.browser.tales import format_link77from lp.app.browser.tales import format_link
77from lp.app.widgets.itemswidgets import (78from lp.app.widgets.itemswidgets import (
@@ -264,6 +265,12 @@
264 step_title='Select a PPA')265 step_title='Select a PPA')
265266
266 @property267 @property
268 def recipe_text_widget(self):
269 """The recipe text as widget HTML."""
270 recipe_text = ISourcePackageRecipe['recipe_text']
271 return TextAreaEditorWidget(self.context, recipe_text, title="")
272
273 @property
267 def daily_build_widget(self):274 def daily_build_widget(self):
268 return BooleanChoiceWidget(275 return BooleanChoiceWidget(
269 self.context, ISourcePackageRecipe['build_daily'],276 self.context, ISourcePackageRecipe['build_daily'],
270277
=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2011-01-21 21:12:58 +0000
+++ lib/lp/code/errors.py 2011-02-17 22:48:58 +0000
@@ -42,10 +42,17 @@
4242
43import httplib43import httplib
4444
45from lazr.restful.declarations import webservice_error45from bzrlib.plugins.builder.recipe import RecipeParseError
46from lazr.restful.declarations import (
47 error_status,
48 webservice_error,
49 )
4650
47from lp.app.errors import NameLookupFailed51from lp.app.errors import NameLookupFailed
4852
53# Annotate the RecipeParseError's with a 400 webservice status.
54error_status(400)(RecipeParseError)
55
4956
50class BadBranchMergeProposalSearchContext(Exception):57class BadBranchMergeProposalSearchContext(Exception):
51 """The context is not valid for a branch merge proposal search."""58 """The context is not valid for a branch merge proposal search."""
@@ -204,11 +211,15 @@
204class NoSuchBranch(NameLookupFailed):211class NoSuchBranch(NameLookupFailed):
205 """Raised when we try to load a branch that does not exist."""212 """Raised when we try to load a branch that does not exist."""
206213
214 webservice_error(400)
215
207 _message_prefix = "No such branch"216 _message_prefix = "No such branch"
208217
209218
210class PrivateBranchRecipe(Exception):219class PrivateBranchRecipe(Exception):
211220
221 webservice_error(400)
222
212 def __init__(self, branch):223 def __init__(self, branch):
213 message = (224 message = (
214 'Recipe may not refer to private branch: %s' %225 'Recipe may not refer to private branch: %s' %
@@ -265,6 +276,8 @@
265class TooNewRecipeFormat(Exception):276class TooNewRecipeFormat(Exception):
266 """The format of the recipe supplied was too new."""277 """The format of the recipe supplied was too new."""
267278
279 webservice_error(400)
280
268 def __init__(self, supplied_format, newest_supported):281 def __init__(self, supplied_format, newest_supported):
269 super(TooNewRecipeFormat, self).__init__()282 super(TooNewRecipeFormat, self).__init__()
270 self.supplied_format = supplied_format283 self.supplied_format = supplied_format
@@ -273,6 +286,8 @@
273286
274class RecipeBuildException(Exception):287class RecipeBuildException(Exception):
275288
289 webservice_error(400)
290
276 def __init__(self, recipe, distroseries, template):291 def __init__(self, recipe, distroseries, template):
277 self.recipe = recipe292 self.recipe = recipe
278 self.distroseries = distroseries293 self.distroseries = distroseries
@@ -283,8 +298,6 @@
283class TooManyBuilds(RecipeBuildException):298class TooManyBuilds(RecipeBuildException):
284 """A build was requested that exceeded the quota."""299 """A build was requested that exceeded the quota."""
285300
286 webservice_error(400)
287
288 def __init__(self, recipe, distroseries):301 def __init__(self, recipe, distroseries):
289 RecipeBuildException.__init__(302 RecipeBuildException.__init__(
290 self, recipe, distroseries,303 self, recipe, distroseries,
@@ -295,8 +308,6 @@
295class BuildAlreadyPending(RecipeBuildException):308class BuildAlreadyPending(RecipeBuildException):
296 """A build was requested when an identical build was already pending."""309 """A build was requested when an identical build was already pending."""
297310
298 webservice_error(400)
299
300 def __init__(self, recipe, distroseries):311 def __init__(self, recipe, distroseries):
301 RecipeBuildException.__init__(312 RecipeBuildException.__init__(
302 self, recipe, distroseries,313 self, recipe, distroseries,
@@ -306,8 +317,6 @@
306class BuildNotAllowedForDistro(RecipeBuildException):317class BuildNotAllowedForDistro(RecipeBuildException):
307 """A build was requested against an unsupported distroseries."""318 """A build was requested against an unsupported distroseries."""
308319
309 webservice_error(400)
310
311 def __init__(self, recipe, distroseries):320 def __init__(self, recipe, distroseries):
312 RecipeBuildException.__init__(321 RecipeBuildException.__init__(
313 self, recipe, distroseries,322 self, recipe, distroseries,
314323
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-14 01:48:57 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2011-02-17 22:48:58 +0000
@@ -25,13 +25,17 @@
25 export_as_webservice_entry,25 export_as_webservice_entry,
26 export_write_operation,26 export_write_operation,
27 exported,27 exported,
28 mutator_for,
29 operation_for_version,
28 operation_parameters,30 operation_parameters,
31 operation_removed_in_version,
29 REQUEST_USER,32 REQUEST_USER,
30 )33 )
31from lazr.restful.fields import (34from lazr.restful.fields import (
32 CollectionField,35 CollectionField,
33 Reference,36 Reference,
34 )37 )
38from lazr.restful.interface import copy_field
35from zope.interface import (39from zope.interface import (
36 Attribute,40 Attribute,
37 Interface,41 Interface,
@@ -97,7 +101,7 @@
97 required=True, readonly=True,101 required=True, readonly=True,
98 vocabulary='ValidPersonOrTeam'))102 vocabulary='ValidPersonOrTeam'))
99103
100 recipe_text = exported(Text())104 recipe_text = exported(Text(readonly=True))
101105
102 def isOverQuota(requester, distroseries):106 def isOverQuota(requester, distroseries):
103 """True if the recipe/requester/distroseries combo is >= quota.107 """True if the recipe/requester/distroseries combo is >= quota.
@@ -135,7 +139,11 @@
135class ISourcePackageRecipeEdit(Interface):139class ISourcePackageRecipeEdit(Interface):
136 """ISourcePackageRecipe methods that require launchpad.Edit permission."""140 """ISourcePackageRecipe methods that require launchpad.Edit permission."""
137141
138 @operation_parameters(recipe_text=Text())142 @mutator_for(ISourcePackageRecipeView['recipe_text'])
143 @operation_for_version("devel")
144 @operation_parameters(
145 recipe_text=copy_field(
146 ISourcePackageRecipeView['recipe_text']))
139 @export_write_operation()147 @export_write_operation()
140 def setRecipeText(recipe_text):148 def setRecipeText(recipe_text):
141 """Set the text of the recipe."""149 """Set the text of the recipe."""
142150
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2011-02-17 04:58:44 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2011-02-17 22:48:58 +0000
@@ -15,7 +15,10 @@
15 datetime,15 datetime,
16 timedelta,16 timedelta,
17 )17 )
18
19from bzrlib.plugins.builder.recipe import RecipeParseError
18from lazr.delegates import delegates20from lazr.delegates import delegates
21from lazr.restful.error import expose
19from pytz import utc22from pytz import utc
20from storm.expr import (23from storm.expr import (
21 And,24 And,
@@ -50,7 +53,10 @@
50from lp.code.errors import (53from lp.code.errors import (
51 BuildAlreadyPending,54 BuildAlreadyPending,
52 BuildNotAllowedForDistro,55 BuildNotAllowedForDistro,
56 NoSuchBranch,
57 PrivateBranchRecipe,
53 TooManyBuilds,58 TooManyBuilds,
59 TooNewRecipeFormat,
54 )60 )
55from lp.code.interfaces.sourcepackagerecipe import (61from lp.code.interfaces.sourcepackagerecipe import (
56 ISourcePackageRecipe,62 ISourcePackageRecipe,
@@ -160,8 +166,13 @@
160 return self._recipe_data.base_branch166 return self._recipe_data.base_branch
161167
162 def setRecipeText(self, recipe_text):168 def setRecipeText(self, recipe_text):
163 parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)169 try:
164 self._recipe_data.setRecipe(parsed)170 parsed = SourcePackageRecipeData.getParsedRecipe(recipe_text)
171 self._recipe_data.setRecipe(parsed)
172 except (RecipeParseError, NoSuchBranch, PrivateBranchRecipe,
173 TooNewRecipeFormat) as e:
174 expose(e)
175 raise
165176
166 @property177 @property
167 def recipe_text(self):178 def recipe_text(self):
168179
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-17 10:10:29 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2011-02-17 22:48:58 +0000
@@ -616,11 +616,40 @@
616 self.assertEqual([build], list(recipe.getBuilds()))616 self.assertEqual([build], list(recipe.getBuilds()))
617 self.assertEqual([], list(recipe.getPendingBuilds()))617 self.assertEqual([], list(recipe.getPendingBuilds()))
618618
619 def test_setRecipeText_private_base_branch(self):
620 source_package_recipe = self.factory.makeSourcePackageRecipe()
621 with person_logged_in(source_package_recipe.owner):
622 branch = self.factory.makeAnyBranch(
623 private=True, owner=source_package_recipe.owner)
624 recipe_text = self.factory.makeRecipeText(branch)
625 e = self.assertRaises(
626 PrivateBranchRecipe, source_package_recipe.setRecipeText,
627 recipe_text)
628 self.assertEqual(
629 'Recipe may not refer to private branch: %s' %
630 branch.bzr_identity, str(e))
631
632 def test_setRecipeText_private_referenced_branch(self):
633 source_package_recipe = self.factory.makeSourcePackageRecipe()
634 with person_logged_in(source_package_recipe.owner):
635 base_branch = self.factory.makeAnyBranch(
636 owner=source_package_recipe.owner)
637 referenced_branch = self.factory.makeAnyBranch(
638 private=True, owner=source_package_recipe.owner)
639 recipe_text = self.factory.makeRecipeText(
640 base_branch, referenced_branch)
641 e = self.assertRaises(
642 PrivateBranchRecipe, source_package_recipe.setRecipeText,
643 recipe_text)
644 self.assertEqual(
645 'Recipe may not refer to private branch: %s' %
646 referenced_branch.bzr_identity, str(e))
647
619 def test_getBuilds_ignores_disabled_archive(self):648 def test_getBuilds_ignores_disabled_archive(self):
620 # Builds into a disabled archive aren't returned.649 # Builds into a disabled archive aren't returned.
621 archive = self.factory.makeArchive()650 archive = self.factory.makeArchive()
622 recipe = self.factory.makeSourcePackageRecipe()651 recipe = self.factory.makeSourcePackageRecipe()
623 build = self.factory.makeSourcePackageRecipeBuild(652 self.factory.makeSourcePackageRecipeBuild(
624 recipe=recipe, archive=archive)653 recipe=recipe, archive=archive)
625 with person_logged_in(archive.owner):654 with person_logged_in(archive.owner):
626 archive.disable()655 archive.disable()
@@ -825,7 +854,7 @@
825 branch = self.factory.makeBranch()854 branch = self.factory.makeBranch()
826 return MINIMAL_RECIPE_TEXT % branch.bzr_identity855 return MINIMAL_RECIPE_TEXT % branch.bzr_identity
827856
828 def makeRecipe(self, user=None, owner=None, recipe_text=None):857 def makeRecipe(self, user=None, owner=None, recipe_text=None, version='devel'):
829 # rockstar 21 Jul 2010 - This function does more commits than I'd858 # rockstar 21 Jul 2010 - This function does more commits than I'd
830 # like, but it's the result of the fact that the webservice runs in a859 # like, but it's the result of the fact that the webservice runs in a
831 # separate thread so doesn't get the database updates without those860 # separate thread so doesn't get the database updates without those
@@ -839,8 +868,9 @@
839 recipe_text = self.makeRecipeText()868 recipe_text = self.makeRecipeText()
840 db_archive = self.factory.makeArchive(owner=owner, name="recipe-ppa")869 db_archive = self.factory.makeArchive(owner=owner, name="recipe-ppa")
841 transaction.commit()870 transaction.commit()
842 launchpad = launchpadlib_for('test', user,871 launchpad = launchpadlib_for(
843 service_root=self.layer.appserver_root_url('api'))872 'test', user, version=version,
873 service_root=self.layer.appserver_root_url('api'))
844 login(ANONYMOUS)874 login(ANONYMOUS)
845 distroseries = ws_object(launchpad, db_distroseries)875 distroseries = ws_object(launchpad, db_distroseries)
846 ws_owner = ws_object(launchpad, owner)876 ws_owner = ws_object(launchpad, owner)
@@ -871,38 +901,21 @@
871 def test_recipe_text(self):901 def test_recipe_text(self):
872 recipe_text2 = self.makeRecipeText()902 recipe_text2 = self.makeRecipeText()
873 recipe = self.makeRecipe()[0]903 recipe = self.makeRecipe()[0]
904 recipe.recipe_text = recipe_text2
905 recipe.lp_save()
906 self.assertEqual(recipe_text2, recipe.recipe_text)
907
908 def test_recipe_text_setRecipeText_not_in_devel(self):
909 recipe = self.makeRecipe()[0]
910 method = getattr(recipe, 'setRecipeText', None)
911 self.assertIs(None, method)
912
913 def test_recipe_text_setRecipeText_in_one_zero(self):
914 recipe_text2 = self.makeRecipeText()
915 recipe = self.makeRecipe(version='1.0')[0]
874 recipe.setRecipeText(recipe_text=recipe_text2)916 recipe.setRecipeText(recipe_text=recipe_text2)
875 self.assertEqual(recipe_text2, recipe.recipe_text)917 self.assertEqual(recipe_text2, recipe.recipe_text)
876918
877 def test_setRecipeText_private_base_branch(self):
878 source_package_recipe = self.factory.makeSourcePackageRecipe()
879 with person_logged_in(source_package_recipe.owner):
880 branch = self.factory.makeAnyBranch(
881 private=True, owner=source_package_recipe.owner)
882 recipe_text = self.factory.makeRecipeText(branch)
883 e = self.assertRaises(
884 PrivateBranchRecipe, source_package_recipe.setRecipeText,
885 recipe_text)
886 self.assertEqual(
887 'Recipe may not refer to private branch: %s' %
888 branch.bzr_identity, str(e))
889
890 def test_setRecipeText_private_referenced_branch(self):
891 source_package_recipe = self.factory.makeSourcePackageRecipe()
892 with person_logged_in(source_package_recipe.owner):
893 base_branch = self.factory.makeAnyBranch(
894 owner=source_package_recipe.owner)
895 referenced_branch = self.factory.makeAnyBranch(
896 private=True, owner=source_package_recipe.owner)
897 recipe_text = self.factory.makeRecipeText(
898 base_branch, referenced_branch)
899 e = self.assertRaises(
900 PrivateBranchRecipe, source_package_recipe.setRecipeText,
901 recipe_text)
902 self.assertEqual(
903 'Recipe may not refer to private branch: %s' %
904 referenced_branch.bzr_identity, str(e))
905
906 def test_getRecipe(self):919 def test_getRecipe(self):
907 """Person.getRecipe returns the named recipe."""920 """Person.getRecipe returns the named recipe."""
908 recipe, user = self.makeRecipe()[:-1]921 recipe, user = self.makeRecipe()[:-1]
909922
=== modified file 'lib/lp/code/templates/sourcepackagerecipe-index.pt'
--- lib/lp/code/templates/sourcepackagerecipe-index.pt 2011-02-11 01:50:20 +0000
+++ lib/lp/code/templates/sourcepackagerecipe-index.pt 2011-02-17 22:48:58 +0000
@@ -12,6 +12,10 @@
12 .binary-build .indent {12 .binary-build .indent {
13 padding-left: 2em;13 padding-left: 2em;
14 }14 }
15 #edit-recipe_text {
16 font-family: "UbuntuBeta Mono","Ubuntu Mono",monospace;
17 margin-top: -15px;
18 }
15 </style>19 </style>
16</metal:block>20</metal:block>
1721
@@ -164,7 +168,7 @@
164 </div>168 </div>
165 <div class='portlet'>169 <div class='portlet'>
166 <h2>Recipe contents</h2>170 <h2>Recipe contents</h2>
167 <pre tal:content="context/recipe_text" />171 <tal:widget replace="structure view/recipe_text_widget"/>
168 </div>172 </div>
169 </div>173 </div>
170 </div>174 </div>
171175
=== modified file 'lib/lp/code/windmill/tests/test_recipe_index.py'
--- lib/lp/code/windmill/tests/test_recipe_index.py 2011-02-14 00:58:45 +0000
+++ lib/lp/code/windmill/tests/test_recipe_index.py 2011-02-17 22:48:58 +0000
@@ -44,3 +44,29 @@
44 freshly_fetched_recipe = Store.of(recipe).find(44 freshly_fetched_recipe = Store.of(recipe).find(
45 SourcePackageRecipe, SourcePackageRecipe.id == recipe.id).one()45 SourcePackageRecipe, SourcePackageRecipe.id == recipe.id).one()
46 self.assertTrue(freshly_fetched_recipe.build_daily)46 self.assertTrue(freshly_fetched_recipe.build_daily)
47
48 def test_inline_recipe_text_errors(self):
49 eric = self.factory.makePerson(
50 name="eric", displayname="Eric the Viking", password="test",
51 email="eric@example.com")
52 recipe = self.factory.makeSourcePackageRecipe(owner=eric)
53 recipe_text = recipe.recipe_text + 'merge WTF?'
54 transaction.commit()
55
56 client, start_url = self.getClientFor(recipe, user=eric)
57 client.click(
58 jquery=u'("div#edit-recipe_text a.yui3-editable_text-trigger")[0]')
59 client.waits.forElement(
60 jquery=u'("div#edit-recipe_text textarea.yui3-ieditor-input")',
61 timeout=FOR_ELEMENT)
62 client.type(
63 text=recipe_text,
64 jquery=u'("div#edit-recipe_text textarea.yui3-ieditor-input")[0]')
65 client.click(
66 jquery=u'("div#edit-recipe_text button.yui3-ieditor-submit_button")[0]')
67 client.waits.forElement(
68 jquery=u'("div#edit-recipe_text textarea.yui3-ieditor-errors")',
69 timeout=FOR_ELEMENT)
70 client.asserts.assertTextIn(
71 jquery=u'("div#edit-recipe_text textarea.yui3-ieditor-errors")[0]',
72 validator=u'End of line while looking for the branch url.')
4773
=== modified file 'versions.cfg'
--- versions.cfg 2011-02-11 04:25:27 +0000
+++ versions.cfg 2011-02-17 22:48:58 +0000
@@ -33,7 +33,7 @@
33lazr.delegates = 1.2.033lazr.delegates = 1.2.0
34lazr.enum = 1.1.234lazr.enum = 1.1.2
35lazr.lifecycle = 1.135lazr.lifecycle = 1.1
36lazr.restful = 0.16.0-r17136lazr.restful = 0.16.1
37lazr.restfulclient = 0.11.237lazr.restfulclient = 0.11.2
38lazr.smtptest = 1.138lazr.smtptest = 1.1
39lazr.testing = 0.1.139lazr.testing = 0.1.1