676
682
cancel_url = canonical_url(self.context)
677
683
return cancel_url
686
def api_request(self):
687
return IWebServiceClientRequest(self.request)
679
689
def initialize(self):
680
690
"""Set up the needed widgets."""
681
691
bug = self.context.bug
682
692
cache = IJSONRequestCache(self.request)
683
693
cache.objects['bug'] = bug
694
subscribers_url_data = {
695
'web_link': canonical_url(bug, rootsite='bugs'),
696
'self_link': absoluteURL(bug, self.api_request),
698
cache.objects['subscribers_portlet_url_data'] = subscribers_url_data
684
699
cache.objects['total_comments_and_activity'] = (
685
700
self.total_comments + self.total_activity)
686
701
cache.objects['initial_comment_batch_offset'] = (
1760
1775
self.updateContextFromData(data)
1778
class BugTaskDeletionView(ReturnToReferrerMixin, LaunchpadFormView):
1779
"""Used to delete a bugtask."""
1784
label = 'Remove bug task'
1789
"""Return the next URL to call when this call completes."""
1790
if not self.request.is_ajax:
1791
return super(BugTaskDeletionView, self).next_url
1794
@action('Delete', name='delete_bugtask')
1795
def delete_bugtask_action(self, action, data):
1796
bugtask = self.context
1798
deleted_bugtask_url = canonical_url(self.context, rootsite='bugs')
1799
message = ("This bug no longer affects %s."
1800
% bugtask.bugtargetdisplayname)
1802
self.request.response.addNotification(message)
1803
if self.request.is_ajax:
1804
launchbag = getUtility(ILaunchBag)
1805
launchbag.add(bug.default_bugtask)
1806
# If we are deleting the current highlighted bugtask via ajax,
1807
# we must force a redirect to the new default bugtask to ensure
1808
# all URLs and other client cache content is correctly refreshed.
1809
# We can't do the redirect here since the XHR caller won't see it
1810
# so we return the URL to go to and let the caller do it.
1811
if self._return_url == deleted_bugtask_url:
1812
next_url = canonical_url(
1813
bug.default_bugtask, rootsite='bugs')
1814
self.request.response.setHeader('Content-type',
1816
return dumps(dict(bugtask_url=next_url))
1817
# No redirect required so return the new bugtask table HTML.
1818
view = getMultiAdapter(
1819
(bug, self.request),
1820
name='+bugtasks-and-nominations-table')
1822
return view.render()
1763
1825
class BugTaskListingView(LaunchpadView):
1764
1826
"""A view designed for displaying bug tasks in lists."""
1765
1827
# Note that this right now is only used in tests and to render
2144
2206
def model(self):
2145
2207
"""Provide flattened data about bugtask for simple templaters."""
2208
age = DateTimeFormatterAPI(self.bug.datecreated).durationsince()
2210
date_last_updated = self.bug.date_last_message
2211
if (date_last_updated is None or
2212
self.bug.date_last_updated > date_last_updated):
2213
date_last_updated = self.bug.date_last_updated
2214
last_updated_formatter = DateTimeFormatterAPI(date_last_updated)
2215
last_updated = last_updated_formatter.displaydate()
2146
2216
badges = getAdapter(self.bugtask, IPathAdapter, 'image').badges()
2147
2217
target_image = getAdapter(self.target, IPathAdapter, 'image')
2218
if self.bugtask.milestone is not None:
2219
milestone_name = self.bugtask.milestone.displayname
2221
milestone_name = None
2223
if self.assignee is not None:
2224
assignee = self.assignee.displayname
2227
'assignee': assignee,
2228
'bug_url': canonical_url(self.bugtask),
2229
'bugtarget': self.bugtargetdisplayname,
2230
'bugtarget_css': target_image.sprite_css(),
2231
'bug_heat_html': self.bug_heat_html,
2149
2234
'importance': self.importance.title,
2150
2235
'importance_class': 'importance' + self.importance.name,
2236
'last_updated': last_updated,
2237
'milestone_name': milestone_name,
2238
'reporter': self.bug.owner.displayname,
2151
2239
'status': self.status.title,
2152
2240
'status_class': 'status' + self.status.name,
2241
'tags': ' '.join(self.bug.tags),
2153
2242
'title': self.bug.title,
2155
'bug_url': canonical_url(self.bugtask),
2156
'bugtarget': self.bugtargetdisplayname,
2157
'bugtarget_css': target_image.sprite_css(),
2158
'bug_heat_html': self.bug_heat_html,
2216
2313
def mustache(self):
2217
2314
"""The rendered mustache template."""
2218
cache = IJSONRequestCache(self.request)
2315
objects = IJSONRequestCache(self.request).objects
2316
if IUnauthenticatedPrincipal.providedBy(self.request.principal):
2317
objects = obfuscate_structure(objects)
2219
2318
return pystache.render(self.mustache_template,
2220
cache.objects['mustache_model'])
2319
objects['mustache_model'])
2223
2322
def model(self):
2224
2323
bugtasks = [bugtask.model for bugtask in self.getBugListingItems()]
2324
for bugtask in bugtasks:
2325
bugtask.update(self.field_visibility)
2225
2326
return {'bugtasks': bugtasks}
3527
3632
def other_users_affected_count(self):
3528
"""The number of other users affected by this bug."""
3529
if self.current_user_affected_status:
3530
return self.context.users_affected_count - 1
3633
"""The number of other users affected by this bug.
3635
if getFeatureFlag('bugs.affected_count_includes_dupes.disabled'):
3636
if self.current_user_affected_status:
3637
return self.context.users_affected_count - 1
3639
return self.context.users_affected_count
3641
return self.context.other_users_affected_count_with_dupes
3644
def total_users_affected_count(self):
3645
"""The number of affected users, typically across all users.
3647
Counting across duplicates may be disabled at run time.
3649
if getFeatureFlag('bugs.affected_count_includes_dupes.disabled'):
3532
3650
return self.context.users_affected_count
3652
return self.context.users_affected_count_with_dupes
3535
3655
def affected_statement(self):
3536
3656
"""The default "this bug affects" statement to show.
3538
3658
The outputs of this method should be mirrored in
3539
3659
MeTooChoiceSource._getSourceNames() (Javascript).
3541
if self.other_users_affected_count == 1:
3542
if self.current_user_affected_status is None:
3661
me_affected = self.current_user_affected_status
3662
other_affected = self.other_users_affected_count
3663
if me_affected is None:
3664
if other_affected == 1:
3543
3665
return "This bug affects 1 person. Does this bug affect you?"
3544
elif self.current_user_affected_status:
3545
return "This bug affects you and 1 other person"
3547
return "This bug affects 1 person, but not you"
3548
elif self.other_users_affected_count > 1:
3549
if self.current_user_affected_status is None:
3666
elif other_affected > 1:
3551
3668
"This bug affects %d people. Does this bug "
3552
"affect you?" % (self.other_users_affected_count))
3553
elif self.current_user_affected_status:
3554
return "This bug affects you and %d other people" % (
3555
self.other_users_affected_count)
3669
"affect you?" % (other_affected))
3557
return "This bug affects %d people, but not you" % (
3558
self.other_users_affected_count)
3560
if self.current_user_affected_status is None:
3561
3671
return "Does this bug affect you?"
3562
elif self.current_user_affected_status:
3672
elif me_affected is True:
3673
if other_affected == 0:
3563
3674
return "This bug affects you"
3675
elif other_affected == 1:
3676
return "This bug affects you and 1 other person"
3678
return "This bug affects you and %d other people" % (
3681
if other_affected == 0:
3565
3682
return "This bug doesn't affect you"
3683
elif other_affected == 1:
3684
return "This bug affects 1 person, but not you"
3685
elif other_affected > 1:
3686
return "This bug affects %d people, but not you" % (
3568
3690
def anon_affected_statement(self):
3569
3691
"""The "this bug affects" statement to show to anonymous users.
3571
3693
The outputs of this method should be mirrored in
3572
3694
MeTooChoiceSource._getSourceNames() (Javascript).
3574
if self.context.users_affected_count == 1:
3696
affected = self.total_users_affected_count
3575
3698
return "This bug affects 1 person"
3576
elif self.context.users_affected_count > 1:
3577
return "This bug affects %d people" % (
3578
self.context.users_affected_count)
3700
return "This bug affects %d people" % affected
3643
3765
super(BugTaskTableRowView, self).__init__(context, request)
3644
3766
self.milestone_source = MilestoneVocabulary
3769
def api_request(self):
3770
return IWebServiceClientRequest(self.request)
3646
3772
def initialize(self):
3647
3773
super(BugTaskTableRowView, self).initialize()
3648
3774
link = canonical_url(self.context)
3649
task_link = edit_link = link + '/+editstatus'
3775
task_link = edit_link = canonical_url(
3776
self.context, view_name='+editstatus')
3777
delete_link = canonical_url(self.context, view_name='+delete')
3650
3778
can_edit = check_permission('launchpad.Edit', self.context)
3651
3779
bugtask_id = self.context.id
3652
3780
launchbag = getUtility(ILaunchBag)
3668
3796
row_css_class='highlight' if is_primary else None,
3669
3797
target_link=canonical_url(self.context.target),
3670
3798
target_link_title=self.target_link_title,
3671
user_can_edit_importance=self.context.userCanEditImportance(
3799
user_can_delete=self.user_can_delete_bugtask,
3800
delete_link=delete_link,
3801
user_can_edit_importance=self.user_can_edit_importance,
3673
3802
importance_css_class='importance' + self.context.importance.name,
3674
3803
importance_title=self.context.importance.title,
3675
3804
# We always look up all milestones, so there's no harm
3676
3805
# using len on the list here and avoid the COUNT query.
3677
3806
target_has_milestones=len(self._visible_milestones) > 0,
3807
user_can_edit_status=self.user_can_edit_status,
3810
if not self.many_bugtasks:
3811
cache = IJSONRequestCache(self.request)
3812
bugtask_data = cache.objects.get('bugtask_data', None)
3813
if bugtask_data is None:
3814
bugtask_data = dict()
3815
cache.objects['bugtask_data'] = bugtask_data
3816
bugtask_data[bugtask_id] = self.bugtask_config()
3680
3818
def canSeeTaskDetails(self):
3681
3819
"""Whether someone can see a task's status details.
3793
3931
"""Return the canonical url for the bugtask."""
3794
3932
return canonical_url(self.context)
3797
3935
def user_can_edit_importance(self):
3798
3936
"""Can the user edit the Importance field?
3800
3938
If yes, return True, otherwise return False.
3802
return self.context.userCanEditImportance(self.user)
3940
bugtask = self.context
3941
return (self.user_can_edit_status
3942
and bugtask.userCanEditImportance(self.user))
3945
def user_can_edit_status(self):
3946
"""Can the user edit the Status field?
3948
If yes, return True, otherwise return False.
3950
bugtask = self.context
3951
edit_allowed = bugtask.target_uses_malone or bugtask.bugwatch
3952
if bugtask.bugwatch:
3953
bugtracker = bugtask.bugwatch.bugtracker
3955
bugtracker.bugtrackertype == BugTrackerType.EMAILADDRESS)
3805
3959
def user_can_edit_assignee(self):
3854
4018
not self.context.userCanSetAnyAssignee(user) and
3855
4019
(user is None or user.teams_participated_in.count() == 0))
3856
4020
cx = self.context
3858
4022
row_id=self.data['row_id'],
4023
form_row_id=self.data['form_row_id'],
3859
4024
bugtask_path='/'.join([''] + self.data['link'].split('/')[3:]),
3860
4025
prefix=get_prefix(cx),
4026
targetname=cx.bugtargetdisplayname,
4027
bug_title=cx.bug.title,
3861
4028
assignee_value=cx.assignee and cx.assignee.name,
3862
4029
assignee_is_team=cx.assignee and cx.assignee.is_team,
3863
4030
assignee_vocabulary=assignee_vocabulary,
3864
4031
assignee_vocabulary_filters=filter_details,
3865
4032
hide_assignee_team_selection=hide_assignee_team_selection,
3866
4033
user_can_unassign=cx.userCanUnassign(user),
4034
user_can_delete=self.user_can_delete_bugtask,
4035
delete_link=self.data['delete_link'],
3867
4036
target_is_product=IProduct.providedBy(cx.target),
3868
4037
status_widget_items=self.status_widget_items,
3869
4038
status_value=cx.status.title,
3873
4042
milestone_value=(
3876
request=IWebServiceClientRequest(self.request))
4045
request=self.api_request)
3877
4046
if cx.milestone else None),
3878
4047
user_can_edit_assignee=self.user_can_edit_assignee,
3879
4048
user_can_edit_milestone=self.user_can_edit_milestone,
3880
user_can_edit_status=not cx.bugwatch,
3881
user_can_edit_importance=(
3882
self.user_can_edit_importance and not cx.bugwatch)
4049
user_can_edit_status=self.user_can_edit_status,
4050
user_can_edit_importance=self.user_can_edit_importance,
3886
4054
class BugsBugTaskSearchListingView(BugTaskSearchListingView):