~launchpad-pqm/launchpad/devel

12293.1.11 by Curtis Hovey
Updated copyright.
1
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
8687.15.17 by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
1102.1.74 by David Allouche
$branch/+edit form
3
4
"""Branch views."""
5
6
__metaclass__ = type
7
1102.1.143 by David Allouche
Review fixes to Branch view.
8
__all__ = [
8120.2.11 by Jonathan Lange
Delete PersonBranchAddView and ProductBranchAddView, since the only
9
    'BranchAddView',
1102.1.143 by David Allouche
Review fixes to Branch view.
10
    'BranchContextMenu',
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
11
    'BranchDeletionView',
9570.12.1 by Tim Penhey
Add a link and simple view for editing the status.
12
    'BranchEditStatusView',
1102.1.143 by David Allouche
Review fixes to Branch view.
13
    'BranchEditView',
5350.3.2 by Tim Penhey
Allow any logged in user to edit the branch whiteboard.
14
    'BranchEditWhiteboardView',
6233.6.1 by Michael Hudson
stuff
15
    'BranchRequestImportView',
7211.1.4 by Tim Penhey
Changed view names and added test.
16
    'BranchReviewerEditView',
5579.2.1 by Tim Penhey
Some queue management
17
    'BranchMergeQueueView',
4795.2.10 by jml at canonical
Rename mirror failure to mirror status
18
    'BranchMirrorStatusView',
11515.8.3 by Brad Crittenden
Redesign code product page. Created code +portlet-codestatistics.
19
    'BranchMirrorMixin',
10456.2.13 by Brad Crittenden
Added browser support for other +setbranch options
20
    'BranchNameValidationMixin',
3283.3.2 by Brad Bollenbach
fixes based on talking to the sab this morning. tightening up the security model a bit by basing privileges on an IHasBug interface.
21
    'BranchNavigation',
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
22
    'BranchEditMenu',
3277.3.2 by David Allouche
better branch listings
23
    'BranchInProductView',
7675.477.1 by Paul Hummer
Reverted the reversion of the patch that went into devel by mistake
24
    'BranchUpgradeView',
7362.4.14 by Jonathan Lange
Move the URL specification into a class in Python, rather than ZCML.
25
    'BranchURL',
1102.1.143 by David Allouche
Review fixes to Branch view.
26
    'BranchView',
4333.2.2 by Tim Penhey
linked, albiet weirdly
27
    'BranchSubscriptionsView',
4414.5.4 by Tim Penhey
First cut at getting a page to register a merge proposal
28
    'RegisterBranchMergeProposalView',
8377.9.2 by Michael Hudson
allow retrying FAILING builds
29
    'TryImportAgainView',
1102.1.143 by David Allouche
Review fixes to Branch view.
30
    ]
31
3691.112.6 by David Allouche
properly validate branch name in branch/+edit
32
import cgi
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
33
from datetime import (
34
    datetime,
35
    timedelta,
36
    )
8137.17.24 by Barry Warsaw
thread merge
37
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
38
from lazr.enum import (
39
    EnumeratedType,
40
    Item,
41
    )
42
from lazr.lifecycle.event import ObjectModifiedEvent
43
from lazr.lifecycle.snapshot import Snapshot
44
from lazr.restful.interface import (
45
    copy_field,
46
    use_template,
47
    )
13931.2.1 by Steve Kowalik
Chip away at canonical.lazr a little more.
48
from lazr.restful.utils import smartquote
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
49
from lazr.uri import URI
1102.1.123 by david
schema supports ghost revisions
50
import pytz
13760.3.6 by Ian Booth
Fix imports
51
from zope.app.form import CustomWidgetFactory
7573.2.10 by Paul Hummer
Made the merge proposal creation form use a fixed width font
52
from zope.app.form.browser import TextAreaWidget
13760.3.6 by Ian Booth
Fix imports
53
from zope.app.form.browser.boolwidgets import CheckBoxWidget
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
54
from zope.component import (
55
    getUtility,
56
    queryAdapter,
57
    )
8971.24.15 by Tim Penhey
Make the edit view notify owner updates.
58
from zope.event import notify
6096.6.3 by Tim Penhey
Make Bazaar Experts and Launchpad administrators able to reassign a branch to anyone.
59
from zope.formlib import form
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
60
from zope.interface import (
61
    implements,
62
    Interface,
63
    providedBy,
64
    )
5430.1.1 by Tim Penhey
Remove the ability for junk branches to traverse to +register-merge.
65
from zope.publisher.interfaces import NotFound
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
66
from zope.schema import (
67
    Bool,
68
    Choice,
69
    Text,
70
    )
71
from zope.schema.vocabulary import (
72
    SimpleTerm,
73
    SimpleVocabulary,
74
    )
75
from zope.traversing.interfaces import IPathAdapter
1102.1.74 by David Allouche
$branch/+edit form
76
14605.1.1 by Curtis Hovey
Moved canonical.config to lp.services.
77
from lp.services.config import config
5001.2.1 by Tim Penhey
Add column and update the date_last_modified on UI updates.
78
from canonical.database.constants import UTC_NOW
14600.1.12 by Curtis Hovey
Move i18n to lp.
79
from lp import _
14593.2.9 by Curtis Hovey
Moved searchbuilder to lp.services.
80
from lp.services import searchbuilder
14557.1.11 by Curtis Hovey
Moved feeds to lp.feeds.
81
from lp.services.feeds.browser import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
82
    BranchFeedLink,
83
    FeedsMixin,
84
    )
14593.2.15 by Curtis Hovey
Moved helpers to lp.services.
85
from lp.services.helpers import truncate_text
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
86
from lp.services.webapp import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
87
    canonical_url,
88
    ContextMenu,
89
    enabled_with_permission,
90
    LaunchpadView,
91
    Link,
92
    Navigation,
93
    NavigationMenu,
94
    stepthrough,
95
    stepto,
96
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
97
from lp.services.webapp.authorization import (
14550.3.3 by Ian Booth
Improve sql used in security adaptor, migrate doc test to unit test, add tests
98
    check_permission,
99
    precache_permission_for_objects,
100
    )
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
101
from lp.services.webapp.interfaces import ICanonicalUrlData
102
from lp.services.webapp.menu import structured
13130.1.12 by Curtis Hovey
Sorted imports.
103
from lp.app.browser.launchpad import Hierarchy
11929.9.1 by Tim Penhey
Move launchpadform into lp.app.browser.
104
from lp.app.browser.launchpadform import (
105
    action,
106
    custom_widget,
107
    LaunchpadEditFormView,
108
    LaunchpadFormView,
109
    )
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
110
from lp.app.browser.lazrjs import EnumChoiceWidget
7675.822.7 by Jeroen Vermeulen
Got TAL rendering. Probably ugly though.
111
from lp.app.errors import NotFoundError
13130.1.12 by Curtis Hovey
Sorted imports.
112
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
12293.1.10 by Curtis Hovey
Formatted imports.
113
from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
114
from lp.app.widgets.suggestion import TargetBranchWidget
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
115
from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
116
from lp.bugs.interfaces.bug import IBugSet
117
from lp.bugs.interfaces.bugbranch import IBugBranch
10974.1.3 by Tim Penhey
Decorate the branch to make it cache most of the query calls.
118
from lp.bugs.interfaces.bugtask import UNRESOLVED_BUGTASK_STATUSES
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
119
from lp.code.browser.branchmergeproposal import (
120
    latest_proposals_for_each_branch,
121
    )
8120.2.6 by Jonathan Lange
Sort imports.
122
from lp.code.browser.branchref import BranchRef
11019.3.1 by Tim Penhey
Extract the DecoratedBug and DecorateBranch out of the browser/branch module and into its own. Use a DecoratedBranch for the new revisions.
123
from lp.code.browser.decorations import DecoratedBranch
7675.618.24 by Paul Hummer
Added links to the recipes pages and accompanying tests.
124
from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
9570.12.3 by Tim Penhey
More js bits.
125
from lp.code.enums import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
126
    BranchType,
127
    CodeImportResultStatus,
128
    CodeImportReviewStatus,
129
    RevisionControlSystems,
130
    UICreatableBranchType,
131
    )
10454.12.4 by James Westby
Use the new method from the existing browser code.
132
from lp.code.errors import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
133
    BranchCreationForbidden,
134
    BranchExists,
13750.1.4 by Aaron Bentley
Ensure view produces a nice notification.
135
    CannotUpgradeBranch,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
136
    CodeImportAlreadyRequested,
137
    CodeImportAlreadyRunning,
138
    CodeImportNotInReviewedState,
139
    InvalidBranchMergeProposal,
140
    )
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
141
from lp.code.interfaces.branch import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
142
    IBranch,
143
    user_has_special_branch_access,
144
    )
12082.2.3 by Ian Booth
Add tests and clean up tales
145
from lp.code.interfaces.branchcollection import IAllBranches
10155.5.1 by Tim Penhey
Make the less used fields on register merge proposal extra options.
146
from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
147
from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy
8120.2.7 by Jonathan Lange
Forbid product from being specified on the add form.
148
from lp.code.interfaces.branchtarget import IBranchTarget
8120.2.6 by Jonathan Lange
Sort imports.
149
from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
150
from lp.registry.interfaces.person import (
151
    IPerson,
152
    IPersonSet,
153
    )
8138.1.2 by Jonathan Lange
Run migrater over lp.code. Many tests broken and imports failing.
154
from lp.registry.interfaces.productseries import IProductSeries
10843.3.7 by Tim Penhey
If we are in the situation where the branch editor isn't in the team of the branch owner (as we get in the situation where an official package branch is being edited by an uploader), add the owner to the vocabulary used for the owner field.~
155
from lp.registry.vocabularies import UserTeamsParticipationPlusSelfVocabulary
11382.6.34 by Gavin Panella
Reformat imports in all files touched so far.
156
from lp.services.propertycache import cachedproperty
7675.822.7 by Jeroen Vermeulen
Got TAL rendering. Probably ugly though.
157
from lp.translations.interfaces.translationtemplatesbuild import (
158
    ITranslationTemplatesBuildSource,
159
    )
4414.5.7 by Tim Penhey
merge initial and custom widget
160
3283.3.3 by Brad Bollenbach
pair code reviewing with lifeless
161
3691.112.6 by David Allouche
properly validate branch name in branch/+edit
162
def quote(text):
163
    return cgi.escape(text, quote=True)
164
165
7362.4.14 by Jonathan Lange
Move the URL specification into a class in Python, rather than ZCML.
166
class BranchURL:
167
    """Branch URL creation rules."""
168
169
    implements(ICanonicalUrlData)
170
171
    rootsite = 'code'
9084.2.1 by Tim Penhey
Make the canonical url of the branch not hit the database to traverse the object.
172
    inside = None
7362.4.14 by Jonathan Lange
Move the URL specification into a class in Python, rather than ZCML.
173
174
    def __init__(self, branch):
175
        self.branch = branch
176
177
    @property
178
    def path(self):
9084.2.1 by Tim Penhey
Make the canonical url of the branch not hit the database to traverse the object.
179
        return self.branch.unique_name
7362.4.14 by Jonathan Lange
Move the URL specification into a class in Python, rather than ZCML.
180
181
9129.3.2 by Tim Penhey
Add adapters for IBranch to IRootContext.
182
def branch_root_context(branch):
183
    """Return the IRootContext for the branch."""
184
    return branch.target.components[0]
185
186
6753.6.1 by Tim Penhey
Make the default context for branches to be the product if there is one.
187
class BranchHierarchy(Hierarchy):
188
    """The hierarchy for a branch should be the product if there is one."""
189
9087.4.7 by Guilherme Salgado
Fix a couple stupid mistakes
190
    @property
9087.4.3 by Guilherme Salgado
Finally, something that works with all examples
191
    def objects(self):
6753.6.1 by Tim Penhey
Make the default context for branches to be the product if there is one.
192
        """See `Hierarchy`."""
9476.1.3 by Tim Penhey
Breadcrumbs for branches, and fix bmp-index a bit.
193
        traversed = list(self.request.traversed_objects)
194
        # Pass back the root object.
195
        yield traversed.pop(0)
