~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.15 by Karl Fogel
Add the copyright header block to files under lib/lp/bugs/.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
3
4
__metaclass__ = type
5
13453.5.1 by Steve Kowalik
First shot at getting *anything* to use the DSP vocab.
6
__all__ = [
7
    'BugAlsoAffectsProductMetaView',
8
    'BugAlsoAffectsDistroMetaView',
9
    'BugAlsoAffectsProductWithProductCreationView'
10
    ]
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
11
12
import cgi
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
13
from textwrap import dedent
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
14
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
15
from lazr.enum import (
16
    EnumeratedType,
17
    Item,
18
    )
19
from lazr.lifecycle.event import ObjectCreatedEvent
20
from z3c.ptcompat import ViewPageTemplateFile
7915.1.2 by Barry Warsaw
pick lint
21
from zope.app.form.browser import DropdownWidget
13506.4.2 by William Grant
Replace valid_upstreamtask with validate_target everywhere.
22
from zope.app.form.interfaces import MissingInputError
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
23
from zope.component import getUtility
24
from zope.event import notify
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
25
from zope.formlib import form
26
from zope.schema import Choice
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
27
from zope.schema.vocabulary import (
28
    SimpleTerm,
29
    SimpleVocabulary,
30
    )
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
31
14600.1.12 by Curtis Hovey
Move i18n to lp.
32
from lp import _
11929.9.1 by Tim Penhey
Move launchpadform into lp.app.browser.
33
from lp.app.browser.launchpadform import (
34
    action,
35
    custom_widget,
36
    LaunchpadFormView,
37
    )
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
38
from lp.app.browser.multistep import (
39
    MultiStepView,
40
    StepView,
41
    )
11411.7.1 by j.c.sackett
Fixed majority of official_malone calls in code-space. Still need to fix templates.
42
from lp.app.enums import ServiceUsage
13130.1.12 by Curtis Hovey
Sorted imports.
43
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
12442.2.9 by j.c.sackett
Ran import reformatter per review.
44
from lp.app.validators.email import email_validator
12293.1.10 by Curtis Hovey
Formatted imports.
45
from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
46
from lp.app.widgets.popup import SearchForUpstreamPopupWidget
47
from lp.app.widgets.textwidgets import StrippedTextWidget
12164.3.5 by Gavin Panella
Move lib/canonical/widgets/bugtask.py to lib/lp/bugs/browser/widgets/bugtask.py and adjust all imports, fix lint, etc.
48
from lp.bugs.browser.widgets.bugtask import (
49
    BugTaskAlsoAffectsSourcePackageNameWidget,
50
    )
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
51
from lp.bugs.interfaces.bug import IBug
52
from lp.bugs.interfaces.bugtask import (
53
    BugTaskImportance,
54
    BugTaskStatus,
55
    IAddBugTaskForm,
56
    IAddBugTaskWithProductCreationForm,
13506.4.2 by William Grant
Replace valid_upstreamtask with validate_target everywhere.
57
    IllegalTarget,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
58
    valid_remote_bug_url,
59
    )
60
from lp.bugs.interfaces.bugtracker import (
61
    BugTrackerType,
62
    IBugTrackerSet,
63
    )
64
from lp.bugs.interfaces.bugwatch import (
65
    IBugWatchSet,
66
    NoBugTrackerFound,
67
    UnrecognizedBugTrackerURL,
68
    )
13506.4.7 by William Grant
Turn validate_new_distrotask into validate_new_target and move it into lp.bugs.
69
from lp.bugs.model.bugtask import (
70
    validate_new_target,
71
    validate_target,
72
    )
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
73
from lp.registry.interfaces.distributionsourcepackage import (
74
    IDistributionSourcePackage,
75
    )
76
from lp.registry.interfaces.packaging import (
77
    IPackagingUtil,
78
    PackagingType,
79
    )
80
from lp.registry.interfaces.product import (
81
    IProductSet,
82
    License,
83
    )
13453.5.1 by Steve Kowalik
First shot at getting *anything* to use the DSP vocab.
84
from lp.services.features import getFeatureFlag
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
85
from lp.services.fields import StrippedTextLine
11382.6.34 by Gavin Panella
Reformat imports in all files touched so far.
86
from lp.services.propertycache import cachedproperty
14612.2.1 by William Grant
format-imports on lib/. So many imports.
87
from lp.services.webapp import canonical_url
88
from lp.services.webapp.interfaces import ILaunchBag
89
from lp.services.webapp.menu import structured
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
90
91
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
92
class BugAlsoAffectsProductMetaView(MultiStepView):
9322.10.7 by Guilherme Salgado
A couple more fixes/hacks
93
    page_title = 'Record as affecting another project'
94
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
95
    @property
7915.5.1 by Barry Warsaw
Responding to Aaron's review:
96
    def first_step(self):
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
97
        return ChooseProductStep
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
98
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
99
100
class BugAlsoAffectsDistroMetaView(MultiStepView):
9322.10.7 by Guilherme Salgado
A couple more fixes/hacks
101
    page_title = 'Record as affecting another distribution/package'
102
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
103
    @property
7915.5.1 by Barry Warsaw
Responding to Aaron's review:
104
    def first_step(self):
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
105
        return DistroBugTaskCreationStep
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
106
107
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
108
class AlsoAffectsStep(StepView):
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
109
    __launchpad_facetname__ = 'bugs'
110
    schema = IAddBugTaskForm
7162.7.4 by Gavin Panella
Add the cancel link.
111
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
112
11110.4.14 by Curtis Hovey
Removed lint, Extract common code for DRY code.
113
class LinkPackgingMixin:
114
115
    @property
116
    def can_link_package(self):
117
        bugtask = self.context
118
        is_package_bugtask = IDistributionSourcePackage.providedBy(
119
            bugtask.target)
120
        return is_package_bugtask and bugtask.target.upstream_product is None
121
122
123
class ChooseProductStep(LinkPackgingMixin, AlsoAffectsStep):
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
124
    """View for choosing a product that is affected by a given bug."""
125
126
    template = ViewPageTemplateFile(
127
        '../templates/bugtask-choose-affected-product.pt')
128
8879.5.2 by Edwin Grubbs
Updated SearchForUpstreamPopupWidget.
129
    custom_widget('product', SearchForUpstreamPopupWidget)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
130
    label = u"Record as affecting another project"
131
    step_name = "choose_product"
