~mars/launchpad/ghost-line

« back to all changes in this revision

Viewing changes to lib/lp/registry/browser/person.py

  • Committer: Tarmac
  • Author(s): Jelmer Vernooij, Henning Eggers, Aaron Bentley, Stuart Bishop, Graham Binns, Tim Penhey, Launchpad Patch Queue Manager, Gary Poster, William Grant, Edwin Grubbs, Robert Collins, j.c.sackett, Abel Deuring, Jonathan Lange, Jeroen Vermeulen, Julian Edwards, Curtis Hovey, Paul Hummer, Brian Murray, Maris Fogels, Danilo Segan, Deryck Hodge, Bryce Harrington, James Westby, Ian Booth, Gavin Panella, LaMont Jones, Danilo Šegan, Francis J. Lacoste, Benji York, Guilherme Salgado, Brad Crittenden, James Westby, Michael Nelson, Henning Eggers, Steve Kowalik, Michael Hudson, Diogo Matsubara, Steve Kowalik, Leonard Richardson, John Arbash Meinel, Ursula Junque, Andrew Mitchell, Anthony Lenton, Martin Pool, Colin Watson, Matthew Revell
  • Date: 2010-12-16 02:19:53 UTC
  • mfrom: (11782.13.2 test-ghost-update)
  • Revision ID: tarmac@sapote-20101216021953-24h56by3bwd0yrx6
[ui=none][r=mars][no-qa] Update to the latest devel

Show diffs side-by-side

added added

removed removed

Lines of Context:
52
52
    'PersonSpecWorkloadTableView',
53
53
    'PersonSpecWorkloadView',
54
54
    'PersonSpecsMenu',
 
55
    'PersonStructuralSubscriptionsView',
55
56
    'PersonSubscribedBugTaskSearchListingView',
56
57
    'PersonSubscriptionsView',
57
58
    'PersonView',
73
74
    'TeamMembershipView',
74
75
    'TeamMugshotView',
75
76
    'TeamNavigation',
 
77
    'TeamOverviewMenu',
76
78
    'TeamOverviewNavigationMenu',
77
 
    'TeamOverviewMenu',
78
79
    'TeamReassignmentView',
79
80
    'archive_to_person',
80
81
    ]
104
105
    URI,
105
106
    )
106
107
import pytz
 