196
        # Now pop until we find the branch.
197
        branch = traversed.pop(0)
198
        while not IBranch.providedBy(branch):
199
            branch = traversed.pop(0)
200
        # Now pass back the branch components.
201
        for component in branch.target.components:
202
            yield component
203
        # Now the branch.
204
        yield branch
205
        # Now whatever is left.
206
        for obj in traversed:
207
            yield obj
6753.6.1 by Tim Penhey
Make the default context for branches to be the product if there is one.
208
209
3283.3.2 by Brad Bollenbach
fixes based on talking to the sab this morning. tightening up the security model a bit by basing privileges on an IHasBug interface.
210
class BranchNavigation(Navigation):
3283.3.3 by Brad Bollenbach
pair code reviewing with lifeless
211
3283.3.2 by Brad Bollenbach
fixes based on talking to the sab this morning. tightening up the security model a bit by basing privileges on an IHasBug interface.
212
    usedfor = IBranch
213
214
    @stepthrough("+bug")
215
    def traverse_bug_branch(self, bugid):
4414.3.4 by Tim Penhey
Adjustments following review comments.
216
        """Traverses to an `IBugBranch`."""
3283.3.2 by Brad Bollenbach
fixes based on talking to the sab this morning. tightening up the security model a bit by basing privileges on an IHasBug interface.
217
        bug = getUtility(IBugSet).get(bugid)
218
8698.10.4 by Paul Hummer
Fixed references to broken code
219
        for bug_branch in bug.linked_branches:
3283.3.2 by Brad Bollenbach
fixes based on talking to the sab this morning. tightening up the security model a bit by basing privileges on an IHasBug interface.
220
            if bug_branch.branch == self.context:
221
                return bug_branch
1102.1.74 by David Allouche
$branch/+edit form
222
3691.160.1 by James Henstridge
Add support for Bazaar branch references to branch, product and product series pages
223
    @stepto(".bzr")
224
    def dotbzr(self):
225
        return BranchRef(self.context)
226
4333.2.2 by Tim Penhey
linked, albiet weirdly
227
    @stepthrough("+subscription")
228
    def traverse_subscription(self, name):
4414.3.4 by Tim Penhey
Adjustments following review comments.
229
        """Traverses to an `IBranchSubcription`."""
4333.2.2 by Tim Penhey
linked, albiet weirdly
230
        person = getUtility(IPersonSet).getByName(name)
231
232
        if person is not None:
233
            return self.context.getSubscription(person)
234
4414.5.12 by Tim Penhey
work in progress
235
    @stepthrough("+merge")
236
    def traverse_merge_proposal(self, id):
237
        """Traverse to an `IBranchMergeProposal`."""
238
        try:
239
            id = int(id)
240
        except ValueError:
241
            # Not a number.
242
            return None
243
        for proposal in self.context.landing_targets:
244
            if proposal.id == id:
245
                return proposal
246
10454.3.1 by James Westby
Basic export of ICodeImport.
247
    @stepto("+code-import")
248
    def traverse_code_import(self):
249
        """Traverses to the `ICodeImport` for the branch."""
250
        return self.context.code_import
251
7675.822.7 by Jeroen Vermeulen
Got TAL rendering. Probably ugly though.
252
    @stepthrough("+translation-templates-build")
253
    def traverse_translation_templates_build(self, id_string):
254
        """Traverses to a `TranslationTemplatesBuild`."""
255
        try:
12979.2.1 by William Grant
TTBJ URLs now use the TTBJ ID, not the BFJ ID.
256
            ttbj_id = int(id_string)
7675.822.7 by Jeroen Vermeulen
Got TAL rendering. Probably ugly though.
257
        except ValueError:
258
            raise NotFoundError(id_string)
259
        source = getUtility(ITranslationTemplatesBuildSource)
12979.2.1 by William Grant
TTBJ URLs now use the TTBJ ID, not the BFJ ID.
260
        return source.getByID(ttbj_id)
7675.822.7 by Jeroen Vermeulen
Got TAL rendering. Probably ugly though.
261
9636.5.4 by Jonathan Lange
XXX notes.
262
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
263
class BranchEditMenu(NavigationMenu):
264
    """Edit menu for IBranch."""
265
266
    usedfor = IBranch
6648.3.1 by Paul Hummer
Adds IBranchNavigationMenu interface
267
    facet = 'branches'
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
268
    title = 'Edit branch'
9562.1.1 by Paul Hummer
Shifted around the BranchEditMenu item ordering
269
    links = (
10891.1.2 by Paul Hummer
Added code for the source package recipe count section
270
        'edit', 'reviewer', 'edit_whiteboard', 'delete')
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
271
272
    def branch_is_import(self):
273
        return self.context.branch_type == BranchType.IMPORTED
274
275
    @enabled_with_permission('launchpad.Edit')
276
    def edit(self):
9301.2.29 by Paul Hummer
Fixed xx-upload-directions
277
        text = 'Change branch details'
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
278
        return Link('+edit', text, icon='edit')
279
280
    @enabled_with_permission('launchpad.Edit')
281
    def delete(self):
282
        text = 'Delete branch'
9301.2.27 by Paul Hummer
Fixed verbage
283
        return Link('+delete', text, icon='trash-icon')
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
284
9301.2.40 by Paul Hummer
Responded to Aaron's review
285
    @enabled_with_permission('launchpad.AnyPerson')
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
286
    def edit_whiteboard(self):
287
        text = 'Edit whiteboard'
9301.2.38 by Paul Hummer
Fixed some lint
288
        enabled = self.branch_is_import()
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
289
        return Link(
9301.2.38 by Paul Hummer
Fixed some lint
290
            '+whiteboard', text, icon='edit', enabled=enabled)
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
291
292
    @enabled_with_permission('launchpad.Edit')
293
    def reviewer(self):
294
        text = 'Set branch reviewer'
295
        return Link('+reviewer', text, icon='edit')
7211.1.4 by Tim Penhey
Changed view names and added test.
296
6648.3.1 by Paul Hummer
Adds IBranchNavigationMenu interface
297
7675.618.24 by Paul Hummer
Added links to the recipes pages and accompanying tests.
298
class BranchContextMenu(ContextMenu, HasRecipesMenuMixin):
1102.1.98 by David Allouche
remove branch-portlet-actions.pt, add BranchContextMenu, add PersonNavigation.traverse_branch, add zcml declaration for RevisionNumber
299
    """Context menu for branches."""
300
301
    usedfor = IBranch
3412.1.19 by David Allouche
use branches facet for all branch pages
302
    facet = 'branches'
9301.2.37 by Paul Hummer
Merge from trunk, resolve conflicts
303
    links = [
10891.1.2 by Paul Hummer
Added code for the source package recipe count section
304
        'add_subscriber', 'browse_revisions', 'create_recipe', 'link_bug',
9570.12.1 by Tim Penhey
Add a link and simple view for editing the status.
305
        'link_blueprint', 'register_merge', 'source', 'subscription',
7675.899.8 by Paul Hummer
Add +create-queue view
306
        'edit_status', 'edit_import', 'upgrade_branch', 'view_recipes',
7675.899.14 by Paul Hummer
Removed link_branch link
307
        'create_queue']
9570.12.1 by Tim Penhey
Add a link and simple view for editing the status.
308
309
    @enabled_with_permission('launchpad.Edit')
310
    def edit_status(self):
311
        text = 'Change branch status'
312
        return Link('+edit-status', text, icon='edit')
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
313
4813.5.1 by Michael Hudson
change the browse code link, add the browse revisions link, add simple test
314
    def browse_revisions(self):
4813.5.3 by Michael Hudson
add two docstrings
315
        """Return a link to the branch's revisions on codebrowse."""
6757.1.1 by Paul Hummer
Changes 'Older revisions' to 'All revisions'
316
        text = 'All revisions'
4813.5.1 by Michael Hudson
change the browse code link, add the browse revisions link, add simple test
317
        enabled = self.context.code_is_browseable
7622.1.2 by Michael Hudson
some progress
318
        url = self.context.codebrowse_url('changes')
6735.1.1 by Paul Hummer
Adds merge proposal stuffs
319
        return Link(url, text, enabled=enabled)
3948.1.1 by James Henstridge
add codebrowse link to branch context menu
320
3691.431.11 by Tim Penhey
Branch subscription pages edited
321
    @enabled_with_permission('launchpad.AnyPerson')
1102.1.98 by David Allouche
remove branch-portlet-actions.pt, add BranchContextMenu, add PersonNavigation.traverse_branch, add zcml declaration for RevisionNumber
322
    def subscription(self):
3691.431.11 by Tim Penhey
Branch subscription pages edited
323
        if self.context.hasSubscription(self.user):
324
            url = '+edit-subscription'
6735.1.1 by Paul Hummer
Adds merge proposal stuffs
325
            text = 'Edit your subscription'
3691.431.11 by Tim Penhey
Branch subscription pages edited
326
            icon = 'edit'
1102.1.98 by David Allouche
remove branch-portlet-actions.pt, add BranchContextMenu, add PersonNavigation.traverse_branch, add zcml declaration for RevisionNumber
327
        else:
3691.431.11 by Tim Penhey
Branch subscription pages edited
328
            url = '+subscribe'
6735.1.1 by Paul Hummer
Adds merge proposal stuffs
329
            text = 'Subscribe yourself'
3691.431.11 by Tim Penhey
Branch subscription pages edited
330
            icon = 'add'
331
        return Link(url, text, icon=icon)
1102.1.98 by David Allouche
remove branch-portlet-actions.pt, add BranchContextMenu, add PersonNavigation.traverse_branch, add zcml declaration for RevisionNumber
332
4333.2.1 by Tim Penhey
Initial subscription works, now how to edit...
333
    @enabled_with_permission('launchpad.AnyPerson')
5579.2.7 by Tim Penhey
Update the existing pagetest for new actions.
334
    def add_subscriber(self):
4333.2.1 by Tim Penhey
Initial subscription works, now how to edit...
335
        text = 'Subscribe someone else'
336
        return Link('+addsubscriber', text, icon='add')
337
5579.2.7 by Tim Penhey
Update the existing pagetest for new actions.
338
    def register_merge(self):
9586.2.1 by Tim Penhey
First hack at it.
339
        text = 'Propose for merging'
9301.2.40 by Paul Hummer
Responded to Aaron's review
340
        enabled = (
341
            self.context.target.supports_merge_proposals and
342
            not self.context.branch_type == BranchType.IMPORTED)
10054.13.1 by Tim Penhey
Use the add icon instead of the merge proposal icon for registering a new proposal.
343
        return Link('+register-merge', text, icon='add', enabled=enabled)
4414.5.4 by Tim Penhey
First cut at getting a page to register a merge proposal
344
5579.2.7 by Tim Penhey
Update the existing pagetest for new actions.
345
    def link_bug(self):
12457.2.3 by Robert Collins
Lock down scaling of branch:+index for product bugtasks - still to do productseries and distroseries.
346
        text = 'Link a bug report'
6369.1.1 by Paul Hummer
Added link to blueprint and link to bug items from the action menu
347
        return Link('+linkbug', text, icon='add')
4789.3.1 by Tim Penhey
First cut.
348
5099.10.9 by Tim Penhey
Updates following review.
349
    def link_blueprint(self):
6369.1.3 by Paul Hummer
Adds text for "Link to [a|another] [bug|blueprint]"
350
        if self.context.spec_links:
351
            text = 'Link to another blueprint'
352
        else:
353
            text = 'Link to a blueprint'
8418.1.1 by Jonathan Lange
Remove a stack of code that lets you move branches.
354
        # XXX: JonathanLange 2009-05-13 spec=package-branches: Actually,
355
        # distroseries can also have blueprints, so it makes sense to
356
        # associate package-branches with them.
357
        #
5099.10.4 by Tim Penhey
Put the product in the launchbag when traversing the branch.
358
        # Since the blueprints are only related to products, there is no
359
        # point showing this link if the branch is junk.
5099.10.5 by Tim Penhey
Some drive by clean up making buttons and links consistent.
360
        enabled = self.context.product is not None