132
11110.4.12 by Curtis Hovey
Do not include the add_packaging field when the source package is already
133
    @property
134
    def _field_names(self):
135
        """The fields needed to choose an existing project."""
136
        names = ['product']
11110.4.13 by Curtis Hovey
Do not access widgets/add_packaging if packaging links cannot be created.
137
        if self.can_link_package:
11110.4.12 by Curtis Hovey
Do not include the add_packaging field when the source package is already
138
            names.append('add_packaging')
139
        return names
140
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
141
    def initialize(self):
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
142
        super(ChooseProductStep, self).initialize()
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
143
        if (self.widgets['product'].hasInput() or
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
144
            not IDistributionSourcePackage.providedBy(self.context.target)):
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
145
            return
146
147
        self.maybeAddNotificationOrTeleport()
148
149
    def maybeAddNotificationOrTeleport(self):
150
        """If we can't infer the upstream and the target distribution has a
151
        currentseries we add a notification message telling the user the
152
        package could be linked to an upstream to avoid this extra step.
153
154
        On the other hand, if the upstream can be infered and there's no task
155
        for it yet, we teleport the user straight to the next step.
156
        """
157
        bugtask = self.context
7672.1.2 by Graham Binns
Added a test for DSP.upstream_product.
158
        upstream = bugtask.target.upstream_product
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
159
        if upstream is not None:
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
160
            try:
13506.4.2 by William Grant
Replace valid_upstreamtask with validate_target everywhere.
161
                validate_target(bugtask.bug, upstream)
162
            except IllegalTarget:
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
163
                # There is already a task for the upstream.
164
                pass
165
            else:
166
                # We can infer the upstream and there's no bugtask for it,
167
                # so we can go straight to the page asking for the remote
168
                # bug URL.
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
169
                self.request.form['field.product'] = upstream.name
11110.4.8 by Curtis Hovey
Create a packaging link when the user checks add_packaging.
170
                self.request.form['field.add_packaging'] = 'off'
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
171
                self.next_step = ProductBugTaskCreationStep
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
172
            return
173
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
174
    def validateStep(self, data):
175
        if data.get('product'):
176
            try:
13506.4.2 by William Grant
Replace valid_upstreamtask with validate_target everywhere.
177
                validate_target(self.context.bug, data.get('product'))
178
            except IllegalTarget as e:
179
                self.setFieldError('product', e[0])
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
180
            return
181
182
        entered_product = self.request.form.get(self.widgets['product'].name)
183
        if not entered_product:
184
            return
185
186
        # The user has entered a product name but we couldn't find it.
5127.4.5 by Guilherme Salgado
Some cleanups
187
        # Tell the user to search for it using the popup widget as it'll allow
188
        # the user to register a new product if the one he is looking for is
189
        # not yet registered.
8879.5.8 by Edwin Grubbs
Made the bug-also-affects error message handle js and non-js browsers.
190
        widget_link_id = self.widgets['product'].show_widget_id
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
191
        self.setFieldError(
192
            'product',
8879.5.8 by Edwin Grubbs
Made the bug-also-affects error message handle js and non-js browsers.
193
            structured("""
11110.4.8 by Curtis Hovey
Create a packaging link when the user checks add_packaging.
194
                There is no project in Launchpad named "%s". Please
8879.5.8 by Edwin Grubbs
Made the bug-also-affects error message handle js and non-js browsers.
195
                <a href="/projects"
11666.5.38 by Deryck Hodge
Convert all YUI instances to use LPS object. Also, make sure
196
                onclick="LPS.use('event').Event.simulate(
8879.5.8 by Edwin Grubbs
Made the bug-also-affects error message handle js and non-js browsers.
197
                         document.getElementById('%s'), 'click');
198
                         return false;"
199
                >search for it</a> as it may be
200
                registered with a different name.""",
201
                entered_product, widget_link_id))
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
202
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
203
    def main_action(self, data):
7915.1.6 by Barry Warsaw
Respond to reviewer comments.
204
        """Perform the 'Continue' action."""
205
        # Inject the selected product into the form and set the next_step to
206
        # be used by our multistep controller.
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
207
        self.request.form['field.product'] = data['product'].name
11110.4.12 by Curtis Hovey
Do not include the add_packaging field when the source package is already
208
        if data.get('add_packaging', False):
11110.4.8 by Curtis Hovey
Create a packaging link when the user checks add_packaging.
209
            self.request.form['field.add_packaging'] = 'on'
210
        else:
211
            self.request.form['field.add_packaging'] = 'off'
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
212
        self.next_step = ProductBugTaskCreationStep
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
213
214
215
class BugTaskCreationStep(AlsoAffectsStep):
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
216
    """The bug task creation step of the AlsoAffects workflow.
217
218
    In this view the user specifies the URL for the remote bug and we create
219
    the new bugtask/bugwatch.
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
220
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
221
    If the bugtracker in the given URL is not registered in Launchpad, we
222
    delegate its creation to another view. This other view should then
223
    delegate the bug task creation to this one once the bugtracker is
224
    registered.
225
    """
226
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
227
    custom_widget('bug_url', StrippedTextWidget, displayWidth=62)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
228
7267.3.1 by Bjorn Tillenius
Focus the URL field on +choose-affected-product so that pasting via the keyboard works.
229
    initial_focus_widget = 'bug_url'
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
230
    step_name = 'specify_remote_bug_url'
231
    target_field_names = ()
232
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
233
    # This is necessary so that other views which dispatch work to this one
234
    # have access to the newly created task.
235
    task_added = None
236
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
237
    def __init__(self, context, request):
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
238
        super(BugTaskCreationStep, self).__init__(context, request)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
239
        self.notifications = []
4681.1.30 by Guilherme Salgado
Add a field_names property to AlsoAffectsStep which always include the 'visited_steps' field so that subclasses don't have to include it in their field_names
240
        self._field_names = ['bug_url'] + list(self.target_field_names)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
241
242
    def setUpWidgets(self):
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
243
        super(BugTaskCreationStep, self).setUpWidgets()
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
244
        self.target_widgets = [
245
            self.widgets[field_name]
246
            for field_name in self.field_names
247
            if field_name in self.target_field_names]
248
        self.bugwatch_widgets = [self.widgets['bug_url']]
249
250
    def getTarget(self, data=None):
251
        """Return the fix target.
252
253
        If data is given extract the target from there. Otherwise extract it
254
        from this view's widgets.
255
        """