108
from storm.expr import Join
107
109
from storm.zope.interfaces import IResultSet
108
110
from z3c.ptcompat import ViewPageTemplateFile
109
111
from zope.app.form.browser import (
176
178
from canonical.launchpad.mailnotification import send_direct_contact_email
177
179
from canonical.launchpad.validators.email import valid_email
178
180
from canonical.launchpad.webapp import (
179
 
    action,
180
181
    ApplicationMenu,
181
182
    canonical_url,
182
183
    ContextMenu,
183
 
    custom_widget,
184
184
    enabled_with_permission,
185
 
    LaunchpadEditFormView,
186
 
    LaunchpadFormView,
187
185
    Link,
188
186
    Navigation,
189
187
    NavigationMenu,
221
219
from lp.answers.interfaces.questioncollection import IQuestionSet
222
220
from lp.answers.interfaces.questionenums import QuestionParticipation
223
221
from lp.answers.interfaces.questionsperson import IQuestionsPerson
 
222
from lp.app.browser.launchpadform import (
 
223
    action,
 
224
    custom_widget,
 
225
    LaunchpadEditFormView,
 
226
    LaunchpadFormView,
 
227
    )
224
228
from lp.app.browser.stringformatter import FormattersAPI
225
229
from lp.app.browser.tales import (
226
230
    DateTimeFormatterAPI,
231
235
    UnexpectedFormData,
232
236
    )
233
237
from lp.blueprints.browser.specificationtarget import HasSpecificationsView
234
 
from lp.blueprints.interfaces.specification import SpecificationFilter
 
238
from lp.blueprints.enums import SpecificationFilter
235
239
from lp.bugs.browser.bugtask import BugTaskSearchListingView
236
240
from lp.bugs.interfaces.bugtask import (
237
241
    BugTaskSearchParams,
239
243
    IBugTaskSet,
240
244
    UNRESOLVED_BUGTASK_STATUSES,
241
245
    )
 
246
from lp.bugs.model.bugtask import BugTask
242
247
from lp.buildmaster.enums import BuildStatus
243
248
from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
244
249
from lp.code.errors import InvalidNamespace
301
306
    IWikiName,
302
307
    IWikiNameSet,
303
308
    )
 
309
from lp.registry.model.milestone import (
 
310
    Milestone,
 
311
    milestone_sort_key,
 
312
    )
304
313
from lp.services.fields import LocationField
305
314
from lp.services.geoip.interfaces import IRequestPreferredLanguages
306
315
from lp.services.openid.adapters.openid import CurrentOpenIDEndPoint
543
552
        """Traverse to this person's recipes."""
544
553
        return self.context.getRecipe(name)
545
554
 
 
555
    @stepthrough('+merge-queues')
 
556
    def traverse_merge_queue(self, name):
 
557
        """Traverse to this person's merge queues."""
 
558
        return self.context.getMergeQueue(name)
 
559
 
546
560
 
547
561
class TeamNavigation(PersonNavigation):
548
562
 
990
1004
        enabled = bool(self.person.getOwnedOrDrivenPillars())
991
1005
        return Link(target, text, enabled=enabled, icon='info')
992
1006
 
 
1007
    def subscriptions(self):
 
1008
        target = '+subscriptions'
 
1009
        text = 'Direct subscriptions'
 
1010
        return Link(target, text, icon='info')
 
1011
 
 
1012
    def structural_subscriptions(self):
 
1013
        target = '+structural-subscriptions'
 
1014
        text = 'Structural subscriptions'
 
1015
        return Link(target, text, icon='info')
 
1016
 
993
1017
 
994
1018
class PersonMenuMixin(CommonMenuLinks):
995
1019
 
1023
1047
 
1024
1048
    usedfor = IPerson
1025
1049
    facet = 'overview'
1026
 
    links = ['edit', 'branding', 'common_edithomepage',
1027
 
             'editemailaddresses', 'editlanguages', 'editwikinames',
1028
 
             'editircnicknames', 'editjabberids',
1029
 
             'editsshkeys', 'editpgpkeys', 'editlocation', 'memberships',
1030
 
             'codesofconduct', 'karma', 'administer', 'administer_account',
1031
 
             'projects', 'activate_ppa', 'maintained',
1032
 
             'view_ppa_subscriptions', 'ppa', 'oauth_tokens',
1033
 
             'related_software_summary', 'view_recipes']
 
1050
    links = [
 
1051
        'edit',
 
1052
        'branding',
 
1053
        'common_edithomepage',
 
1054
        'editemailaddresses',
 
1055
        'editlanguages',
 
1056
        'editwikinames',
 
1057
        'editircnicknames',
 
1058
        'editjabberids',
 
1059
        'editsshkeys',
 
1060
        'editpgpkeys',
 
1061
        'editlocation',
 
1062
        'memberships',
 
1063
        'codesofconduct',
 
1064
        'karma',
 
1065
        'administer',
 
1066
        'administer_account',
 
1067
        'projects',
 
1068
        'activate_ppa',
 
1069
        'maintained',
 
1070
        'view_ppa_subscriptions',
 
1071
        'ppa',
 
1072
        'oauth_tokens',
 
1073
        'related_software_summary',
 
1074
        'view_recipes',
 
1075
        'subscriptions',
 
1076
        'structural_subscriptions',
 
1077
        ]
1034
1078
 
1035
1079
    def related_software_summary(self):
1036
1080
        target = '+related-software'
1310
1354
            self.person.displayname)
1311
1355
        return Link(target, text, summary, icon='edit')
1312
1356
 
1313
 
    @enabled_with_permission('launchpad.MailingListManager')
 
1357
    @enabled_with_permission('launchpad.Moderate')
1314
1358
    def configure_mailing_list(self):
1315
1359
        target = '+mailinglist'
1316
1360
        mailing_list = self.person.mailing_list
1370
1414
 
1371
1415
    usedfor = ITeam
1372
1416
    facet = 'overview'