6369.1.1 by Paul Hummer
Added link to blueprint and link to bug items from the action menu
361
        return Link('+linkblueprint', text, icon='add', enabled=enabled)
5099.10.3 by Tim Penhey
Add 'Link to blueprint' to the branch page.
362
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
363
    def source(self):
364
        """Return a link to the branch's file listing on codebrowse."""
12707.2.1 by Huw Wilkins
Renamed "View the branch content" to "Browse the code".
365
        text = 'Browse the code'
9301.2.26 by Paul Hummer
Added minor changes for details where portlets don't have anything to put in them
366
        enabled = self.context.code_is_browseable
9301.2.20 by Paul Hummer
Added BranchEditMenu, removed BranchNavigationMenu
367
        url = self.context.codebrowse_url('files')
368
        return Link(url, text, icon='info', enabled=enabled)
6757.1.4 by Paul Hummer
Changes "Edit import" link to actually be a Link
369
10174.1.2 by Paul Hummer
Moved the code import section back inline.
370
    def edit_import(self):
371
        text = 'Edit import source or review import'
372
        enabled = True
373
        enabled = (
374
            self.context.branch_type == BranchType.IMPORTED and
375
            check_permission('launchpad.Edit', self.context.code_import))
376
        return Link(
377
            '+edit-import', text, icon='edit', enabled=enabled)
378
7675.477.1 by Paul Hummer
Reverted the reversion of the patch that went into devel by mistake
379
    @enabled_with_permission('launchpad.Edit')
380
    def upgrade_branch(self):
381
        enabled = False
382
        if self.context.needs_upgrading:
383
            enabled = True
384
        return Link(
385
            '+upgrade', 'Upgrade this branch', icon='edit', enabled=enabled)
386
10891.1.2 by Paul Hummer
Added code for the source package recipe count section
387
    def create_recipe(self):
12373.1.2 by Tim Penhey
Make all recipe beta and enablement checks user feature flags.
388
        # You can't create a recipe for a private branch.
389
        enabled = not self.context.private
10891.1.2 by Paul Hummer
Added code for the source package recipe count section
390
        text = 'Create packaging recipe'
391
        return Link('+new-recipe', text, enabled=enabled, icon='add')
392
7675.899.8 by Paul Hummer
Add +create-queue view
393
    @enabled_with_permission('launchpad.Edit')
394
    def create_queue(self):
395
        return Link('+create-queue', 'Create a new queue', icon='add')
396
1102.1.98 by David Allouche
remove branch-portlet-actions.pt, add BranchContextMenu, add PersonNavigation.traverse_branch, add zcml declaration for RevisionNumber
397
11515.8.3 by Brad Crittenden
Redesign code product page. Created code +portlet-codestatistics.
398
class BranchMirrorMixin:
399
    """Provide mirror_location property.
400
401
    Requires self.branch to be set by the class using this mixin.
402
    """
403
404
    @property
405
    def mirror_location(self):
406
        """Check the mirror location to see if it is a private one."""
407
        branch = self.branch
408
409
        # If the user has edit permissions, then show the actual location.
12276.1.1 by William Grant
Empty remote branch URLs are always OK, so don't attempt to check their non-existent domain.
410
        if branch.url is None or check_permission('launchpad.Edit', branch):
11515.8.3 by Brad Crittenden
Redesign code product page. Created code +portlet-codestatistics.
411
            return branch.url
412
12924.3.3 by Tim Penhey
Fix up the branch_type vocabulary, and remove some XXX comments that are not worthy.
413
        # XXX: Tim Penhey, 2008-05-30, bug 235916
11515.8.3 by Brad Crittenden
Redesign code product page. Created code +portlet-codestatistics.
414
        # Instead of a configuration hack we should support the users
415
        # specifying whether or not they want the mirror location
416
        # hidden or not.  Given that this is a database patch,
417
        # it isn't going to happen today.
418
        hosts = config.codehosting.private_mirror_hosts.split(',')
419
        private_mirror_hosts = [name.strip() for name in hosts]
420
421
        uri = URI(branch.url)
422
        for private_host in private_mirror_hosts:
423
            if uri.underDomain(private_host):
424
                return '<private server>'
425
426
        return branch.url
427
428
429
class BranchView(LaunchpadView, FeedsMixin, BranchMirrorMixin):
1102.1.75 by David Allouche
wrap branch urls in the page index page
430
5204.6.26 by Brad Crittenden
Added feed for latest revisions on a given branch.
431
    feed_types = (
432
        BranchFeedLink,
433
        )
434
9301.2.39 by Paul Hummer
Moved registrant up to registering slot
435
    @property
436
    def page_title(self):
437
        return self.context.bzr_identity
438
439
    label = page_title
440
1102.1.128 by david
hide comitters in branch-index if not logged in
441
    def initialize(self):
11515.8.3 by Brad Crittenden
Redesign code product page. Created code +portlet-codestatistics.
442
        self.branch = self.context
1102.1.85 by David Allouche
can subscribe to and unsubscribe from branch
443
        self.notices = []
14550.3.3 by Ian Booth
Improve sql used in security adaptor, migrate doc test to unit test, add tests
444
        # Cache permission so private team owner can be rendered.
445
        # The security adaptor will do the job also but we don't want or need
446
        # the expense of running several complex SQL queries.
447
        authorised_people = [self.branch.owner]
448
        if self.user is not None:
449
            precache_permission_for_objects(
450
                self.request, "launchpad.LimitedView", authorised_people)
10974.1.7 by Tim Penhey
Use isinstance.
451
        # Replace our context with a decorated branch, if it is not already
452
        # decorated.
453
        if not isinstance(self.context, DecoratedBranch):
10974.1.3 by Tim Penhey
Decorate the branch to make it cache most of the query calls.
454
            self.context = DecoratedBranch(self.context)
1102.1.75 by David Allouche
wrap branch urls in the page index page
455
1102.1.143 by David Allouche
Review fixes to Branch view.
456
    def user_is_subscribed(self):
457
        """Is the current user subscribed to this branch?"""
1102.1.85 by David Allouche
can subscribe to and unsubscribe from branch
458
        if self.user is None:
1102.1.143 by David Allouche
Review fixes to Branch view.
459
            return False
3691.431.11 by Tim Penhey
Branch subscription pages edited
460
        return self.context.hasSubscription(self.user)
1102.1.85 by David Allouche
can subscribe to and unsubscribe from branch
461
3024.1.39 by Christian Reis
Improve the person-branches page by caching in the view code and using a small amount of prejoining; based on recent oops report for the vcs-imports user
462
    def recent_revision_count(self, days=30):
1102.1.121 by david
show author of revisions and number of revisions in the last 30 days
463
        """Number of revisions committed during the last N days."""
1102.1.123 by david
schema supports ghost revisions
464
        timestamp = datetime.now(pytz.UTC) - timedelta(days=days)
9984.4.1 by Tim Penhey
Rename revisions_since to follow the LP coding standard.
465
        return self.context.getRevisionsSince(timestamp).count()
1102.1.121 by david
show author of revisions and number of revisions in the last 30 days
466
5786.1.1 by Tim Penhey
Changed nearby to use owned/registered and person branch links to be Registered/Owned/Subscribed.
467
    def owner_is_registrant(self):
468
        """Is the branch owner the registrant?"""
469
        return self.context.owner == self.context.registrant
1102.1.134 by David Allouche
cosmetic fixes
470
9301.2.40 by Paul Hummer
Responded to Aaron's review
471
    def owner_is_reviewer(self):
472
        """Is the branch owner the default reviewer?"""
473
        if self.context.reviewer == None:
474
            return True
475
        return self.context.owner == self.context.reviewer
476
8137.17.24 by Barry Warsaw
thread merge
477
    def show_whiteboard(self):
478
        """Return whether or not the whiteboard should be shown.
479
480
        The whiteboard is only shown for import branches.
481
        """
9301.2.25 by Paul Hummer
Fixed the branch page to work well with import branches
482
        if (self.context.branch_type == BranchType.IMPORTED and
483
            self.context.whiteboard):
8137.17.24 by Barry Warsaw
thread merge
484
            return True
485
        else:
486
            return False
487
9301.2.32 by Paul Hummer
Fixed xx-branch-index
488
    def has_metadata(self):
489
        """Return whether there is branch metadata to display."""
9301.2.26 by Paul Hummer
Added minor changes for details where portlets don't have anything to put in them
490
        return (
491
            self.context.branch_format or
492
            self.context.repository_format or
9301.2.32 by Paul Hummer
Fixed xx-branch-index
493
            self.context.control_format or
494
            self.context.stacked_on)
9301.2.26 by Paul Hummer
Added minor changes for details where portlets don't have anything to put in them
495
4743.2.1 by Tim Penhey
Revision numbers on branch details page now link to codebrowse.
496
    @property
12054.1.1 by Aaron Bentley
Suggest --use-existing for empty branches
497
    def is_empty_directory(self):
498
        """True if the branch is an empty directory without even a '.bzr'."""
499
        return self.context.control_format is None
500
501
    @property
4743.2.1 by Tim Penhey
Revision numbers on branch details page now link to codebrowse.
502
    def codebrowse_url(self):
503
        """Return the link to codebrowse for this branch."""
7622.1.2 by Michael Hudson
some progress
504
        return self.context.codebrowse_url()
4743.2.1 by Tim Penhey
Revision numbers on branch details page now link to codebrowse.
505
8128.7.7 by Jonathan Lange
Make the template use the view for obtaining pending-writes, since the
506
    @property
507
    def pending_writes(self):
508
        """Whether or not there are pending writes for this branch."""
509
        return self.context.pending_writes
510
4813.8.1 by Tim Penhey
Lots of url moving on the branch index page.
511
    def bzr_download_url(self):
512
        """Return the generic URL for downloading the branch."""
513
        if self.user_can_download():
5805.2.7 by Tim Penhey
Fixed branch tests.
514
            return self.context.bzr_identity
4813.8.5 by Tim Penhey
Updates following review.
515
        else:
516
            return None
4813.8.1 by Tim Penhey
Lots of url moving on the branch index page.
517
518
    def bzr_upload_url(self):
519
        """Return the generic URL for uploading the branch."""
520
        if self.user_can_upload():
5805.2.7 by Tim Penhey
Fixed branch tests.
521
            return self.context.bzr_identity
4813.8.5 by Tim Penhey
Updates following review.
522
        else:
523
            return None
2868.2.1 by David Allouche
[incomplete] optional-branch-title
524
10355.2.1 by Tim Penhey
Change the method for identifying whether a person can upload to use Launchpad.Edit.
525
    @property
3691.303.1 by David Allouche
display contextual upload directions on the index page of upload branches
526
    def user_can_upload(self):
527
        """Whether the user can upload to this branch."""
10355.2.1 by Tim Penhey
Change the method for identifying whether a person can upload to use Launchpad.Edit.
528
        branch = self.context
529
        if branch.branch_type != BranchType.HOSTED:
530
            return False
531
        return check_permission('launchpad.Edit', branch)
3691.303.1 by David Allouche
display contextual upload directions on the index page of upload branches
532
4813.8.1 by Tim Penhey
Lots of url moving on the branch index page.
533
    def user_can_download(self):
534
        """Whether the user can download this branch."""
535
        return (self.context.branch_type != BranchType.REMOTE and
536
                self.context.revision_count > 0)
3691.303.1 by David Allouche
display contextual upload directions on the index page of upload branches
537
4414.5.12 by Tim Penhey
work in progress
538
    @cachedproperty
539
    def landing_targets(self):
9691.7.9 by Tim Penhey
Extract the logic into a common method and test it.
540
        """Return a filtered list of landing targets."""
14414.2.5 by Ian Booth
Remove old code
541
        return latest_proposals_for_each_branch(self.context.landing_targets)
4414.5.12 by Tim Penhey
work in progress
542
5280.4.25 by Tim Penhey
Updates following review.
543
    @property
4414.5.13 by Tim Penhey
Still work in progress
544
    def latest_landing_candidates(self):
545
        """Return a decorated filtered list of landing candidates."""
546
        # Only show the most recent 5 landing_candidates
5280.4.25 by Tim Penhey
Updates following review.
547
        return self.landing_candidates[:5]