256
        raise NotImplementedError()
257
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
258
    def main_action(self, data):
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
259
        """Create the new bug task.
260
261
        If a remote bug URL is given and there's no bug watch registered with
262
        that URL we create a bug watch and link it to the newly created bug
263
        task.
264
        """
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
265
        bug_url = data.get('bug_url', '')
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
266
        target = self.getTarget(data)
267
268
        extracted_bug = None
269
        extracted_bugtracker = None
270
        if bug_url:
271
            try:
272
                extracted_bugtracker, extracted_bug = getUtility(
273
                    IBugWatchSet).extractBugTrackerAndBug(bug_url)
274
            except NoBugTrackerFound:
275
                # Delegate to another view which will ask the user if (s)he
276
                # wants to create the bugtracker now.
11110.4.10 by Curtis Hovey
Fixed checks for product bugtask creation scenario...they were entering into
277
                if 'product' in self.target_field_names:
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
278
                    self.next_step = UpstreamBugTrackerCreationStep
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
279
                else:
280
                    assert 'distribution' in self.target_field_names
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
281
                    self.next_step = DistroBugTrackerCreationStep
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
282
                return
283
7996.1.7 by Bjorn Tillenius
Don't use create task in browser/bugalsoaffects.py
284
        if data.get('product') is not None:
7996.1.12 by Bjorn Tillenius
Fix test failures.
285
            task_target = data['product']
7996.1.8 by Bjorn Tillenius
Fix typo.
286
        else:
7996.1.12 by Bjorn Tillenius
Fix test failures.
287
            task_target = data['distribution']
7996.1.7 by Bjorn Tillenius
Don't use create task in browser/bugalsoaffects.py
288
            if data.get('sourcepackagename') is not None:
14400.2.23 by Curtis Hovey
Use an alternate schema instead of setUpFields to switch from spn to dsp.
289
                spn_or_dsp = data['sourcepackagename']
14400.2.27 by Curtis Hovey
Fix unbound variable error reported by tests.
290
                if IDistributionSourcePackage.providedBy(spn_or_dsp):
291
                    task_target = spn_or_dsp
292
                else:
293
                    task_target = task_target.getSourcePackage(spn_or_dsp)
7996.1.7 by Bjorn Tillenius
Don't use create task in browser/bugalsoaffects.py
294
        self.task_added = self.context.bug.addTask(
7996.1.12 by Bjorn Tillenius
Fix test failures.
295
            getUtility(ILaunchBag).user, task_target)
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
296
        task_added = self.task_added
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
297
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
298
        if extracted_bug is not None:
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
299
            assert extracted_bugtracker is not None, (
300
                "validate() should have ensured that bugtracker is not None.")
4982.1.3 by Bjorn Tillenius
clean up implementation.
301
            # Display a notification, if another bug is already linked
302
            # to the same external bug.
303
            other_bugs_already_watching = [
304
                bug for bug in extracted_bugtracker.getBugsWatching(
305
                    extracted_bug)
306
                if bug != self.context.bug]
307
            # Simply add one notification per bug to simplify the
308
            # implementation; most of the time it will be only one bug.
309
            for other_bug in other_bugs_already_watching:
5594.1.11 by Maris Fogels
Beautified the addNotification(structured(...)) construct.
310
                self.request.response.addInfoNotification(
311
                    structured(
4982.1.5 by Bjorn Tillenius
change the notification message.
312
                    '<a href="%(bug_url)s">Bug #%(bug_id)s</a> also links'
313
                    ' to the added bug watch'
314
                    ' (%(bugtracker_name)s #%(remote_bug)s).',
5594.1.23 by Maris Fogels
Fixed an issue with the structured() constructor, which only takes string, receiving an integer.
315
                    bug_url=canonical_url(other_bug),
316
                    bug_id=str(other_bug.id),
4982.1.5 by Bjorn Tillenius
change the notification message.
317
                    bugtracker_name=extracted_bugtracker.name,
5594.1.9 by Maris Fogels
Fixed all of the calls to addInfoNotification() that contain HTML so that they use the structured() class. Also fixed a number of broken uses of the _() translation function.
318
                    remote_bug=extracted_bug))
4982.1.3 by Bjorn Tillenius
clean up implementation.
319
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
320
            # Make sure that we don't add duplicate bug watches.
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
321
            bug_watch = task_added.bug.getBugWatch(
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
322
                extracted_bugtracker, extracted_bug)
323
            if bug_watch is None:
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
324
                bug_watch = task_added.bug.addWatch(
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
325
                    extracted_bugtracker, extracted_bug, self.user)
11411.7.1 by j.c.sackett
Fixed majority of official_malone calls in code-space. Still need to fix templates.
326
            if target.bug_tracking_usage != ServiceUsage.LAUNCHPAD:
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
327
                task_added.bugwatch = bug_watch
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
328
11411.7.28 by j.c.sackett
Lint fixes.
329
        if (target.bug_tracking_usage != ServiceUsage.LAUNCHPAD
11411.7.1 by j.c.sackett
Fixed majority of official_malone calls in code-space. Still need to fix templates.
330
            and task_added.bugwatch is not None
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
331
            and (task_added.bugwatch.bugtracker.bugtrackertype !=
332
                 BugTrackerType.EMAILADDRESS)):
333
            # A remote bug task gets its status from a bug watch, so
334
            # we want its status/importance to be UNKNOWN when
335
            # created. Status updates cannot be fetched from Email
336
            # Address bug trackers, and we expect the status and
337
            # importance to be updated manually, so we do not reset
338
            # the status and importance here.
6602.5.15 by Tom Berger
when resetting a bugtask to use a bugwatch, use the bug importer
339
            bug_importer = getUtility(ILaunchpadCelebrities).bug_importer
340
            task_added.transitionToStatus(
341
                BugTaskStatus.UNKNOWN, bug_importer)
6602.5.5 by Tom Berger
fix some lint errors
342
            task_added.transitionToImportance(
6602.5.15 by Tom Berger
when resetting a bugtask to use a bugwatch, use the bug importer
343
                BugTaskImportance.UNKNOWN, bug_importer)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
344
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
345
        notify(ObjectCreatedEvent(task_added))
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
346
        self.next_url = canonical_url(task_added)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