1373
 
    links = ['edit', 'branding', 'common_edithomepage', 'members', 'mugshots',
1374
 
             'add_member', 'proposed_members',
1375
 
             'memberships', 'received_invitations',
1376
 
             'editemail', 'configure_mailing_list', 'moderate_mailing_list',
1377
 
             'editlanguages', 'map', 'polls',
1378
 
             'add_poll', 'join', 'leave', 'add_my_teams',
1379
 
             'reassign', 'projects', 'activate_ppa', 'maintained', 'ppa',
1380
 
             'related_software_summary', 'view_recipes']
 
1417
    links = [
 
1418
        'edit',
 
1419
        'branding',
 
1420
        'common_edithomepage',
 
1421
        'members',
 
1422
        'mugshots',
 
1423
        'add_member',
 
1424
        'proposed_members',
 
1425
        'memberships',
 
1426
        'received_invitations',
 
1427
        'editemail',
 
1428
        'configure_mailing_list',
 
1429
        'moderate_mailing_list',
 
1430
        'editlanguages',
 
1431
        'map',
 
1432
        'polls',
 
1433
        'add_poll',
 
1434
        'join',
 
1435
        'leave',
 
1436
        'add_my_teams',
 
1437
        'reassign',
 
1438
        'projects',
 
1439
        'activate_ppa',
 
1440
        'maintained',
 
1441
        'ppa',
 
1442
        'related_software_summary',
 
1443
        'view_recipes',
 
1444
        'subscriptions',
 
1445
        'structural_subscriptions',
 
1446
        ]
1381
1447
 
1382
1448
 
1383
1449
class TeamOverviewNavigationMenu(NavigationMenu, TeamMenuMixin):
1711
1777
        # Only the IPerson can be traversed to, so it provides the IAccount.
1712
1778
        # It also means that permissions are checked on IAccount, not IPerson.
1713
1779
        self.person = self.context
1714
 
        from canonical.launchpad.interfaces import IMasterObject
 
1780
        from canonical.launchpad.interfaces.lpstorm import IMasterObject
1715
1781
        self.context = IMasterObject(self.context.account)
1716
1782
        # Set fields to be displayed.
1717
1783
        self.field_names = ['status', 'status_comment']
2073
2139
 
2074
2140
    def getMilestoneWidgetValues(self):
2075
2141
        """Return data used to render the milestone checkboxes."""
2076
 
        milestones = getUtility(IBugTaskSet).getAssignedMilestonesFromSearch(
2077
 
            self.searchUnbatched())
 
2142
        prejoins = [
 
2143
            (Milestone, Join(Milestone, BugTask.milestone == Milestone.id))]
 
2144
        milestones = [
 
2145
            bugtask.milestone
 
2146
            for bugtask in self.searchUnbatched(prejoins=prejoins)]
 
2147
        milestones = sorted(milestones, key=milestone_sort_key, reverse=True)
2078
2148
        return [
2079
2149
            dict(title=milestone.title, value=milestone.id, checked=False)
2080
2150
            for milestone in milestones]
2090
2160
    page_title = 'Related bugs'
2091
2161
 
2092
2162
    def searchUnbatched(self, searchtext=None, context=None,
2093
 
                        extra_params=None):
 
2163
                        extra_params=None, prejoins=[]):