4414.5.13 by Tim Penhey
Still work in progress
548
549
    @cachedproperty
550
    def landing_candidates(self):
551
        """Return a decorated list of landing candidates."""
552
        candidates = self.context.landing_candidates
9691.7.8 by Tim Penhey
Rip out the DecoratedMergeProposal as it is only used in a deprecated macro.
553
        return [proposal for proposal in candidates
8537.7.1 by Tim Penhey
Filter the branches and proposals in the view to not include those that the user cannot see.
554
                if check_permission('launchpad.View', proposal)]
4414.5.13 by Tim Penhey
Still work in progress
555
9301.2.40 by Paul Hummer
Responded to Aaron's review
556
    @property
10891.1.2 by Paul Hummer
Added code for the source package recipe count section
557
    def recipe_count_text(self):
12397.2.8 by Ian Booth
Change from using getter methods to properties for exported recipe and build accessors
558
        count = self.context.recipes.count()
10891.1.2 by Paul Hummer
Added code for the source package recipe count section
559
        if count == 0:
560
            return 'No recipes'
561
        elif count == 1:
562
            return '1 recipe'
563
        else:
564
            return '%s recipes' % count
565
566
    @property
9301.2.40 by Paul Hummer
Responded to Aaron's review
567
    def is_import_branch_with_no_landing_candidates(self):
568
        """Is the branch an import branch with no landing candidates?"""
569
        if self.landing_candidates:
570
            return False
571
        if not self.context.branch_type == BranchType.IMPORTED:
572
            return False
573
        return True
574
6735.1.1 by Paul Hummer
Adds merge proposal stuffs
575
    def _getBranchCountText(self, count):
576
        """Help to show user friendly text."""
577
        if count == 0:
578
            return 'No branches'
579
        elif count == 1:
580
            return '1 branch'
581
        else:
582
            return '%s branches' % count
583
584
    @cachedproperty
585
    def dependent_branch_count_text(self):
8537.7.1 by Tim Penhey
Filter the branches and proposals in the view to not include those that the user cannot see.
586
        branch_count = len(self.dependent_branches)
6735.1.1 by Paul Hummer
Adds merge proposal stuffs
587
        return self._getBranchCountText(branch_count)
588
589
    @cachedproperty
590
    def landing_candidate_count_text(self):
8537.7.1 by Tim Penhey
Filter the branches and proposals in the view to not include those that the user cannot see.
591
        branch_count = len(self.landing_candidates)
6735.1.1 by Paul Hummer
Adds merge proposal stuffs
592
        return self._getBranchCountText(branch_count)
593
6699.2.22 by Paul Hummer
UI changes
594
    @cachedproperty
6699.2.24 by Tim Penhey
UI updates
595
    def dependent_branches(self):
8537.7.1 by Tim Penhey
Filter the branches and proposals in the view to not include those that the user cannot see.
596
        return [branch for branch in self.context.dependent_branches
597
                if check_permission('launchpad.View', branch)]
6699.2.24 by Tim Penhey
UI updates
598
599
    @cachedproperty
6699.2.22 by Paul Hummer
UI changes
600
    def no_merges(self):
601
        """Return true if there are no pending merges"""
602
        return (len(self.landing_targets) +
6699.2.24 by Tim Penhey
UI updates
603
                len(self.landing_candidates) +
604
                len(self.dependent_branches) == 0)
6699.2.22 by Paul Hummer
UI changes
605
5280.4.25 by Tim Penhey
Updates following review.
606
    @property
607
    def show_candidate_more_link(self):
608
        """Only show the link if there are more than five."""
609
        return len(self.landing_candidates) > 5
610
12082.2.5 by Ian Booth
Move revision_info implementation to branch collection and add tests
611
    @cachedproperty
12457.2.3 by Robert Collins
Lock down scaling of branch:+index for product bugtasks - still to do productseries and distroseries.
612
    def linked_bugtasks(self):
12457.2.6 by Robert Collins
Review feedback: use a shortlist, don't spread the any pollution of default symbols. Docstring fixup and unbreak the legacy linked_bugs.
613
        """Return a list of bugtasks linked to the branch."""
12082.2.5 by Ian Booth
Move revision_info implementation to branch collection and add tests
614
        if self.context.is_series_branch:
12457.2.6 by Robert Collins
Review feedback: use a shortlist, don't spread the any pollution of default symbols. Docstring fixup and unbreak the legacy linked_bugs.
615
            status_filter = searchbuilder.any(*UNRESOLVED_BUGTASK_STATUSES)
12457.2.3 by Robert Collins
Lock down scaling of branch:+index for product bugtasks - still to do productseries and distroseries.
616
        else:
617
            status_filter = None
618
        return list(self.context.getLinkedBugTasks(
619
            self.user, status_filter))
8752.4.16 by Paul Hummer
Implemented linked_bugs into the templates
620
621
    @cachedproperty
12082.2.3 by Ian Booth
Add tests and clean up tales
622
    def revision_info(self):
623
        collection = getUtility(IAllBranches).visibleByUser(self.user)
12082.2.5 by Ian Booth
Move revision_info implementation to branch collection and add tests
624
        return collection.getExtendedRevisionDetails(
12505.6.1 by Ian Booth
Remove DecoratedBug and refactor mp linked_bugs to use branch.getRelatedBugTasks
625
            self.user, self.context.latest_revisions)
12082.2.3 by Ian Booth
Add tests and clean up tales
626
627
    @cachedproperty
6233.7.1 by Tim Penhey
Make the page look right.
628
    def latest_code_import_results(self):
629
        """Return the last 10 CodeImportResults."""
6233.7.7 by Tim Penhey
Add TestCodeImportResultsAttribute.
630
        return list(self.context.code_import.results[:10])
6233.7.1 by Tim Penhey
Make the page look right.
631
10224.12.9 by Michael Hudson
self-review begins
632
    def iconForCodeImportResultStatus(self, status):
633
        """The icon to represent the `CodeImportResultStatus` `status`."""
10365.2.2 by Michael Hudson
oops, still get the icon right
634
        if status == CodeImportResultStatus.SUCCESS_PARTIAL:
635
            return "/@@/yes-gray"
636
        elif status in CodeImportResultStatus.successes:
10224.12.6 by Michael Hudson
changes to model/view code
637
            return "/@@/yes"
638
        else:
639
            return "/@@/no"
640
6387.1.1 by Tim Penhey
Add a configuration option for private_mirror_hosts, and hide the branch location for branches that are under one of those domains unless the user is able to edit the branch.
641
    @property
9946.1.10 by Michael Hudson
some kind of fix
642
    def is_svn_import(self):
643
        """True if an imported branch is a SVN import."""
9946.1.15 by Michael Hudson
review comments
644
        # You should only be calling this if it's a code import
9946.1.10 by Michael Hudson
some kind of fix
645
        assert self.context.code_import
646
        return self.context.code_import.rcs_type in \
647
               (RevisionControlSystems.SVN, RevisionControlSystems.BZR_SVN)
648
649
    @property
13756.5.15 by Jelmer Vernooij
Fix UI.
650
    def url_is_web(self):
651
        """True if an imported branch's URL is HTTP or HTTPS."""
652
        # You should only be calling this if it's an SVN, BZR, GIT or HG code
653
        # import
6721.2.22 by Christian Reis
Address jml's review comments: better comments and simplify svn_url_is_web
654
        assert self.context.code_import
10129.6.9 by Tim Penhey
Yet more test fixes.
655
        url = self.context.code_import.url
656
        assert url
6721.2.22 by Christian Reis
Address jml's review comments: better comments and simplify svn_url_is_web
657
        # https starts with http too!
658
        return url.startswith("http")
6721.2.15 by Christian Reis
Linkify imported branch URLs if they are HTTP/HTTPs.
659
660
    @property
8269.7.1 by Paul Hummer
Added BranchView.show_merge_links and accompanying tests.
661
    def show_merge_links(self):
662
        """Return whether or not merge proposal links should be shown.
663
11350.1.3 by Paul Hummer
Fixed lint
664
        Merge proposal links should not be shown if there is only one branch
665
        in a non-final state.
8269.7.1 by Paul Hummer
Added BranchView.show_merge_links and accompanying tests.
666
        """
8377.8.14 by Tim Penhey
Enable the links on the page.
667
        if not self.context.target.supports_merge_proposals:
8269.7.1 by Paul Hummer
Added BranchView.show_merge_links and accompanying tests.
668
            return False
8269.7.10 by Paul Hummer
Working on tests
669
        return self.context.target.collection.getBranches().count() > 1
8269.7.1 by Paul Hummer
Added BranchView.show_merge_links and accompanying tests.
670
8771.6.1 by Jeroen Vermeulen
UI for exporting translations to bzr branch.
671
    def translations_sources(self):
672
        """Anything that automatically exports its translations here.
673
674
        Produces a list, so that the template can easily check whether
675
        there are any translations sources.
676
        """
677
        # Actually only ProductSeries currently do that.
678
        return list(self.context.getProductSeriesPushingTranslations())
679
9570.12.3 by Tim Penhey
More js bits.
680
    @property
12534.2.1 by Tim Penhey
Add an EnumChoiceWidget, and use it on the branch page.
681
    def status_widget(self):
9570.12.3 by Tim Penhey
More js bits.
682
        """The config to configure the ChoiceSource JS widget."""
12534.2.1 by Tim Penhey
Add an EnumChoiceWidget, and use it on the branch page.
683
        return EnumChoiceWidget(
684
            self.context.branch, IBranch['lifecycle_status'],
685
            header='Change status to', css_class_prefix='branchstatus')
9570.12.3 by Tim Penhey
More js bits.
686
4414.5.12 by Tim Penhey
work in progress
687
3277.3.2 by David Allouche
better branch listings
688
class BranchInProductView(BranchView):
689
690
    show_person_link = True
691
    show_product_link = False
692
693
3691.112.6 by David Allouche
properly validate branch name in branch/+edit
694
class BranchNameValidationMixin:
695
    """Provide name validation logic used by several branch view classes."""
696
10456.2.13 by Brad Crittenden
Added browser support for other +setbranch options
697
    def _setBranchExists(self, existing_branch, field_name='name'):
7362.3.4 by Jonathan Lange
Factor out branch existence failure logic.
698
        owner = existing_branch.owner
699
        if owner == self.user:
700
            prefix = "You already have"
701
        else:
702
            prefix = "%s already has" % cgi.escape(owner.displayname)
8120.2.3 by Jonathan Lange
No need to refer to product here.
703
        message = (
704
            "%s a branch for <em>%s</em> called <em>%s</em>."
8120.2.24 by Jonathan Lange
Use display name
705
            % (prefix, existing_branch.target.displayname,
706
               existing_branch.name))
10456.2.13 by Brad Crittenden
Added browser support for other +setbranch options
707
        self.setFieldError(field_name, structured(message))
7362.3.4 by Jonathan Lange
Factor out branch existence failure logic.
708
3691.112.9 by David Allouche
review fixes, convert xx-team-branches.txt to testbrowser
709
8137.17.24 by Barry Warsaw
thread merge
710
class BranchEditSchema(Interface):
711
    """Defines the fields for the edit form.
712
713
    This is necessary so as to make an editable field for the branch privacy.
714
    Normally the field is not editable through the interface in order to stop
715
    direct setting of the private attribute, but in this case we actually want
716
    the user to be able to edit it.
717
    """
718
    use_template(IBranch, include=[
8418.1.1 by Jonathan Lange
Remove a stack of code that lets you move branches.
719
        'name',
720
        'url',
8503.2.1 by Paul Hummer
Re-introduced IBranch.description
721
        'description',
8418.1.1 by Jonathan Lange
Remove a stack of code that lets you move branches.
722
        'lifecycle_status',
723
        'whiteboard',
724
        ])
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
725
    explicitly_private = copy_field(
726
        IBranch['explicitly_private'], readonly=False)
9041.5.1 by Tim Penhey
Mid change when I realise I need trunk.
727
    reviewer = copy_field(IBranch['reviewer'], required=True)
8971.24.15 by Tim Penhey
Make the edit view notify owner updates.
728
    owner = copy_field(IBranch['owner'], readonly=False)