347
348
14400.2.23 by Curtis Hovey
Use an alternate schema instead of setUpFields to switch from spn to dsp.
349
class IAddDistroBugTaskForm(IAddBugTaskForm):
350
351
    sourcepackagename = Choice(
352
        title=_("Source Package Name"), required=False,
353
        description=_("The source package in which the bug occurs. "
354
                      "Leave blank if you are not sure."),
355
        vocabulary='DistributionSourcePackage')
356
357
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
358
class DistroBugTaskCreationStep(BugTaskCreationStep):
359
    """Specialized BugTaskCreationStep for reporting a bug in a distribution.
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
360
    """
361
14400.2.23 by Curtis Hovey
Use an alternate schema instead of setUpFields to switch from spn to dsp.
362
    @property
363
    def schema(self):
364
        if bool(getFeatureFlag('disclosure.dsp_picker.enabled')):
365
            return IAddDistroBugTaskForm
366
        else:
367
            return IAddBugTaskForm
368
6325.1.1 by Bjorn Tillenius
prevent non-published packages from being targeted on +distrotask.
369
    custom_widget(
370
        'sourcepackagename', BugTaskAlsoAffectsSourcePackageNameWidget)
371
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
372
    template = ViewPageTemplateFile('../templates/bugtask-requestfix.pt')
373
374
    label = "Also affects distribution/package"
375
    target_field_names = ('distribution', 'sourcepackagename')
376
5398.1.1 by Graham Binns
The default value of the distro field for the +distrotask form is now Ubuntu>
377
    @property
378
    def initial_values(self):
5398.1.2 by Graham Binns
Added a docstring to DistroBugTaskCreationStep.initial_values.
379
        """Return the initial values for the view's fields."""
5398.1.1 by Graham Binns
The default value of the distro field for the +distrotask form is now Ubuntu>
380
        return {'distribution': getUtility(ILaunchpadCelebrities).ubuntu}
381
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
382
    def getTarget(self, data=None):
383
        if data is not None:
384
            return data.get('distribution')
385
        else:
386
            return self.widgets['distribution'].getInputValue()
387
6105.8.2 by Gavin Panella
Remove the confirmation step.
388
    def main_action(self, data):
389
        """Create the new bug task, confirming if necessary."""
390
        bug_url = data.get('bug_url', '')
391
        target = self.getTarget(data)
