1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
|
|
from django.contrib.auth import get_user_model |
4
|
|
|
from django.contrib.auth.decorators import permission_required |
5
|
|
|
from django.core.exceptions import ObjectDoesNotExist |
6
|
|
|
from django.http import HttpResponseRedirect |
7
|
|
|
from django.shortcuts import get_object_or_404, render |
8
|
|
|
from django.urls import reverse |
9
|
|
|
from django.utils.decorators import method_decorator |
10
|
|
|
from django.utils.translation import gettext_lazy as _ |
11
|
|
|
from django.views.generic import DetailView |
12
|
|
|
from django.views.generic.base import TemplateView, View |
13
|
|
|
from django.views.generic.edit import UpdateView |
14
|
|
|
from guardian.decorators import permission_required as object_permission_required |
15
|
|
|
|
16
|
|
|
from tcms.core.contrib.linkreference.forms import LinkReferenceForm |
17
|
|
|
from tcms.core.forms import SimpleCommentForm |
18
|
|
|
from tcms.testcases.models import BugSystem, TestCase, TestCasePlan, TestCaseStatus |
19
|
|
|
from tcms.testplans.models import TestPlan |
20
|
|
|
from tcms.testruns.forms import NewRunForm, SearchRunForm |
21
|
|
|
from tcms.testruns.models import ( |
22
|
|
|
Environment, |
23
|
|
|
EnvironmentProperty, |
24
|
|
|
TestExecutionStatus, |
25
|
|
|
TestRun, |
26
|
|
|
) |
27
|
|
|
|
28
|
|
|
User = get_user_model() # pylint: disable=invalid-name |
29
|
|
|
|
30
|
|
|
|
31
|
|
|
@method_decorator(permission_required("testruns.add_testrun"), name="dispatch") |
32
|
|
|
class NewTestRunView(View): |
33
|
|
|
"""Display new test run page.""" |
34
|
|
|
|
35
|
|
|
template_name = "testruns/mutable.html" |
36
|
|
|
http_method_names = ["post", "get"] |
37
|
|
|
|
38
|
|
|
def get(self, request, form_initial=None, is_cloning=False): |
39
|
|
|
plan_id = request.GET.get("p") |
40
|
|
|
|
41
|
|
|
# note: ordered by pk for test_show_create_new_run_page() |
42
|
|
|
test_cases = ( |
43
|
|
|
TestCase.objects.filter(pk__in=request.GET.getlist("c")) |
44
|
|
|
.select_related("author", "case_status", "category", "priority") |
45
|
|
|
.order_by("pk") |
46
|
|
|
) |
47
|
|
|
|
48
|
|
|
test_plan = TestPlan.objects.filter(pk=plan_id).first() |
49
|
|
|
if not form_initial: |
50
|
|
|
form_initial = { |
51
|
|
|
"summary": f"Test run for {test_plan.name}" if test_plan else "", |
52
|
|
|
"manager": test_plan.author.email if test_plan else "", |
53
|
|
|
"default_tester": request.user.email, |
54
|
|
|
"notes": "", |
55
|
|
|
"plan": plan_id, |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
form = NewRunForm(initial=form_initial) |
59
|
|
|
form.populate(plan_id) |
60
|
|
|
|
61
|
|
|
context_data = { |
62
|
|
|
"plan_id": plan_id, # used for UI conditionals |
63
|
|
|
"test_cases": test_cases, |
64
|
|
|
"form": form, |
65
|
|
|
"disabled_cases": get_disabled_test_cases_count(test_cases), |
66
|
|
|
"is_cloning": is_cloning, |
67
|
|
|
} |
68
|
|
|
return render(request, self.template_name, context_data) |
69
|
|
|
|
70
|
|
|
def post(self, request): |
71
|
|
|
form = NewRunForm(data=request.POST) |
72
|
|
|
form.populate(request.POST.get("plan")) |
73
|
|
|
|
74
|
|
|
if form.is_valid(): |
75
|
|
|
test_run = form.save() |
76
|
|
|
|
77
|
|
|
# copy all of the selected properties into the test run |
78
|
|
|
for prop in EnvironmentProperty.objects.filter( |
79
|
|
|
environment__in=form.cleaned_data["environment"] |
80
|
|
|
): |
81
|
|
|
test_run.property_set.create(name=prop.name, value=prop.value) |
82
|
|
|
|
83
|
|
|
loop = 1 |
84
|
|
|
|
85
|
|
|
for case in form.cleaned_data["case"]: |
86
|
|
|
try: |
87
|
|
|
tcp = TestCasePlan.objects.get( |
88
|
|
|
plan=form.cleaned_data["plan"], case=case |
89
|
|
|
) |
90
|
|
|
sortkey = tcp.sortkey |
91
|
|
|
except ObjectDoesNotExist: |
92
|
|
|
sortkey = loop * 10 |
93
|
|
|
|
94
|
|
|
test_run.create_execution( |
95
|
|
|
case=case, |
96
|
|
|
assignee=form.cleaned_data["default_tester"], |
97
|
|
|
sortkey=sortkey, |
98
|
|
|
matrix_type=form.cleaned_data["matrix_type"], |
99
|
|
|
) |
100
|
|
|
loop += 1 |
101
|
|
|
|
102
|
|
|
return HttpResponseRedirect( |
103
|
|
|
reverse( |
104
|
|
|
"testruns-get", |
105
|
|
|
args=[ |
106
|
|
|
test_run.pk, |
107
|
|
|
], |
108
|
|
|
) |
109
|
|
|
) |
110
|
|
|
|
111
|
|
|
test_cases = ( |
112
|
|
|
TestCase.objects.filter(pk__in=request.POST.getlist("case")) |
113
|
|
|
.select_related("author", "case_status", "category", "priority") |
114
|
|
|
.order_by("pk") |
115
|
|
|
) |
116
|
|
|
|
117
|
|
|
context_data = { |
118
|
|
|
"plan_id": request.POST.get("plan"), |
119
|
|
|
"test_cases": test_cases, |
120
|
|
|
"form": form, |
121
|
|
|
"disabled_cases": get_disabled_test_cases_count(test_cases), |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
return render(request, self.template_name, context_data) |
125
|
|
|
|
126
|
|
|
|
127
|
|
|
@method_decorator(permission_required("testruns.view_testrun"), name="dispatch") |
128
|
|
|
class SearchTestRunView(TemplateView): |
129
|
|
|
|
130
|
|
|
template_name = "testruns/search.html" |
131
|
|
|
|
132
|
|
|
def get_context_data(self, **kwargs): |
133
|
|
|
form = SearchRunForm(self.request.GET) |
134
|
|
|
form.populate(product_id=self.request.GET.get("product")) |
135
|
|
|
|
136
|
|
|
return { |
137
|
|
|
"form": form, |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
|
141
|
|
|
@method_decorator( |
142
|
|
|
object_permission_required( |
143
|
|
|
"testruns.view_testrun", (TestRun, "pk", "pk"), accept_global_perms=True |
144
|
|
|
), |
145
|
|
|
name="dispatch", |
146
|
|
|
) |
147
|
|
|
class GetTestRunView(DetailView): |
148
|
|
|
|
149
|
|
|
template_name = "testruns/get.html" |
150
|
|
|
http_method_names = ["get"] |
151
|
|
|
model = TestRun |
152
|
|
|
|
153
|
|
|
def get_context_data(self, **kwargs): |
154
|
|
|
context = super().get_context_data(**kwargs) |
155
|
|
|
context["execution_statuses"] = TestExecutionStatus.objects.order_by( |
156
|
|
|
"-weight", "name" |
157
|
|
|
) |
158
|
|
|
context["confirmed_statuses"] = TestCaseStatus.objects.filter(is_confirmed=True) |
159
|
|
|
context["link_form"] = LinkReferenceForm() |
160
|
|
|
context["bug_trackers"] = BugSystem.objects.all() |
161
|
|
|
context["comment_form"] = SimpleCommentForm() |
162
|
|
|
context["OBJECT_MENU_ITEMS"] = [ |
163
|
|
|
( |
164
|
|
|
"...", |
165
|
|
|
[ |
166
|
|
|
( |
167
|
|
|
_("Edit"), |
168
|
|
|
reverse("testruns-edit", args=[self.object.pk]), |
169
|
|
|
), |
170
|
|
|
( |
171
|
|
|
_("Clone"), |
172
|
|
|
reverse("testruns-clone", args=[self.object.pk]), |
173
|
|
|
), |
174
|
|
|
( |
175
|
|
|
_("History"), |
176
|
|
|
f"/admin/testruns/testrun/{self.object.pk}/history/", |
177
|
|
|
), |
178
|
|
|
("-", "-"), |
179
|
|
|
( |
180
|
|
|
_("Object permissions"), |
181
|
|
|
reverse( |
182
|
|
|
"admin:testruns_testrun_permissions", |
183
|
|
|
args=[self.object.pk], |
184
|
|
|
), |
185
|
|
|
), |
186
|
|
|
("-", "-"), |
187
|
|
|
( |
188
|
|
|
_("Delete"), |
189
|
|
|
reverse( |
190
|
|
|
"admin:testruns_testrun_delete", |
191
|
|
|
args=[self.object.pk], |
192
|
|
|
), |
193
|
|
|
), |
194
|
|
|
], |
195
|
|
|
) |
196
|
|
|
] |
197
|
|
|
|
198
|
|
|
return context |
199
|
|
|
|
200
|
|
|
|
201
|
|
|
@method_decorator( |
202
|
|
|
object_permission_required( |
203
|
|
|
"testruns.change_testrun", (TestRun, "pk", "pk"), accept_global_perms=True |
204
|
|
|
), |
205
|
|
|
name="dispatch", |
206
|
|
|
) |
207
|
|
|
class EditTestRunView(UpdateView): |
208
|
|
|
model = TestRun |
209
|
|
|
template_name = "testruns/mutable.html" |
210
|
|
|
form_class = NewRunForm |
211
|
|
|
|
212
|
|
|
def get_form(self, form_class=None): |
213
|
|
|
form = super().get_form(form_class) |
214
|
|
|
form.populate(self.object.plan_id) |
215
|
|
|
|
216
|
|
|
return form |
217
|
|
|
|
218
|
|
|
def get_context_data(self, **kwargs): |
219
|
|
|
context = super().get_context_data(**kwargs) |
220
|
|
|
context["plan_id"] = self.object.plan |
221
|
|
|
return context |
222
|
|
|
|
223
|
|
|
def get_initial(self): |
224
|
|
|
return { |
225
|
|
|
"manager": self.object.manager, |
226
|
|
|
"default_tester": self.object.default_tester, |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
|
230
|
|
|
@method_decorator(permission_required("testruns.add_testrun"), name="dispatch") |
231
|
|
|
class CloneTestRunView(NewTestRunView): |
232
|
|
|
# note: post is handled directly by NewTestRunView |
233
|
|
|
# b/c <form action> points to testruns-new URL |
234
|
|
|
http_method_names = ["get"] |
235
|
|
|
|
236
|
|
|
def get(self, request, pk): # pylint: disable=arguments-differ |
237
|
|
|
test_run = get_object_or_404(TestRun, pk=pk) |
238
|
|
|
|
239
|
|
|
request.GET._mutable = True # pylint: disable=protected-access |
240
|
|
|
request.GET["p"] = test_run.plan_id |
241
|
|
|
request.GET.setlist( |
242
|
|
|
"c", test_run.executions.all().values_list("case", flat=True) |
243
|
|
|
) |
244
|
|
|
|
245
|
|
|
form_initial = { |
246
|
|
|
"summary": _("Clone of ") + test_run.summary, |
247
|
|
|
"notes": test_run.notes, |
248
|
|
|
"manager": test_run.manager, |
249
|
|
|
"build": test_run.build_id, |
250
|
|
|
"default_tester": test_run.default_tester, |
251
|
|
|
"plan": test_run.plan_id, |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
return super().get(request, form_initial=form_initial, is_cloning=True) |
255
|
|
|
|
256
|
|
|
|
257
|
|
|
def get_disabled_test_cases_count(test_cases): |
258
|
|
|
return test_cases.filter(case_status__is_confirmed=False).count() |
259
|
|
|
|
260
|
|
|
|
261
|
|
|
@method_decorator( |
262
|
|
|
object_permission_required( |
263
|
|
|
"testruns.view_environment", (Environment, "pk", "pk"), accept_global_perms=True |
264
|
|
|
), |
265
|
|
|
name="dispatch", |
266
|
|
|
) |
267
|
|
|
class GetEnvironment(DetailView): |
268
|
|
|
template_name = "testruns/environment.html" |
269
|
|
|
http_method_names = ["get"] |
270
|
|
|
model = Environment |
271
|
|
|
|
272
|
|
|
def get_context_data(self, **kwargs): |
273
|
|
|
context = super().get_context_data(**kwargs) |
274
|
|
|
context["OBJECT_MENU_ITEMS"] = [ |
275
|
|
|
( |
276
|
|
|
"...", |
277
|
|
|
[ |
278
|
|
|
( |
279
|
|
|
_("Edit"), |
280
|
|
|
reverse( |
281
|
|
|
"admin:testruns_environment_change", |
282
|
|
|
args=[self.object.pk], |
283
|
|
|
), |
284
|
|
|
), |
285
|
|
|
("-", "-"), |
286
|
|
|
( |
287
|
|
|
_("Object permissions"), |
288
|
|
|
reverse( |
289
|
|
|
"admin:testruns_environment_permissions", |
290
|
|
|
args=[self.object.pk], |
291
|
|
|
), |
292
|
|
|
), |
293
|
|
|
("-", "-"), |
294
|
|
|
( |
295
|
|
|
_("Delete"), |
296
|
|
|
reverse( |
297
|
|
|
"admin:testruns_environment_delete", |
298
|
|
|
args=[self.object.pk], |
299
|
|
|
), |
300
|
|
|
), |
301
|
|
|
], |
302
|
|
|
) |
303
|
|
|
] |
304
|
|
|
|
305
|
|
|
return context |
306
|
|
|
|