8137.17.24 by Barry Warsaw
thread merge
729
730
3691.112.9 by David Allouche
review fixes, convert xx-team-branches.txt to testbrowser
731
class BranchEditFormView(LaunchpadEditFormView):
732
    """Base class for forms that edit a branch."""
733
8137.17.24 by Barry Warsaw
thread merge
734
    schema = BranchEditSchema
3691.112.9 by David Allouche
review fixes, convert xx-team-branches.txt to testbrowser
735
    field_names = None
736
8137.17.24 by Barry Warsaw
thread merge
737
    @property
9041.5.1 by Tim Penhey
Mid change when I realise I need trunk.
738
    def page_title(self):
9041.5.4 by Tim Penhey
Remove the h1 on the edit page and add a label.
739
        return 'Edit %s' % self.context.displayname
740
741
    @property
742
    def label(self):
743
        return self.page_title
9041.5.1 by Tim Penhey
Mid change when I realise I need trunk.
744
745
    @property
8137.17.24 by Barry Warsaw
thread merge
746
    def adapters(self):
747
        """See `LaunchpadFormView`"""
748
        return {BranchEditSchema: self.context}
749
3691.112.9 by David Allouche
review fixes, convert xx-team-branches.txt to testbrowser
750
    @action('Change Branch', name='change')
751
    def change_action(self, action, data):
6096.6.1 by Tim Penhey
Add owner to the branch edit page (and removed author).
752
        # If the owner or product has changed, add an explicit notification.
8971.24.27 by Tim Penhey
Updates following review. Just some more tests for error conditions to go.
753
        # We take our own snapshot here to make sure that the snapshot records
754
        # changes to the owner and private, and we notify the listeners
755
        # explicitly below rather than the notification that would normally be
756
        # sent in updateContextFromData.
9041.5.3 by Tim Penhey
Clean up setting the reviewer.
757
        changed = False
8971.24.15 by Tim Penhey
Make the edit view notify owner updates.
758
        branch_before_modification = Snapshot(
759
            self.context, providing=providedBy(self.context))
6096.6.1 by Tim Penhey
Add owner to the branch edit page (and removed author).
760
        if 'owner' in data:
8971.24.15 by Tim Penhey
Make the edit view notify owner updates.
761
            new_owner = data.pop('owner')
6096.6.1 by Tim Penhey
Add owner to the branch edit page (and removed author).
762
            if new_owner != self.context.owner:
8971.24.15 by Tim Penhey
Make the edit view notify owner updates.
763
                self.context.setOwner(new_owner, self.user)
9041.5.3 by Tim Penhey
Clean up setting the reviewer.
764
                changed = True
6096.6.1 by Tim Penhey
Add owner to the branch edit page (and removed author).
765
                self.request.response.addNotification(
766
                    "The branch owner has been changed to %s (%s)"
767
                    % (new_owner.displayname, new_owner.name))
8137.17.24 by Barry Warsaw
thread merge
768
        if 'private' in data:
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
769
            # Read only for display.
770
            data.pop('private')
771
        if 'explicitly_private' in data:
772
            private = data.pop('explicitly_private')
13760.3.5 by Ian Booth
Add story tests and implementation for branch edit page
773
            if (private != self.context.private
774
                and self.context.private == self.context.explicitly_private):
8137.17.24 by Barry Warsaw
thread merge
775
                # We only want to show notifications if it actually changed.
9941.1.4 by Tim Penhey
Update the view class to pass through the user and update the story.
776
                self.context.setPrivate(private, self.user)
9041.5.3 by Tim Penhey
Clean up setting the reviewer.
777
                changed = True
8137.17.24 by Barry Warsaw
thread merge
778
                if private:
779
                    self.request.response.addNotification(
780
                        "The branch is now private, and only visible to the "
781
                        "owner and to subscribers.")
782
                else:
783
                    self.request.response.addNotification(
784
                        "The branch is now publicly accessible.")
9041.5.3 by Tim Penhey
Clean up setting the reviewer.
785
        if 'reviewer' in data:
9084.1.11 by Tim Penhey
Fix the test.
786
            reviewer = data.pop('reviewer')
9041.5.3 by Tim Penhey
Clean up setting the reviewer.
787
            if reviewer != self.context.code_reviewer:
788
                if reviewer == self.context.owner:
789
                    # Clear the reviewer if set to the same as the owner.
790
                    self.context.reviewer = None
791
                else:
792
                    self.context.reviewer = reviewer
793
                changed = True
794
8971.24.15 by Tim Penhey
Make the edit view notify owner updates.
795
        if self.updateContextFromData(data, notify_modified=False):
9041.5.3 by Tim Penhey
Clean up setting the reviewer.
796
            changed = True
797
798
        if changed:
8971.24.15 by Tim Penhey
Make the edit view notify owner updates.
799
            # Notify the object has changed with the snapshot that was taken
800
            # earler.
801
            field_names = [
802
                form_field.__name__ for form_field in self.form_fields]
803
            notify(ObjectModifiedEvent(
804
                self.context, branch_before_modification, field_names))
5001.2.5 by Tim Penhey
Only update last modified if there were in fact changes.
805
            # Only specify that the context was modified if there
806
            # was in fact a change.
807
            self.context.date_last_modified = UTC_NOW
3691.112.9 by David Allouche
review fixes, convert xx-team-branches.txt to testbrowser
808
809
    @property
810
    def next_url(self):
811
        return canonical_url(self.context)
812
6096.6.5 by Tim Penhey
Change cancel button to cancel link.
813
    cancel_url = next_url
814
3691.112.9 by David Allouche
review fixes, convert xx-team-branches.txt to testbrowser
815
8211.5.3 by Paul Hummer
Don't want to completely remove whiteboards, because vcs-imports have a valid use case for them
816
class BranchEditWhiteboardView(BranchEditFormView):
817
    """A view for editing the whiteboard only."""
818
819
    field_names = ['whiteboard']
820
821
9570.12.1 by Tim Penhey
Add a link and simple view for editing the status.
822
class BranchEditStatusView(BranchEditFormView):
823
    """A view for editing the lifecycle status only."""
824
825
    field_names = ['lifecycle_status']
826
827
4795.2.10 by jml at canonical
Rename mirror failure to mirror status
828
class BranchMirrorStatusView(LaunchpadFormView):
829
    """This view displays the mirror status of a branch.
830
831
    This includes the next mirror time and any failures that may have
832
    occurred.
833
    """
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
834
835
    MAXIMUM_STATUS_MESSAGE_LENGTH = 128
836
837
    schema = Interface
838
839
    field_names = []
840
5637.2.1 by Tim Penhey
Moved the error text around and fixed for Bazaar experts.
841
    @property
842
    def show_detailed_error_message(self):
843
        """Show detailed error message for branch owner and experts."""
844
        if self.user is None:
845
            return False
846
        else:
847
            celebs = getUtility(ILaunchpadCelebrities)
848
            return (self.user.inTeam(self.context.owner) or
13401.2.2 by Steve Kowalik
Kill all references to the celebrity.
849
                    self.user.inTeam(celebs.admin))
5637.2.1 by Tim Penhey
Moved the error text around and fixed for Bazaar experts.
850
851
    @property
852
    def mirror_of_ssh(self):
853
        """True if this a mirror branch with an sftp or bzr+ssh URL."""
854
        if not self.context.url:
13717.1.5 by Aaron Bentley
Fix lint.
855
            return False  # not a mirror branch
5637.2.1 by Tim Penhey
Moved the error text around and fixed for Bazaar experts.
856
        uri = URI(self.context.url)
857
        return uri.scheme in ('sftp', 'bzr+ssh')
858
859
    @property
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
860
    def in_mirror_queue(self):
861
        """Is it likely that the branch is being mirrored in the next run of
862
        the puller?
863
        """
5223.7.2 by jml at canonical
Rename mirror_request_time to next_mirror_time in code.
864
        return self.context.next_mirror_time < datetime.now(pytz.UTC)
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
865
5637.2.1 by Tim Penhey
Moved the error text around and fixed for Bazaar experts.
866
    @property
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
867
    def mirror_disabled(self):
868
        """Has mirroring this branch been disabled?"""
5223.7.2 by jml at canonical
Rename mirror_request_time to next_mirror_time in code.
869
        return self.context.next_mirror_time is None
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
870
5637.2.1 by Tim Penhey
Moved the error text around and fixed for Bazaar experts.
871
    @property
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
872
    def mirror_failed_once(self):
873
        """Has there been exactly one failed attempt to mirror this branch?"""
874
        return self.context.mirror_failures == 1
875
5637.2.1 by Tim Penhey
Moved the error text around and fixed for Bazaar experts.
876
    @property
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
877
    def mirror_status_message(self):
878
        """A message from a bad scan or pull, truncated for display."""
879
        message = self.context.mirror_status_message
880
        if len(message) <= self.MAXIMUM_STATUS_MESSAGE_LENGTH:
881
            return message
882
        return truncate_text(
883
            message, self.MAXIMUM_STATUS_MESSAGE_LENGTH) + ' ...'
884
5637.2.1 by Tim Penhey
Moved the error text around and fixed for Bazaar experts.
885
    @property
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
886
    def show_mirror_failure(self):
887
        """True if mirror_of_ssh is false and branch mirroring failed."""
5637.2.1 by Tim Penhey
Moved the error text around and fixed for Bazaar experts.
888
        return not self.mirror_of_ssh and self.context.mirror_failures
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
889
4795.2.15 by jml at canonical
Add proper urls for status view.
890
    @property
891
    def action_url(self):
4795.2.16 by jml at canonical
Fully rename to mirror-status and fix up a couple of bugs.
892
        return "%s/+mirror-status" % canonical_url(self.context)
4795.2.15 by jml at canonical
Add proper urls for status view.
893
894
    @property
895
    def next_url(self):
896
        return canonical_url(self.context)
897
4795.2.7 by jml at canonical
Make the "Try Again" button work at the expense of breaking a couple of
898
    @action('Try again', name='try-again')
899
    def retry(self, action, data):
900
        self.context.requestMirror()
901
902
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
903
class BranchDeletionView(LaunchpadFormView):
904
    """Used to delete a branch."""
905
906
    schema = IBranch
907
    field_names = []
908
9084.3.3 by Tim Penhey
Update branch-delete.pt
909
    @property
910
    def page_title(self):
911
        return smartquote('Delete branch "%s"' % self.context.displayname)
912
5616.3.35 by Aaron Bentley
Updates from review
913
    @cachedproperty
914
    def display_deletion_requirements(self):
5616.3.11 by Aaron Bentley
Refactor and document interface
915
        """Normal deletion requirements, indication of permissions.
916
917
        :return: A list of tuples of (item, action, reason, allowed)
918
        """
5616.3.1 by Aaron Bentley
Initial implementation of smart branch deletion page
919
        reqs = []
920
        for item, (action, reason) in (
921
            self.context.deletionRequirements().iteritems()):
922
            allowed = check_permission('launchpad.Edit', item)
923
            reqs.append((item, action, reason, allowed))
924
        return reqs
925
7735.1.1 by Tim Penhey
Don't allow branches with other branches stacked on them to be deleted.
926
    @cachedproperty
927
    def stacked_branches_count(self):
928
        """Cache a count of the branches stacked on this."""
929
        return self.context.getStackedBranches().count()
930
7735.1.2 by Tim Penhey
Fix plural text.
931
    def stacked_branches_text(self):
932
        """Cache a count of the branches stacked on this."""
933
        if self.stacked_branches_count == 1:
934
            return _('branch')
935
        else:
936
            return _('branches')
937
5616.3.2 by Aaron Bentley
Only show delete button when deletion will succeed
938
    def all_permitted(self):
5616.3.11 by Aaron Bentley
Refactor and document interface
939
        """Return True if all deletion requirements are permitted, else False.
940
5616.3.35 by Aaron Bentley
Updates from review
941
        Uses display_deletion_requirements as its source data.
5616.3.11 by Aaron Bentley
Refactor and document interface
942
        """
7735.1.1 by Tim Penhey
Don't allow branches with other branches stacked on them to be deleted.
943
        # Not permitted if there are any branches stacked on this.