392
393
        if (not bug_url and
394
            not self.request.get('ignore_missing_remote_bug') and
11411.7.20 by j.c.sackett
Fixed yet more bad references missed in the initial pass.
395
            target.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
6105.8.2 by Gavin Panella
Remove the confirmation step.
396
            # We have no URL for the remote bug and the target does not use
397
            # Launchpad for bug tracking, so we warn the user this is not
398
            # optimal and ask for his confirmation.
399
400
            # Add a hidden field to fool LaunchpadFormView into thinking we
401
            # submitted the action it expected when in fact we're submiting
402
            # something else to indicate the user has confirmed.
403
            confirm_button = (
404
                '<input type="hidden" name="%s" value="1" />'
405
                '<input style="font-size: smaller" type="submit"'
406
                ' value="Add Anyway" name="ignore_missing_remote_bug" />'
407
                % self.continue_action.__name__)
408
            self.notifications.append(_(dedent("""
409
                %s doesn't use Launchpad as its bug tracker. Without a bug
410
                URL to watch, the %s status will not update automatically.
411
                %s""" % (cgi.escape(target.displayname),
412
                         cgi.escape(target.displayname),
413
                         confirm_button))))
414
            return None
415
        # Create the task.
416
        return super(DistroBugTaskCreationStep, self).main_action(data)
417
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
418
    def validateStep(self, data):
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
419
        """Check that
420
421
        1. there's no bug_url if the target uses malone;
422
        2. there is a package with the given name;
423
        3. it's possible to create a new task for the given package/distro.
424
        """
425
        target = self.getTarget(data)
426
        bug_url = data.get('bug_url')
11411.7.1 by j.c.sackett
Fixed majority of official_malone calls in code-space. Still need to fix templates.
427
        if bug_url and target.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
428
            self.addError(
429
                "Bug watches can not be added for %s, as it uses Launchpad"
430
                " as its official bug tracker. Alternatives are to add a"
431
                " watch for another project, or a comment containing a"
5653.2.1 by Maris Fogels
Changed occurrances of addError() so that the conform to the new API. Fixed a number of broken msgids.
432
                " URL to the related bug report." % target.displayname)
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
433
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
434
        distribution = data.get('distribution')
435
        sourcepackagename = data.get('sourcepackagename')
436
        entered_package = self.request.form.get(
437
            self.widgets['sourcepackagename'].name)
438
        if sourcepackagename is None and entered_package:
439
            # The entered package doesn't exist.
13169.1.2 by Steve Kowalik
Use structured properly, and slightly re-flow.
440
            if distribution.has_published_binaries:
441
                binary_tracking = ''
442
            else:
13169.1.1 by Steve Kowalik
* Stop instructing users to file a bug if they can't find a package, and tell
443
                binary_tracking = structured(
444
                    ' Launchpad does not track binary package names '
13169.1.2 by Steve Kowalik
Use structured properly, and slightly re-flow.
445
                    'in %s.', distribution.displayname)
13169.1.1 by Steve Kowalik
* Stop instructing users to file a bug if they can't find a package, and tell
446
            error = structured(
447
                'There is no package in %s named "%s".%s',
448
                distribution.displayname, entered_package,
449
                binary_tracking)
450
            self.setFieldError('sourcepackagename', error)
14400.2.23 by Curtis Hovey
Use an alternate schema instead of setUpFields to switch from spn to dsp.
451
        elif not IDistributionSourcePackage.providedBy(sourcepackagename):
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
452
            try:
13506.4.7 by William Grant
Turn validate_new_distrotask into validate_new_target and move it into lp.bugs.
453
                target = distribution
454
                if sourcepackagename:
455
                    target = target.getSourcePackage(sourcepackagename)
456
                validate_new_target(self.context.bug, target)
13506.4.8 by William Grant
validate_new_target now raises IllegalTarget instead of LaunchpadValidationError.
457
            except IllegalTarget as e:
14174.2.9 by Ian Booth
Add doc test
458
                if sourcepackagename:
459
                    self.setFieldError('sourcepackagename', e[0])
460
                else:
461
                    self.setFieldError('distribution', e[0])
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
462
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
463
        super(DistroBugTaskCreationStep, self).validateStep(data)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
464
465
    def render(self):
466
        for bugtask in IBug(self.context).bugtasks:
467
            if (IDistributionSourcePackage.providedBy(bugtask.target) and
468
                (not self.widgets['sourcepackagename'].hasInput())):
469
                self.widgets['sourcepackagename'].setRenderedValue(
470
                    bugtask.sourcepackagename)
471
                break
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
472
        return super(DistroBugTaskCreationStep, self).render()
473
474
5906.1.4 by Gavin Panella
Move form definitions to the browser package.
475
class LinkUpstreamHowOptions(EnumeratedType):
476
    LINK_UPSTREAM = Item(
477
        """I have the URL for the upstream bug:
478
479
        Enter the URL in the upstream bug tracker. If it's in a
480
        supported upstream bug tracker, Launchpad can download the
481
        status and display it in the bug report.
482
        """)
483
484
# XXX: GavinPanella 2008-02-13 bug=201793: This will be uncommented in
485
# a later branch.
486
#
487
#     EMAIL_UPSTREAM = Item(
488
#         """I would like to email an upstream bug contact.
489
#
490
#         Launchpad will prepare an example email containing all the
491
#         pertinent details. You can send it from Launchpad or from your
492
#         own mail software. If you send it from Launchpad, it'll save
493
#         the message id and - in the future - will use it to try and
494
#         follow the resulting conversation, provided it happens on a
495
#         public mailing list.
496
#         """)
497
498
    EMAIL_UPSTREAM_DONE = Item(
499
        """I have already emailed an upstream bug contact:
500
501
        Launchpad will record that.
502
        """)
503
504
# XXX: GavinPanella 2008-02-13 bug=201793: This additional description
505
# for EMAIL_UPSTREAM_DONE should be appended when EMAIL_UPSTREAM is
506
# made available.
507
#
508
#   "Next time, try using Launchpad to send the message upstream
509
#    too. That way it may be able to follow the conversation that
510
#    results from your bug report. This is especially true for public
511
#    mailing lists."
512
513
    UNLINKED_UPSTREAM = Item(
514
        """I just want to register that it is upstream right now; \
515
           I don't have any way to link it.
516
517
        Launchpad will record that.
518
        """)
519
520
521
class IAddBugTaskWithUpstreamLinkForm(IAddBugTaskForm):
522
    """Form for adding an upstream bugtask with linking options.
523
524
    The choices in link_upstream_how correspond to zero or one of the
525
    text fields. For example, if link_upstream_how is LINK_UPSTREAM
526
    then bug_url is the relevant field, and the other text fields,
527
    like upstream_email_address_done, can be ignored.
528
529
    That also explains why none of the text fields are required. That
530
    check is left to the view, in part so that better error messages
531
    can be provided.
532
    """
7403.4.5 by Edwin Grubbs
Fixed different paths through ProductBugTaskCreationStep views.
533
    # link_upstream_how must have required=False, since
534
    # ProductBugTaskCreationStep doesn't always display a form input for it.
5906.1.4 by Gavin Panella
Move form definitions to the browser package.
535
    link_upstream_how = Choice(
7403.4.5 by Edwin Grubbs
Fixed different paths through ProductBugTaskCreationStep views.
536
        title=_('How'), required=False,
5906.1.4 by Gavin Panella
Move form definitions to the browser package.
537
        vocabulary=LinkUpstreamHowOptions,
538
        default=LinkUpstreamHowOptions.LINK_UPSTREAM,
539
        description=_("How to link to an upstream bug."))
540
    bug_url = StrippedTextLine(
541
        title=_('Bug URL'), required=False, constraint=valid_remote_bug_url,
542
        description=_("The URL of this bug in the remote bug tracker."))
543
    upstream_email_address_done = StrippedTextLine(
544
        title=_('Email Address'), required=False, constraint=email_validator,
545
        description=_("The upstream email address that this bug has been "
546
                      "forwarded to."))
547
548
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
549
class ProductBugTaskCreationStep(BugTaskCreationStep):
550
    """Specialized BugTaskCreationStep for reporting a bug in an upstream."""
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
551
552
    template = ViewPageTemplateFile(
553
        '../templates/bugtask-requestfix-upstream.pt')
554
555
    label = "Confirm project"
11110.4.8 by Curtis Hovey
Create a packaging link when the user checks add_packaging.
556
    target_field_names = ('product', 'add_packaging')
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
557
    main_action_label = u'Add to Bug Report'
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
558
    schema = IAddBugTaskWithUpstreamLinkForm
559
7403.4.5 by Edwin Grubbs
Fixed different paths through ProductBugTaskCreationStep views.
560
    custom_widget('link_upstream_how', LaunchpadRadioWidget,
561
                  _displayItemForMissingValue=False)
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
562
    custom_widget('bug_url', StrippedTextWidget, displayWidth=42)
563
    custom_widget('upstream_email_address_done',
564
                  StrippedTextWidget, displayWidth=42)
565
566
    @property
567
    def field_names(self):
568
        return ['link_upstream_how', 'upstream_email_address_done'] + (
569
            super(ProductBugTaskCreationStep, self).field_names)
570
571
    def validate_widgets(self, data, names=None):
5906.1.3 by Gavin Panella
Changes as requested by bac in review.
572
        # The form is essentially just a radio group, with zero or one
573
        # related text widgets per choice. The text widget should be
574
        # validated when its corresponding radio button has been
575
        # selected, otherwise we should do no validation because we
576
        # don't want to issue errors for widgets that we and the user
577
        # are not interested in.
578
579
        # Collect all the widget names.
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
580
        if names is None:
581
            names = set()
582
        else:
583
            names = set(names)
584
        names.update(widget.context.__name__ for widget in self.widgets)
585
5906.1.3 by Gavin Panella
Changes as requested by bac in review.
586
        # A mapping from radio buttons to their related text widgets.
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
587
        link_upstream_options = {
588
            LinkUpstreamHowOptions.LINK_UPSTREAM:
589
                'bug_url',
590
            LinkUpstreamHowOptions.EMAIL_UPSTREAM_DONE:
11110.4.8 by Curtis Hovey
Create a packaging link when the user checks add_packaging.
591
                'upstream_email_address_done'}
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
592
5906.1.3 by Gavin Panella
Changes as requested by bac in review.
593
        # Examine the radio group if it has valid input.
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
594
        link_upstream_how = self.widgets['link_upstream_how']
595
        if link_upstream_how.hasValidInput():
596
            link_upstream_how = link_upstream_how.getInputValue()
597
5906.1.3 by Gavin Panella
Changes as requested by bac in review.
598
            # Don't request validation for text widgets that are not
599
            # related to the current radio selection.
600
            for option, name in link_upstream_options.iteritems():
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
601
                if link_upstream_how != option:
602
                    names.discard(name)
603
                elif self.widgets[name].hasValidInput():
5906.1.3 by Gavin Panella
Changes as requested by bac in review.
604
                    # Check that input has been provided because the
605
                    # fields in the schema are set to required=False
606
                    # to make the radio+text-widget mechanism work.
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
607
                    if not self.widgets[name].getInputValue():
608
                        self.setFieldError(
609
                            name, 'Required input is missing.')
610
611
        else:
612
            # Don't validate these widgets when we don't yet know how
613
            # we intend to link upstream.
614
            names.difference_update(link_upstream_options.itervalues())
615
616
        return super(ProductBugTaskCreationStep,
617
                     self).validate_widgets(data, names)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
618
619
    def getTarget(self, data=None):
620
        if data is not None:
621
            return data.get('product')
622
        else:
623
            return self.widgets['product'].getInputValue()
624
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
625
    @cachedproperty
626
    def link_upstream_how_items(self):
627
        """Manually create and pick apart a radio widget.
628
5906.1.3 by Gavin Panella
Changes as requested by bac in review.
629
        On its own, `LaunchpadRadioWidget` does not render quite how
630
        we need it, because we're interspersing related text
631
        widgets. We need to dig down a bit and place the individually
632
        rendered radio buttons into our custom layout.
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
633
        """
634
        widget = self.widgets['link_upstream_how']
635
        try:
636
            current_value = widget.getInputValue()
637
        except MissingInputError:
638
            current_value = LinkUpstreamHowOptions.LINK_UPSTREAM
639
        items = widget.renderItems(current_value)
7403.4.1 by Edwin Grubbs
Removed LaunchpadRadioWidget.renderItemsWithValues() and updated affected views.
640
641
        # The items list is returned in the same order as the
7403.4.5 by Edwin Grubbs
Fixed different paths through ProductBugTaskCreationStep views.
642
        # widget.vocabulary enumerator. It is important that
643
        # link_upstream_how has _displayItemForMissingValue=False
7403.4.1 by Edwin Grubbs
Removed LaunchpadRadioWidget.renderItemsWithValues() and updated affected views.
644
        # so that renderItems() doesn't return an extra radio button which
7403.4.5 by Edwin Grubbs
Fixed different paths through ProductBugTaskCreationStep views.
645
        # prevents it from matching widget.vocabulary's ordering.
646
        return dict((entry.token, items[i])
647
                    for i, entry in enumerate(widget.vocabulary))
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
648
649
    def main_action(self, data):
650
        link_upstream_how = data.get('link_upstream_how')
651
5906.1.3 by Gavin Panella
Changes as requested by bac in review.
652
        if link_upstream_how == LinkUpstreamHowOptions.UNLINKED_UPSTREAM:
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
653
            # Erase bug_url because we don't want to create a bug
654
            # watch against a specific URL.
655
            if 'bug_url' in data:
656
                del data['bug_url']
657
        elif link_upstream_how == LinkUpstreamHowOptions.EMAIL_UPSTREAM_DONE:
658
            # Ensure there's a bug tracker for this email address.
659
            bug_url = 'mailto:' + data['upstream_email_address_done']
10734.1.8 by Edwin Grubbs
Fixed lint errors.
660
            getUtility(IBugTrackerSet).ensureBugTracker(
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
661
                bug_url, self.user, BugTrackerType.EMAILADDRESS)
662
            data['bug_url'] = bug_url
11110.4.10 by Curtis Hovey
Fixed checks for product bugtask creation scenario...they were entering into
663
        if data.get('add_packaging', False):
11110.4.8 by Curtis Hovey
Create a packaging link when the user checks add_packaging.
664
            # Create a packaging link so that Launchpad will suggest the
665
            # upstream project to the user.
14617.1.1 by Steve Kowalik
Only add packaging if distribution.currentseries is not None.
666
            series = self.context.target.distribution.currentseries
667
            if series:
668
                getUtility(IPackagingUtil).createPackaging(
669
                    productseries=data['product'].development_focus,
670
                    sourcepackagename=self.context.target.sourcepackagename,
671
                    distroseries=series, packaging=PackagingType.PRIME,
672
                    owner=self.user)
5906.1.1 by Gavin Panella
Initial forwarding-to-email-address UI changes.
673
        return super(ProductBugTaskCreationStep, self).main_action(data)
674
7849.6.1 by Graham Binns
Moved upstream_bugtracker_links from Product DB class into view.
675
    @property
676
    def upstream_bugtracker_links(self):
7849.6.2 by Graham Binns
Completed the move of upstream_bugtracker_links from Product to ProductBugTaskCreationStep.
677
        """Return the upstream bugtracker links for the current target.
7849.6.1 by Graham Binns
Moved upstream_bugtracker_links from Product DB class into view.
678
7849.6.5 by Graham Binns
Removed unneccessary check for the bug target being a product. We know that it's a product because it's in the +choose-affected-PRODUCT view, which makes it kinda obvious.
679
        :return: The bug tracker links for the target, as returned by
680
            BugTracker.getBugFilingAndSearchLinks(). If product.bugtracker
681
            is None, return None.
7849.6.1 by Graham Binns
Moved upstream_bugtracker_links from Product DB class into view.
682
        """
7849.6.2 by Graham Binns
Completed the move of upstream_bugtracker_links from Product to ProductBugTaskCreationStep.
683
        target = self.getTarget()
684
685
        if not target.bugtracker:
7849.6.1 by Graham Binns
Moved upstream_bugtracker_links from Product DB class into view.
686
            return None
13405.7.1 by Bryce Harrington
Refactor else clause away.
687
688
        bug = self.context.bug
689
        title = bug.title
690
        description = u"Originally reported at:\n  %s\n\n%s" % (
691
            canonical_url(bug), bug.description)
692
        return target.bugtracker.getBugFilingAndSearchLinks(
693
            target.remote_product, title, description)
7849.6.1 by Graham Binns
Moved upstream_bugtracker_links from Product DB class into view.
694
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
695
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
696
class BugTrackerCreationStep(AlsoAffectsStep):
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
697
    """View for creating a bugtracker from the given URL.
698
699
    This view will ask the user if he really wants to register the new bug
700
    tracker, perform the registration and then delegate to one of
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
701
    BugTaskCreationStep's subclasses.
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
702
    """
703
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
704
    custom_widget('bug_url', StrippedTextWidget, displayWidth=62)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
705
    step_name = "bugtracker_creation"
706
    main_action_label = u'Register Bug Tracker and Add to Bug Report'
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
707
    _next_step = None
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
708
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
709
    def main_action(self, data):
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
710
        assert self._next_step is not None, (
711
            "_next_step must be specified in subclasses.")
4681.1.28 by Guilherme Salgado
Move some common things to AlsoAffectsStep add some docstrings and remove some dead code.
712
        bug_url = data.get('bug_url').strip()
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
713
        try:
714
            getUtility(IBugWatchSet).extractBugTrackerAndBug(bug_url)
715
        except NoBugTrackerFound, error:
716
            getUtility(IBugTrackerSet).ensureBugTracker(
717
                error.base_url, self.user, error.bugtracker_type)
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
718
        self.next_step = self._next_step
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
719
720
721
class DistroBugTrackerCreationStep(BugTrackerCreationStep):
722
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
723
    _next_step = DistroBugTaskCreationStep
4681.1.30 by Guilherme Salgado
Add a field_names property to AlsoAffectsStep which always include the 'visited_steps' field so that subclasses don't have to include it in their field_names
724
    _field_names = ['distribution', 'sourcepackagename', 'bug_url']
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
725
    custom_widget('distribution', DropdownWidget, visible=False)
726
    custom_widget('sourcepackagename', DropdownWidget, visible=False)
727
    label = "Also affects distribution/package"
728
    template = ViewPageTemplateFile(
729
        '../templates/bugtask-confirm-bugtracker-creation.pt')
730
4681.1.27 by Guilherme Salgado
Drop AlsoAffects prefix from bugalsoaffects.py views
731
732
class UpstreamBugTrackerCreationStep(BugTrackerCreationStep):
733
6105.8.2 by Gavin Panella
Remove the confirmation step.
734
    schema = IAddBugTaskWithUpstreamLinkForm
7915.1.1 by Barry Warsaw
Refactoring multistep pages for re-use.
735
    _next_step = ProductBugTaskCreationStep
6105.8.3 by Gavin Panella
Don't need the email address to be copied because we won't end up here if an email address has been entered.
736
    _field_names = ['product', 'bug_url', 'link_upstream_how']
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
737
    custom_widget('product', DropdownWidget, visible=False)
6105.8.2 by Gavin Panella
Remove the confirmation step.
738
    custom_widget('link_upstream_how',
739
                  LaunchpadRadioWidget, visible=False)
4681.1.26 by Guilherme Salgado
Split all views related to the bug-also-affects workflow into a separate file (bugalsoaffects.py)
740
    label = "Confirm project"
741
    template = ViewPageTemplateFile(
742
        '../templates/bugtask-confirm-bugtracker-creation.pt')
743
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
744
11110.4.14 by Curtis Hovey
Removed lint, Extract common code for DRY code.
745
class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
746
                                                   LaunchpadFormView):
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
747
    """Register a product and indicate this bug affects it.
748
749
    If there's no bugtracker with the given URL registered in Launchpad, then
750
    a new bugtracker is created as well.
751
    """
