~launchpad-pqm/launchpad/devel

12293.1.11 by Curtis Hovey
Updated copyright.
1
# Copyright 2010-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).
2764.2.1 by Brad Bollenbach
apply my +filebug patch on a fresh new bzr branch
3
4
"""IBugTarget-related browser views."""
5
6
__metaclass__ = type
7
3539.1.2 by Brad Bollenbach
fix failing tests
8
__all__ = [
9565.2.1 by Gary Poster
fix vhost breadcrumbs so that we do not duplicate information
9
    "BugsVHostBreadcrumb",
10137.7.43 by Karl Fogel
Incorporate some more review feedback from beuno and intellectronic:
10
    "BugsPatchesView",
3539.1.2 by Brad Bollenbach
fix failing tests
11
    "BugTargetBugListingView",
9360.1.1 by Gavin Panella
Remove bugtarget-filebug-advanced.pt, and clean up the view module.
12
    "BugTargetBugTagsView",
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
13
    "BugTargetBugsView",
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
14
    "FileBugAdvancedView",
3691.249.20 by Brad Bollenbach
Get the +package filebug workflow working again, using the same form and views as the other filebug workflows
15
    "FileBugGuidedView",
9360.1.1 by Gavin Panella
Remove bugtarget-filebug-advanced.pt, and clean up the view module.
16
    "FileBugViewBase",
10898.5.2 by Curtis Hovey
Include the bug supervisor and security contact in the +configure-bugtraker.
17
    "IProductBugConfiguration",
7968.4.1 by Abel Deuring
non-js version of editing official bug tags
18
    "OfficialBugTagsManageView",
10898.5.2 by Curtis Hovey
Include the bug supervisor and security contact in the +configure-bugtraker.
19
    "ProductConfigureBugTrackerView",
3764.1.2 by Bjorn Tillenius
make it possible to file a bug from the project page.
20
    "ProjectFileBugGuidedView",
10898.5.2 by Curtis Hovey
Include the bug supervisor and security contact in the +configure-bugtraker.
21
    "product_to_productbugconfiguration",
3539.1.2 by Brad Bollenbach
fix failing tests
22
    ]
2764.2.1 by Brad Bollenbach
apply my +filebug patch on a fresh new bzr branch
23
3691.320.9 by Bjorn Tillenius
display feedback of what was added to the bug report.
24
import cgi
3691.320.5 by Bjorn Tillenius
support adding attachments to the bug report.
25
from cStringIO import StringIO
10137.7.9 by Karl Fogel
With Abel, use the view instead of the model to prepare data for display.
26
from datetime import datetime
12584.1.2 by Gary Poster
clean lint
27
from operator import itemgetter
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
28
import urllib
29
30
from lazr.restful.interface import copy_field
10137.7.9 by Karl Fogel
With Abel, use the view instead of the model to prepare data for display.
31
from pytz import timezone
7968.6.1 by Abel Deuring
parital JS code for the Official Bug Tags management page
32
from simplejson import dumps
7675.547.2 by Graham Binns
FileBugViewBase now has working extra_data_processing_job and extra_data_processed properties.
33
from sqlobject import SQLObjectNotFound
8426.8.14 by Graham Binns
Merged devel.
34
from z3c.ptcompat import ViewPageTemplateFile
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
35
from zope import formlib
4338.3.11 by Gavin Panella
Remove superfluous bug_already_reported field and widget.
36
from zope.app.form.browser import TextWidget
3938.1.1 by kiko
Implement first cut at a better +filebug page for non-Malone-using contexts. Factor out doesn't-use-Malone code to a macro. Infrastructure: provide a fmt:link for HasGotchiAndEmblemFormatterAPI, add a helpwanted CSS message class, add a distrosourcepackages to Product, and prejoin the packaging query.
37
from zope.app.form.interfaces import InputErrors
10898.5.4 by Curtis Hovey
Added bug supervisor and security contact to +configure-bugtracker.
38
from zope.component import getUtility
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
39
from zope.interface import (
40
    alsoProvides,
41
    implements,
42
    Interface,
43
    )
3691.320.10 by Bjorn Tillenius
make publishTraverse work properly when it can't find the tokens.
44
from zope.publisher.interfaces import NotFound
3691.320.2 by Bjorn Tillenius
support adding extra description to the filed bug.
45
from zope.publisher.interfaces.browser import IBrowserPublisher
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
46
from zope.schema import (
47
    Bool,
48
    Choice,
49
    )
12337.3.11 by Curtis Hovey
Replaced doctest with unittest. Updated the validation method to use the errors
50
from zope.schema.interfaces import TooLong
10017.3.8 by Graham Binns
De-lint.
51
from zope.schema.vocabulary import SimpleVocabulary
10898.5.9 by Curtis Hovey
Add IProductBugConfiguration to the instance, not the implemented interface.
52
from zope.security.proxy import removeSecurityProxy
2764.2.1 by Brad Bollenbach
apply my +filebug patch on a fresh new bzr branch
53
9366.2.1 by Tom Berger
make it possible to disable th web interface filebug for ubuntu
54
from canonical.config import config
10699.1.2 by Tom Berger
Additional adjustments to make the security field writable in forms and correct some tests.
55
from canonical.launchpad import _
5429.1.2 by Edwin Grubbs
Changed templates and HasAnnouncementsView to use the FeedsMixin
56
from canonical.launchpad.browser.feeds import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
57
    BugFeedLink,
58
    BugTargetLatestBugsFeedLink,
59
    FeedsMixin,
60
    )
11235.8.5 by Abel Deuring
change the bug attachment download URL shown in bug-portlet-attachments.py; fix failures in test_bugchanges.txt
61
from canonical.launchpad.browser.librarian import ProxiedLibraryFileAlias
12164.2.6 by Gavin Panella
Move canonical.widgets.bug to lp.bugs.browser.widgets.bug.
62
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
10230.1.1 by Deryck Hodge
Bring back the move from hot_bugtasks to hot_bugs,
63
from canonical.launchpad.searchbuilder import any
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
64
from canonical.launchpad.webapp import (
65
    canonical_url,
66
    LaunchpadView,
67
    urlappend,
68
    )
69
from canonical.launchpad.webapp.authorization import check_permission
70
from canonical.launchpad.webapp.batching import BatchNavigator
9209.4.5 by Guilherme Salgado
Update all existing breadcrumb adapters to use Breadcrumb rather than BreadcrumbBuilder. Also rename them all
71
from canonical.launchpad.webapp.breadcrumb import Breadcrumb
11270.1.3 by Tim Penhey
Changed NotFoundError imports - gee there were a lot of them.
72
from canonical.launchpad.webapp.interfaces import ILaunchBag
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
73
from canonical.launchpad.webapp.menu import structured
12811.2.16 by Ian Booth
Test fixes
74
from canonical.launchpad.webapp.publisher import HTTP_MOVED_PERMANENTLY
11929.9.1 by Tim Penhey
Move launchpadform into lp.app.browser.
75
from lp.app.browser.launchpadform import (
76
    action,
77
    custom_widget,
78
    LaunchpadEditFormView,
79
    LaunchpadFormView,
80
    safe_action,
81
    )
12289.7.1 by William Grant
Run text_to_html over the bug acknowledgement message before displaying it.
82
from lp.app.browser.stringformatter import FormattersAPI
7675.916.58 by Jeroen Vermeulen
Merged db-stable.
83
from lp.app.browser.tales import BugTrackerFormatterAPI
11411.8.1 by j.c.sackett
Changes from review.
84
from lp.app.enums import ServiceUsage
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
85
from lp.app.errors import (
86
    NotFoundError,
87
    UnexpectedFormData,
88
    )
11411.7.7 by j.c.sackett
Added adapter for distroseries to serviceusage and updated bugtarget.py to make use of it.
89
from lp.app.interfaces.launchpad import (
90
    ILaunchpadUsage,
91
    IServiceUsage,
92
    )
12442.2.9 by j.c.sackett
Ran import reformatter per review.
93
from lp.app.validators.name import valid_name_pattern
12293.1.10 by Curtis Hovey
Formatted imports.
94
from lp.app.widgets.product import (
95
    GhostCheckBoxWidget,
96
    GhostWidget,
97
    ProductBugTrackerWidget,
98
    )
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
99
from lp.bugs.browser.bugrole import BugRoleMixin
100
from lp.bugs.browser.bugtask import BugTaskSearchListingView
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
101
from lp.bugs.browser.structuralsubscription import (
12393.30.1 by Brad Crittenden
Add structural subscription link to all IStructuralSubscriptionTargets
102
    expose_structural_subscription_data_to_js,
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
103
    )
12164.2.6 by Gavin Panella
Move canonical.widgets.bug to lp.bugs.browser.widgets.bug.
104
from lp.bugs.browser.widgets.bug import (
105
    BugTagsWidget,
106
    LargeBugTagsWidget,
107
    )
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.
108
from lp.bugs.browser.widgets.bugtask import NewLineToSpacesWidget
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
109
from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
8523.3.1 by Gavin Panella
Bugs tree reorg after automated migration.
110
from lp.bugs.interfaces.bug import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
111
    CreateBugParams,
12811.2.2 by Ian Booth
Refactor bug submission form
112
    IBug,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
113
    IBugAddForm,
114
    IBugSet,
115
    IProjectGroupBugAddForm,
116
    )
117
from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
118
from lp.bugs.interfaces.bugtarget import (
119
    IBugTarget,
120
    IOfficialBugTagTargetPublic,
121
    IOfficialBugTagTargetRestricted,
122
    )
123
from lp.bugs.interfaces.bugtask import (
124
    BugTaskSearchParams,
125
    BugTaskStatus,
126
    IBugTaskSet,
127
    UNRESOLVED_BUGTASK_STATUSES,
128
    )
129
from lp.bugs.interfaces.bugtracker import IBugTracker
8523.3.1 by Gavin Panella
Bugs tree reorg after automated migration.
130
from lp.bugs.interfaces.malone import IMaloneApplication
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
131
from lp.bugs.interfaces.securitycontact import IHasSecurityContact
12392.4.4 by Robert Collins
Use aggregation rather that looping in python to determine series bug counts in bug searches.
132
from lp.bugs.model.bugtask import BugTask
12584.1.1 by Gary Poster
add a simple page to start building JS on
133
from lp.bugs.model.structuralsubscription import (
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
134
    get_structural_subscriptions_for_target,
12584.1.1 by Gary Poster
add a simple page to start building JS on
135
    )
7675.547.10 by Graham Binns
Removed lint.
136
from lp.bugs.utilities.filebugdataparser import FileBugData
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
137
from lp.hardwaredb.interfaces.hwdb import IHWSubmissionSet
138
from lp.registry.browser.product import ProductConfigureBase
10100.1.24 by Jonathan Lange
Catch a few more not exercised by an empty test run.
139
from lp.registry.interfaces.distribution import IDistribution
8523.3.23 by Gavin Panella
Merge devel.
140
from lp.registry.interfaces.distributionsourcepackage import (
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
141
    IDistributionSourcePackage,
142
    )
8523.3.1 by Gavin Panella
Bugs tree reorg after automated migration.
143
from lp.registry.interfaces.distroseries import IDistroSeries
10137.10.2 by Gavin Panella
When the context is an IPerson, fix the silly title wording and show the package in the results table.
144
from lp.registry.interfaces.person import IPerson
10100.1.25 by Jonathan Lange
Fix the last of them
145
from lp.registry.interfaces.product import IProduct
8523.3.1 by Gavin Panella
Bugs tree reorg after automated migration.
146
from lp.registry.interfaces.productseries import IProductSeries
10326.1.2 by Henning Eggers
Renamed project interfaces module to projectgroup.
147
from lp.registry.interfaces.projectgroup import IProjectGroup
7675.338.3 by Deryck Hodge
Get the fix right so that we do allow Ubuntu's bug
148
from lp.registry.interfaces.sourcepackage import ISourcePackage
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
149
from lp.registry.vocabularies import ValidPersonOrTeamVocabulary
7675.547.2 by Graham Binns
FileBugViewBase now has working extra_data_processing_job and extra_data_processed properties.
150
from lp.services.job.interfaces.job import JobStatus
11382.6.34 by Gavin Panella
Reformat imports in all files touched so far.
151
from lp.services.propertycache import cachedproperty
3691.249.3 by Brad Bollenbach
make the dupes search form work with new formlib goo
152
8426.8.1 by Graham Binns
Added the duplicate finder overlay. Woot, etc.
153
# A simple vocabulary for the subscribe_to_existing_bug form field.
154
SUBSCRIBE_TO_BUG_VOCABULARY = SimpleVocabulary.fromItems(
155
    [('yes', True), ('no', False)])
156
157
10898.5.2 by Curtis Hovey
Include the bug supervisor and security contact in the +configure-bugtraker.
158
class IProductBugConfiguration(Interface):
159
    """A composite schema for editing bug app configuration."""
160
161
    bug_supervisor = copy_field(
162
        IHasBugSupervisor['bug_supervisor'], readonly=False)
163
    security_contact = copy_field(IHasSecurityContact['security_contact'])
164
    official_malone = copy_field(ILaunchpadUsage['official_malone'])
165
    enable_bug_expiration = copy_field(
166
        ILaunchpadUsage['enable_bug_expiration'])
167
    bugtracker = copy_field(IProduct['bugtracker'])
168
    remote_product = copy_field(IProduct['remote_product'])
169
    bug_reporting_guidelines = copy_field(
170
        IBugTarget['bug_reporting_guidelines'])
10977.7.2 by Abel Deuring
the relevant edit forms for ProjectGroup, Product, Distribution, DistributionSourcePackage now have fields to edit the bug report acknowledgement message
171
    bug_reported_acknowledgement = copy_field(
172
        IBugTarget['bug_reported_acknowledgement'])
7675.993.1 by Abel Deuring
The forms to configure the bugtracker for products and for DSPs now contains a field enable_bugfiling_duplicate_search; bug filing pages show immediately the complete form if enable_bugfiling_duplicate_search is turned off.
173
    enable_bugfiling_duplicate_search = copy_field(
174
        IBugTarget['enable_bugfiling_duplicate_search'])