944
        if self.stacked_branches_count > 0:
945
            return False
5616.3.2 by Aaron Bentley
Only show delete button when deletion will succeed
946
        return len([item for item, action, reason, allowed in
5616.3.35 by Aaron Bentley
Updates from review
947
            self.display_deletion_requirements if not allowed]) == 0
5616.3.2 by Aaron Bentley
Only show delete button when deletion will succeed
948
5616.3.35 by Aaron Bentley
Updates from review
949
    @action('Delete', name='delete_branch',
5616.3.2 by Aaron Bentley
Only show delete button when deletion will succeed
950
            condition=lambda x, y: x.all_permitted())
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
951
    def delete_branch_action(self, action, data):
952
        branch = self.context
5616.3.2 by Aaron Bentley
Only show delete button when deletion will succeed
953
        if self.all_permitted():
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
954
            # Since the user is going to delete the branch, we need to have
7847.1.23 by Jonathan Lange
Make deleting a package branch go back to the package branch listing
955
            # somewhere valid to send them next.
7847.1.25 by Jonathan Lange
Simplify by just getting the canonical url of the branch target.
956
            self.next_url = canonical_url(branch.target)
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
957
            message = "Branch %s deleted." % branch.unique_name
5616.3.1 by Aaron Bentley
Initial implementation of smart branch deletion page
958
            self.context.destroySelf(break_references=True)
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
959
            self.request.response.addNotification(message)
960
        else:
961
            self.request.response.addNotification(
962
                "This branch cannot be deleted.")
963
            self.next_url = canonical_url(branch)
964
5616.3.1 by Aaron Bentley
Initial implementation of smart branch deletion page
965
    @property
966
    def branch_deletion_actions(self):
5616.3.11 by Aaron Bentley
Refactor and document interface
967
        """Return the branch deletion actions as a zpt-friendly dict.
968
969
        The keys are 'delete' and 'alter'; the values are dicts of
970
        'item', 'reason' and 'allowed'.
971
        """
5616.3.12 by Aaron Bentley
Update from comments
972
        row_dict = {'delete': [], 'alter': [], 'break_link': []}
5616.3.1 by Aaron Bentley
Initial implementation of smart branch deletion page
973
        for item, action, reason, allowed in (
5616.3.35 by Aaron Bentley
Updates from review
974
            self.display_deletion_requirements):
5616.3.1 by Aaron Bentley
Initial implementation of smart branch deletion page
975
            if IBugBranch.providedBy(item):
5616.3.12 by Aaron Bentley
Update from comments
976
                action = 'break_link'
5616.3.1 by Aaron Bentley
Initial implementation of smart branch deletion page
977
            elif ISpecificationBranch.providedBy(item):
5616.3.12 by Aaron Bentley
Update from comments
978
                action = 'break_link'
979
            elif IProductSeries.providedBy(item):
980
                action = 'break_link'
5616.3.1 by Aaron Bentley
Initial implementation of smart branch deletion page
981
            row = {'item': item,
982
                   'reason': reason,
983
                   'allowed': allowed,
984
                  }
985
            row_dict[action].append(row)
986
        return row_dict
987
6618.2.1 by Paul Hummer
Adds cancel_url, fixes tests
988
    @property
989
    def cancel_url(self):
990
        return canonical_url(self.context)
5616.3.35 by Aaron Bentley
Updates from review
991
4629.4.1 by Tim Penhey
First cut, and some branch cleanup.
992
7675.477.1 by Paul Hummer
Reverted the reversion of the patch that went into devel by mistake
993
class BranchUpgradeView(LaunchpadFormView):
994
    """Used to upgrade a branch."""
995
996
    schema = IBranch
997
    field_names = []
998
999
    @property
1000
    def page_title(self):
1001
        return smartquote('Upgrade branch "%s"' % self.context.displayname)
1002
1003
    @property
1004
    def next_url(self):
1005
        return canonical_url(self.context)
1006
1007
    cancel_url = next_url
1008
1009
    @action('Upgrade', name='upgrade_branch')
1010
    def upgrade_branch_action(self, action, data):
13750.1.4 by Aaron Bentley
Ensure view produces a nice notification.
1011
        try:
1012
            self.context.requestUpgrade(self.user)
1013
        except CannotUpgradeBranch as e:
1014
            self.request.response.addErrorNotification(e)
7675.477.1 by Paul Hummer
Reverted the reversion of the patch that went into devel by mistake
1015
1016
3691.112.9 by David Allouche
review fixes, convert xx-team-branches.txt to testbrowser
1017
class BranchEditView(BranchEditFormView, BranchNameValidationMixin):
5350.3.2 by Tim Penhey
Allow any logged in user to edit the branch whiteboard.
1018
    """The main branch view for editing the branch attributes."""
3691.68.12 by James Henstridge
convert branch edit and add views to LaunchpadFormView
1019
6096.6.1 by Tim Penhey
Add owner to the branch edit page (and removed author).
1020
    field_names = [
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
1021
        'owner', 'name', 'explicitly_private', 'url', 'description',
1022
        'lifecycle_status']
3691.68.12 by James Henstridge
convert branch edit and add views to LaunchpadFormView
1023
5430.5.2 by Tim Penhey
Setting the radio button for the status widget when editing
1024
    custom_widget('lifecycle_status', LaunchpadRadioWidgetWithDescription)
1025
3691.68.12 by James Henstridge
convert branch edit and add views to LaunchpadFormView
1026
    def setUpFields(self):
1027
        LaunchpadFormView.setUpFields(self)
1028
        # This is to prevent users from converting push/import
1029
        # branches to pull branches.
4333.6.1 by Tim Penhey
Allow the user to toggle branch privacy if the should be allowed to.
1030
        branch = self.context
4693.1.1 by Tim Penhey
First part of remote branches.
1031
        if branch.branch_type in (BranchType.HOSTED, BranchType.IMPORTED):
3691.68.12 by James Henstridge
convert branch edit and add views to LaunchpadFormView
1032
            self.form_fields = self.form_fields.omit('url')
1033
8137.17.24 by Barry Warsaw
thread merge
1034
        policy = IBranchNamespacePolicy(branch.namespace)
1035
        if branch.private:
1036
            # If the branch is private, and can be public, show the field.
1037
            show_private_field = policy.canBranchesBePublic()
13760.3.5 by Ian Booth
Add story tests and implementation for branch edit page
1038
1039
            # If this branch is public but is deemed private because it is
1040
            # stacked on a private branch, disable the field.
1041
            if not branch.explicitly_private:
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
1042
                show_private_field = False
13760.3.5 by Ian Booth
Add story tests and implementation for branch edit page
1043
                private_info = Bool(
1044
                    __name__="private",
1045
                    title=_("Branch is confidential"),
1046
                    description=_(
1047
                        "This branch is confidential because it is stacked "
1048
                        "on a private branch."))
1049
                private_info_field = form.Fields(
1050
                    private_info, render_context=self.render_context)
1051
                self.form_fields = self.form_fields.omit('private')
1052
                self.form_fields = private_info_field + self.form_fields
1053
                self.form_fields['private'].custom_widget = (
1054
                    CustomWidgetFactory(
1055
                        CheckBoxWidget, extra='disabled="disabled"'))
4333.6.1 by Tim Penhey
Allow the user to toggle branch privacy if the should be allowed to.
1056
        else:
8137.17.24 by Barry Warsaw
thread merge
1057
            # If the branch is public, and can be made private, show the
9941.1.4 by Tim Penhey
Update the view class to pass through the user and update the story.
1058
            # field.  Users with special access rights to branches can set
1059
            # public branches as private.
1060
            show_private_field = (
1061
                policy.canBranchesBePrivate() or
1062
                user_has_special_branch_access(self.user))
4333.6.1 by Tim Penhey
Allow the user to toggle branch privacy if the should be allowed to.
1063
8137.17.24 by Barry Warsaw
thread merge
1064
        if not show_private_field:
13760.3.10 by Ian Booth
Rework implementation to remove metaclass and setattr - use explicitly_private property
1065
            self.form_fields = self.form_fields.omit('explicitly_private')
4333.6.1 by Tim Penhey
Allow the user to toggle branch privacy if the should be allowed to.
1066
6096.6.3 by Tim Penhey
Make Bazaar Experts and Launchpad administrators able to reassign a branch to anyone.
1067
        # If the user can administer branches, then they should be able to
1068
        # assign the ownership of the branch to any valid person or team.
1069
        if check_permission('launchpad.Admin', branch):
1070
            owner_field = self.schema['owner']
1071
            any_owner_choice = Choice(
1072
                __name__='owner', title=owner_field.title,
13314.12.2 by Ian Booth
Lint
1073
                description=_("As an administrator you are able to reassign"
6096.6.3 by Tim Penhey
Make Bazaar Experts and Launchpad administrators able to reassign a branch to anyone.
1074
                                " this branch to any person or team."),
1075
                required=True, vocabulary='ValidPersonOrTeam')
1076
            any_owner_field = form.Fields(
1077
                any_owner_choice, render_context=self.render_context)
1078
            # Replace the normal owner field with a more permissive vocab.
1079
            self.form_fields = self.form_fields.omit('owner')
1080
            self.form_fields = any_owner_field + self.form_fields
10843.3.7 by Tim Penhey
If we are in the situation where the branch editor isn't in the team of the branch owner (as we get in the situation where an official package branch is being edited by an uploader), add the owner to the vocabulary used for the owner field.~
1081
        else:
1082
            # For normal users, there is an edge case with package branches
1083
            # where the editor may not be in the team of the branch owner.  In
1084
            # these cases we need to extend the vocabulary connected to the
1085
            # owner field.
1086
            if not self.user.inTeam(self.context.owner):
1087
                vocab = UserTeamsParticipationPlusSelfVocabulary()
1088
                owner = self.context.owner
1089
                terms = [SimpleTerm(
13314.13.1 by Ian Booth
Fix implementation - use custom vocab
1090
                    owner, owner.name, owner.unique_displayname)]
10843.3.7 by Tim Penhey
If we are in the situation where the branch editor isn't in the team of the branch owner (as we get in the situation where an official package branch is being edited by an uploader), add the owner to the vocabulary used for the owner field.~
1091
                terms.extend([term for term in vocab])
1092
                owner_field = self.schema['owner']
1093
                owner_choice = Choice(
1094
                    __name__='owner', title=owner_field.title,
13314.12.2 by Ian Booth
Lint
1095
                    description=owner_field.description,
10843.3.7 by Tim Penhey
If we are in the situation where the branch editor isn't in the team of the branch owner (as we get in the situation where an official package branch is being edited by an uploader), add the owner to the vocabulary used for the owner field.~
1096
                    required=True, vocabulary=SimpleVocabulary(terms))
1097
                new_owner_field = form.Fields(
1098
                    owner_choice, render_context=self.render_context)
1099
                # Replace the normal owner field with a more permissive vocab.
1100
                self.form_fields = self.form_fields.omit('owner')
1101
                self.form_fields = new_owner_field + self.form_fields
6096.6.3 by Tim Penhey
Make Bazaar Experts and Launchpad administrators able to reassign a branch to anyone.
1102
3691.112.6 by David Allouche
properly validate branch name in branch/+edit
1103
    def validate(self, data):
4673.7.1 by Michael Hudson
prevent reassignment of +junk branches to teams, test, and a couple more
1104
        # Check that we're not moving a team branch to the +junk
1105
        # pseudo project.
6096.6.4 by Tim Penhey
Update the pagetests.
1106
        owner = data['owner']
8418.1.1 by Jonathan Lange
Remove a stack of code that lets you move branches.
1107
        if 'name' in data:
8418.1.7 by Jonathan Lange
Remove any references to product in the branch edit form.
1108
            # Only validate if the name has changed or the owner has changed.
8418.1.1 by Jonathan Lange
Remove a stack of code that lets you move branches.
1109
            if ((data['name'] != self.context.name) or
6194.1.1 by Tim Penhey
Back out the change that required branches names to be unique across a product, but keep the good error messages.
1110
                (owner != self.context.owner)):
8418.1.8 by Jonathan Lange
Clean up the code, improve test coverage.
1111
                # We only allow moving within the same branch target for now.