752
753
    label = "Register project affected by this bug"
754
    schema = IAddBugTaskWithProductCreationForm
755
    custom_widget('bug_url', StrippedTextWidget, displayWidth=62)
756
    custom_widget('existing_product', LaunchpadRadioWidget)
757
    existing_products = None
758
    MAX_PRODUCTS_TO_DISPLAY = 10
8781.2.3 by Brad Crittenden
Change +affects-new-product to assign the new product to the regsitry admins and set the license to DONT_KNOW
759
    licenses = [License.DONT_KNOW]
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
760
11110.4.12 by Curtis Hovey
Do not include the add_packaging field when the source package is already
761
    @property
762
    def field_names(self):
763
        """The fields needed to choose an existing project."""
764
        names = ['bug_url', 'displayname', 'name', 'summary']
11110.4.13 by Curtis Hovey
Do not access widgets/add_packaging if packaging links cannot be created.
765
        if self.can_link_package:
11110.4.12 by Curtis Hovey
Do not include the add_packaging field when the source package is already
766
            names.append('add_packaging')
767
        return names
768
5127.4.9 by Guilherme Salgado
Bunch of changes suggested by Francis
769
    def _loadProductsUsingBugTracker(self):
5127.4.5 by Guilherme Salgado
Some cleanups
770
        """Find products using the bugtracker wich runs on the given URL.
771
772
        These products are stored in self.existing_products.
773
774
        If there are too many products using that bugtracker then we'll store
775
        only the first ones that somehow match the name given.
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
776
        """
