1
|
|
|
# Copyright (c) 2019 Alexander Todorov <[email protected]> |
2
|
|
|
|
3
|
|
|
# Licensed under the GPL 2.0: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html |
4
|
|
|
|
5
|
|
|
|
6
|
|
|
from django.contrib.auth.decorators import permission_required |
7
|
|
|
from django.urls import reverse |
8
|
|
|
from django.http import HttpResponseRedirect |
9
|
|
|
from django.test import modify_settings |
10
|
|
|
from django.utils.decorators import method_decorator |
11
|
|
|
from django.views.generic import DetailView |
12
|
|
|
from django.views.generic.edit import CreateView |
13
|
|
|
from django.views.generic.edit import UpdateView |
14
|
|
|
from django.views.generic.base import View |
15
|
|
|
from django.views.generic.base import TemplateView |
16
|
|
|
from django.utils.translation import ugettext_lazy as _ |
17
|
|
|
|
18
|
|
|
from tcms.bugs.models import Bug |
19
|
|
|
from tcms.management.models import Component |
20
|
|
|
from tcms.bugs.forms import NewBugForm, BugCommentForm |
21
|
|
|
from tcms.core.helpers.comments import add_comment |
22
|
|
|
from tcms.core.response import ModifySettingsTemplateResponse |
23
|
|
|
|
24
|
|
|
|
25
|
|
|
class Get(DetailView): # pylint: disable=missing-permission-required |
26
|
|
|
model = Bug |
27
|
|
|
template_name = 'bugs/get.html' |
28
|
|
|
http_method_names = ['get'] |
29
|
|
|
response_class = ModifySettingsTemplateResponse |
30
|
|
|
|
31
|
|
|
def render_to_response(self, context, **response_kwargs): |
32
|
|
|
self.response_class.modify_settings = modify_settings( |
33
|
|
|
MENU_ITEMS={'append': [ |
34
|
|
|
('...', [ |
35
|
|
|
( |
36
|
|
|
_('Edit'), |
37
|
|
|
reverse('bugs-edit', args=[self.object.pk]) |
38
|
|
|
), |
39
|
|
|
]) |
40
|
|
|
]} |
41
|
|
|
) |
42
|
|
|
return super().render_to_response(context, **response_kwargs) |
43
|
|
|
|
44
|
|
|
def get_context_data(self, **kwargs): |
45
|
|
|
context = super().get_context_data(**kwargs) |
46
|
|
|
context['comment_form'] = BugCommentForm() |
47
|
|
|
context['comment_form'].populate(self.object.pk) |
48
|
|
|
context['executions'] = self.object.executions.all() |
49
|
|
|
|
50
|
|
|
return context |
51
|
|
|
|
52
|
|
|
|
53
|
|
|
@method_decorator(permission_required('bugs.add_bug'), name='dispatch') |
54
|
|
|
class New(CreateView): |
55
|
|
|
model = Bug |
56
|
|
|
form_class = NewBugForm |
57
|
|
|
template_name = 'bugs/mutable.html' |
58
|
|
|
|
59
|
|
|
def get_context_data(self, **kwargs): |
60
|
|
|
context = super().get_context_data(**kwargs) |
61
|
|
|
context['page_title'] = _('New bug') |
62
|
|
|
context['form_post_url'] = reverse('bugs-new') |
63
|
|
|
return context |
64
|
|
|
|
65
|
|
|
def get_form_kwargs(self): |
66
|
|
|
kwargs = super().get_form_kwargs() |
67
|
|
|
kwargs['initial'].update({ |
68
|
|
|
'reporter': self.request.user, |
69
|
|
|
}) |
70
|
|
|
return kwargs |
71
|
|
|
|
72
|
|
|
def get_form(self): |
73
|
|
|
form = super().get_form() |
74
|
|
|
# clear fields which are set dynamically via JavaScript |
75
|
|
|
form.populate(self.request.POST.get('product', -1)) |
76
|
|
|
return form |
77
|
|
|
|
78
|
|
|
def form_valid(self, form): |
79
|
|
|
response = super().form_valid(form) |
80
|
|
|
|
81
|
|
|
if not self.object.assignee: |
82
|
|
|
self.object.assignee = New.find_assignee(self.request.POST) |
83
|
|
|
self.object.save() |
84
|
|
|
add_comment([self.object], form.cleaned_data['text'], self.request.user) |
85
|
|
|
|
86
|
|
|
return response |
87
|
|
|
|
88
|
|
|
@staticmethod |
89
|
|
|
def assignee_from_components(components): |
90
|
|
|
""" |
91
|
|
|
Return the first owner which is assigned to any of the |
92
|
|
|
components. This is as best as we can to automatically figure |
93
|
|
|
out who should be assigned to this bug. |
94
|
|
|
""" |
95
|
|
|
for component in components: |
96
|
|
|
if component.initial_owner: |
97
|
|
|
return component.initial_owner |
98
|
|
|
|
99
|
|
|
return None |
100
|
|
|
|
101
|
|
|
@staticmethod |
102
|
|
|
def find_assignee(data): |
103
|
|
|
""" |
104
|
|
|
Try to automatically find an assignee for Bug by first scanning |
105
|
|
|
TestCase components (if present) and then components for the |
106
|
|
|
product. |
107
|
|
|
""" |
108
|
|
|
assignee = None |
109
|
|
|
if '_execution' in data: |
110
|
|
|
assignee = New.assignee_from_components(data['_execution'].case.component.all()) |
111
|
|
|
del data['_execution'] |
112
|
|
|
|
113
|
|
|
if not assignee: |
114
|
|
|
assignee = New.assignee_from_components( |
115
|
|
|
Component.objects.filter(product=data['product'])) |
116
|
|
|
|
117
|
|
|
return assignee |
118
|
|
|
|
119
|
|
|
@staticmethod |
120
|
|
|
def create_bug(data): |
121
|
|
|
""" |
122
|
|
|
Helper method used within Issue Tracker integration. |
123
|
|
|
|
124
|
|
|
:param data: Untrusted input, usually via HTTP request |
125
|
|
|
:type data: dict |
126
|
|
|
:return: bug |
127
|
|
|
:rtype: Model |
128
|
|
|
""" |
129
|
|
|
bug = None |
130
|
|
|
|
131
|
|
|
if 'assignee' not in data or not data['assignee']: |
132
|
|
|
data['assignee'] = New.find_assignee(data) |
133
|
|
|
|
134
|
|
|
text = data['text'] |
135
|
|
|
del data['text'] |
136
|
|
|
|
137
|
|
|
bug = Bug.objects.create(**data) |
138
|
|
|
add_comment([bug], text, bug.reporter) |
139
|
|
|
|
140
|
|
|
return bug |
141
|
|
|
|
142
|
|
|
|
143
|
|
|
@method_decorator(permission_required('bugs.change_bug'), name='dispatch') |
144
|
|
|
class Edit(UpdateView): |
145
|
|
|
model = Bug |
146
|
|
|
# todo: try using NewBugForm instead of duplicating the field names here |
147
|
|
|
# must figure out how to collect info about changes and hide comments |
148
|
|
|
fields = ['summary', 'assignee', 'reporter', 'product', 'version', 'build'] |
149
|
|
|
template_name = 'bugs/mutable.html' |
150
|
|
|
_values_before_update = {} |
151
|
|
|
|
152
|
|
|
def _record_changes(self, new_data): |
153
|
|
|
result = "" |
154
|
|
|
for field in self.fields: |
155
|
|
|
if self._values_before_update[field] != new_data[field]: |
156
|
|
|
result += "%s: %s -> %s\n" % (field.title(), |
157
|
|
|
self._values_before_update[field], |
158
|
|
|
new_data[field]) |
159
|
|
|
if result: |
160
|
|
|
add_comment([self.object], result, self.request.user) |
161
|
|
|
|
162
|
|
|
def get_context_data(self, **kwargs): |
163
|
|
|
context = super().get_context_data(**kwargs) |
164
|
|
|
context['page_title'] = _('Edit bug') |
165
|
|
|
context['form_post_url'] = reverse('bugs-edit', args=[self.object.pk, ]) |
166
|
|
|
return context |
167
|
|
|
|
168
|
|
|
def get_object(self, queryset=None): |
169
|
|
|
object = super().get_object(queryset) |
170
|
|
|
for field in self.fields: |
171
|
|
|
self._values_before_update[field] = getattr(object, field) |
172
|
|
|
return object |
173
|
|
|
|
174
|
|
|
def form_valid(self, form): |
175
|
|
|
self._record_changes(form.cleaned_data) |
176
|
|
|
return super().form_valid(form) |
177
|
|
|
|
178
|
|
|
|
179
|
|
|
@method_decorator(permission_required('django_comments.add_comment'), name='dispatch') |
180
|
|
|
class AddComment(View): |
181
|
|
|
http_methods = ['post'] |
182
|
|
|
|
183
|
|
|
def post(self, request, *args, **kwargs): |
184
|
|
|
form = BugCommentForm(request.POST) |
185
|
|
|
|
186
|
|
|
if form.is_valid(): |
187
|
|
|
bug = form.cleaned_data['bug'] |
188
|
|
|
if form.cleaned_data['text']: |
189
|
|
|
add_comment([bug], form.cleaned_data['text'], request.user) |
190
|
|
|
|
191
|
|
|
if request.POST.get('action') == 'close': |
192
|
|
|
bug.status = False |
193
|
|
|
bug.save() |
194
|
|
|
add_comment([bug], _('*bug closed*'), request.user) |
195
|
|
|
|
196
|
|
|
return HttpResponseRedirect(reverse('bugs-get', args=[bug.pk])) |
|
|
|
|
197
|
|
|
|
198
|
|
|
|
199
|
|
|
class Search(TemplateView): # pylint: disable=missing-permission-required |
200
|
|
|
template_name = 'bugs/search.html' |
201
|
|
|
|
202
|
|
|
def get_context_data(self, **kwargs): |
203
|
|
|
form = NewBugForm(self.request.GET) |
204
|
|
|
form.populate(self.request.GET.get('product', -1)) |
205
|
|
|
|
206
|
|
|
return {'form': form} |
207
|
|
|
|