1112
                namespace = self.context.target.getNamespace(owner)
8418.1.7 by Jonathan Lange
Remove any references to product in the branch edit form.
1113
                try:
1114
                    namespace.validateMove(
1115
                        self.context, self.user, name=data['name'])
13025.3.1 by William Grant
Display an error when attempting to change the owner of a branch to a value forbidden by the visibility policy.
1116
                except BranchCreationForbidden:
1117
                    self.addError(
1118
                        "%s is not allowed to own branches in %s." % (
1119
                        owner.displayname, self.context.target.displayname))
8418.1.7 by Jonathan Lange
Remove any references to product in the branch edit form.
1120
                except BranchExists, e:
1121
                    self._setBranchExists(e.existing_branch)
4953.6.1 by Tim Penhey
Removed unhelpful or obsolete help, and corrected URL validation
1122
1123
        # If the branch is a MIRRORED branch, then the url
1124
        # must be supplied, and if HOSTED the url must *not*
1125
        # be supplied.
1126
        url = data.get('url')
4693.1.1 by Tim Penhey
First part of remote branches.
1127
        if self.context.branch_type == BranchType.MIRRORED:
4953.6.1 by Tim Penhey
Removed unhelpful or obsolete help, and corrected URL validation
1128
            if url is None:
1129
                # If the url is not set due to url validation errors,
1130
                # there will be an error set for it.
5243.1.1 by Jonathan Knowles
Renaming method getWidgetError (in LaunchpadFormView) to getFieldError.
1131
                error = self.getFieldError('url')
4953.6.1 by Tim Penhey
Removed unhelpful or obsolete help, and corrected URL validation
1132
                if not error:
1133
                    self.setFieldError(
1134
                        'url',
1135
                        'Branch URLs are required for Mirrored branches.')
1136
        else:
4953.6.2 by Tim Penhey
Updates following review comments.
1137
            # We don't care about whether the URL is set for REMOTE branches,
1138
            # and the URL field is not shown for IMPORT or HOSTED branches.
4953.6.1 by Tim Penhey
Removed unhelpful or obsolete help, and corrected URL validation
1139
            pass
3691.112.9 by David Allouche
review fixes, convert xx-team-branches.txt to testbrowser
1140
1141
9041.5.1 by Tim Penhey
Mid change when I realise I need trunk.
1142
class BranchReviewerEditView(BranchEditFormView):
7211.1.4 by Tim Penhey
Changed view names and added test.
1143
    """The view to set the review team."""
1144
9041.5.1 by Tim Penhey
Mid change when I realise I need trunk.
1145
    field_names = ['reviewer']
7211.1.4 by Tim Penhey
Changed view names and added test.
1146
1147
    @property
1148
    def initial_values(self):
1149
        return {'reviewer': self.context.code_reviewer}
7211.1.2 by Tim Penhey
Added the views.
1150
1151
3691.112.6 by David Allouche
properly validate branch name in branch/+edit
1152
class BranchAddView(LaunchpadFormView, BranchNameValidationMixin):
3691.68.12 by James Henstridge
convert branch edit and add views to LaunchpadFormView
1153
12924.3.3 by Tim Penhey
Fix up the branch_type vocabulary, and remove some XXX comments that are not worthy.
1154
    class schema(Interface):
1155
        use_template(
13756.5.1 by Jelmer Vernooij
Reimport bzr code import support UI patch.
1156
            IBranch, include=['owner', 'name', 'lifecycle_status'])
12924.3.3 by Tim Penhey
Fix up the branch_type vocabulary, and remove some XXX comments that are not worthy.
1157
6954.3.3 by Tim Penhey
Make the read only fields available for initial creation.
1158
    for_input = True
13756.5.1 by Jelmer Vernooij
Reimport bzr code import support UI patch.
1159
    field_names = ['owner', 'name', 'lifecycle_status']
3691.112.1 by David Allouche
Fixing $branch/+edit, day one
1160
3691.68.12 by James Henstridge
convert branch edit and add views to LaunchpadFormView
1161
    branch = None
5430.5.1 by Tim Penhey
Hacking the radio widget to show vocab descriptions
1162
    custom_widget('lifecycle_status', LaunchpadRadioWidgetWithDescription)
3691.68.12 by James Henstridge
convert branch edit and add views to LaunchpadFormView
1163
8120.2.12 by Jonathan Lange
Fix up the initial focus -- lost in combining the two views.
1164
    initial_focus_widget = 'name'
1165
4693.1.1 by Tim Penhey
First part of remote branches.
1166
    @property
9084.3.2 by Tim Penhey
Update branch-add.pt.
1167
    def page_title(self):
1168
        return 'Register a branch'
1169
1170
    @property
4693.1.1 by Tim Penhey
First part of remote branches.
1171
    def initial_values(self):
8120.2.11 by Jonathan Lange
Delete PersonBranchAddView and ProductBranchAddView, since the only
1172
        return {
1173
            'owner': self.default_owner,
1174
            'branch_type': UICreatableBranchType.MIRRORED}
1175
1176
    @property
8120.2.13 by Jonathan Lange
Make the unique name javascript work by making it use the name of the
1177
    def target(self):
1178
        """The branch target for the context."""
1179
        return IBranchTarget(self.context)
1180
1181
    @property
8120.2.11 by Jonathan Lange
Delete PersonBranchAddView and ProductBranchAddView, since the only
1182
    def default_owner(self):
1183
        """The default owner of branches in this context.
1184
1185
        If the context is a person, then it's the context. If the context is
1186
        not a person, then the default owner is the currently logged-in user.
1187
        """
1188
        return IPerson(self.context, self.user)
4693.1.1 by Tim Penhey
First part of remote branches.
1189
5543.5.10 by Tim Penhey
Updates following review.
1190
    @action('Register Branch', name='add')
3691.68.12 by James Henstridge
convert branch edit and add views to LaunchpadFormView
1191
    def add_action(self, action, data):
1192
        """Handle a request to create a new branch for this product."""
4333.5.5 by Tim Penhey
Updating the views for creating branches, and the XMLRPC views
1193
        try:
8120.2.14 by Jonathan Lange
Minor cleanup
1194
            namespace = self.target.getNamespace(data['owner'])
7959.1.10 by Tim Penhey
Kill IBranchSet.new.
1195
            self.branch = namespace.createBranch(
13756.5.1 by Jelmer Vernooij
Reimport bzr code import support UI patch.
1196
                branch_type=BranchType.HOSTED,
4333.5.5 by Tim Penhey
Updating the views for creating branches, and the XMLRPC views
1197
                name=data['name'],
6043.1.20 by Jonathan Lange
Uses of the creator kwarg that I missed.
1198
                registrant=self.user,
13756.5.1 by Jelmer Vernooij
Reimport bzr code import support UI patch.
1199
                url=None,
8137.17.24 by Barry Warsaw
thread merge
1200
                lifecycle_status=data['lifecycle_status'])
4333.5.13 by Tim Penhey
Updates following review comments.
1201
        except BranchCreationForbidden:
8120.2.10 by Jonathan Lange
Many classes of error go away when you are only allowed to create branches
1202
            self.addError(
1203
                "You are not allowed to create branches in %s." %
1204
                self.context.displayname)
7362.3.6 by Jonathan Lange
Don't look before you leap.
1205
        except BranchExists, e:
1206
            self._setBranchExists(e.existing_branch)
4333.5.13 by Tim Penhey
Updates following review comments.
1207
        else:
4333.5.5 by Tim Penhey
Updating the views for creating branches, and the XMLRPC views
1208
            self.next_url = canonical_url(self.branch)
1209
3691.112.5 by David Allouche
properly validate branch name in +addbranch
1210
    def validate(self, data):
6194.1.1 by Tim Penhey
Back out the change that required branches names to be unique across a product, but keep the good error messages.
1211
        owner = data['owner']
5543.5.1 by Tim Penhey
Refactoring the +addbranch forms
1212
1213
        if not self.user.inTeam(owner):
1214
            self.setFieldError(
1215
                'owner',
5543.5.6 by Tim Penhey
Tidying up
1216
                'You are not a member of %s' % owner.displayname)
5543.5.1 by Tim Penhey
Refactoring the +addbranch forms
1217
8120.2.17 by Jonathan Lange
Restore the cancel URL link, lost when combining the view classes.
1218
    @property
1219
    def cancel_url(self):
1220
        return canonical_url(self.context)
1221
3691.112.5 by David Allouche
properly validate branch name in +addbranch
1222
4333.2.2 by Tim Penhey
linked, albiet weirdly
1223
class BranchSubscriptionsView(LaunchpadView):
4333.2.10 by Tim Penhey
yet more review updates
1224
    """The view for the branch subscriptions portlet.
4333.2.9 by Tim Penhey
more updates following review
1225
1226
    The view is used to provide a decorated list of branch subscriptions
1227
    in order to provide links to be able to edit the subscriptions
1228
    based on whether or not the user is able to edit the subscription.
1229
    """
4333.2.2 by Tim Penhey
linked, albiet weirdly
1230
6699.2.4 by Paul Hummer
Adds the new subscription portlet
1231
    def owner_is_registrant(self):
1232
        """Return whether or not owner is the same as the registrant"""
1233
        return self.context.owner == self.context.registrant
6735.1.1 by Paul Hummer
Adds merge proposal stuffs
1234
4414.5.4 by Tim Penhey
First cut at getting a page to register a merge proposal
1235
5579.2.1 by Tim Penhey
Some queue management
1236
class BranchMergeQueueView(LaunchpadView):
5579.2.7 by Tim Penhey
Update the existing pagetest for new actions.
1237
    """The view used to render the merge queue for a branch."""
5579.2.11 by Tim Penhey
Updates following review.
1238
5579.2.1 by Tim Penhey
Some queue management
1239
    @cachedproperty
1240
    def merge_queue(self):
1241
        """Get the merge queue and check visibility."""
1242
        result = []
5579.2.11 by Tim Penhey
Updates following review.
1243
        for proposal in self.context.getMergeQueue():
1244
            # If the logged in user cannot view the proposal then we
1245
            # show a "place holder" in the queue position.
5579.2.1 by Tim Penhey
Some queue management
1246
            if check_permission('launchpad.View', proposal):
5579.2.11 by Tim Penhey
Updates following review.
1247
                result.append(proposal)
5579.2.1 by Tim Penhey
Some queue management
1248
            else:
5579.2.11 by Tim Penhey
Updates following review.
1249
                result.append(None)
5579.2.1 by Tim Penhey
Some queue management
1250
        return result
1251
1252
6623.3.1 by Tim Penhey
Add an option on proposing a merge to choose needs review or work in progress.
1253
class RegisterProposalStatus(EnumeratedType):
1254
    """A restricted status enum for the register proposal form."""
1255
6623.3.3 by Tim Penhey
Updates following review.
1256
    # The text in this enum is different from the general proposal status
1257
    # enum as we want the help text that is shown in the form to be more
1258
    # relevant to the registration of the proposal.
1259
6623.3.1 by Tim Penhey
Add an option on proposing a merge to choose needs review or work in progress.
1260
    NEEDS_REVIEW = Item("""
1261
        Needs review
1262
1263
        The changes are ready for review.
1264
        """)
1265
1266
    WORK_IN_PROGRESS = Item("""
1267
        Work in progress
1268
1269
        The changes are still being actively worked on, and are not
1270
        yet ready for review.
1271
        """)
1272
1273
1274
class RegisterProposalSchema(Interface):
1275
    """The schema to define the form for registering a new merge proposal."""
7325.8.4 by Paul Hummer
Added source branch attribute to bmp api
1276
    target_branch = Choice(
1277
        title=_('Target Branch'),
8377.8.25 by Tim Penhey
Add a pagetest for merging between packages and products.
1278
        vocabulary='Branch', required=True, readonly=True,
7325.8.4 by Paul Hummer
Added source branch attribute to bmp api
1279
        description=_(
1280
            "The branch that the source branch will be merged into."))