10898.5.2 by Curtis Hovey
Include the bug supervisor and security contact in the +configure-bugtraker.
175
176
177
def product_to_productbugconfiguration(product):
178
    """Adapts an `IProduct` into an `IProductBugConfiguration`."""
10898.5.9 by Curtis Hovey
Add IProductBugConfiguration to the instance, not the implemented interface.
179
    alsoProvides(
180
        removeSecurityProxy(product), IProductBugConfiguration)
10898.5.2 by Curtis Hovey
Include the bug supervisor and security contact in the +configure-bugtraker.
181
    return product
182
183
10898.5.4 by Curtis Hovey
Added bug supervisor and security contact to +configure-bugtracker.
184
class ProductConfigureBugTrackerView(BugRoleMixin, ProductConfigureBase):
10876.5.2 by Curtis Hovey
Remove configure-bugtracker and remove_product tests.
185
    """View class to configure the bug tracker for a project."""
186
187
    label = "Configure bug tracker"
10898.5.2 by Curtis Hovey
Include the bug supervisor and security contact in the +configure-bugtraker.
188
    schema = IProductBugConfiguration
10995.1.3 by Curtis Hovey
Render the enable_bug_expiration and remote_product field as subordinate fields
189
    # This ProductBugTrackerWidget renders enable_bug_expiration and
10995.1.16 by Curtis Hovey
Fixed grammar.
190
    # remote_product as subordinate fields, so this view suppresses them.
10876.5.2 by Curtis Hovey
Remove configure-bugtracker and remove_product tests.
191
    custom_widget('bugtracker', ProductBugTrackerWidget)
10995.1.11 by Curtis Hovey
Pushed the bugtracker vocab rules to the base vocab. Reimplemented GhostWidget as a mixin to cover bool fields.
192
    custom_widget('enable_bug_expiration', GhostCheckBoxWidget)
10995.1.3 by Curtis Hovey
Render the enable_bug_expiration and remote_product field as subordinate fields
193
    custom_widget('remote_product', GhostWidget)
10876.5.2 by Curtis Hovey
Remove configure-bugtracker and remove_product tests.
194
11587.3.7 by Brian Murray
allow bug supervisor to view some +configure-bugtracker fields
195
    @property
196
    def field_names(self):
197
        """Return the list of field names to display."""
198
        field_names = [
7675.993.1 by Abel Deuring
The forms to configure the bugtracker for products and for DSPs now contains a field enable_bugfiling_duplicate_search; bug filing pages show immediately the complete form if enable_bugfiling_duplicate_search is turned off.
199
            "bugtracker",
200
            "enable_bug_expiration",
201
            "remote_product",
202
            "bug_reporting_guidelines",
203
            "bug_reported_acknowledgement",
204
            "enable_bugfiling_duplicate_search",
205
            ]
11587.3.7 by Brian Murray
allow bug supervisor to view some +configure-bugtracker fields
206
        if check_permission("launchpad.Edit", self.context):
207
            field_names.extend(["bug_supervisor", "security_contact"])
208
209
        return field_names
210
10876.5.2 by Curtis Hovey
Remove configure-bugtracker and remove_product tests.
211
    def validate(self, data):
212
        """Constrain bug expiration to Launchpad Bugs tracker."""
11823.1.1 by Brian Murray
allow bug supervisor to commit changes on the +configure-bugtracker page
213
        if check_permission("launchpad.Edit", self.context):
214
            self.validateBugSupervisor(data)
215
            self.validateSecurityContact(data)
10876.5.2 by Curtis Hovey
Remove configure-bugtracker and remove_product tests.
216
        # enable_bug_expiration is disabled by JavaScript when bugtracker
217
        # is not 'In Launchpad'. The constraint is enforced here in case the
218
        # JavaScript fails to activate or run. Note that the bugtracker
219
        # name : values are {'In Launchpad' : object, 'Somewhere else' : None
220
        # 'In a registered bug tracker' : IBugTracker}.
221
        bugtracker = data.get('bugtracker', None)
222
        if bugtracker is None or IBugTracker.providedBy(bugtracker):
223
            data['enable_bug_expiration'] = False
224
10898.5.4 by Curtis Hovey
Added bug supervisor and security contact to +configure-bugtracker.
225
    @action("Change", name='change')
226
    def change_action(self, action, data):
11134.5.1 by Curtis Hovey
Allow owners to change bug configuration fields they have permission to change.
227
        # bug_supervisor and security_contactrequires a transition method,
228
        # so it must be handled separately and removed for the
229
        # updateContextFromData to work as expected.
11823.1.1 by Brian Murray
allow bug supervisor to commit changes on the +configure-bugtracker page
230
        if check_permission("launchpad.Edit", self.context):
231
            self.changeBugSupervisor(data['bug_supervisor'])
232
            del data['bug_supervisor']
233
            self.changeSecurityContact(data['security_contact'])
234
            del data['security_contact']
10898.5.4 by Curtis Hovey
Added bug supervisor and security contact to +configure-bugtracker.
235
        self.updateContextFromData(data)
236
10876.5.2 by Curtis Hovey
Remove configure-bugtracker and remove_product tests.
237
12811.2.11 by Ian Booth
Code review fixes and ensure changing products hides/shows the relevant bug detail fields
238
class FileBugReportingGuidelines(LaunchpadFormView):
12811.2.2 by Ian Booth
Refactor bug submission form
239
    """Provides access to common bug reporting attributes.
240
241
    Attributes provided are: security_related and bug_reporting_guidelines.
242
243
    This view is a superclass of `FileBugViewBase` so that non-ajax browsers
244
    can load the file bug form, and it is also invoked directly via an XHR
245
    request to provide an HTML snippet for Javascript enabled browsers.
246
    """
247
248
    schema = IBug
12811.2.8 by Ian Booth
Lint fixes
249
12811.2.2 by Ian Booth
Refactor bug submission form
250
    @property
251
    def field_names(self):
252
        """Return the list of field names to display."""
253
        return ['security_related']
254
255
    def setUpFields(self):
256
        """Set up the form fields. See `LaunchpadFormView`."""
12811.2.11 by Ian Booth
Code review fixes and ensure changing products hides/shows the relevant bug detail fields
257
        super(FileBugReportingGuidelines, self).setUpFields()
12811.2.2 by Ian Booth
Refactor bug submission form
258
259
        security_related_field = Bool(
260
            __name__='security_related',
261
            title=_("This bug is a security vulnerability"),
262
            required=False, default=False)
263
264
        self.form_fields = self.form_fields.omit('security_related')
265
        self.form_fields += formlib.form.Fields(security_related_field)
266
267
    @property
268
    def bug_reporting_guidelines(self):
269
        """Guidelines for filing bugs in the current context.
270
271
        Returns a list of dicts, with each dict containing values for
272
        "preamble" and "content".
273
        """
274
275
        def target_name(target):
276
            # IProjectGroup can be considered the target of a bug during
277
            # the bug filing process, but does not extend IBugTarget
278
            # and ultimately cannot actually be the target of a
279
            # bug. Hence this function to determine a suitable
280
            # name/title to display. Hurrumph.
281
            if IBugTarget.providedBy(target):
282
                return target.bugtargetdisplayname
283
            else:
12811.2.11 by Ian Booth
Code review fixes and ensure changing products hides/shows the relevant bug detail fields
284
                return target.displayname
12811.2.2 by Ian Booth
Refactor bug submission form
285
286
        guidelines = []
287
        bugtarget = self.context
288
        if bugtarget is not None:
289
            content = bugtarget.bug_reporting_guidelines
290
            if content is not None and len(content) > 0:
291
                guidelines.append({
292
                        "source": target_name(bugtarget),
293
                        "content": content,
294
                        })
295
            # Distribution source packages are shown with both their
296
            # own reporting guidelines and those of their
297
            # distribution.
298
            if IDistributionSourcePackage.providedBy(bugtarget):
299
                distribution = bugtarget.distribution
300
                content = distribution.bug_reporting_guidelines
301
                if content is not None and len(content) > 0:
302
                    guidelines.append({
303
                            "source": target_name(distribution),
304
                            "content": content,
305
                            })
306
        return guidelines
307
308
    def getMainContext(self):
309
        if IDistributionSourcePackage.providedBy(self.context):
310
            return self.context.distribution
311
        else:
312
            return self.context
313
314
12811.2.11 by Ian Booth
Code review fixes and ensure changing products hides/shows the relevant bug detail fields
315
class FileBugViewBase(FileBugReportingGuidelines, LaunchpadFormView):
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
316
    """Base class for views related to filing a bug."""
3691.249.29 by Brad Bollenbach
add a basic filebug steps macro, for mpt to tweak
317
3691.320.2 by Bjorn Tillenius
support adding extra description to the filed bug.
318
    implements(IBrowserPublisher)
319
3691.342.1 by Bjorn Tillenius
make it possible to specify the default bug summary in the +filebug blob.
320
    extra_data_token = None
3691.415.2 by Bjorn Tillenius
add a Tags: field to the advanced filebug page.
321
    advanced_form = False
4486.4.24 by Graham Binns
UI and pagetest tweaks
322
    frontpage_form = False
7143.4.20 by Bjorn Tillenius
make sure temporary files are removed.
323
    data_parser = None
3691.342.1 by Bjorn Tillenius
make it possible to specify the default bug summary in the +filebug blob.
324
325
    def __init__(self, context, request):
326
        LaunchpadFormView.__init__(self, context, request)
327
        self.extra_data = FileBugData()
3691.320.2 by Bjorn Tillenius
support adding extra description to the filed bug.
328
10054.11.3 by Graham Binns
Refactored the FileBug views to ensure that the extra data code doesn't get moved unnecessarily.
329
    def initialize(self):
330
        LaunchpadFormView.initialize(self)
331
        if (not self.redirect_ubuntu_filebug and
7675.547.8 by Graham Binns
Hurrah, the +filebug form now works with processed apport blob jobs.
332
            self.extra_data_token is not None and
333
            not self.extra_data_to_process):
10054.11.3 by Graham Binns
Refactored the FileBug views to ensure that the extra data code doesn't get moved unnecessarily.
334
            # self.extra_data has been initialized in publishTraverse().
335
            if self.extra_data.initial_summary:
336
                self.widgets['title'].setRenderedValue(
337
                    self.extra_data.initial_summary)
338
            if self.extra_data.initial_tags:
339
                self.widgets['tags'].setRenderedValue(
340
                    self.extra_data.initial_tags)
341
            # XXX: Bjorn Tillenius 2006-01-15:
342
            #      We should include more details of what will be added
343
            #      to the bug report.
344
            self.request.response.addNotification(
345
                'Extra debug information will be added to the bug report'
346
                ' automatically.')
347
348
    @cachedproperty
349
    def redirect_ubuntu_filebug(self):
350
        if IDistribution.providedBy(self.context):
351
            bug_supervisor = self.context.bug_supervisor
352
        elif (IDistributionSourcePackage.providedBy(self.context) or
353
              ISourcePackage.providedBy(self.context)):
354
            bug_supervisor = self.context.distribution.bug_supervisor
10054.11.4 by Graham Binns
Fixed a test error with bug supervisors not being set in redirect_ubuntu_filebug.
355
        else:
356
            bug_supervisor = None
10054.11.3 by Graham Binns
Refactored the FileBug views to ensure that the extra data code doesn't get moved unnecessarily.
357
358
        # Work out whether the redirect should be overidden.
359
        do_not_redirect = (
360
            self.request.form.get('no-redirect') is not None or
361
            [key for key in self.request.form.keys()
362
            if 'field.actions' in key] != [] or
363
            self.user.inTeam(bug_supervisor))
364
365
        return (
366
            config.malone.ubuntu_disable_filebug and
367
            self.targetIsUbuntu() and
368
            self.extra_data_token is None and
369
            not do_not_redirect)
3691.320.12 by Bjorn Tillenius
display a notification that extra information will be added to the bug.
370
3691.249.23 by Brad Bollenbach
fix test failures
371
    @property
3995.4.1 by Bjorn Tillenius
move field_names generation into FileBugViewBase.
372
    def field_names(self):
373
        """Return the list of field names to display."""
374
        context = self.context
4338.3.4 by Gavin Panella
Add bug_already_reported and bug_already_reported_as.
375
        field_names = ['title', 'comment', 'tags', 'security_related',
5491.1.6 by Graham Binns
Added fields to bugtarget.FileBugViewBase.
376
                       'bug_already_reported_as', 'filecontent', 'patch',
8426.8.1 by Graham Binns
Added the duplicate finder overlay. Woot, etc.
377
                       'attachment_description', 'subscribe_to_existing_bug']
3995.4.1 by Bjorn Tillenius
move field_names generation into FileBugViewBase.
378
        if (IDistribution.providedBy(context) or
379
            IDistributionSourcePackage.providedBy(context)):
380
            field_names.append('packagename')
381
        elif IMaloneApplication.providedBy(context):
382
            field_names.append('bugtarget')
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
383
        elif IProjectGroup.providedBy(context):
3995.4.1 by Bjorn Tillenius
move field_names generation into FileBugViewBase.
384
            field_names.append('product')
385
        elif not IProduct.providedBy(context):
386
            raise AssertionError('Unknown context: %r' % context)
387
12811.2.12 by Ian Booth
Fix rendering of assignee/importance etc fields for project groups, fix test
388
        # If the context is a project group we want to render the optional
389
        # fields since they will initially be hidden and later exposed if the
390
        # selected project supports them.
391
        include_extra_fields = IProjectGroup.providedBy(context)
392
        if not include_extra_fields and IHasBugSupervisor.providedBy(context):
393
            include_extra_fields = self.user.inTeam(context.bug_supervisor)
394
395
        if include_extra_fields:
396
            field_names.extend(
397
                ['assignee', 'importance', 'milestone', 'status'])
8137.17.24 by Barry Warsaw
thread merge
398
3995.4.3 by Bjorn Tillenius
add a security checkbox on +filebug.
399
        return field_names
3995.4.1 by Bjorn Tillenius
move field_names generation into FileBugViewBase.
400
401
    @property