777
        bug_url = self.request.form.get('field.bug_url')
778
        if not bug_url:
5127.4.5 by Guilherme Salgado
Some cleanups
779
            return
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
780
781
        bugwatch_set = getUtility(IBugWatchSet)
782
        try:
783
            bugtracker, bug = bugwatch_set.extractBugTrackerAndBug(bug_url)
5127.4.9 by Guilherme Salgado
Bunch of changes suggested by Francis
784
        except (NoBugTrackerFound, UnrecognizedBugTrackerURL):
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
785
            # There's no bugtracker registered with the given URL, so we
786
            # don't need to worry about finding products using it.
5127.4.9 by Guilherme Salgado
Bunch of changes suggested by Francis
787
            return
5127.4.5 by Guilherme Salgado
Some cleanups
788
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
789
        count = bugtracker.products.count()
790
        if count > 0 and count <= self.MAX_PRODUCTS_TO_DISPLAY:
5127.4.9 by Guilherme Salgado
Bunch of changes suggested by Francis
791
            self.existing_products = list(bugtracker.products)
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
792
        elif count > self.MAX_PRODUCTS_TO_DISPLAY:
5127.4.9 by Guilherme Salgado
Bunch of changes suggested by Francis
793
            # Use a local import as we don't want removeSecurityProxy used
794
            # anywhere else.
795
            from zope.security.proxy import removeSecurityProxy
12641.1.3 by Robert Collins
Add back in a SQLObject version of ProductSet.search because migration cascades, oh my yes it does.
796
            name_matches = removeSecurityProxy(
797
                getUtility(IProductSet).search_sqlobject(
798
                self.request.form.get('field.name')))