1281
9773.1.9 by Aaron Bentley
Allow setting and viewing prerequisite branch.
1282
    prerequisite_branch = Choice(
1283
        title=_('Prerequisite Branch'),
1284
        vocabulary='Branch', required=False, readonly=False,
1285
        description=_(
9773.1.15 by Aaron Bentley
Clarify text.
1286
            'A branch that should be merged before this one.  (Its changes'
9773.1.16 by Aaron Bentley
Fix syntax in RegisterProposalSchema.
1287
            ' will not be shown in the diff.)'))
9773.1.9 by Aaron Bentley
Allow setting and viewing prerequisite branch.
1288
7074.2.3 by Tim Penhey
Test the date_review_requested, and other reviewer comments.
1289
    comment = Text(
7675.548.2 by Tim Penhey
Update for the description field.
1290
        title=_('Description of the Change'), required=False,
1291
        description=_('Describe what changes your branch introduces, '
1292
                      'what bugs it fixes, or what features it implements. '
1293
                      'Ideally include rationale and how to test.'))
7074.2.1 by Tim Penhey
Change the register a merge proposal to be a form where the user can specify an initial comment and request a reviewer.
1294
7055.6.16 by Tim Penhey
vote tag and field name fixes.
1295
    reviewer = copy_field(
1296
        ICodeReviewVoteReference['reviewer'], required=False)
7074.2.1 by Tim Penhey
Change the register a merge proposal to be a form where the user can specify an initial comment and request a reviewer.
1297
9656.1.4 by Nathan Handler
Wrap lines to resolve some make lint warnings
1298
    review_type = copy_field(
1299
        ICodeReviewVoteReference['review_type'],
1300
        description=u'Lowercase keywords describing the type of review you '
1301
                     'would like to be performed.')
6623.3.1 by Tim Penhey
Add an option on proposing a merge to choose needs review or work in progress.
1302
10155.5.1 by Tim Penhey
Make the less used fields on register merge proposal extra options.
1303
    commit_message = IBranchMergeProposal['commit_message']
1304
1305
    needs_review = Bool(
1306
        title=_("Needs review"), required=True, default=True,
1307
        description=_(
1308
            "Is the proposal ready for review now?"))
1309
6623.3.1 by Tim Penhey
Add an option on proposing a merge to choose needs review or work in progress.
1310
4414.5.4 by Tim Penhey
First cut at getting a page to register a merge proposal
1311
class RegisterBranchMergeProposalView(LaunchpadFormView):
1312
    """The view to register new branch merge proposals."""
6623.3.1 by Tim Penhey
Add an option on proposing a merge to choose needs review or work in progress.
1313
    schema = RegisterProposalSchema
5121.5.10 by Tim Penhey
lint fixes
1314
    for_input = True
4414.5.6 by Tim Penhey
Limiting fields
1315
5121.5.1 by Tim Penhey
Initial work with new widget.
1316
    custom_widget('target_branch', TargetBranchWidget)
7573.2.10 by Paul Hummer
Made the merge proposal creation form use a fixed width font
1317
    custom_widget('comment', TextAreaWidget, cssClass='codereviewcomment')
7074.2.1 by Tim Penhey
Change the register a merge proposal to be a form where the user can specify an initial comment and request a reviewer.
1318
9209.1.2 by Tim Penhey
Use generic edit to propose for merging.
1319
    page_title = label = 'Propose branch for merging'
1320
7074.2.1 by Tim Penhey
Change the register a merge proposal to be a form where the user can specify an initial comment and request a reviewer.
1321
    @property
6618.2.1 by Paul Hummer
Adds cancel_url, fixes tests
1322
    def cancel_url(self):
5280.4.19 by Tim Penhey
merge poposal page test now passes.
1323
        return canonical_url(self.context)
1324
5430.1.1 by Tim Penhey
Remove the ability for junk branches to traverse to +register-merge.
1325
    def initialize(self):
8377.8.4 by Tim Penhey
Ask the branch target if it supports merge proposals.
1326
        """Show a 404 if the branch target doesn't support proposals."""
1327
        if not self.context.target.supports_merge_proposals:
5430.1.1 by Tim Penhey
Remove the ability for junk branches to traverse to +register-merge.
1328
            raise NotFound(self.context, '+register-merge')
1329
        LaunchpadFormView.initialize(self)
1330
7074.2.1 by Tim Penhey
Change the register a merge proposal to be a form where the user can specify an initial comment and request a reviewer.
1331
    @action('Propose Merge', name='register')
4414.5.8 by Tim Penhey
Changing fields and getting it working.
1332
    def register_action(self, action, data):
1333
        """Register the new branch merge proposal."""
1334
1335
        registrant = self.user
1336
        source_branch = self.context
4414.5.25 by Tim Penhey
Updates following review
1337
        target_branch = data['target_branch']
9773.1.12 by Aaron Bentley
Make prerequisite_branch optional.
1338
        prerequisite_branch = data.get('prerequisite_branch')
4414.5.13 by Tim Penhey
Still work in progress
1339
7334.4.18 by Tim Penhey
Updates following review comments.
1340
        review_requests = []
11542.3.3 by Ian Booth
Refactor initial changes
1341
        reviewer = data.get('reviewer')
1342
        review_type = data.get('review_type')
11542.3.20 by Ian Booth
Test refactoring as per code review
1343
        if reviewer is None:
1344
            reviewer = target_branch.code_reviewer
11542.3.3 by Ian Booth
Refactor initial changes
1345
        if reviewer is not None:
1346
            review_requests.append((reviewer, review_type))
7334.4.18 by Tim Penhey
Updates following review comments.
1347
4414.5.13 by Tim Penhey
Still work in progress
1348
        try:
6618.2.1 by Paul Hummer
Adds cancel_url, fixes tests
1349
            proposal = source_branch.addLandingTarget(
4414.5.13 by Tim Penhey
Still work in progress
1350
                registrant=registrant, target_branch=target_branch,
10155.5.1 by Tim Penhey
Make the less used fields on register merge proposal extra options.
1351
                prerequisite_branch=prerequisite_branch,
1352
                needs_review=data['needs_review'],
7675.548.9 by Tim Penhey
Stop pretending it is an initial comment.
1353
                description=data.get('comment'),
10155.5.1 by Tim Penhey
Make the less used fields on register merge proposal extra options.
1354
                review_requests=review_requests,
11542.3.20 by Ian Booth
Test refactoring as per code review
1355
                commit_message=data.get('commit_message'))
6618.2.1 by Paul Hummer
Adds cancel_url, fixes tests
1356
            self.next_url = canonical_url(proposal)
4414.5.13 by Tim Penhey
Still work in progress
1357
        except InvalidBranchMergeProposal, error:
1358
            self.addError(str(error))
5280.4.19 by Tim Penhey
merge poposal page test now passes.
1359
4414.5.13 by Tim Penhey
Still work in progress
1360
    def validate(self, data):
1361
        source_branch = self.context
1362
        target_branch = data.get('target_branch')
4414.5.12 by Tim Penhey
work in progress
1363
4414.5.8 by Tim Penhey
Changing fields and getting it working.
1364
        # Make sure that the target branch is different from the context.
4414.5.13 by Tim Penhey
Still work in progress
1365
        if target_branch is None:
1366
            # Skip the following tests.
1367
            # The existance of the target_branch is handled by the form
1368
            # machinery.
1369
            pass
1370
        elif source_branch == target_branch:
4414.5.8 by Tim Penhey
Changing fields and getting it working.
1371
            self.setFieldError(
1372
                'target_branch',
1373
                "The target branch cannot be the same as the source branch.")
1374
        else:
1375
            # Make sure that the target_branch is in the same project.
8377.8.7 by Tim Penhey
Fix the view to not check .product.
1376
            if not target_branch.isBranchMergeable(source_branch):
4414.5.8 by Tim Penhey
Changing fields and getting it working.
1377
                self.setFieldError(
1378
                    'target_branch',
8377.8.7 by Tim Penhey
Fix the view to not check .product.
1379
                    "This branch is not mergeable into %s." %
1380
                    target_branch.bzr_identity)
4414.5.8 by Tim Penhey
Changing fields and getting it working.
1381
6233.6.1 by Michael Hudson
stuff
1382
1383
class BranchRequestImportView(LaunchpadFormView):
6233.6.10 by Michael Hudson
a docstring
1384
    """The view to provide an 'Import now' button on the branch index page.
1385
1386
    This only appears on the page of a branch with an associated code import
1387
    that is being actively imported and where there is a import scheduled at
1388
    some point in the future.
1389
    """
6233.6.1 by Michael Hudson
stuff
1390
1391
    schema = IBranch
1392
    field_names = []
1393
6233.6.9 by Michael Hudson
review comments
1394
    form_style = "display: inline"
6233.6.1 by Michael Hudson
stuff
1395
1396
    @property
1397
    def next_url(self):
1398
        return canonical_url(self.context)
1399
1400
    @action('Import Now', name='request')
1401
    def request_import_action(self, action, data):
10454.12.4 by James Westby
Use the new method from the existing browser code.
1402
        try:
1403
            self.context.code_import.requestImport(
1404
                self.user, error_if_already_requested=True)
1405
            self.request.response.addNotification(
1406
                "Import will run as soon as possible.")
1407
        except CodeImportNotInReviewedState:
6233.6.2 by Michael Hudson
tests and stuff
1408
            self.request.response.addNotification(
6233.6.9 by Michael Hudson
review comments
1409
                "This import is no longer being updated automatically.")
10454.12.4 by James Westby
Use the new method from the existing browser code.
1410
        except CodeImportAlreadyRunning:
6233.6.2 by Michael Hudson
tests and stuff
1411
            self.request.response.addNotification(
1412
                "The import is already running.")
10454.12.4 by James Westby
Use the new method from the existing browser code.
1413
        except CodeImportAlreadyRequested, e:
1414
            user = e.requesting_user
6233.6.2 by Michael Hudson
tests and stuff
1415
            adapter = queryAdapter(user, IPathAdapter, 'fmt')
1416
            self.request.response.addNotification(
6233.6.5 by Michael Hudson
Much better tests. Shame they don't work.
1417
                structured("The import has already been requested by %s." %
7119.1.5 by Guilherme Salgado
Fix a couple tests
1418
                           adapter.link(None)))
6233.6.1 by Michael Hudson
stuff
1419
1420
    @property
1421
    def prefix(self):
1422
        return "request%s" % self.context.id
1423
1424
    @property
1425
    def action_url(self):
1426
        return "%s/@@+request-import" % canonical_url(self.context)
8137.17.24 by Barry Warsaw
thread merge
1427
1428
8377.9.2 by Michael Hudson
allow retrying FAILING builds
1429
class TryImportAgainView(LaunchpadFormView):
8377.9.4 by Michael Hudson
minor progress
1430
    """The view to provide an 'Try again' button on the branch index page.
8377.9.2 by Michael Hudson
allow retrying FAILING builds
1431
1432
    This only appears on the page of a branch with an associated code import
8377.9.4 by Michael Hudson
minor progress
1433
    that is marked as failing.
8377.9.2 by Michael Hudson
allow retrying FAILING builds
1434
    """
1435
1436
    schema = IBranch
1437
    field_names = []
1438
1439
    @property
1440
    def next_url(self):
1441
        return canonical_url(self.context)
1442
1443
    @action('Try Again', name='tryagain')
1444
    def request_try_again(self, action, data):
8377.9.17 by Michael Hudson
review comments
1445
        if (self.context.code_import.review_status !=
1446
            CodeImportReviewStatus.FAILING):
8377.9.2 by Michael Hudson
allow retrying FAILING builds
1447
            self.request.response.addNotification(
8377.9.4 by Michael Hudson
minor progress
1448
                "The import is now %s."
1449
                % self.context.code_import.review_status.name)
8377.9.2 by Michael Hudson
allow retrying FAILING builds
1450
        else:
8377.9.14 by Michael Hudson
test passes now
1451
            self.context.code_import.tryFailingImportAgain(self.user)
8377.9.2 by Michael Hudson
allow retrying FAILING builds
1452
            self.request.response.addNotification(
1453
                "Import will be tried again as soon as possible.")
1454
1455
    @property
1456
    def prefix(self):
8377.9.22 by Michael Hudson
ui review part 2
1457
        return "tryagain"