2094
2164
        """Return the open bugs related to a person.
2095
2165
 
2096
2166
        :param extra_params: A dict that provides search params added to
2121
2191
 
2122
2192
        return context.searchTasks(
2123
2193
            assignee_params, subscriber_params, owner_params,
2124
 
            commenter_params)
 
2194
            commenter_params, prejoins=prejoins)
2125
2195
 
2126
2196
    def getSearchPageHeading(self):
2127
2197
        return "Bugs related to %s" % self.context.displayname
2150
2220
    page_title = 'Assigned bugs'
2151
2221
 
2152
2222
    def searchUnbatched(self, searchtext=None, context=None,
2153
 
                        extra_params=None):
 
2223
                        extra_params=None, prejoins=[]):
2154
2224
        """Return the open bugs assigned to a person."""
2155
2225
        if context is None:
2156
2226
            context = self.context
2162
2232
        extra_params['assignee'] = context
2163
2233
 
2164
2234
        sup = super(PersonAssignedBugTaskSearchListingView, self)
2165
 
        return sup.searchUnbatched(searchtext, context, extra_params)
 
2235
        return sup.searchUnbatched(
 
2236
            searchtext, context, extra_params, prejoins)
2166
2237
 
2167
2238
    def shouldShowAssigneeWidget(self):
2168
2239
        """Should the assignee widget be shown on the advanced search page?"""
2207
2278
    page_title = 'Commented bugs'
2208
2279
 
2209
2280
    def searchUnbatched(self, searchtext=None, context=None,
2210
 
                        extra_params=None):
 
2281
                        extra_params=None, prejoins=[]):
2211
2282
        """Return the open bugs commented on by a person."""
2212
2283
        if context is None:
2213
2284
            context = self.context
2219
2290
        extra_params['bug_commenter'] = context
2220
2291
 
2221
2292
        sup = super(PersonCommentedBugTaskSearchListingView, self)
2222
 
        return sup.searchUnbatched(searchtext, context, extra_params)
 
2293
        return sup.searchUnbatched(
 
2294
            searchtext, context, extra_params, prejoins)
2223
2295
 
2224
2296
    def getSearchPageHeading(self):
2225
2297
        """The header for the search page."""
2252
2324
    page_title = 'Reported bugs'
2253
2325
 
2254
2326
    def searchUnbatched(self, searchtext=None, context=None,
2255
 
                        extra_params=None):
 
2327
                        extra_params=None, prejoins=[]):
2256
2328
        """Return the bugs reported by a person."""
2257
2329
        if context is None:
2258
2330
            context = self.context
2267
2339
        extra_params['bug_reporter'] = context
2268
2340
 
2269
2341
        sup = super(PersonReportedBugTaskSearchListingView, self)
2270
 
        return sup.searchUnbatched(searchtext, context, extra_params)
 
2342
        return sup.searchUnbatched(
 
2343
            searchtext, context, extra_params, prejoins)
2271
2344
 
2272
2345
    def getSearchPageHeading(self):
2273
2346
        """The header for the search page."""
2308
2381
    page_title = 'Subscribed bugs'
2309
2382
 
2310
2383
    def searchUnbatched(self, searchtext=None, context=None,
2311
 
                        extra_params=None):
 
2384
                        extra_params=None, prejoins=[]):
2312
2385
        """Return the bugs subscribed to by a person."""
2313
2386
        if context is None:
2314
2387
            context = self.context
2320
2393
        extra_params['subscriber'] = context
2321
2394
 
2322
2395
        sup = super(PersonSubscribedBugTaskSearchListingView, self)
2323
 
        return sup.searchUnbatched(searchtext, context, extra_params)
 
2396
        return sup.searchUnbatched(
 
2397
            searchtext, context, extra_params, prejoins)
2324
2398
 
2325
2399
    def getSearchPageHeading(self):
2326
2400
        """The header for the search page."""
2344
2418
        return self.getSearchPageHeading()
2345
2419
 
2346
2420
 
2347
 
class PersonSubscriptionsView(BugTaskSearchListingView):
 
2421
class PersonSubscriptionsView(LaunchpadView):
2348
2422
    """All the subscriptions for a person."""
2349
2423
 
2350
2424
    page_title = 'Subscriptions'
2351
2425
 
2352
2426
    def subscribedBugTasks(self):
2353
 
        """Return a BatchNavigator for distinct bug tasks to which the
2354
 
        person is subscribed."""
 
2427
        """
 
2428
        Return a BatchNavigator for distinct bug tasks to which the person is
 
2429
        subscribed.
 