799
            products = bugtracker.products.intersect(name_matches)
5127.4.9 by Guilherme Salgado
Bunch of changes suggested by Francis
800
            self.existing_products = list(
801
                products[:self.MAX_PRODUCTS_TO_DISPLAY])
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
802
        else:
803
            # The bugtracker is registered in Launchpad but there are no
804
            # products using it at the moment.
805
            pass
806
807
    def setUpFields(self):
5127.4.5 by Guilherme Salgado
Some cleanups
808
        """Setup an extra field with all products using the given bugtracker.
809
810
        This extra field is setup only if there is one or more products using
811
        that bugtracker.
812
        """
5292.1.2 by Guilherme Salgado
Fix a couple things spotted by Tom Berger
813
        super(
814
            BugAlsoAffectsProductWithProductCreationView, self).setUpFields()
5127.4.9 by Guilherme Salgado
Bunch of changes suggested by Francis
815
        self._loadProductsUsingBugTracker()
816
        if self.existing_products is None or len(self.existing_products) < 1:
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
817
            # No need to setup any extra fields.
818
            return
819
820
        terms = []
5127.4.5 by Guilherme Salgado
Some cleanups
821
        for product in self.existing_products:
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
822
            terms.append(SimpleTerm(product, product.name, product.title))
823
        existing_product = form.FormField(
824
            Choice(__name__='existing_product',
825
                   title=_("Existing project"), required=True,
7076.5.2 by Gary Poster
initial cut at changing files to take into account new behavior. four tests do not pass.
826
                   vocabulary=SimpleVocabulary(terms)))
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
827
        self.form_fields += form.Fields(existing_product)
828
        if 'field.existing_product' not in self.request.form:
829
            # This is the first time the form is being submitted, so the
830
            # request doesn't contain a value for the existing_product
831
            # widget and thus we'll end up rendering an error message around
832
            # said widget unless we sneak a value for it in our request.
833
            self.request.form['field.existing_product'] = terms[0].token
834
835
    def validate_existing_product(self, action, data):
5127.4.5 by Guilherme Salgado
Some cleanups
836
        """Check if the chosen project is not already affected by this bug."""
5127.4.4 by Guilherme Salgado
Fix the custom validation for the use-existing-project action
837
        self._validate(action, data)
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
838
        project = data.get('existing_product')
839
        try:
13506.4.2 by William Grant
Replace valid_upstreamtask with validate_target everywhere.
840
            validate_target(self.context.bug, project)
841
        except IllegalTarget as e:
842
            self.setFieldError('existing_product', e[0])
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
843
844
    @action('Use Existing Project', name='use_existing_product',
845
            validator=validate_existing_product)
846
    def use_existing_product_action(self, action, data):
5127.4.5 by Guilherme Salgado
Some cleanups
847
        """Record the chosen project as being affected by this bug.
848
849
        Also creates a bugwatch for the given remote bug.
850
        """
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
851
        data['product'] = data['existing_product']
852
        self._createBugTaskAndWatch(data)
853
854
    @action('Continue', name='continue')
855
    def continue_action(self, action, data):
856
        """Create a new product and a bugtask for this bug on that product.
857
858
        If the URL of the remote bug given is of a bugtracker used by any
859
        other products registered in Launchpad, then we show these products to
860
        the user and ask if he doesn't want to create the task in one of them.
861
        """
862
        if self.existing_products and not self.request.form.get('create_new'):
863
            # Present the projects using that bugtracker to the user as
864
            # possible options to report the bug on. If there are too many
865
            # projects using that bugtracker then show only the ones that
866
            # match the text entered as the project's name
867
            return
8781.2.3 by Brad Crittenden
Change +affects-new-product to assign the new product to the regsitry admins and set the license to DONT_KNOW
868
        # Products created through this view have DONT_KNOW licensing.
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
869
        product = getUtility(IProductSet).createProduct(
8781.2.3 by Brad Crittenden
Change +affects-new-product to assign the new product to the regsitry admins and set the license to DONT_KNOW
870
            owner=self.user,
871
            name=data['name'],
872
            displayname=data['displayname'], title=data['displayname'],
873
            summary=data['summary'], licenses=self.licenses,
11110.4.8 by Curtis Hovey
Create a packaging link when the user checks add_packaging.
874
            registrant=self.user)
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
875
        data['product'] = product
5292.1.1 by Guilherme Salgado
Fix the bug
876
        self._createBugTaskAndWatch(data, set_bugtracker=True)
8781.2.3 by Brad Crittenden
Change +affects-new-product to assign the new product to the regsitry admins and set the license to DONT_KNOW
877
        # Now that the product is configured set the owner to be the registry
878
        # experts team.
879
        product.owner = getUtility(ILaunchpadCelebrities).registry_experts
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
880
5292.1.1 by Guilherme Salgado
Fix the bug
881
    def _createBugTaskAndWatch(self, data, set_bugtracker=False):
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
882
        """Create a bugtask and bugwatch on the chosen product.
883
5292.1.1 by Guilherme Salgado
Fix the bug
884
        If set_bugtracker is True then the bugtracker of the newly created
885
        watch is set as the product's bugtracker.
886
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
887
        This is done by manually calling the main_action() method of
888
        UpstreamBugTrackerCreationStep and ProductBugTaskCreationStep.
889
890
        This method also sets self.next_url to the URL of the newly added
891
        bugtask.
892
        """
6916.1.1 by Curtis Hovey
Fixed comment formats to fidn missing persons, dates, and some bugs.
893
        # XXX: Guilherme Salgado, 2007-11-20: This relies on the fact that
894
        # these actions work using only the form data and the context.
895
        # (They don't require any side-effects done  during initialize().)
896
        # They should probably be extracted outside of the view to
897
        # make that explicit.
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
898
        view = UpstreamBugTrackerCreationStep(self.context, self.request)
899
        view.main_action(data)
900
901
        view = ProductBugTaskCreationStep(self.context, self.request)
902
        view.main_action(data)
903
5292.1.1 by Guilherme Salgado
Fix the bug
904
        if set_bugtracker:
905
            data['product'].bugtracker = view.task_added.bugwatch.bugtracker
5127.4.1 by Guilherme Salgado
Allos users to Register a product while adding an upstream task
906
        self.next_url = canonical_url(view.task_added)