~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/browser/bugtarget.py

  • Committer: Curtis Hovey
  • Date: 2011-08-21 14:21:06 UTC
  • mto: This revision was merged to the branch mainline in revision 13745.
  • Revision ID: curtis.hovey@canonical.com-20110821142106-x93hajd6iguma8gx
Update test that was enforcing bad grammar.

Show diffs side-by-side

added added

removed removed

Lines of Context:
10
10
    "BugsPatchesView",
11
11
    "BugTargetBugListingView",
12
12
    "BugTargetBugTagsView",
 
13
    "BugTargetBugsView",
13
14
    "FileBugAdvancedView",
14
15
    "FileBugGuidedView",
15
16
    "FileBugViewBase",
24
25
from cStringIO import StringIO
25
26
from datetime import datetime
26
27
from functools import partial
 
28
from operator import itemgetter
27
29
import httplib
28
 
from operator import itemgetter
29
30
import urllib
30
31
from urlparse import urljoin
31
32
 
55
56
 
56
57
from canonical.config import config
57
58
from canonical.launchpad import _
 
59
from canonical.launchpad.browser.feeds import (
 
60
    BugFeedLink,
 
61
    BugTargetLatestBugsFeedLink,
 
62
    FeedsMixin,
 
63
    )
 
64
from canonical.launchpad.browser.librarian import ProxiedLibraryFileAlias
 
65
from canonical.launchpad.searchbuilder import any
58
66
from canonical.launchpad.webapp import (
59
67
    canonical_url,
60
68
    LaunchpadView,
73
81
    safe_action,
74
82
    )
75
83
from lp.app.browser.stringformatter import FormattersAPI
 
84
from lp.app.browser.tales import BugTrackerFormatterAPI
76
85
from lp.app.enums import ServiceUsage
77
86
from lp.app.errors import (
78
87
    NotFoundError,
81
90
from lp.app.interfaces.launchpad import (
82
91
    ILaunchpadCelebrities,
83
92
    ILaunchpadUsage,
 
93
    IServiceUsage,
84
94
    )
85
95
from lp.app.validators.name import valid_name_pattern
86
96
from lp.app.widgets.product import (
89
99
    ProductBugTrackerWidget,
90
100
    )
91
101
from lp.bugs.browser.bugrole import BugRoleMixin
 
102
from lp.bugs.browser.bugtask import BugTaskSearchListingView
92
103
from lp.bugs.browser.structuralsubscription import (
93
104
    expose_structural_subscription_data_to_js,
94
105
    )
96
107
    BugTagsWidget,
97
108
    LargeBugTagsWidget,
98
109
    )
 
110
from lp.bugs.browser.widgets.bugtask import NewLineToSpacesWidget
99
111
from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
100
112
from lp.bugs.interfaces.bug import (
101
113
    CreateBugParams,
111
123
    IOfficialBugTagTargetRestricted,
112
124
    )
113
125
from lp.bugs.interfaces.bugtask import (
 
126
    BugTaskSearchParams,
 
127
    BugTaskStatus,
114
128
    IBugTaskSet,
115
129
    UNRESOLVED_BUGTASK_STATUSES,
116
130
    )
137
151
from lp.registry.interfaces.sourcepackage import ISourcePackage
138
152
from lp.registry.vocabularies import ValidPersonOrTeamVocabulary
139
153
from lp.services.job.interfaces.job import JobStatus
140
 
from lp.services.librarian.browser import ProxiedLibraryFileAlias
141
154
from lp.services.propertycache import cachedproperty
142
155
 
143
156
# A simple vocabulary for the subscribe_to_existing_bug form field.
390
403
        # fields since they will initially be hidden and later exposed if the
391
404
        # selected project supports them.
392
405
        include_extra_fields = IProjectGroup.providedBy(context)
393
 
        if not include_extra_fields:
394
 
            include_extra_fields = (
395
 
                BugTask.userHasBugSupervisorPrivilegesContext(
396
 
                    context, self.user))
 
406
        if not include_extra_fields and IHasBugSupervisor.providedBy(context):
 
407
            include_extra_fields = self.user.inTeam(context.bug_supervisor)
397
408
 
398
409
        if include_extra_fields:
399
410
            field_names.extend(
616
627
        if extra_data.private:
617
628
            params.private = extra_data.private
618
629
 
619
 
        # Apply any extra options given by privileged users.
620
 
        if BugTask.userHasBugSupervisorPrivilegesContext(context, self.user):
621
 
            if 'assignee' in data:
622
 
                params.assignee = data['assignee']
623
 
            if 'status' in data:
624
 
                params.status = data['status']
625
 
            if 'importance' in data:
626
 
                params.importance = data['importance']
627
 
            if 'milestone' in data:
628
 
                params.milestone = data['milestone']
 
630
        # Apply any extra options given by a bug supervisor.
 
631
        if IHasBugSupervisor.providedBy(context):
 
632
            if self.user.inTeam(context.bug_supervisor):
 
633
                if 'assignee' in data:
 
634
                    params.assignee = data['assignee']
 
635
                if 'status' in data:
 
636
                    params.status = data['status']
 
637
                if 'importance' in data:
 
638
                    params.importance = data['importance']
 
639
                if 'milestone' in data:
 
640
                    params.milestone = data['milestone']
629
641
 
630
642
        self.added_bug = bug = context.createBug(params)
631
643
 
1061
1073
            self.request.response.redirect(
1062
1074
                config.malone.ubuntu_bug_filing_url)
1063
1075
 
1064
 
    @property
1065
 
    def page_title(self):
1066
 
        if IMaloneApplication.providedBy(self.context):
1067
 
            return 'Report a bug'
1068
 
        else:
1069
 
            return 'Report a bug about %s' % self.context.title
1070
 
 
1071
1076
    @safe_action
1072
1077
    @action("Continue", name="projectgroupsearch",
1073
1078
            validator="validate_search")
1220
1225
        elif IProduct(self.context, None):
1221
1226
            milestone_resultset = self.context.milestones
1222
1227
        else:
1223
 
            raise AssertionError(
1224
 
                "milestones_list called with illegal context")
 
1228
            raise AssertionError("milestones_list called with illegal context")
1225
1229
        return list(milestone_resultset)
1226
1230
 
1227
1231
    @property
1307
1311
            self.color = 'MochiKit.Color.Color["%sColor"]()' % color
1308
1312
 
1309
1313
 
 
1314
class BugTargetBugsView(BugTaskSearchListingView, FeedsMixin):
 
1315
    """View for the Bugs front page."""
 
1316
 
 
1317
    # We have a custom searchtext widget here so that we can set the
 
1318
    # width of the search box properly.
 
1319
    custom_widget('searchtext', NewLineToSpacesWidget, displayWidth=36)
 
1320
 
 
1321
    # Only include <link> tags for bug feeds when using this view.
 
1322
    feed_types = (
 
1323
        BugFeedLink,
 
1324
        BugTargetLatestBugsFeedLink,
 
1325
        )
 
1326
 
 
1327
    # XXX: Bjorn Tillenius 2007-02-13:
 
1328
    #      These colors should be changed. It's the same colors that are used
 
1329
    #      to color statuses in buglistings using CSS, but there should be one
 
1330
    #      unique color for each status in the pie chart
 
1331
    status_color = {
 
1332
        BugTaskStatus.NEW: '#993300',
 
1333
        BugTaskStatus.INCOMPLETE: 'red',
 
1334
        BugTaskStatus.CONFIRMED: 'orange',
 
1335
        BugTaskStatus.TRIAGED: 'black',
 
1336
        BugTaskStatus.INPROGRESS: 'blue',
 
1337
        BugTaskStatus.FIXCOMMITTED: 'green',
 
1338
        BugTaskStatus.FIXRELEASED: 'magenta',
 
1339
        BugTaskStatus.INVALID: 'yellow',
 
1340
        BugTaskStatus.UNKNOWN: 'purple',
 
1341
    }
 
1342
 
 
1343
    override_title_breadcrumbs = True
 
1344
 
 
1345
    @property
 
1346
    def label(self):
 
1347
        """The display label for the view."""
 
1348
        return 'Bugs in %s' % self.context.title
 
1349
 
 
1350
    def initialize(self):
 
1351
        super(BugTargetBugsView, self).initialize()
 
1352
        bug_statuses_to_show = list(UNRESOLVED_BUGTASK_STATUSES)
 
1353
        if IDistroSeries.providedBy(self.context):
 
1354
            bug_statuses_to_show.append(BugTaskStatus.FIXRELEASED)
 
1355
        expose_structural_subscription_data_to_js(
 
1356
            self.context, self.request, self.user)
 
1357
 
 
1358
    @property
 
1359
    def can_have_external_bugtracker(self):
 
1360
        return (IProduct.providedBy(self.context)
 
1361
                or IProductSeries.providedBy(self.context))
 
1362
 
 
1363
    @property
 
1364
    def bug_tracking_usage(self):
 
1365
        """Whether the context tracks bugs in launchpad.
 
1366
 
 
1367
        :returns: ServiceUsage enum value
 
1368
        """
 
1369
        service_usage = IServiceUsage(self.context)
 
1370
        return service_usage.bug_tracking_usage
 
1371
 
 
1372
    @property
 
1373
    def bugtracker(self):
 
1374
        """Description of the context's bugtracker.
 
1375
 
 
1376
        :returns: str which may contain HTML.
 
1377
        """
 
1378
        if self.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
 
1379
            return 'Launchpad'
 
1380
        elif self.external_bugtracker:
 
1381
            return BugTrackerFormatterAPI(self.external_bugtracker).link(None)
 
1382
        else:
 
1383
            return 'None specified'
 
1384
 
 
1385
    @cachedproperty
 
1386
    def hot_bugs_info(self):
 
1387
        """Return a dict of the 10 hottest tasks and a has_more_bugs flag."""
 
1388
        has_more_bugs = False
 
1389
        params = BugTaskSearchParams(
 
1390
            orderby=['-heat', 'task'], omit_dupes=True,
 
1391
            user=self.user, status=any(*UNRESOLVED_BUGTASK_STATUSES))
 
1392
        # Use 4x as many tasks as bugs that are needed to improve performance.
 
1393
        bugtasks = self.context.searchTasks(params)[:40]
 
1394
        hot_bugtasks = []
 
1395
        hot_bugs = []
 
1396
        for task in bugtasks:
 
1397
            # Use hot_bugs list to ensure a bug is only listed once.
 
1398
            if task.bug not in hot_bugs:
 
1399
                if len(hot_bugtasks) < 10:
 
1400
                    hot_bugtasks.append(task)
 
1401
                    hot_bugs.append(task.bug)
 
1402
                else:
 
1403
                    has_more_bugs = True
 
1404
                    break
 
1405
        return {'has_more_bugs': has_more_bugs, 'bugtasks': hot_bugtasks}
 
1406
 
 
1407
 
1310
1408
class BugTargetBugTagsView(LaunchpadView):
1311
1409
    """Helper methods for rendering the bug tags portlet."""
1312
1410
 
1315
1413
        # Use path_only here to reduce the size of the rendered page.
1316
1414
        return "+bugs?field.tag=%s" % urllib.quote(tag)
1317
1415
 
 
1416
    def _calculateFactor(self, tag, count, max_count, official_tags):
 
1417
        bonus = 1.5 if tag in official_tags else 1
 
1418
        return (count / max_count) + bonus
 
1419
 
1318
1420
    @property
1319
1421
    def tags_cloud_data(self):
1320
1422
        """The data for rendering a tags cloud"""
1321
1423
        official_tags = self.context.official_bug_tags
1322
1424
        tags = self.context.getUsedBugTagsWithOpenCounts(
1323
1425
            self.user, 10, official_tags)
 
1426
        max_count = float(max([1] + tags.values()))
1324
1427
 
1325
1428
        return sorted(
1326
1429
            [dict(
1327
1430
                tag=tag,
1328
 
                count=count,
 
1431
                factor=self._calculateFactor(
 
1432
                    tag, count, max_count, official_tags),
1329
1433
                url=self._getSearchURL(tag),
1330
1434
                )
1331
1435
            for (tag, count) in tags.iteritems()],
1332
 
            key=itemgetter('count'), reverse=True)
 
1436
            key=itemgetter('tag'))
1333
1437
 
1334
1438
    @property
1335
1439
    def show_manage_tags_link(self):