2430
        """
2355
2431
        bug_tasks = self.context.searchTasks(None, user=self.user,
2356
2432
            order_by='-date_last_updated',
2357
2433
            status=(BugTaskStatus.NEW,
2364
2440
            bug_subscriber=self.context)
2365
2441
 
2366
2442
        sub_bug_tasks = []
2367
 
        sub_bugs = []
 
2443
        sub_bugs = set()
2368
2444
 
 
2445
        # XXX: GavinPanella 2010-10-08 bug=656904: This materializes the
 
2446
        # entire result set. It would probably be more efficient implemented
 
2447
        # with a pre_iter_hook on a DecoratedResultSet.
2369
2448
        for task in bug_tasks:
 
2449
            # We order the bugtasks by date_last_updated but we always display
 
2450
            # the default task for the bug. This is to avoid ordering issues
 
2451
            # in tests and also prevents user confusion (because nothing is
 
2452
            # more confusing than your subscription targets changing seemingly
 
2453
            # at random).
2370
2454
            if task.bug not in sub_bugs:
2371
 
                # We order the bugtasks by date_last_updated but we
2372
 
                # always display the default task for the bug. This is
2373
 
                # to avoid ordering issues in tests and also prevents
2374
 
                # user confusion (because nothing is more confusing than
2375
 
                # your subscription targets changing seemingly at
2376
 
                # random).
 
2455
                # XXX: GavinPanella 2010-10-08 bug=656904: default_bugtask
 
2456
                # causes a query to be executed. It would be more efficient to
 
2457
                # get the default bugtask in bulk, in a pre_iter_hook on a
 
2458
                # DecoratedResultSet perhaps.
2377
2459
                sub_bug_tasks.append(task.bug.default_bugtask)
2378
 
                sub_bugs.append(task.bug)
 
2460
                sub_bugs.add(task.bug)
 
2461
 
2379
2462
        return BatchNavigator(sub_bug_tasks, self.request)
2380
2463
 
2381
2464
    def canUnsubscribeFromBugTasks(self):
2382
 
        viewed_person = self.context
2383
 
        if self.user is None:
2384
 
            return False
2385
 
        elif viewed_person == self.user:
2386
 
            return True
2387
 
        elif (viewed_person.is_team and
2388
 
              self.user.inTeam(viewed_person)):
2389
 
            return True
 
2465
        """Can the current user unsubscribe from the bug tasks shown?"""
 
2466
        return (self.user is not None and
 
2467
                self.user.inTeam(self.context))
2390
2468
 
2391
 
    def getSubscriptionsPageHeading(self):
 
2469
    @property
 
2470
    def label(self):
2392
2471
        """The header for the subscriptions page."""
2393
2472
        return "Subscriptions for %s" % self.context.displayname
2394
2473
 
 
2474
 
 
2475
class PersonStructuralSubscriptionsView(LaunchpadView):
 
2476
    """All the structural subscriptions for a person."""
 
2477
 
 
2478
    page_title = 'Structural subscriptions'
 
2479
 
 
2480
    def canUnsubscribeFromBugTasks(self):
 
2481
        """Can the current user modify subscriptions for the context?"""
 
2482
        return (self.user is not None and
 
2483
                self.user.inTeam(self.context))
 
2484
 
2395
2485
    @property
2396
2486
    def label(self):
2397
 
        return self.getSubscriptionsPageHeading()
 
2487
        """The header for the structural subscriptions page."""
 
2488
        return "Structural subscriptions for %s" % self.context.displayname
2398
2489
 
2399
2490
 
2400
2491
class PersonVouchersView(LaunchpadFormView):
3408
3499
                        'center_lng': self.context.longitude}
3409
3500
        return u"""
3410
3501
            <script type="text/javascript">
3411
 
                YUI().use('node', 'lp.app.mapping', function(Y) {
 
3502
                LPS.use('node', 'lp.app.mapping', function(Y) {
3412
3503
                    function renderMap() {
3413
3504
                        Y.lp.app.mapping.renderPersonMapSmall(
3414
3505
                            %(center_lat)s, %(center_lng)s);
3476
3567
            return 'portlet'
3477
3568
        return 'portlet private'
3478
3569
 
 
3570
    @property
 
3571
    def add_member_step_title(self):
 
3572
        """A string for setup_add_member_handler with escaped quotes."""
 
3573
        vocabulary_registry = getVocabularyRegistry()
 
3574
        vocabulary = vocabulary_registry.get(self.context, 'ValidTeamMember')
 
3575
        return vocabulary.step_title.replace("'", "\\'").replace('"', '\\"')
 
3576
 
3479
3577
 
3480
3578
class PersonCodeOfConductEditView(LaunchpadView):
3481
3579
    """View for the ~person/+codesofconduct pages."""