3691.249.23 by Brad Bollenbach
fix test failures
402
    def initial_values(self):
403
        """Give packagename a default value, if applicable."""
404
        if not IDistributionSourcePackage.providedBy(self.context):
405
            return {}
406
407
        return {'packagename': self.context.name}
3691.249.29 by Brad Bollenbach
add a basic filebug steps macro, for mpt to tweak
408
4640.3.15 by Matthew Paul Thomas
Fixes from Tim's first review.
409
    def isPrivate(self):
4640.3.9 by Matthew Paul Thomas
Gives bug-reporting pages the private appearance if you're reporting a bug on a project that has private bugs by default.
410
        """Whether bug reports on this target are private by default."""
411
        return IProduct.providedBy(self.context) and self.context.private_bugs
412
3938.1.1 by kiko
Implement first cut at a better +filebug page for non-Malone-using contexts. Factor out doesn't-use-Malone code to a macro. Infrastructure: provide a fmt:link for HasGotchiAndEmblemFormatterAPI, add a helpwanted CSS message class, add a distrosourcepackages to Product, and prejoin the packaging query.
413
    def contextIsProduct(self):
414
        return IProduct.providedBy(self.context)
415
4508.3.32 by Graham Binns
Added a list of products that don't use Malone to the bug target not_uses_malone macro
416
    def contextIsProject(self):
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
417
        return IProjectGroup.providedBy(self.context)
4508.3.32 by Graham Binns
Added a list of products that don't use Malone to the bug target not_uses_malone macro
418
9366.2.1 by Tom Berger
make it possible to disable th web interface filebug for ubuntu
419
    def targetIsUbuntu(self):
420
        ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
421
        return (self.context == ubuntu or
422
                (IMaloneApplication.providedBy(self.context) and
423
                 self.request.form.get('field.bugtarget.distribution') ==
9731.1.1 by Tom Berger
disable +filebug redirection for ubuntu packages
424
                 ubuntu.name))
9366.2.1 by Tom Berger
make it possible to disable th web interface filebug for ubuntu
425
3691.249.23 by Brad Bollenbach
fix test failures
426
    def getPackageNameFieldCSSClass(self):
427
        """Return the CSS class for the packagename field."""
428
        if self.widget_errors.get("packagename"):
429
            return 'error'
430
        else:
431
            return ''
432
433
    def validate(self, data):
434
        """Make sure the package name, if provided, exists in the distro."""
4338.3.10 by Gavin Panella
Vastly improved bug form behaviour.
435
        # The comment field is only required if filing a new bug.
436
        if self.submit_bug_action.submitted():
5243.3.7 by Jonathan Knowles
Removing prototype length validator function.
437
            comment = data.get('comment')
12337.3.11 by Curtis Hovey
Replaced doctest with unittest. Updated the validation method to use the errors
438
            # The widget only exposes the error message. The private
439
            # attr contains the real error.
440
            widget_error = self.widgets.get('comment')._error
441
            if widget_error and isinstance(widget_error.errors, TooLong):
442
                self.setFieldError('comment',
443
                    'The description is too long. If you have lots '
444
                    'text to add, attach a file to the bug instead.')
445
            elif not comment or widget_error is not None:
12337.3.1 by Curtis Hovey
Used NoneableDescription to ensure empty or whitespace-only bug comments are
446
                self.setFieldError(
447
                    'comment', "Provide details about the issue.")
4338.3.10 by Gavin Panella
Vastly improved bug form behaviour.
448
        # Check a bug has been selected when the user wants to
449
        # subscribe to an existing bug.
450
        elif self.this_is_my_bug_action.submitted():
451
            if not data.get('bug_already_reported_as'):
5309.1.3 by Edwin Grubbs
The <link> tag for referencing atom feeds in other web pages is now
452
                self.setFieldError('bug_already_reported_as',
453
                                   "Please choose a bug.")
4338.3.10 by Gavin Panella
Vastly improved bug form behaviour.
454
        else:
4338.3.23 by Gavin Panella
Changes suggested by Tim Penhey in review.
455
            # We only care about those two actions.
4338.3.10 by Gavin Panella
Vastly improved bug form behaviour.
456
            pass
4338.3.5 by Gavin Panella
Add handling for the Yes/No radio buttons.
457
3691.249.23 by Brad Bollenbach
fix test failures
458
        # We have to poke at the packagename value directly in the
459
        # request, because if validation failed while getting the
460
        # widget's data, it won't appear in the data dict.
461
        form = self.request.form
462
        if form.get("packagename_option") == "choose":
463
            packagename = form.get("field.packagename")
464
            if packagename:
465
                if IDistribution.providedBy(self.context):
466
                    distribution = self.context
3691.249.33 by Bjorn Tillenius
fix test failures, unbreak the +package page
467
                elif 'distribution' in data:
468
                    distribution = data['distribution']
3691.249.23 by Brad Bollenbach
fix test failures
469
                else:
470
                    assert IDistributionSourcePackage.providedBy(self.context)
471
                    distribution = self.context.distribution
472
473
                try:
474
                    distribution.guessPackageNames(packagename)
475
                except NotFoundError:
9760.8.1 by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase.
476
                    if distribution.series:
477
                        # If a distribution doesn't have any series,
3691.249.33 by Bjorn Tillenius
fix test failures, unbreak the +package page
478
                        # it won't have any source packages published at
479
                        # all, so we set the error only if there are
9760.8.1 by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase.
480
                        # series.
3691.249.33 by Bjorn Tillenius
fix test failures, unbreak the +package page
481
                        packagename_error = (
482
                            '"%s" does not exist in %s. Please choose a '
3691.249.34 by Bjorn Tillenius
make the guided filebug ui more like the support tracker's.
483
                            "different package. If you're unsure, please "
3691.249.33 by Bjorn Tillenius
fix test failures, unbreak the +package page
484
                            'select "I don\'t know"' % (
485
                                packagename, distribution.displayname))
486
                        self.setFieldError("packagename", packagename_error)
3691.249.23 by Brad Bollenbach
fix test failures
487
            else:
9565.2.1 by Gary Poster
fix vhost breadcrumbs so that we do not duplicate information
488
                self.setFieldError("packagename",
5309.1.3 by Edwin Grubbs
The <link> tag for referencing atom feeds in other web pages is now
489
                                   "Please enter a package name")
3553.3.11 by Brad Bollenbach
checkpoint
490
4486.4.15 by Graham Binns
Review-suggested changes
491
        # If we've been called from the frontpage filebug forms we must check
492
        # that whatever product or distro is having a bug filed against it
4486.4.25 by Graham Binns
Fixed a typo
493
        # actually uses Malone for its bug tracking.
4486.4.15 by Graham Binns
Review-suggested changes
494
        product_or_distro = self.getProductOrDistroFromContext()
