1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
""" |
3
|
|
|
Shared functions for plan/case/run. |
4
|
|
|
|
5
|
|
|
Most of these functions are use for Ajax. |
6
|
|
|
""" |
7
|
|
|
from http import HTTPStatus |
8
|
|
|
|
9
|
|
|
from django.db.models import Count |
10
|
|
|
from django.http import JsonResponse |
11
|
|
|
from django.views.generic.base import View |
12
|
|
|
from django.contrib.auth import get_user_model |
13
|
|
|
from django.utils.decorators import method_decorator |
14
|
|
|
from django.views.decorators.http import require_POST |
15
|
|
|
from django.shortcuts import render |
16
|
|
|
from django.utils.translation import ugettext_lazy as _ |
17
|
|
|
from django.contrib.auth.decorators import permission_required |
18
|
|
|
|
19
|
|
|
from tcms.testcases.models import TestCase |
20
|
|
|
from tcms.testcases.models import TestCaseTag |
21
|
|
|
from tcms.testplans.models import TestPlan, TestPlanTag |
22
|
|
|
from tcms.testruns.models import TestExecution, TestRunTag |
23
|
|
|
from tcms.core.helpers.comments import add_comment |
24
|
|
|
|
25
|
|
|
|
26
|
|
|
def tags(request): |
27
|
|
|
""" |
28
|
|
|
Get tags for TestPlan or TestCase. |
29
|
|
|
Also counts how many times the same tag has been used for |
30
|
|
|
different objects. Used in TP -> Tags and TC -> Tags tabs! |
31
|
|
|
""" |
32
|
|
|
|
33
|
|
|
template_name, obj = _TagObjects(request).get() |
34
|
|
|
|
35
|
|
|
all_tags = obj.tag.all().order_by('pk') |
36
|
|
|
test_plan_tags = TestPlanTag.objects.filter( |
37
|
|
|
tag__in=all_tags).values('tag').annotate(num_plans=Count('tag')).order_by('tag') |
38
|
|
|
test_case_tags = TestCaseTag.objects.filter( |
39
|
|
|
tag__in=all_tags).values('tag').annotate(num_cases=Count('tag')).order_by('tag') |
40
|
|
|
test_run_tags = TestRunTag.objects.filter( |
41
|
|
|
tag__in=all_tags).values('tag').annotate(num_runs=Count('tag')).order_by('tag') |
42
|
|
|
|
43
|
|
|
plan_counter = _TagCounter('num_plans', test_plan_tags) |
44
|
|
|
case_counter = _TagCounter('num_cases', test_case_tags) |
45
|
|
|
run_counter = _TagCounter('num_runs', test_run_tags) |
46
|
|
|
|
47
|
|
|
for tag in all_tags: |
48
|
|
|
tag.num_plans = plan_counter.calculate_tag_count(tag) |
49
|
|
|
tag.num_cases = case_counter.calculate_tag_count(tag) |
50
|
|
|
tag.num_runs = run_counter.calculate_tag_count(tag) |
51
|
|
|
|
52
|
|
|
api_module = 'NotExisting' |
53
|
|
|
if isinstance(obj, TestPlan): |
54
|
|
|
api_module = 'TestPlan' |
55
|
|
|
|
56
|
|
|
if isinstance(obj, TestCase): |
57
|
|
|
api_module = 'TestCase' |
58
|
|
|
|
59
|
|
|
context_data = { |
60
|
|
|
'tags': all_tags, |
61
|
|
|
'object': obj, |
62
|
|
|
'api_module': api_module, |
63
|
|
|
} |
64
|
|
|
# todo: convert this method into returning pure JSON and |
65
|
|
|
# render inside the browser. Also move it under management.views |
66
|
|
|
return render(request, template_name, context_data) |
67
|
|
|
|
68
|
|
|
|
69
|
|
|
class _TagObjects: |
70
|
|
|
""" Used for getting the chosen object(TestPlan, TestCase or TestRun) from the database """ |
71
|
|
|
|
72
|
|
|
def __init__(self, request): |
73
|
|
|
""" |
74
|
|
|
:param request: An HTTP GET request, containing the primary key |
75
|
|
|
and the type of object to be selected |
76
|
|
|
:type request: HttpRequest |
77
|
|
|
""" |
78
|
|
|
for obj in ['plan', 'case']: |
79
|
|
|
if request.GET.get(obj): |
80
|
|
|
self.object = obj |
81
|
|
|
self.object_pk = request.GET.get(obj) |
82
|
|
|
break |
83
|
|
|
|
84
|
|
|
def get(self): |
85
|
|
|
func = getattr(self, self.object) |
86
|
|
|
return func() |
87
|
|
|
|
88
|
|
|
def plan(self): |
89
|
|
|
return 'management/get_tag.html', TestPlan.objects.get(pk=self.object_pk) |
90
|
|
|
|
91
|
|
|
def case(self): |
92
|
|
|
return 'management/get_tag.html', TestCase.objects.get(pk=self.object_pk) |
93
|
|
|
|
94
|
|
|
|
95
|
|
|
class _TagCounter: # pylint: disable=too-few-public-methods |
96
|
|
|
""" Used for counting the number of times a tag is assigned to TestRun/TestCase/TestPlan """ |
97
|
|
|
|
98
|
|
|
def __init__(self, key, test_tags): |
99
|
|
|
""" |
100
|
|
|
:param key: either 'num_plans', 'num_cases', 'num_runs', depending on what you want count |
101
|
|
|
:type key: str |
102
|
|
|
:param test_tags: query set, containing the Tag->Object relationship, ordered by tag and |
103
|
|
|
annotated by key |
104
|
|
|
e.g. TestPlanTag, TestCaseTag ot TestRunTag |
105
|
|
|
:type test_tags: QuerySet |
106
|
|
|
""" |
107
|
|
|
self.key = key |
108
|
|
|
self.test_tags = iter(test_tags) |
109
|
|
|
self.counter = {'tag': 0} |
110
|
|
|
|
111
|
|
|
def calculate_tag_count(self, tag): |
112
|
|
|
""" |
113
|
|
|
:param tag: the tag you do the counting for |
114
|
|
|
:type tag: :class:`tcms.management.models.Tag` |
115
|
|
|
:return: the number of times a tag is assigned to object |
116
|
|
|
:rtype: int |
117
|
|
|
""" |
118
|
|
|
if self.counter['tag'] != tag.pk: |
119
|
|
|
try: |
120
|
|
|
self.counter = self.test_tags.__next__() |
121
|
|
|
except StopIteration: |
122
|
|
|
return 0 |
123
|
|
|
|
124
|
|
|
if tag.pk == self.counter['tag']: |
125
|
|
|
return self.counter[self.key] |
126
|
|
|
return 0 |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
def say_no(error_msg): |
130
|
|
|
return JsonResponse({'rc': 1, 'response': error_msg}) |
131
|
|
|
|
132
|
|
|
|
133
|
|
|
def say_yes(): |
134
|
|
|
return JsonResponse({'rc': 0, 'response': 'ok'}) |
135
|
|
|
|
136
|
|
|
|
137
|
|
|
@method_decorator(permission_required('testcases.change_testcase'), name='dispatch') |
138
|
|
|
class UpdateTestCaseActorsView(View): |
139
|
|
|
""" |
140
|
|
|
Updates TestCase.default_tester_id or TestCase.reviewer_id. |
141
|
|
|
Called from the front-end. |
142
|
|
|
""" |
143
|
|
|
|
144
|
|
|
http_method_names = ['post'] |
145
|
|
|
|
146
|
|
|
def post(self, request): |
147
|
|
|
username = request.POST.get('username') |
148
|
|
|
User = get_user_model() # pylint: disable=invalid-name |
149
|
|
|
try: |
150
|
|
|
user = User.objects.get(username=username) |
151
|
|
|
except User.DoesNotExist: |
152
|
|
|
try: |
153
|
|
|
user = User.objects.get(email=username) |
154
|
|
|
except User.DoesNotExist: |
155
|
|
|
return JsonResponse({'rc': 1, |
156
|
|
|
'response': _('User %s not found!' % username)}, |
157
|
|
|
status=HTTPStatus.NOT_FOUND) |
158
|
|
|
|
159
|
|
|
what_to_update = request.POST.get('what_to_update') |
160
|
|
|
case_ids = request.POST.getlist('case[]') |
161
|
|
|
for test_case in TestCase.objects.filter(pk__in=case_ids): |
162
|
|
|
if what_to_update == 'default_tester': |
163
|
|
|
test_case.default_tester_id = user.pk |
164
|
|
|
elif what_to_update == 'reviewer': |
165
|
|
|
test_case.reviewer_id = user.pk |
166
|
|
|
|
167
|
|
|
test_case.save() |
168
|
|
|
|
169
|
|
|
return JsonResponse({'rc': 0, 'response': 'ok'}) |
170
|
|
|
|
171
|
|
|
|
172
|
|
|
@require_POST |
173
|
|
|
def comment_case_runs(request): |
174
|
|
|
""" |
175
|
|
|
Add comment to one or more caseruns at a time. |
176
|
|
|
""" |
177
|
|
|
data = request.POST.copy() |
178
|
|
|
comment = data.get('comment', None) |
179
|
|
|
if not comment: |
180
|
|
|
return say_no('Comments needed') |
181
|
|
|
run_ids = [] |
182
|
|
|
for run_id in data.get('run', '').split(','): |
183
|
|
|
if run_id: |
184
|
|
|
run_ids.append(run_id) |
185
|
|
|
if not run_ids: |
186
|
|
|
return say_no('No runs selected.') |
187
|
|
|
runs = TestExecution.objects.filter(pk__in=run_ids).only('pk') |
188
|
|
|
if not runs: |
189
|
|
|
return say_no('No caserun found.') |
190
|
|
|
add_comment(runs, comment, request.user) |
191
|
|
|
return say_yes() |
192
|
|
|
|