495
        if (product_or_distro is not None and
11411.7.1 by j.c.sackett
Fixed majority of official_malone calls in code-space. Still need to fix templates.
496
            product_or_distro.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
5309.1.3 by Edwin Grubbs
The <link> tag for referencing atom feeds in other web pages is now
497
            self.setFieldError(
498
                'bugtarget',
499
                "%s does not use Launchpad as its bug tracker " %
500
                product_or_distro.displayname)
4486.4.15 by Graham Binns
Review-suggested changes
501
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
502
    def setUpWidgets(self):
4338.3.25 by Gavin Panella
Revert to using showOptionalMarker after discussion with BjornT.
503
        """Customize the onKeyPress event of the package name chooser."""
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
504
        LaunchpadFormView.setUpWidgets(self)
505
506
        if "packagename" in self.field_names:
507
            self.widgets["packagename"].onKeyPress = (
508
                "selectWidget('choose', event)")
509
8426.8.1 by Graham Binns
Added the duplicate finder overlay. Woot, etc.
510
    def setUpFields(self):
511
        """Set up the form fields. See `LaunchpadFormView`."""
512
        super(FileBugViewBase, self).setUpFields()
513
514
        # Override the vocabulary for the subscribe_to_existing_bug
515
        # field.
516
        subscribe_field = Choice(
517
            __name__='subscribe_to_existing_bug',
8426.8.8 by Graham Binns
Fixed a a silly pagetest error.
518
            title=u'Subscribe to this bug',
8426.8.1 by Graham Binns
Added the duplicate finder overlay. Woot, etc.
519
            vocabulary=SUBSCRIBE_TO_BUG_VOCABULARY,
520
            required=True, default=False)
521
522
        self.form_fields = self.form_fields.omit('subscribe_to_existing_bug')
8426.8.5 by Graham Binns
Fixed some lint.
523
        self.form_fields += formlib.form.Fields(subscribe_field)
8426.8.1 by Graham Binns
Added the duplicate finder overlay. Woot, etc.
524
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
525
    def contextUsesMalone(self):
526
        """Does the context use Malone as its official bugtracker?"""
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
527
        if IProjectGroup.providedBy(self.context):
3764.1.4 by Bjorn Tillenius
refactor the filebug views to reduce code duplication.
528
            products_using_malone = [
529
                product for product in self.context.products
11411.7.1 by j.c.sackett
Fixed majority of official_malone calls in code-space. Still need to fix templates.
530
                if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD]
3764.1.4 by Bjorn Tillenius
refactor the filebug views to reduce code duplication.
531
            return len(products_using_malone) > 0
532
        else:
11411.7.27 by j.c.sackett
Lint fixes.
533
            bug_tracking_usage = self.getMainContext().bug_tracking_usage
534
            return bug_tracking_usage == ServiceUsage.LAUNCHPAD
3764.1.4 by Bjorn Tillenius
refactor the filebug views to reduce code duplication.
535
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
536
    def shouldSelectPackageName(self):
3691.249.23 by Brad Bollenbach
fix test failures
537
        """Should the radio button to select a package be selected?"""
538
        return (
539
            self.request.form.get("field.packagename") or
540
            self.initial_values.get("packagename"))
541
542
    def handleSubmitBugFailure(self, action, data, errors):
543
        return self.showFileBugForm()
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
544
545
    @action("Submit Bug Report", name="submit_bug",
3691.249.23 by Brad Bollenbach
fix test failures
546
            failure=handleSubmitBugFailure)
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
547
    def submit_bug_action(self, action, data):
2764.2.1 by Brad Bollenbach
apply my +filebug patch on a fresh new bzr branch
548
        """Add a bug to this IBugTarget."""
3691.320.2 by Bjorn Tillenius
support adding extra description to the filed bug.
549
        title = data["title"]
3691.320.9 by Bjorn Tillenius
display feedback of what was added to the bug report.
550
        comment = data["comment"].rstrip()
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
551
        packagename = data.get("packagename")
3691.249.20 by Brad Bollenbach
Get the +package filebug workflow working again, using the same form and views as the other filebug workflows
552
        security_related = data.get("security_related", False)
553
        distribution = data.get(
554
            "distribution", getUtility(ILaunchBag).distribution)
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
555
2846.1.36 by Brad Bollenbach
more small fixes
556
        context = self.context
557
        if distribution is not None:
3691.249.20 by Brad Bollenbach
Get the +package filebug workflow working again, using the same form and views as the other filebug workflows
558
            # We're being called from the generic bug filing form, so
559
            # manually set the chosen distribution as the context.
2846.1.36 by Brad Bollenbach
more small fixes
560
            context = distribution
12811.2.5 by Ian Booth
Fix tests
561
        elif IProjectGroup.providedBy(context):
562
            context = data['product']
3778.2.3 by Bjorn Tillenius
make the general +filebug page work.
563
        elif IMaloneApplication.providedBy(context):
3778.2.11 by Bjorn Tillenius
clean-up.
564
            context = data['bugtarget']
2764.2.1 by Brad Bollenbach
apply my +filebug patch on a fresh new bzr branch
565
3327.1.7 by Brad Bollenbach
checkpoint
566
        # Ensure that no package information is used, if the user
567
        # enters a package name but then selects "I don't know".
568
        if self.request.form.get("packagename_option") == "none":
569
            packagename = None
570
3553.3.17 by Brad Bollenbach
revert the createBug API change that collapsed security_related and private into just security_related
571
        # Security bugs are always private when filed, but can be disclosed
572
        # after they've been reported.
573
        if security_related:
574
            private = True
575
        else:
576
            private = False
577
12289.7.1 by William Grant
Run text_to_html over the bug acknowledgement message before displaying it.
578
        linkified_ack = structured(FormattersAPI(
12289.7.3 by William Grant
Remove the margin from the bottom paragraph of the bug acknowledgement notification, since the notification div has its own padding.
579
            self.getAcknowledgementMessage(self.context)).text_to_html(
580
                last_paragraph_class="last"))
12289.7.1 by William Grant
Run text_to_html over the bug acknowledgement message before displaying it.
581
        notifications = [linkified_ack]
3691.415.1 by Bjorn Tillenius
factor out the creation of CreateBugParams in FileBugBaseView.
582
        params = CreateBugParams(
583
            title=title, comment=comment, owner=self.user,
3691.415.2 by Bjorn Tillenius
add a Tags: field to the advanced filebug page.
584
            security_related=security_related, private=private,
585
            tags=data.get('tags'))
2846.1.36 by Brad Bollenbach
more small fixes
586
        if IDistribution.providedBy(context) and packagename:
2846.1.32 by Brad Bollenbach
simplify and fix package name guessing
587
            # We don't know if the package name we got was a source or binary
588
            # package name, so let the Soyuz API figure it out for us.
3691.315.12 by kiko
The BinaryAndSourcePackageNameVocabulary no longer uses name as its object, so account for that in browser code
589
            packagename = str(packagename.name)
1716.3.41 by kiko
Fix for bug 31367: Specifying a non-published binary package when filing a bug causes an oops. Change the add bug process to ignore the package if it was never published in the distribution, and test it using Gentoo (which now uses Malone officially, yay. Or at least in sampledata). This fixes a topcrasher, so hooray for us.
590
            try:
591
                sourcepackagename, binarypackagename = (
3504.1.58 by kiko
Hack Distribution.getPackageNames around a bit: rename it to guessPackageNames to make clear it is a best-effort and might not give the right answer; test the fact that it restricts publication to a single distribution, and ensure that the code does that; revamp comments.
592
                    context.guessPackageNames(packagename))
1716.3.41 by kiko
Fix for bug 31367: Specifying a non-published binary package when filing a bug causes an oops. Change the add bug process to ignore the package if it was never published in the distribution, and test it using Gentoo (which now uses Malone officially, yay. Or at least in sampledata). This fixes a topcrasher, so hooray for us.
593
            except NotFoundError:
3504.1.58 by kiko
Hack Distribution.getPackageNames around a bit: rename it to guessPackageNames to make clear it is a best-effort and might not give the right answer; test the fact that it restricts publication to a single distribution, and ensure that the code does that; revamp comments.
594
                # guessPackageNames may raise NotFoundError. It would be
1716.3.41 by kiko
Fix for bug 31367: Specifying a non-published binary package when filing a bug causes an oops. Change the add bug process to ignore the package if it was never published in the distribution, and test it using Gentoo (which now uses Malone officially, yay. Or at least in sampledata). This fixes a topcrasher, so hooray for us.
595
                # nicer to allow people to indicate a package even if
596
                # never published, but the quick fix for now is to note
597
                # the issue and move on.
3691.320.16 by Bjorn Tillenius
fix test failure.
598
                notifications.append(
3691.320.9 by Bjorn Tillenius
display feedback of what was added to the bug report.
599
                    "The package %s is not published in %s; the "
3553.3.17 by Brad Bollenbach
revert the createBug API change that collapsed security_related and private into just security_related
600
                    "bug was targeted only to the distribution."
601
                    % (packagename, context.displayname))
3691.415.1 by Bjorn Tillenius
factor out the creation of CreateBugParams in FileBugBaseView.
602
                params.comment += (
603
                    "\r\n\r\nNote: the original reporter indicated "
604
                    "the bug was in package %r; however, that package "
605
                    "was not published in %s." % (
606
                        packagename, context.displayname))
1716.3.41 by kiko
Fix for bug 31367: Specifying a non-published binary package when filing a bug causes an oops. Change the add bug process to ignore the package if it was never published in the distribution, and test it using Gentoo (which now uses Malone officially, yay. Or at least in sampledata). This fixes a topcrasher, so hooray for us.
607
            else:
3598.1.31 by Brad Bollenbach
response to code review
608
                context = context.getSourcePackage(sourcepackagename.name)
3691.415.1 by Bjorn Tillenius
factor out the creation of CreateBugParams in FileBugBaseView.
609
                params.binarypackagename = binarypackagename
2764.2.1 by Brad Bollenbach
apply my +filebug patch on a fresh new bzr branch
610
3691.342.1 by Bjorn Tillenius
make it possible to specify the default bug summary in the +filebug blob.
611
        extra_data = self.extra_data
3691.320.9 by Bjorn Tillenius
display feedback of what was added to the bug report.
612
        if extra_data.extra_description:
613
            params.comment = "%s\n\n%s" % (
614
                params.comment, extra_data.extra_description)
615
            notifications.append(
616
                'Additional information was added to the bug description.')
617
4374.1.2 by David Murphy
Incorporated review feedback from BjornT. Improved doctest, and switched change to modifiy params instead of bug.
618
        if extra_data.private:
619
            params.private = extra_data.private
620
3691.320.1 by Bjorn Tillenius
add a test for FileBugBaseView.
621
        self.added_bug = bug = context.createBug(params)
8137.17.24 by Barry Warsaw
thread merge
622
623
        # Apply any extra options given by a bug supervisor.
624
        bugtask = self.added_bug.default_bugtask
12811.2.11 by Ian Booth
Code review fixes and ensure changing products hides/shows the relevant bug detail fields
625
        if IHasBugSupervisor.providedBy(context):
626
            if self.user.inTeam(context.bug_supervisor):
627
                if 'assignee' in data:
628
                    bugtask.transitionToAssignee(data['assignee'])
629
                if 'status' in data:
630
                    bugtask.transitionToStatus(data['status'], self.user)
631
                if 'importance' in data:
632
                    bugtask.transitionToImportance(
633
                        data['importance'], self.user)
634
                if 'milestone' in data:
635
                    bugtask.milestone = data['milestone']
8137.17.24 by Barry Warsaw
thread merge
636
3691.320.3 by Bjorn Tillenius
support more than one inline part, which will be added as comments.
637
        for comment in extra_data.comments:
638
            bug.newMessage(self.user, bug.followup_subject(), comment)
3691.320.9 by Bjorn Tillenius
display feedback of what was added to the bug report.
639
            notifications.append(
640
                'A comment with additional information was added to the'
641
                ' bug report.')
3691.320.3 by Bjorn Tillenius
support more than one inline part, which will be added as comments.
642
5491.1.14 by Graham Binns
Review changes.
643
        # XXX 2007-01-19 gmb:
644
        #     We need to have a proper FileUpload widget rather than
645
        #     this rather hackish solution.
5491.1.10 by Graham Binns
Put attachment handling with existing attachment handling code.
646
        attachment = self.request.form.get(self.widgets['filecontent'].name)
5491.1.20 by Graham Binns
Fixed bug 186564.
647
        if attachment or extra_data.attachments:
3691.365.1 by Bjorn Tillenius
collapse the attachments uploaded on +storeblob into one comment.
648
            # Attach all the comments to a single empty comment.
649
            attachment_comment = bug.newMessage(
650
                owner=self.user, subject=bug.followup_subject(), content=None)
5491.1.10 by Graham Binns
Put attachment handling with existing attachment handling code.
651
652
            # Deal with attachments added in the filebug form.
5491.1.20 by Graham Binns
Fixed bug 186564.
653
            if attachment:
5491.1.10 by Graham Binns
Put attachment handling with existing attachment handling code.
654
                # We convert slashes in filenames to hyphens to avoid
655
                # problems.
656
                filename = attachment.filename.replace('/', '-')
657
658
                # If the user hasn't entered a description for the
659
                # attachment we use its name.
660
                file_description = None
661
                if 'attachment_description' in data:
662
                    file_description = data['attachment_description']
5491.1.13 by Graham Binns
Review changes for sinzui.
663
                if file_description is None:
5491.1.10 by Graham Binns
Put attachment handling with existing attachment handling code.
664
                    file_description = filename
665
666
                bug.addAttachment(
6655.4.5 by Gavin Panella
Test fixes for addAttachment signature change.
667
                    owner=self.user, data=StringIO(data['filecontent']),
5491.1.10 by Graham Binns
Put attachment handling with existing attachment handling code.
668
                    filename=filename, description=file_description,
669
                    comment=attachment_comment, is_patch=data['patch'])
670
671
                notifications.append(
672
                    'The file "%s" was attached to the bug report.' %
673
                        cgi.escape(filename))
674
5491.1.16 by Graham Binns
Fixed some spurious test failures.
675
            for attachment in extra_data.attachments:
7675.547.8 by Graham Binns
Hurrah, the +filebug form now works with processed apport blob jobs.
676
                bug.linkAttachment(
677
                    owner=self.user, file_alias=attachment['file_alias'],
5491.1.16 by Graham Binns
Fixed some spurious test failures.
678
                    description=attachment['description'],
11634.2.9 by Robert Collins
Another missed fixture stateless-use.
679
                    comment=attachment_comment,
680
                    send_notifications=False)
5491.1.16 by Graham Binns
Fixed some spurious test failures.
681
                notifications.append(
682
                    'The file "%s" was attached to the bug report.' %
7675.547.8 by Graham Binns
Hurrah, the +filebug form now works with processed apport blob jobs.
683
                        cgi.escape(attachment['file_alias'].filename))
5491.1.16 by Graham Binns
Fixed some spurious test failures.
684
4375.1.1 by David Murphy
Added the ability to subscribe people via the extra data file (used by Apport).
685
        if extra_data.subscribers:
686
            # Subscribe additional subscribers to this bug
687
            for subscriber in extra_data.subscribers:
688
                valid_person_vocabulary = ValidPersonOrTeamVocabulary()
689
                try:
690
                    person = valid_person_vocabulary.getTermByToken(
691
                        subscriber).value
4375.1.4 by David Murphy
Changed field name to Subscribers (was Subscribe).
692
                except LookupError:
693
                    # We cannot currently pass this error up to the user, so
694
                    # we'll just ignore it.
695
                    pass
696
                else:
5454.1.5 by Tom Berger
record who created each bug subscription, and display the result in the title of the subscriber link
697
                    bug.subscribe(person, self.user)
4375.1.1 by David Murphy
Added the ability to subscribe people via the extra data file (used by Apport).
698
                    notifications.append(
699
                        '%s has been subscribed to this bug.' %
700
                        person.displayname)
701
9360.3.1 by Abel Deuring
create links between a bug and a HWSubmission if bug data provided by apport contains the header HWDB-Submission
702
        submission_set = getUtility(IHWSubmissionSet)
703
        for submission_key in extra_data.hwdb_submission_keys:
704
            submission = submission_set.getBySubmissionKey(
705
                submission_key, self.user)
706
            if submission is not None:
707
                bug.linkHWSubmission(submission)
708
2828.1.1 by Brad Bollenbach
add a feedback message for a successfully opened bug
709
        # Give the user some feedback on the bug just opened.
3691.320.9 by Bjorn Tillenius
display feedback of what was added to the bug report.
710
        for notification in notifications:
711
            self.request.response.addNotification(notification)
4374.1.1 by David Murphy
Added support for a Private field in the extra data file. This sets the bugs private flag accordingly.
712
        if bug.security_related:
5594.1.11 by Maris Fogels
Beautified the addNotification(structured(...)) construct.
713
            self.request.response.addNotification(
714
                structured(
5512.1.5 by Matthew Paul Thomas
Fixes a typo (ironic, that).
715
                'Security-related bugs are by default private '
5512.1.1 by Matthew Paul Thomas
Fixes bug 174086 ('Publicly' misspelled as 'publically' after reporting security bug).
716
                '(visible only to their direct subscribers). '
717
                'You may choose to <a href="+secrecy">publicly '
5594.1.7 by Maris Fogels
Fixed all of the calls to addNotification() that contain HTML so that they use the structured() class.
718
                'disclose</a> this bug.'))
4374.1.1 by David Murphy
Added support for a Private field in the extra data file. This sets the bugs private flag accordingly.
719
        if bug.private and not bug.security_related:
5594.1.11 by Maris Fogels
Beautified the addNotification(structured(...)) construct.
720
            self.request.response.addNotification(
721
                structured(
5512.1.1 by Matthew Paul Thomas
Fixes bug 174086 ('Publicly' misspelled as 'publically' after reporting security bug).
722
                'This bug report has been marked private '
723
                '(visible only to its direct subscribers). '
5594.1.28 by Maris Fogels
Merge from RF; resolved one conflict.
724
                'You may choose to <a href="+secrecy">change this</a>.'))
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
725
726
        self.request.response.redirect(canonical_url(bug.bugtasks[0]))
3691.249.29 by Brad Bollenbach
add a basic filebug steps macro, for mpt to tweak
727
8401.1.1 by Graham Binns
The default action when the user clicks "this is my bug" on the guided
728
    @action("Yes, this is the bug I'm trying to report",
729
            name="this_is_my_bug", failure=handleSubmitBugFailure)
4338.3.2 by Gavin Panella
Improve duplicate list:
730
    def this_is_my_bug_action(self, action, data):
4338.3.23 by Gavin Panella
Changes suggested by Tim Penhey in review.
731
        """Subscribe to the bug suggested."""
4338.3.12 by Gavin Panella
Subscribe to selected bug and notify user.
732
        bug = data.get('bug_already_reported_as')
8426.8.1 by Graham Binns
Added the duplicate finder overlay. Woot, etc.
733
        subscribe = data.get('subscribe_to_existing_bug')
4338.3.12 by Gavin Panella
Subscribe to selected bug and notify user.
734
8401.1.1 by Graham Binns
The default action when the user clicks "this is my bug" on the guided
735
        if bug.isUserAffected(self.user):
4338.3.12 by Gavin Panella
Subscribe to selected bug and notify user.
736
            self.request.response.addNotification(
8401.1.1 by Graham Binns
The default action when the user clicks "this is my bug" on the guided
737
                "This bug is already marked as affecting you.")
4338.3.12 by Gavin Panella
Subscribe to selected bug and notify user.
738
        else:
8401.1.1 by Graham Binns
The default action when the user clicks "this is my bug" on the guided
739
            bug.markUserAffected(self.user)
4338.3.12 by Gavin Panella
Subscribe to selected bug and notify user.
740
            self.request.response.addNotification(
8401.1.1 by Graham Binns
The default action when the user clicks "this is my bug" on the guided
741
                "This bug has been marked as affecting you.")
4338.3.12 by Gavin Panella
Subscribe to selected bug and notify user.
742
8426.8.1 by Graham Binns
Added the duplicate finder overlay. Woot, etc.
743
        # If the user wants to be subscribed, subscribe them, unless
744
        # they're already subscribed.
745
        if subscribe:
746
            if bug.isSubscribed(self.user):
747
                self.request.response.addNotification(
748
                    "You are already subscribed to this bug.")
749
            else:
750
                bug.subscribe(self.user, self.user)
751
                self.request.response.addNotification(
752
                    "You have been subscribed to this bug.")
753
4338.3.23 by Gavin Panella
Changes suggested by Tim Penhey in review.
754
        self.next_url = canonical_url(bug.bugtasks[0])
4338.3.2 by Gavin Panella
Improve duplicate list:
755
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
756
    def showFileBugForm(self):
757
        """Override this method in base classes to show the filebug form."""
758
        raise NotImplementedError
759
9947.2.8 by Graham Binns
Merged old dupefinder work.
760
    @property
10017.3.4 by Graham Binns
De-hackified the loading of the +filebug form and resetting of the dupe search URL.
761
    def inline_filebug_base_url(self):
762
        """Return the base URL for the current request.
763
764
        This allows us to build URLs in Javascript without guessing at
765
        domains.
766
        """
767
        return self.request.getRootURL(None)
768
769
    @property
9947.2.8 by Graham Binns
Merged old dupefinder work.
770
    def inline_filebug_form_url(self):
10017.3.4 by Graham Binns
De-hackified the loading of the +filebug form and resetting of the dupe search URL.
771
        """Return the URL to the inline filebug form.
9947.2.8 by Graham Binns
Merged old dupefinder work.
772
773
        If a token was passed to this view, it will be be passed through
774
        to the inline bug filing form via the returned URL.
775
        """
9947.2.21 by Graham Binns
Review changes for Gavin.
776
        url = canonical_url(self.context, view_name='+filebug-inline-form')
9947.2.8 by Graham Binns
Merged old dupefinder work.
777
        if self.extra_data_token is not None:
778
            url = urlappend(url, self.extra_data_token)
779
        return url
780
781
    @property
782
    def duplicate_search_url(self):
10017.3.4 by Graham Binns
De-hackified the loading of the +filebug form and resetting of the dupe search URL.
783
        """Return the URL to the inline duplicate search view."""
9947.2.21 by Graham Binns
Review changes for Gavin.
784
        url = canonical_url(self.context, view_name='+filebug-show-similar')
9947.2.8 by Graham Binns
Merged old dupefinder work.
785
        if self.extra_data_token is not None:
786
            url = urlappend(url, self.extra_data_token)
787
        return url
788
3691.320.2 by Bjorn Tillenius
support adding extra description to the filed bug.
789
    def publishTraverse(self, request, name):
790
        """See IBrowserPublisher."""
3691.342.1 by Bjorn Tillenius
make it possible to specify the default bug summary in the +filebug blob.
791
        if self.extra_data_token is not None:
3691.342.2 by Bjorn Tillenius
make it possible to specify the default bug summary in the +filebug blob.
792
            # publishTraverse() has already been called once before,
793
            # which means that he URL contains more path components than
794
            # expected.
3691.320.10 by Bjorn Tillenius
make publishTraverse work properly when it can't find the tokens.
795
            raise NotFound(self, name, request=request)
796
7675.547.6 by Graham Binns
FileBugData parsed from a blob by a ProcessApportBlobJob is now used during the filebug process, rather than parsing the blob in the request.
797
        self.extra_data_token = name
798
        if self.extra_data_processing_job is None:
3691.320.10 by Bjorn Tillenius
make publishTraverse work properly when it can't find the tokens.
799
            # The URL might be mistyped, or the blob has expired.
4664.1.1 by Curtis Hovey
Normalized comments for bug 3732.
800
            # XXX: Bjorn Tillenius 2006-01-15:
801
            #      We should handle this case better, since a user might
3691.320.11 by Bjorn Tillenius
raise NotFound if the token can't be found.
802
            #      come to this page when finishing his account
803
            #      registration. In that case we should inform the user
804
            #      that the blob has expired.
805
            raise NotFound(self, name, request=request)
7675.547.6 by Graham Binns
FileBugData parsed from a blob by a ProcessApportBlobJob is now used during the filebug process, rather than parsing the blob in the request.
806
        else:
807
            self.extra_data = self.extra_data_processing_job.getFileBugData()
808
3691.320.2 by Bjorn Tillenius
support adding extra description to the filed bug.
809
        return self
810
811
    def browserDefault(self, request):
812
        """See IBrowserPublisher."""
813
        return self, ()
814
3938.1.1 by kiko
Implement first cut at a better +filebug page for non-Malone-using contexts. Factor out doesn't-use-Malone code to a macro. Infrastructure: provide a fmt:link for HasGotchiAndEmblemFormatterAPI, add a helpwanted CSS message class, add a distrosourcepackages to Product, and prejoin the packaging query.
815
    def getProductOrDistroFromContext(self):
816
        """Return the product or distribution relative to the context.
3938.1.5 by kiko
Fix up comment
817
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
818
        For instance, if the context is an IDistroSeries, return the
3938.1.5 by kiko
Fix up comment
819
        distribution related to it. Will return None if the context is
820
        not related to a product or a distro.
821
        """
3938.1.10 by kiko
Fix wrapping even betterly
822
        context = self.context
823
        if IProduct.providedBy(context) or IDistribution.providedBy(context):
824
            return context
825
        elif IProductSeries.providedBy(context):
826
            return context.product
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
827
        elif (IDistroSeries.providedBy(context) or
3938.1.10 by kiko
Fix wrapping even betterly
828
              IDistributionSourcePackage.providedBy(context)):
829
            return context.distribution
3938.1.1 by kiko
Implement first cut at a better +filebug page for non-Malone-using contexts. Factor out doesn't-use-Malone code to a macro. Infrastructure: provide a fmt:link for HasGotchiAndEmblemFormatterAPI, add a helpwanted CSS message class, add a distrosourcepackages to Product, and prejoin the packaging query.
830
        else:
831
            return None
832
4338.3.25 by Gavin Panella
Revert to using showOptionalMarker after discussion with BjornT.
833
    def showOptionalMarker(self, field_name):
4338.4.6 by Gavin Panella
Add docstrings and other small changes.
834
        """See `LaunchpadFormView`."""
4338.3.25 by Gavin Panella
Revert to using showOptionalMarker after discussion with BjornT.
835
        # The comment field _is_ required, but only when filing the
836
        # bug. Since the same form is also used for subscribing to a
837
        # bug, the comment field in the schema cannot be marked
838
        # required=True. Instead it's validated in
839
        # FileBugViewBase.validate. So... we need to suppress the
840
        # "(Optional)" marker.
841
        if field_name == 'comment':
842
            return False
843
        else:
844
            return LaunchpadFormView.showOptionalMarker(self, field_name)
845
4338.4.4 by Gavin Panella
More logic to get a relevant bugtask for display, and how to cope if one cannot be found.
846
    def getRelevantBugTask(self, bug):
5723.4.5 by Gavin Panella
Use BjornT's suggested implementation; remove XXX comment.
847
        """Return the first bugtask from this bug that's relevant in the
848
        current context.
4338.4.9 by Gavin Panella
Address review comments from spiv.
849
5723.4.5 by Gavin Panella
Use BjornT's suggested implementation; remove XXX comment.
850
        This is a pragmatic function, not general purpose. It tries to
851
        find a bugtask that can be used to pretty-up the page, making
852
        it more user-friendly and informative. It's not concerned by
853
        total accuracy, and will return the first 'relevant' bugtask
854
        it finds even if there are other candidates. Be warned!
4338.4.6 by Gavin Panella
Add docstrings and other small changes.
855
        """
4338.4.4 by Gavin Panella
More logic to get a relevant bugtask for display, and how to cope if one cannot be found.
856
        context = self.context
857
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
858
        if IProjectGroup.providedBy(context):
5723.4.5 by Gavin Panella
Use BjornT's suggested implementation; remove XXX comment.
859
            contexts = set(context.products)
4338.4.4 by Gavin Panella
More logic to get a relevant bugtask for display, and how to cope if one cannot be found.
860
        else:
5723.4.5 by Gavin Panella
Use BjornT's suggested implementation; remove XXX comment.
861
            contexts = [context]
4338.4.4 by Gavin Panella
More logic to get a relevant bugtask for display, and how to cope if one cannot be found.
862
5723.4.5 by Gavin Panella
Use BjornT's suggested implementation; remove XXX comment.
863
        for bugtask in bug.bugtasks:
864
            if bugtask.target in contexts or bugtask.pillar in contexts:
4338.4.6 by Gavin Panella
Add docstrings and other small changes.
865
                return bugtask
5723.4.5 by Gavin Panella
Use BjornT's suggested implementation; remove XXX comment.
866
        return None
4338.4.4 by Gavin Panella
More logic to get a relevant bugtask for display, and how to cope if one cannot be found.
867
7251.2.11 by Gavin Panella
Move the 'append the distro guidelines to the distro source package guidelines' logic into the view.
868
    @property
7251.2.12 by Gavin Panella
Display bug reporting guidelines when bugs are reported via the bugs front page.
869
    def bugtarget(self):
870
        """The bugtarget we're currently assuming.
871
872
        The same as the context.
873
        """
874
        return self.context
875
10977.7.1 by Abel Deuring
show customized bug report acknowledgement message in notifications
876
    default_bug_reported_acknowledgement = "Thank you for your bug report."
877
878
    def getAcknowledgementMessage(self, context):
879
        """An acknowlegement message displayed to the user."""
10977.7.4 by Abel Deuring
fixed grammar bug in doc string
880
        # If a given context doesnot have a custom message, we go up in the
10977.7.1 by Abel Deuring
show customized bug report acknowledgement message in notifications
881
        # "object hierachy" until we find one. If no cusotmized messages
882
        # exist for any conext, a default message is returned.
883
        #
884
        # bug_reported_acknowledgement is defined as a "real" property
885
        # for IDistribution, IDistributionSourcePackage, IProduct and
886
        # IProjectGroup. Other IBugTarget implementations inherit this
887
        # property from their parents. For these classes, we can directly
888
        # try to find a custom message farther up in the hierarchy.
889
        message = context.bug_reported_acknowledgement
890
        if message is not None and len(message.strip()) > 0:
891
            return message
892
        next_context = None
893
        if IProductSeries.providedBy(context):
894
            # we don't need to look at
895
            # context.product.bug_reported_acknowledgement because a
896
            # product series inherits this property from the product.
897
            next_context = context.product.project
898
        elif IProduct.providedBy(context):
899
            next_context = context.project
900
        elif IDistributionSourcePackage.providedBy(context):
901
            next_context = context.distribution
902
        # IDistroseries and ISourcePackage inherit
903
        # bug_reported_acknowledgement from their IDistribution, so we
904
        # don't need to look up this property in IDistribution.
905
        # IDistribution and IProjectGroup don't have any parents.
906
        elif (IDistribution.providedBy(context) or
907
              IProjectGroup.providedBy(context) or
908
              IDistroSeries.providedBy(context) or
909
              ISourcePackage.providedBy(context)):
910
            pass
911
        else:
912
            raise TypeError("Unexpected bug target: %r" % context)
913
        if next_context is not None:
914
            return self.getAcknowledgementMessage(next_context)
915
        return self.default_bug_reported_acknowledgement
916
7675.547.6 by Graham Binns
FileBugData parsed from a blob by a ProcessApportBlobJob is now used during the filebug process, rather than parsing the blob in the request.
917
    @cachedproperty
7675.547.2 by Graham Binns
FileBugViewBase now has working extra_data_processing_job and extra_data_processed properties.
918
    def extra_data_processing_job(self):
7675.547.1 by Graham Binns
Added a _getApportBlobJobForToken() method to FileBugViewBase.
919
        """Return the ProcessApportBlobJob for a given BLOB token."""
7675.547.2 by Graham Binns
FileBugViewBase now has working extra_data_processing_job and extra_data_processed properties.
920
        if self.extra_data_token is None:
921
            # If there's no extra data token, don't bother looking for a
922
            # ProcessApportBlobJob.
923
            return None
924
925
        try:
926
            return getUtility(IProcessApportBlobJobSource).getByBlobUUID(
927
                self.extra_data_token)
928
        except SQLObjectNotFound:
929
            return None
930
931
    @property
7675.547.3 by Graham Binns
Renamed extra_data_processed -> extra_data_to_process so that it made more sense in the context of using it in templates.
932
    def extra_data_to_process(self):
933
        """Return True if there is extra data to process."""
7675.547.2 by Graham Binns
FileBugViewBase now has working extra_data_processing_job and extra_data_processed properties.
934
        apport_processing_job = self.extra_data_processing_job
7675.547.3 by Graham Binns
Renamed extra_data_processed -> extra_data_to_process so that it made more sense in the context of using it in templates.
935
        if apport_processing_job is None:
936
            return False
937
        elif apport_processing_job.job.status == JobStatus.COMPLETED:
938
            return False
939
        else:
7675.547.2 by Graham Binns
FileBugViewBase now has working extra_data_processing_job and extra_data_processed properties.
940
            return True
7675.547.1 by Graham Binns
Added a _getApportBlobJobForToken() method to FileBugViewBase.
941
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
942
9947.2.8 by Graham Binns
Merged old dupefinder work.
943
class FileBugInlineFormView(FileBugViewBase):
944
    """A browser view for displaying the inline filebug form."""
945
    schema = IBugAddForm
946
947
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
948
class FileBugAdvancedView(FileBugViewBase):
949
    """Browser view for filing a bug.
950
8971.19.14 by Graham Binns
Removed any unnecessary references to +filebug-advanced.
951
    This view exists only to redirect from +filebug-advanced to +filebug.
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
952
    """
10234.3.5 by Curtis Hovey
Quiet lint.
953
8971.19.1 by Graham Binns
Added a redirect from +filebug-advanced to +filebug.
954
    def initialize(self):
8971.19.15 by Graham Binns
Made 301 a constant.
955
        filebug_url = canonical_url(
956
            self.context, rootsite='bugs', view_name='+filebug')
9360.1.1 by Gavin Panella
Remove bugtarget-filebug-advanced.pt, and clean up the view module.
957
        self.request.response.redirect(
958
            filebug_url, status=HTTP_MOVED_PERMANENTLY)
8971.19.1 by Graham Binns
Added a redirect from +filebug-advanced to +filebug.
959
3691.249.19 by Brad Bollenbach
get +filebug-advanced working
960
8137.17.24 by Barry Warsaw
thread merge
961
class FilebugShowSimilarBugsView(FileBugViewBase):
962
    """A view for showing possible dupes for a bug.
963
964
    This view will only be used to populate asynchronously-driven parts
965
    of a page.
966
    """
8223.5.7 by Graham Binns
Removed the useless mixing and merged it with FileBugShowSimilarBugsView, which FileBugGuidedView now extends.
967
    schema = IBugAddForm
968
9947.2.8 by Graham Binns
Merged old dupefinder work.
969
    # XXX: Brad Bollenbach 2006-10-04: This assignment to actions is a
970
    # hack to make the action decorator Just Work across inheritance.
971
    actions = FileBugViewBase.actions
972
    custom_widget('title', TextWidget, displayWidth=40)
973
    custom_widget('tags', BugTagsWidget)
974
3691.249.5 by Brad Bollenbach
checkpoint
975
    _MATCHING_BUGS_LIMIT = 10
9947.2.9 by Graham Binns
The title for the async results set is now more meaningful.
976
    show_summary_in_results = False
8137.17.24 by Barry Warsaw
thread merge
977
978
    @property
10224.5.1 by Deryck Hodge
Force the +filbug-inline-form to use +filebug
979
    def action_url(self):
980
        """Return the +filebug page as the action URL.
981
982
        This enables better validation error handling,
983
        since the form is always used inline on the +filebug page.
984
        """
10224.5.2 by Deryck Hodge
Ensure the data blob is added back.
985
        url = '%s/+filebug' % canonical_url(self.context)
986
        if self.extra_data_token is not None:
987
            url = urlappend(url, self.extra_data_token)
988
        return url
10224.5.1 by Deryck Hodge
Force the +filbug-inline-form to use +filebug
989
990
    @property
8137.17.24 by Barry Warsaw
thread merge
991
    def search_context(self):
3778.2.11 by Bjorn Tillenius
clean-up.
992
        """Return the context used to search for similar bugs."""
8137.17.24 by Barry Warsaw
thread merge
993
        return self.context
994
995
    @property
996
    def search_text(self):
997
        """Return the search string entered by the user."""
998
        return self.request.get('title')
8223.5.5 by Graham Binns
Added tests to cover FileBugShowSimilarBugsView.
999
3691.249.34 by Bjorn Tillenius
make the guided filebug ui more like the support tracker's.
1000
    @cachedproperty
1001
    def similar_bugs(self):
3691.249.13 by Brad Bollenbach
move the workflow steps code into the view code
1002
        """Return the similar bugs based on the user search."""
8137.17.24 by Barry Warsaw
thread merge
1003
        title = self.search_text
3691.251.2 by Bjorn Tillenius
make sure that input errors are handled correctly. includes converting the template to use the standard form macros.
1004
        if not title:
1005
            return []
8137.17.24 by Barry Warsaw
thread merge
1006
        search_context = self.search_context
4097.1.1 by Gavin Panella
Test and initial implementation for bug-103364
1007
        if search_context is None:
1008
            return []
1009
        elif IProduct.providedBy(search_context):
3691.249.43 by Bjorn Tillenius
use IBugTaskSet.findSimilar when searching for existings bugs on +filebug
1010
            context_params = {'product': search_context}
3778.2.3 by Bjorn Tillenius
make the general +filebug page work.
1011
        elif IDistribution.providedBy(search_context):
3691.249.43 by Bjorn Tillenius
use IBugTaskSet.findSimilar when searching for existings bugs on +filebug
1012
            context_params = {'distribution': search_context}
3778.2.3 by Bjorn Tillenius
make the general +filebug page work.
1013
        else:
1014
            assert IDistributionSourcePackage.providedBy(search_context), (
1015
                    'Unknown search context: %r' % search_context)
1016
            context_params = {
1017
                'distribution': search_context.distribution,
1018
                'sourcepackagename': search_context.sourcepackagename}
1019
3691.249.43 by Bjorn Tillenius
use IBugTaskSet.findSimilar when searching for existings bugs on +filebug
1020
        matching_bugtasks = getUtility(IBugTaskSet).findSimilar(
1021
            self.user, title, **context_params)
8486.16.9 by Graham Binns
Extracted common portion of BugTask.findSimilar() and FilebugShowSimilarBugsView.similar_bugs into IBugSet.getDistinctBugsForBugTasks().
1022
        matching_bugs = getUtility(IBugSet).getDistinctBugsForBugTasks(
1023
            matching_bugtasks, self.user, self._MATCHING_BUGS_LIMIT)
3691.249.13 by Brad Bollenbach
move the workflow steps code into the view code
1024
        return matching_bugs
1025
9947.2.8 by Graham Binns
Merged old dupefinder work.
1026
    @property
1027
    def show_duplicate_list(self):
1028
        """Return whether or not to show the duplicate list.
1029
1030
        We only show the dupes if:
1031
          - The context uses Malone AND
1032
          - There are dupes to show AND
1033
          - There are no widget errors.
1034
        """
1035
        return (
1036
            self.contextUsesMalone and
1037
            len(self.similar_bugs) > 0 and
1038
            len(self.widget_errors) == 0)
1039
8137.17.24 by Barry Warsaw
thread merge
1040
1041
class FileBugGuidedView(FilebugShowSimilarBugsView):
1042
1043
    _SEARCH_FOR_DUPES = ViewPageTemplateFile(
1044
        "../templates/bugtarget-filebug-search.pt")
12811.2.13 by Ian Booth
Rework product group bug filing so that it works
1045
    _PROJECTGROUP_SEARCH_FOR_DUPES = ViewPageTemplateFile(
1046
        "../templates/projectgroup-filebug-search.pt")
8137.17.24 by Barry Warsaw
thread merge
1047
    _FILEBUG_FORM = ViewPageTemplateFile(
1048
        "../templates/bugtarget-filebug-submit-bug.pt")
1049
9947.2.8 by Graham Binns
Merged old dupefinder work.
1050
    # XXX 2009-07-17 Graham Binns
1051
    #     As above, this assignment to actions is to make sure that the
1052
    #     actions from the ancestor views are preserved, otherwise they
1053
    #     get nuked.
1054
    actions = FilebugShowSimilarBugsView.actions
8137.17.24 by Barry Warsaw
thread merge
1055
    template = _SEARCH_FOR_DUPES
1056
1057
    focused_element_id = 'field.title'
9947.2.9 by Graham Binns
The title for the async results set is now more meaningful.
1058
    show_summary_in_results = True
8137.17.24 by Barry Warsaw
thread merge
1059
10054.11.2 by Graham Binns
Moved no_ubuntu_redirect into FileBugGuidedView, since that's the place where it actually has an effect.
1060
    def initialize(self):
10054.11.3 by Graham Binns
Refactored the FileBug views to ensure that the extra data code doesn't get moved unnecessarily.
1061
        FilebugShowSimilarBugsView.initialize(self)
1062
        if self.redirect_ubuntu_filebug:
10054.11.2 by Graham Binns
Moved no_ubuntu_redirect into FileBugGuidedView, since that's the place where it actually has an effect.
1063
            # The user is trying to file a new Ubuntu bug via the web
1064
            # interface and without using apport. Redirect to a page
1065
            # explaining the preferred bug-filing procedure.
1066
            self.request.response.redirect(
1067
                config.malone.ubuntu_bug_filing_url)
1068
8137.17.24 by Barry Warsaw
thread merge
1069
    @safe_action
12811.2.17 by Ian Booth
Fix tests
1070
    @action("Continue", name="projectgroupsearch", validator="validate_search")
12811.2.13 by Ian Booth
Rework product group bug filing so that it works
1071
    def projectgroup_search_action(self, action, data):
1072
        """Search for similar bug reports."""
1073
        # Don't give focus to any widget, to ensure that the browser
1074
        # won't scroll past the "possible duplicates" list.
1075
        self.initial_focus_widget = None
1076
        return self._PROJECTGROUP_SEARCH_FOR_DUPES()
1077
1078
    @safe_action
8137.17.24 by Barry Warsaw
thread merge
1079
    @action("Continue", name="search", validator="validate_search")
1080
    def search_action(self, action, data):
1081
        """Search for similar bug reports."""
1082
        # Don't give focus to any widget, to ensure that the browser
1083
        # won't scroll past the "possible duplicates" list.
1084
        self.initial_focus_widget = None
1085
        return self.showFileBugForm()
1086
1087
    @property
1088
    def search_context(self):
1089
        """Return the context used to search for similar bugs."""
1090
        if IDistributionSourcePackage.providedBy(self.context):
1091
            return self.context
1092
1093
        search_context = self.getMainContext()
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
1094
        if IProjectGroup.providedBy(search_context):
8137.17.24 by Barry Warsaw
thread merge
1095
            assert self.widgets['product'].hasValidInput(), (
1096
                "This method should be called only when we know which"
1097
                " product the user selected.")
1098
            search_context = self.widgets['product'].getInputValue()
1099
        elif IMaloneApplication.providedBy(search_context):
1100
            if self.widgets['bugtarget'].hasValidInput():
1101
                search_context = self.widgets['bugtarget'].getInputValue()
1102
            else:
1103
                search_context = None
1104
1105
        return search_context
1106
1107
    @property
1108
    def search_text(self):
3691.249.13 by Brad Bollenbach
move the workflow steps code into the view code
1109
        """Return the search string entered by the user."""
3691.251.2 by Bjorn Tillenius
make sure that input errors are handled correctly. includes converting the template to use the standard form macros.
1110
        try:
1111
            return self.widgets['title'].getInputValue()
1112
        except InputErrors:
1113
            return None
3691.249.13 by Brad Bollenbach
move the workflow steps code into the view code
1114
3691.249.3 by Brad Bollenbach
make the dupes search form work with new formlib goo
1115
    def validate_search(self, action, data):
1116
        """Make sure some keywords are provided."""
1117
        try:
1118
            data['title'] = self.widgets['title'].getInputValue()
1119
        except InputErrors, error:
1120
            self.setFieldError("title", "A summary is required.")
1121
            return [error]
1122
1123
        # Return an empty list of errors to satisfy the validation API,
1124
        # and say "we've handled the validation and found no errors."
3778.2.3 by Bjorn Tillenius
make the general +filebug page work.
1125
        return []
3691.249.29 by Brad Bollenbach
add a basic filebug steps macro, for mpt to tweak
1126
3691.249.25 by Brad Bollenbach
add tests for the guided bug workflow
1127
    def validate_no_dupe_found(self, action, data):
1128
        return ()
3691.249.11 by Brad Bollenbach
fix some crasher issues with distro guided filebug workflow
1129
3691.249.25 by Brad Bollenbach
add tests for the guided bug workflow
1130
    @action("Continue", name="continue",
1131
            validator="validate_no_dupe_found")
1132
    def continue_action(self, action, data):
1133
        """The same action as no-dupe-found, with a different label."""
1134
        return self.showFileBugForm()
3691.249.13 by Brad Bollenbach
move the workflow steps code into the view code
1135
1136
    def showFileBugForm(self):
3691.249.11 by Brad Bollenbach
fix some crasher issues with distro guided filebug workflow
1137
        return self._FILEBUG_FORM()
1138
4338.3.20 by Gavin Panella
Prevent the (Optional) marker from appearing on the comment field.
1139
3764.1.2 by Bjorn Tillenius
make it possible to file a bug from the project page.
1140
class ProjectFileBugGuidedView(FileBugGuidedView):
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
1141
    """Guided filebug pages for IProjectGroup."""
3764.1.2 by Bjorn Tillenius
make it possible to file a bug from the project page.
1142
3764.1.6 by Bjorn Tillenius
final tweaks before review.
1143
    # Make inheriting the base class' actions work.
3764.1.2 by Bjorn Tillenius
make it possible to file a bug from the project page.
1144
    actions = FileBugGuidedView.actions
10326.1.1 by Henning Eggers
Mechanically renamed IProject* to IProjectGroup*.
1145
    schema = IProjectGroupBugAddForm
3764.1.2 by Bjorn Tillenius
make it possible to file a bug from the project page.
1146
10017.3.1 by Graham Binns
ProjectFileBugGuidedView is now ajax-enabled.
1147
    @cachedproperty
1148
    def products_using_malone(self):
1149
        return [
1150
            product for product in self.context.products
11411.7.1 by j.c.sackett
Fixed majority of official_malone calls in code-space. Still need to fix templates.
1151
            if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD]
10017.3.1 by Graham Binns
ProjectFileBugGuidedView is now ajax-enabled.
1152
1153
    @property
1154
    def default_product(self):
1155
        if len(self.products_using_malone) > 0:
1156
            return self.products_using_malone[0]
1157
        else:
1158
            return None
1159
1160
    @property
1161
    def inline_filebug_form_url(self):
10017.3.5 by Graham Binns
Added tests for the additional FileBug view links.
1162
        """Return the URL to the inline filebug form.
10017.3.1 by Graham Binns
ProjectFileBugGuidedView is now ajax-enabled.
1163
1164
        If a token was passed to this view, it will be be passed through
1165
        to the inline bug filing form via the returned URL.
1166
1167
        The URL returned will be the URL of the first of the current
10724.1.1 by Henning Eggers
First batch of Project -> ProjectGrpoup renamings.
1168
        ProjectGroup's products, since that's the product that will be
10017.3.1 by Graham Binns
ProjectFileBugGuidedView is now ajax-enabled.
1169
        selected by default when the view is rendered.
1170
        """
1171
        url = canonical_url(
1172
            self.default_product, view_name='+filebug-inline-form')
1173
        if self.extra_data_token is not None:
1174
            url = urlappend(url, self.extra_data_token)
1175
        return url
1176
1177
    @property
1178
    def duplicate_search_url(self):
10017.3.5 by Graham Binns
Added tests for the additional FileBug view links.
1179
        """Return the URL to the inline duplicate search view.
10017.3.1 by Graham Binns
ProjectFileBugGuidedView is now ajax-enabled.
1180
1181
        The URL returned will be the URL of the first of the current
10724.1.1 by Henning Eggers
First batch of Project -> ProjectGrpoup renamings.
1182
        ProjectGroup's products, since that's the product that will be
10017.3.1 by Graham Binns
ProjectFileBugGuidedView is now ajax-enabled.
1183
        selected by default when the view is rendered.
1184
        """
1185
        url = canonical_url(
1186
            self.default_product, view_name='+filebug-show-similar')
1187
        if self.extra_data_token is not None:
1188
            url = urlappend(url, self.extra_data_token)
1189
        return url
1190
3764.1.3 by Bjorn Tillenius
make the +filebug-advanced page work for IProject.
1191
3539.1.2 by Brad Bollenbach
fix failing tests
1192
class BugTargetBugListingView:
1193
    """Helper methods for rendering bug listings."""
1194
1195
    @property
9448.1.1 by Deryck Hodge
Add a portlet for links to milestone-targeted bugs.
1196
    def series_list(self):
4285.2.5 by Mark Shuttleworth
Test fixes for renamed series
1197
        if IDistribution(self.context, None):
9760.8.1 by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase.
1198
            series = self.context.series
4285.2.5 by Mark Shuttleworth
Test fixes for renamed series
1199
        elif IProduct(self.context, None):
9760.8.1 by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase.
1200
            series = self.context.series
4285.2.5 by Mark Shuttleworth
Test fixes for renamed series
1201
        elif IDistroSeries(self.context, None):
9760.8.1 by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase.
1202
            series = self.context.distribution.series
4285.2.5 by Mark Shuttleworth
Test fixes for renamed series
1203
        elif IProductSeries(self.context, None):
9760.8.1 by Brad Crittenden
Change the non-English 'serieses' to 'series' throughout our codebase.
1204
            series = self.context.product.series
3539.1.2 by Brad Bollenbach
fix failing tests
1205
        else:
9448.1.5 by Deryck Hodge
Stylistic updates after review.
1206
            raise AssertionError("series_list called with illegal context")
12392.4.4 by Robert Collins
Use aggregation rather that looping in python to determine series bug counts in bug searches.
1207
        return list(series)
9448.1.1 by Deryck Hodge
Add a portlet for links to milestone-targeted bugs.
1208
1209
    @property
12415.6.1 by Robert Collins
refactor out the milestones list.
1210
    def milestones_list(self):
12415.6.2 by Robert Collins
Avoid late evaluation of milestones per series - instead use the same context the series are coming from to generate one set of milestones.
1211
        if IDistribution(self.context, None):
1212
            milestone_resultset = self.context.milestones
1213
        elif IProduct(self.context, None):
1214
            milestone_resultset = self.context.milestones
1215
        elif IDistroSeries(self.context, None):
1216
            milestone_resultset = self.context.distribution.milestones
1217
        elif IProductSeries(self.context, None):
1218
            milestone_resultset = self.context.product.milestones
1219
        else:
1220
            raise AssertionError("series_list called with illegal context")
1221
        return list(milestone_resultset)
12415.6.1 by Robert Collins
refactor out the milestones list.
1222
1223
    @property
9448.1.1 by Deryck Hodge
Add a portlet for links to milestone-targeted bugs.
1224
    def series_buglistings(self):
1225
        """Return a buglisting for each series.
1226
1227
        The list is sorted newest series to oldest.
1228
1229
        The count only considers bugs that the user would actually be
1230
        able to see in a listing.
1231
        """
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
1232
        series_buglistings = []
12392.4.4 by Robert Collins
Use aggregation rather that looping in python to determine series bug counts in bug searches.
1233
        bug_task_set = getUtility(IBugTaskSet)
12415.4.2 by Robert Collins
Fix defect where we did searches that could not match anything and tripped a setTarget assertion.
1234
        series_list = self.series_list
1235
        if not series_list:
1236
            return series_buglistings
12392.4.4 by Robert Collins
Use aggregation rather that looping in python to determine series bug counts in bug searches.
1237
        open_bugs = bug_task_set.open_bugtask_search
12415.4.2 by Robert Collins
Fix defect where we did searches that could not match anything and tripped a setTarget assertion.
1238
        open_bugs.setTarget(any(*series_list))
12392.4.4 by Robert Collins
Use aggregation rather that looping in python to determine series bug counts in bug searches.
1239
        # This would be better as delegation not a case statement.
1240
        if IDistribution(self.context, None):
1241
            backlink = BugTask.distroseriesID
1242
        elif IProduct(self.context, None):
1243
            backlink = BugTask.productseriesID
1244
        elif IDistroSeries(self.context, None):
1245
            backlink = BugTask.distroseriesID
1246
        elif IProductSeries(self.context, None):
1247
            backlink = BugTask.productseriesID
1248
        else:
1249
            raise AssertionError("illegal context %r" % self.context)
1250
        counts = bug_task_set.countBugs(open_bugs, (backlink,))
12415.4.2 by Robert Collins
Fix defect where we did searches that could not match anything and tripped a setTarget assertion.
1251
        for series in series_list:
12392.4.4 by Robert Collins
Use aggregation rather that looping in python to determine series bug counts in bug searches.
1252
            series_bug_count = counts.get((series.id,), 0)
9122.3.1 by Deryck Hodge
Don't show links to series bugs on the project bugs home if the
1253
            if series_bug_count > 0:
1254
                series_buglistings.append(
1255
                    dict(
1256
                        title=series.name,
1257
                        url=canonical_url(series) + "/+bugs",
9448.1.5 by Deryck Hodge
Stylistic updates after review.
1258
                        count=series_bug_count,
1259
                        ))
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
1260
        return series_buglistings
3691.40.12 by Bjorn Tillenius
add a tags portlet to buglistings.
1261
9448.1.1 by Deryck Hodge
Add a portlet for links to milestone-targeted bugs.
1262
    @property
1263
    def milestone_buglistings(self):
1264
        """Return a buglisting for each milestone."""
1265
        milestone_buglistings = []
12415.4.1 by Robert Collins
Also do milestone bug listings with one pass over the bugtasks table. Drive by to handle conjoined bugtasks properly if someone needs that in countBugs.
1266
        bug_task_set = getUtility(IBugTaskSet)
12415.6.1 by Robert Collins
refactor out the milestones list.
1267
        milestones = self.milestones_list
12415.4.2 by Robert Collins
Fix defect where we did searches that could not match anything and tripped a setTarget assertion.
1268
        if not milestones:
1269
            return milestone_buglistings
12415.4.1 by Robert Collins
Also do milestone bug listings with one pass over the bugtasks table. Drive by to handle conjoined bugtasks properly if someone needs that in countBugs.
1270
        open_bugs = bug_task_set.open_bugtask_search
1271
        open_bugs.setTarget(any(*milestones))
1272
        counts = bug_task_set.countBugs(open_bugs, (BugTask.milestoneID,))
1273
        for milestone in milestones:
1274
            milestone_bug_count = counts.get((milestone.id,), 0)
1275
            if milestone_bug_count > 0:
1276
                milestone_buglistings.append(
1277
                    dict(
1278
                        title=milestone.name,
1279
                        url=canonical_url(milestone),
1280
                        count=milestone_bug_count,
1281
                        ))
9448.1.1 by Deryck Hodge
Add a portlet for links to milestone-targeted bugs.
1282
        return milestone_buglistings
1283
3691.40.12 by Bjorn Tillenius
add a tags portlet to buglistings.
1284
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
1285
class BugCountDataItem:
3789.2.12 by Bjorn Tillenius
clean-up
1286
    """Data about bug count for a status."""
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
1287
1288
    def __init__(self, label, count, color):
1289
        self.label = label
1290
        self.count = count
1291
        if color.startswith('#'):
1292
            self.color = 'MochiKit.Color.Color.fromHexString("%s")' % color
1293
        else:
1294
            self.color = 'MochiKit.Color.Color["%sColor"]()' % color
1295
3789.2.8 by Bjorn Tillenius
add a pie chart to the distrorelease Bugs page.
1296
5309.1.2 by Edwin Grubbs
Created mixin to include feeds link
1297
class BugTargetBugsView(BugTaskSearchListingView, FeedsMixin):
3789.2.12 by Bjorn Tillenius
clean-up
1298
    """View for the Bugs front page."""
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
1299
8919.4.1 by Graham Binns
Expanded and centred the search box on the BugTargetBugsView.
1300
    # We have a custom searchtext widget here so that we can set the
1301
    # width of the search box properly.
1302
    custom_widget('searchtext', NewLineToSpacesWidget, displayWidth=36)
1303
5429.1.2 by Edwin Grubbs
Changed templates and HasAnnouncementsView to use the FeedsMixin
1304
    # Only include <link> tags for bug feeds when using this view.
1305
    feed_types = (
1306
        BugFeedLink,
1307
        BugTargetLatestBugsFeedLink,
1308
        )
1309
4664.1.1 by Curtis Hovey
Normalized comments for bug 3732.
1310
    # XXX: Bjorn Tillenius 2007-02-13:
1311
    #      These colors should be changed. It's the same colors that are used
3789.2.12 by Bjorn Tillenius
clean-up
1312
    #      to color statuses in buglistings using CSS, but there should be one
1313
    #      unique color for each status in the pie chart
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
1314
    status_color = {
4318.3.3 by Gavin Panella
First round of status renames throughout the tree.
1315
        BugTaskStatus.NEW: '#993300',
1316
        BugTaskStatus.INCOMPLETE: 'red',
3789.2.14 by Bjorn Tillenius
choose some better colors.
1317
        BugTaskStatus.CONFIRMED: 'orange',
4450.2.1 by Bjorn Tillenius
include Triaged in the pie chart.
1318
        BugTaskStatus.TRIAGED: 'black',
3789.2.14 by Bjorn Tillenius
choose some better colors.
1319
        BugTaskStatus.INPROGRESS: 'blue',
1320
        BugTaskStatus.FIXCOMMITTED: 'green',
1321
        BugTaskStatus.FIXRELEASED: 'magenta',
4318.3.3 by Gavin Panella
First round of status renames throughout the tree.
1322
        BugTaskStatus.INVALID: 'yellow',
3789.2.14 by Bjorn Tillenius
choose some better colors.
1323
        BugTaskStatus.UNKNOWN: 'purple',
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
1324
    }
1325
9389.10.15 by Tom Berger
override the title
1326
    override_title_breadcrumbs = True
1327
9389.10.11 by Tom Berger
correct placement of breadcrumbs and title
1328
    @property
1329
    def label(self):
1330
        """The display label for the view."""
1331
        return 'Bugs in %s' % self.context.title
1332
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
1333
    def initialize(self):
12393.30.17 by Brad Crittenden
Merge from yellow + use expose_structural_subscription_data_to_js instead of new JSMixin
1334
        super(BugTargetBugsView, self).initialize()
4450.2.1 by Bjorn Tillenius
include Triaged in the pie chart.
1335
        bug_statuses_to_show = list(UNRESOLVED_BUGTASK_STATUSES)
4285.2.1 by Mark Shuttleworth
Massive renaming of distrorelease to distroseries
1336
        if IDistroSeries.providedBy(self.context):
3789.2.12 by Bjorn Tillenius
clean-up
1337
            bug_statuses_to_show.append(BugTaskStatus.FIXRELEASED)
12393.30.1 by Brad Crittenden
Add structural subscription link to all IStructuralSubscriptionTargets
1338
        expose_structural_subscription_data_to_js(
1339
            self.context, self.request, self.user)
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
1340
11462.2.3 by Edwin Grubbs
Fixed some tests.
1341
    @property
11462.2.12 by Edwin Grubbs
Made changes for UI reviews.
1342
    def can_have_external_bugtracker(self):
1343
        return (IProduct.providedBy(self.context)
1344
                or IProductSeries.providedBy(self.context))
1345
1346
    @property
11462.2.3 by Edwin Grubbs
Fixed some tests.
1347
    def bug_tracking_usage(self):
1348
        """Whether the context tracks bugs in launchpad.
1349
1350
        :returns: ServiceUsage enum value
1351
        """
1352
        service_usage = IServiceUsage(self.context)
1353
        return service_usage.bug_tracking_usage
1354
1355
    @property
6649.1.7 by Edwin Grubbs
Implemented reviewers suggestions
1356
    def bugtracker(self):
1357
        """Description of the context's bugtracker.
1358
1359
        :returns: str which may contain HTML.
1360
        """
11462.2.3 by Edwin Grubbs
Fixed some tests.
1361
        if self.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
6649.1.7 by Edwin Grubbs
Implemented reviewers suggestions
1362
            return 'Launchpad'
1363
        elif self.external_bugtracker:
7119.1.5 by Guilherme Salgado
Fix a couple tests
1364
            return BugTrackerFormatterAPI(self.external_bugtracker).link(None)
6649.1.7 by Edwin Grubbs
Implemented reviewers suggestions
1365
        else:
1366
            return 'None specified'
1367
10230.1.13 by Deryck Hodge
Updates after review, to make the code a bit more
1368
    @cachedproperty
1369
    def hot_bugs_info(self):
10230.1.12 by Deryck Hodge
Add a better doc string.
1370
        """Return a dict of the 10 hottest tasks and a has_more_bugs flag."""
10230.1.5 by Deryck Hodge
Add a link to all hot bugs from the bugs home.
1371
        has_more_bugs = False
10230.1.1 by Deryck Hodge
Bring back the move from hot_bugtasks to hot_bugs,
1372
        params = BugTaskSearchParams(
7675.1086.2 by Robert Collins
Change hottest bugs query to trigger the use of the new bugtask indices.
1373
            orderby=['-heat', 'task'], omit_dupes=True,
10230.1.1 by Deryck Hodge
Bring back the move from hot_bugtasks to hot_bugs,
1374
            user=self.user, status=any(*UNRESOLVED_BUGTASK_STATUSES))
1375
        # Use 4x as many tasks as bugs that are needed to improve performance.
1376
        bugtasks = self.context.searchTasks(params)[:40]
10230.1.13 by Deryck Hodge
Updates after review, to make the code a bit more
1377
        hot_bugtasks = []
10230.1.1 by Deryck Hodge
Bring back the move from hot_bugtasks to hot_bugs,
1378
        hot_bugs = []
1379
        for task in bugtasks:
10230.1.13 by Deryck Hodge
Updates after review, to make the code a bit more
1380
            # Use hot_bugs list to ensure a bug is only listed once.
1381
            if task.bug not in hot_bugs:
1382
                if len(hot_bugtasks) < 10:
1383
                    hot_bugtasks.append(task)
1384
                    hot_bugs.append(task.bug)
10230.1.1 by Deryck Hodge
Bring back the move from hot_bugtasks to hot_bugs,
1385
                else:
10230.1.5 by Deryck Hodge
Add a link to all hot bugs from the bugs home.
1386
                    has_more_bugs = True
10230.1.1 by Deryck Hodge
Bring back the move from hot_bugtasks to hot_bugs,
1387
                    break
10230.1.13 by Deryck Hodge
Updates after review, to make the code a bit more
1388
        return {'has_more_bugs': has_more_bugs, 'bugtasks': hot_bugtasks}
10230.1.1 by Deryck Hodge
Bring back the move from hot_bugtasks to hot_bugs,
1389
3789.2.7 by Bjorn Tillenius
add a pie chart to the distributin Bugs page.
1390
3691.40.12 by Bjorn Tillenius
add a tags portlet to buglistings.
1391
class BugTargetBugTagsView(LaunchpadView):
1392
    """Helper methods for rendering the bug tags portlet."""
1393
1394
    def _getSearchURL(self, tag):
1395
        """Return the search URL for the tag."""
3973.1.96 by Steve Alexander
make bug and bugtask canonical URLs be on the bugs site. change fmt:url to use links relative to the current site where possible. make links to bug tag search pages not include the host, all in the name of saving a lot of rendered characters on large search results pages.
1396
        # Use path_only here to reduce the size of the rendered page.
4758.1.1 by Bjorn Tillenius
make the bug tags portlet work on the Bugs page for a product.
1397
        return "+bugs?field.tag=%s" % urllib.quote(tag)
3691.40.12 by Bjorn Tillenius
add a tags portlet to buglistings.
1398
12775.3.10 by William Grant
Extract factor calculation, turn for loop into list comprehension.
1399
    def _calculateFactor(self, tag, count, max_count, official_tags):
1400
        bonus = 1.5 if tag in official_tags else 1
1401
        return (count / max_count) + bonus
1402
7968.4.2 by Abel Deuring
provide a link for editing official bug tags
1403
    @property
9389.10.3 by Tom Berger
interim commit
1404
    def tags_cloud_data(self):
1405
        """The data for rendering a tags cloud"""
12775.3.8 by William Grant
Fix comment typo.
1406
        official_tags = self.context.official_bug_tags
1407
1408
        # Construct a dict of official and top 10 tags.
1409
        # getUsedBugTagsWithOpenCounts is expensive, so do the union in
1410
        # SQL. Also preseed with 0 for all the official tags, as gUBTWOC
1411
        # won't return unused ones.
1412
        top_ten = removeSecurityProxy(
1413
            self.context.getUsedBugTagsWithOpenCounts(self.user)[:10])
1414
        official = removeSecurityProxy(
1415
            self.context.getUsedBugTagsWithOpenCounts(
1416
                self.user, official_tags))
12775.3.11 by William Grant
Remove tags local, rename raw_tags to tags.
1417
        tags = dict((tag, 0) for tag in official_tags)
1418
        tags.update(dict(top_ten.union(official)))
1419
1420
        max_count = float(max([1] + tags.values()))
1421
1422
        return sorted(
1423
            [dict(
12775.3.10 by William Grant
Extract factor calculation, turn for loop into list comprehension.
1424
                tag=tag,
1425
                factor=self._calculateFactor(
1426
                    tag, count, max_count, official_tags),
1427
                url=self._getSearchURL(tag),
1428
                )
12775.3.11 by William Grant
Remove tags local, rename raw_tags to tags.
1429
            for (tag, count) in tags.iteritems()],
1430
            key=itemgetter('tag'))
9389.10.3 by Tom Berger
interim commit
1431
1432
    @property
7968.4.2 by Abel Deuring
provide a link for editing official bug tags
1433
    def show_manage_tags_link(self):
7968.4.3 by Abel Deuring
test for LargeBugTagsWidget added
1434
        """Should a link to a "manage official tags" page be shown?"""
7968.4.2 by Abel Deuring
provide a link for editing official bug tags
1435
        return (IOfficialBugTagTargetRestricted.providedBy(self.context) and
11587.4.3 by Brian Murray
allow BugSupervisor to actually set the official_bug_tags
1436
                check_permission('launchpad.BugSupervisor', self.context))
7968.4.2 by Abel Deuring
provide a link for editing official bug tags
1437
7968.4.1 by Abel Deuring
non-js version of editing official bug tags
1438
1439
class OfficialBugTagsManageView(LaunchpadEditFormView):
1440
    """View class for management of official bug tags."""
1441
1442
    schema = IOfficialBugTagTargetPublic
1443
    custom_widget('official_bug_tags', LargeBugTagsWidget)
1444
9332.2.1 by Deryck Hodge
Update the page for managing tags for 3.0 UI.
1445
    @property
1446
    def label(self):
1447
        """The form label."""
1448
        return 'Manage official bug tags for %s' % self.context.title
1449
1450
    @property
1451
    def page_title(self):
1452
        """The page title."""
1453
        return self.label
1454
7968.4.1 by Abel Deuring
non-js version of editing official bug tags
1455
    @action('Save', name='save')
1456
    def save_action(self, action, data):
1457
        """Action for saving new official bug tags."""
1458
        self.context.official_bug_tags = data['official_bug_tags']
1459
        self.next_url = canonical_url(self.context)
7968.6.1 by Abel Deuring
parital JS code for the Official Bug Tags management page
1460
1461
    @property
1462
    def tags_js_data(self):
1463
        """Return the JSON representation of the bug tags."""
7968.6.11 by Tom Berger
enable/disbale buttons and other ui improvements
1464
        used_tags = dict(self.context.getUsedBugTagsWithOpenCounts(self.user))
1465
        official_tags = list(self.context.official_bug_tags)
7968.6.1 by Abel Deuring
parital JS code for the Official Bug Tags management page
1466
        return """<script type="text/javascript">
1467
                      var used_bug_tags = %s;
1468
                      var official_bug_tags = %s;
7968.6.7 by Tom Berger
it works
1469
                      var valid_name_pattern = %s;
7968.6.1 by Abel Deuring
parital JS code for the Official Bug Tags management page
1470
                  </script>
7968.6.7 by Tom Berger
it works
1471
               """ % (
7968.6.11 by Tom Berger
enable/disbale buttons and other ui improvements
1472
               dumps(used_tags),
1473
               dumps(official_tags),
7968.6.14 by Tom Berger
change the background colour of selected items
1474
               dumps(valid_name_pattern.pattern))
7968.6.10 by Tom Berger
merge changes from rocketfuel and resolve conflicts
1475
7968.6.11 by Tom Berger
enable/disbale buttons and other ui improvements
1476
    @property
7968.4.6 by Abel Deuring
fixed some UI issues
1477
    def cancel_url(self):
1478
        """The URL the user is sent to when clicking the "cancel" link."""
1479
        return canonical_url(self.context)
7968.6.10 by Tom Berger
merge changes from rocketfuel and resolve conflicts
1480
9087.4.9 by Guilherme Salgado
Fix a couple existing tests and tweak my implementation to not break others
1481
9565.2.1 by Gary Poster
fix vhost breadcrumbs so that we do not duplicate information
1482
class BugsVHostBreadcrumb(Breadcrumb):
9087.4.9 by Guilherme Salgado
Fix a couple existing tests and tweak my implementation to not break others
1483
    rootsite = 'bugs'
9565.2.1 by Gary Poster
fix vhost breadcrumbs so that we do not duplicate information
1484
    text = 'Bugs'
10137.7.1 by Karl Fogel
Start on bug #506018 ("implement +patches view").
1485
1486
1487
class BugsPatchesView(LaunchpadView):
1488
    """View list of patch attachments associated with bugs."""
1489
1490
    @property
1491
    def label(self):
1492
        """The display label for the view."""
10137.10.2 by Gavin Panella
When the context is an IPerson, fix the silly title wording and show the package in the results table.
1493
        if IPerson.providedBy(self.context):
1494
            return 'Patch attachments for %s' % self.context.displayname
1495
        else:
1496
            return 'Patch attachments in %s' % self.context.displayname
10137.7.9 by Karl Fogel
With Abel, use the view instead of the model to prepare data for display.
1497
7675.549.1 by Karl Fogel
Start on bug #515584 (use Zope form for patches view task orderings).
1498
    @property
7675.549.4 by Karl Fogel
Fixes in response to intellectronica's review.
1499
    def patch_task_orderings(self):
7675.549.1 by Karl Fogel
Start on bug #515584 (use Zope form for patches view task orderings).
1500
        """The list of possible sort orderings for the patches view.
1501
1502
        The orderings are a list of tuples of the form:
1503
          [(DisplayName, InternalOrderingName), ...]
1504
        For example:
1505
          [("Patch age", "-latest_patch_uploaded"),
1506
           ("Importance", "-importance"),
1507
           ...]
1508
        """
7675.549.4 by Karl Fogel
Fixes in response to intellectronica's review.
1509
        orderings = [("patch age", "-latest_patch_uploaded"),
1510
                     ("importance", "-importance"),
1511
                     ("status", "status"),
1512
                     ("oldest first", "datecreated"),
1513
                     ("newest first", "-datecreated")]
7675.549.7 by Karl Fogel
Fix a regression from the previous change.
1514
        targetname = self.targetName()
1515
        if targetname is not None:
1516
            # Lower case for consistency with the other orderings.
1517
            orderings.append((targetname.lower(), "targetname"))
7675.549.1 by Karl Fogel
Start on bug #515584 (use Zope form for patches view task orderings).
1518
        return orderings
1519
10137.7.11 by Karl Fogel
With Abel, implement pagination on the result set.
1520
    def batchedPatchTasks(self):
1521
        """Return a BatchNavigator for bug tasks with patch attachments."""
7675.513.1 by Tom Berger
Make it possible to sort by patch age. Also fix validation and test it.
1522
        orderby = self.request.get("orderby", "-latest_patch_uploaded")
7675.549.4 by Karl Fogel
Fixes in response to intellectronica's review.
1523
        if orderby not in [x[1] for x in self.patch_task_orderings]:
10137.7.53 by Karl Fogel
Various small fixups, after suggestions by Michael Hudson.
1524
            raise UnexpectedFormData(
1525
                "Unexpected value for field 'orderby': '%s'" % orderby)
10137.7.11 by Karl Fogel
With Abel, implement pagination on the result set.
1526
        return BatchNavigator(
10137.7.43 by Karl Fogel
Incorporate some more review feedback from beuno and intellectronic:
1527
            self.context.searchTasks(
1528
                None, user=self.user, order_by=orderby,
10480.1.1 by Karl Fogel
Fix bug #532022 ("don't show Fix Released bugs in patches view"):
1529
                status=UNRESOLVED_BUGTASK_STATUSES,
10137.7.43 by Karl Fogel
Incorporate some more review feedback from beuno and intellectronic:
1530
                omit_duplicates=True, has_patch=True),
10137.7.11 by Karl Fogel
With Abel, implement pagination on the result set.
1531
            self.request)
10137.10.2 by Gavin Panella
When the context is an IPerson, fix the silly title wording and show the package in the results table.
1532
10137.7.55 by Karl Fogel
In patches view, make a column name dynamic on package vs project.
1533
    def targetName(self):
1534
        """Return the name of the current context's target type, or None.
1535
1536
        The name is something like "Package" or "Project" (meaning
1537
        Product); it is intended to be appropriate to use as a column
1538
        name in a web page, for example.  If no target type is
1539
        appropriate for the current context, then return None.
1540
        """
1541
        if (IDistribution.providedBy(self.context) or
1542
            IDistroSeries.providedBy(self.context)):
1543
            return "Package"
7675.537.1 by Henning Eggers
Found one last IProject.
1544
        elif (IProjectGroup.providedBy(self.context) or
7675.512.9 by Karl Fogel
Merge from lp:~allenap/launchpad/patch-report-for-people-and-teams-bug-506018
1545
              IPerson.providedBy(self.context)):
1546
            # In the case of an IPerson, the target column can vary
1547
            # row-by-row, showing both packages and products.  We
1548
            # decided to go with the table header "Project" for both,
1549
            # as its meaning is broad and could conceivably cover
1550
            # packages too.  We also considered "Target", but rejected
1551
            # it because it's used as a verb elsewhere in Launchpad's
1552
            # UI, with a totally different meaning.  If anyone can
1553
            # think of a better term than "Project", please JFDI here.
1554
            return "Project"  # "Project" meaning Product, of course
10137.7.55 by Karl Fogel
In patches view, make a column name dynamic on package vs project.
1555
        else:
1556
            return None
10137.7.9 by Karl Fogel
With Abel, use the view instead of the model to prepare data for display.
1557
10137.7.43 by Karl Fogel
Incorporate some more review feedback from beuno and intellectronic:
1558
    def patchAge(self, patch):
10137.7.9 by Karl Fogel
With Abel, use the view instead of the model to prepare data for display.
1559
        """Return a timedelta object for the age of a patch attachment."""
1560
        now = datetime.now(timezone('UTC'))
1561
        return now - patch.message.datecreated
11235.8.5 by Abel Deuring
change the bug attachment download URL shown in bug-portlet-attachments.py; fix failures in test_bugchanges.txt
1562
1563
    def proxiedUrlForLibraryFile(self, patch):
1564
        """Return the proxied download URL for a Librarian file."""
1565
        return ProxiedLibraryFileAlias(patch.libraryfile, patch).http_url
12584.1.1 by Gary Poster
add a simple page to start building JS on
1566
1567
1568
class TargetSubscriptionView(LaunchpadView):
1569
    """A view to show all a person's structural subscriptions to a target."""
1570
12636.1.2 by Gary Poster
add missing test file from ~yellow branch; tweak views to use initialize
1571
    def initialize(self):
1572
        super(TargetSubscriptionView, self).initialize()
12393.30.17 by Brad Crittenden
Merge from yellow + use expose_structural_subscription_data_to_js instead of new JSMixin
1573
        expose_structural_subscription_data_to_js(
1574
            self.context, self.request, self.user, self.subscriptions)
1575
12636.1.8 by Gary Poster
respond to review
1576
    @property
1577
    def subscriptions(self):
1578
        return get_structural_subscriptions_for_target(
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
1579
            self.context, self.user)
1580
12584.1.1 by Gary Poster
add a simple page to start building JS on
1581
    @property
1582
    def label(self):
1583
        return "Your subscriptions to %s" % (self.context.displayname,)
1584
1585
    page_title = label