1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
|
|
from modernrpc.core import rpc_method, REQUEST_KEY |
4
|
|
|
|
5
|
|
|
from tcms.core.utils import form_errors_to_list |
6
|
|
|
from tcms.management.models import Tag |
7
|
|
|
from tcms.testplans.models import TestPlan |
8
|
|
|
from tcms.testcases.models import TestCase, TestCasePlan |
9
|
|
|
|
10
|
|
|
from tcms.rpc import utils |
11
|
|
|
from tcms.rpc.api.forms.testplan import EditPlanForm, NewPlanForm |
12
|
|
|
from tcms.rpc.decorators import permissions_required |
13
|
|
|
|
14
|
|
|
__all__ = ( |
15
|
|
|
'create', |
16
|
|
|
'update', |
17
|
|
|
'filter', |
18
|
|
|
|
19
|
|
|
'add_case', |
20
|
|
|
'remove_case', |
21
|
|
|
|
22
|
|
|
'add_tag', |
23
|
|
|
'remove_tag', |
24
|
|
|
|
25
|
|
|
'add_attachment', |
26
|
|
|
'list_attachments', |
27
|
|
|
) |
28
|
|
|
|
29
|
|
|
|
30
|
|
|
@permissions_required('testplans.add_testplan') |
31
|
|
|
@rpc_method(name='TestPlan.create') |
32
|
|
|
def create(values, **kwargs): |
33
|
|
|
""" |
34
|
|
|
.. function:: XML-RPC TestPlan.create(values) |
35
|
|
|
|
36
|
|
|
Create new Test Plan object and store it in the database. |
37
|
|
|
|
38
|
|
|
:param values: Field values for :class:`tcms.testplans.models.TestPlan` |
39
|
|
|
:type values: dict |
40
|
|
|
:return: Serialized :class:`tcms.testplans.models.TestPlan` object |
41
|
|
|
:rtype: dict |
42
|
|
|
:raises: PermissionDenied if missing *testplans.add_testplan* permission |
43
|
|
|
:raises: ValueError if data validation fails |
44
|
|
|
|
45
|
|
|
Minimal parameters:: |
46
|
|
|
|
47
|
|
|
>>> values = { |
48
|
|
|
'product': 61, |
49
|
|
|
'name': 'Testplan foobar', |
50
|
|
|
'type': 1, |
51
|
|
|
'parent_id': 150, |
52
|
|
|
'default_product_version': 93, |
53
|
|
|
'text':'Testing TCMS', |
54
|
|
|
} |
55
|
|
|
>>> TestPlan.create(values) |
56
|
|
|
""" |
57
|
|
|
if values.get('default_product_version'): |
58
|
|
|
values['product_version'] = values.pop('default_product_version') |
59
|
|
|
|
60
|
|
|
if not values.get('product'): |
61
|
|
|
raise ValueError('Value of product is required') |
62
|
|
|
|
63
|
|
|
form = NewPlanForm(values) |
64
|
|
|
form.populate(product_id=values['product']) |
65
|
|
|
|
66
|
|
|
if form.is_valid(): |
67
|
|
|
request = kwargs.get(REQUEST_KEY) |
68
|
|
|
test_plan = TestPlan.objects.create( |
69
|
|
|
product=form.cleaned_data['product'], |
70
|
|
|
name=form.cleaned_data['name'], |
71
|
|
|
type=form.cleaned_data['type'], |
72
|
|
|
author=request.user, |
73
|
|
|
product_version=form.cleaned_data['product_version'], |
74
|
|
|
parent=form.cleaned_data['parent'], |
75
|
|
|
is_active=form.cleaned_data['is_active'], |
76
|
|
|
text=form.cleaned_data['text'], |
77
|
|
|
) |
78
|
|
|
|
79
|
|
|
return test_plan.serialize() |
80
|
|
|
raise ValueError(form_errors_to_list(form)) |
81
|
|
|
|
82
|
|
|
|
83
|
|
|
@rpc_method(name='TestPlan.filter') |
84
|
|
|
def filter(query=None): # pylint: disable=redefined-builtin |
85
|
|
|
""" |
86
|
|
|
.. function:: XML-RPC TestPlan.filter(query) |
87
|
|
|
|
88
|
|
|
Perform a search and return the resulting list of test plans. |
89
|
|
|
|
90
|
|
|
:param query: Field lookups for :class:`tcms.testplans.models.TestPlan` |
91
|
|
|
:type query: dict |
92
|
|
|
:return: List of serialized :class:`tcms.testplans.models.TestPlan` objects |
93
|
|
|
:rtype: list(dict) |
94
|
|
|
""" |
95
|
|
|
|
96
|
|
|
if query is None: |
97
|
|
|
query = {} |
98
|
|
|
|
99
|
|
|
return TestPlan.to_xmlrpc(query) |
100
|
|
|
|
101
|
|
|
|
102
|
|
|
@permissions_required('testplans.add_testplantag') |
103
|
|
|
@rpc_method(name='TestPlan.add_tag') |
104
|
|
|
def add_tag(plan_id, tag_name, **kwargs): |
105
|
|
|
""" |
106
|
|
|
.. function:: XML-RPC TestPlan.add_tag(plan_id, tag_name) |
107
|
|
|
|
108
|
|
|
Add a tag to the specified test plan. |
109
|
|
|
|
110
|
|
|
:param plan_id: PK of TestPlan to modify |
111
|
|
|
:type plan_id: int |
112
|
|
|
:param tag_name: Tag name to add |
113
|
|
|
:type tag_name: str |
114
|
|
|
:return: None |
115
|
|
|
:raises: PermissionDenied if missing *testplans.add_testplantag* permission |
116
|
|
|
:raises: TestPlan.DoesNotExist if object specified by PK doesn't exist |
117
|
|
|
:raises: Tag.DoesNotExist if missing *management.add_tag* permission and *tag_name* |
118
|
|
|
doesn't exist in the database! |
119
|
|
|
""" |
120
|
|
|
request = kwargs.get(REQUEST_KEY) |
121
|
|
|
tag, _ = Tag.get_or_create(request.user, tag_name) |
122
|
|
|
TestPlan.objects.get(pk=plan_id).add_tag(tag) |
123
|
|
|
|
124
|
|
|
|
125
|
|
|
@permissions_required('testplans.delete_testplantag') |
126
|
|
|
@rpc_method(name='TestPlan.remove_tag') |
127
|
|
|
def remove_tag(plan_id, tag_name): |
128
|
|
|
""" |
129
|
|
|
.. function:: XML-RPC TestPlan.remove_tag(plan_id, tag_name) |
130
|
|
|
|
131
|
|
|
Remove tag from the specified test plan. |
132
|
|
|
|
133
|
|
|
:param plan_id: PK of TestPlan to modify |
134
|
|
|
:type plan_id: int |
135
|
|
|
:param tag_name: Tag name to remove |
136
|
|
|
:type tag_name: str |
137
|
|
|
:return: None |
138
|
|
|
:raises: PermissionDenied if missing *testplans.delete_testplantag* permission |
139
|
|
|
:raises: DoesNotExist if objects specified don't exist |
140
|
|
|
""" |
141
|
|
|
tag = Tag.objects.get(name=tag_name) |
142
|
|
|
TestPlan.objects.get(pk=plan_id).remove_tag(tag) |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
@permissions_required('testplans.change_testplan') |
146
|
|
|
@rpc_method(name='TestPlan.update') |
147
|
|
|
def update(plan_id, values): |
148
|
|
|
""" |
149
|
|
|
.. function:: XML-RPC TestPlan.update(plan_id, values) |
150
|
|
|
|
151
|
|
|
Update the fields of the selected test plan. |
152
|
|
|
|
153
|
|
|
:param plan_id: PK of TestPlan to modify |
154
|
|
|
:type plan_id: int |
155
|
|
|
:param values: Field values for :class:`tcms.testplans.models.TestPlan` |
156
|
|
|
:type values: dict |
157
|
|
|
:return: Serialized :class:`tcms.testplans.models.TestPlan` object |
158
|
|
|
:rtype: dict |
159
|
|
|
:raises: TestPlan.DoesNotExist if object specified by PK doesn't exist |
160
|
|
|
:raises: PermissionDenied if missing *testplans.change_testplan* permission |
161
|
|
|
:raises: ValueError if validations fail |
162
|
|
|
""" |
163
|
|
|
|
164
|
|
|
if values.get('default_product_version'): |
165
|
|
|
values['product_version'] = values.pop('default_product_version') |
166
|
|
|
|
167
|
|
|
if values.get('product_version') and not values.get('product'): |
168
|
|
|
raise ValueError('Field "product" is required by product_version') |
169
|
|
|
|
170
|
|
|
if values.get('product') and not values.get('product_version'): |
171
|
|
|
raise ValueError('Field "product_version" is required by product') |
172
|
|
|
|
173
|
|
|
form = EditPlanForm(values) |
174
|
|
|
if values.get('product_version') and values.get('product'): |
175
|
|
|
form.populate(product_id=values['product']) |
176
|
|
|
|
177
|
|
|
test_plan = TestPlan.objects.get(pk=plan_id) |
178
|
|
|
|
179
|
|
|
if form.is_valid(): |
180
|
|
|
return _get_updated_test_plan(values, form, test_plan).serialize() |
181
|
|
|
|
182
|
|
|
raise ValueError(form_errors_to_list(form)) |
183
|
|
|
|
184
|
|
|
|
185
|
|
|
def _get_updated_test_plan(values, form, test_plan): |
186
|
|
|
if form.cleaned_data['name']: |
187
|
|
|
test_plan.name = form.cleaned_data['name'] |
188
|
|
|
|
189
|
|
|
if form.cleaned_data['type']: |
190
|
|
|
test_plan.type = form.cleaned_data['type'] |
191
|
|
|
|
192
|
|
|
if form.cleaned_data['product']: |
193
|
|
|
test_plan.product = form.cleaned_data['product'] |
194
|
|
|
|
195
|
|
|
if form.cleaned_data['product_version']: |
196
|
|
|
test_plan.product_version = form.cleaned_data['product_version'] |
197
|
|
|
|
198
|
|
|
if form.cleaned_data['parent']: |
199
|
|
|
test_plan.parent = form.cleaned_data['parent'] |
200
|
|
|
|
201
|
|
|
if values.get('is_active') is not None: |
202
|
|
|
test_plan.is_active = form.cleaned_data['is_active'] |
203
|
|
|
|
204
|
|
|
if form.cleaned_data['text']: |
205
|
|
|
test_plan.text = form.cleaned_data['text'] |
206
|
|
|
|
207
|
|
|
test_plan.save() |
208
|
|
|
|
209
|
|
|
return test_plan |
210
|
|
|
|
211
|
|
|
|
212
|
|
|
@permissions_required('testcases.add_testcaseplan') |
213
|
|
|
@rpc_method(name='TestPlan.add_case') |
214
|
|
|
def add_case(plan_id, case_id): |
215
|
|
|
""" |
216
|
|
|
.. function:: XML-RPC TestPlan.add_case(plan_id, case_id) |
217
|
|
|
|
218
|
|
|
Link test case to the given plan. |
219
|
|
|
|
220
|
|
|
:param plan_id: PK of TestPlan to modify |
221
|
|
|
:type plan_id: int |
222
|
|
|
:param case_id: PK of TestCase to be added to plan |
223
|
|
|
:type case_id: int |
224
|
|
|
:return: None |
225
|
|
|
:raises: TestPlan.DoesNotExit or TestCase.DoesNotExist if objects specified |
226
|
|
|
by PKs are missing |
227
|
|
|
:raises: PermissionDenied if missing *testcases.add_testcaseplan* permission |
228
|
|
|
""" |
229
|
|
|
plan = TestPlan.objects.get(pk=plan_id) |
230
|
|
|
case = TestCase.objects.get(pk=case_id) |
231
|
|
|
plan.add_case(case) |
232
|
|
|
|
233
|
|
|
|
234
|
|
|
@permissions_required('testcases.delete_testcaseplan') |
235
|
|
|
@rpc_method(name='TestPlan.remove_case') |
236
|
|
|
def remove_case(plan_id, case_id): |
237
|
|
|
""" |
238
|
|
|
.. function:: XML-RPC TestPlan.remove_case(plan_id, case_id) |
239
|
|
|
|
240
|
|
|
Unlink a test case from the given plan. |
241
|
|
|
|
242
|
|
|
:param plan_id: PK of TestPlan to modify |
243
|
|
|
:type plan_id: int |
244
|
|
|
:param case_id: PK of TestCase to be removed from plan |
245
|
|
|
:type case_id: int |
246
|
|
|
:return: None |
247
|
|
|
:raises: PermissionDenied if missing *testcases.delete_testcaseplan* permission |
248
|
|
|
""" |
249
|
|
|
TestCasePlan.objects.filter(case=case_id, plan=plan_id).delete() |
250
|
|
|
|
251
|
|
|
|
252
|
|
|
@permissions_required('attachments.view_attachment') |
253
|
|
|
@rpc_method(name='TestPlan.list_attachments') |
254
|
|
|
def list_attachments(plan_id, **kwargs): |
255
|
|
|
""" |
256
|
|
|
.. function:: XML-RPC TestPlan.list_attachments(plan_id) |
257
|
|
|
|
258
|
|
|
List attachments for the given TestPlan. |
259
|
|
|
|
260
|
|
|
:param plan_id: PK of TestPlan to inspect |
261
|
|
|
:type plan_id: int |
262
|
|
|
:return: A list containing information and download URLs for attachements |
263
|
|
|
:rtype: list |
264
|
|
|
:raises: TestPlan.DoesNotExit if object specified by PK is missing |
265
|
|
|
""" |
266
|
|
|
plan = TestPlan.objects.get(pk=plan_id) |
267
|
|
|
request = kwargs.get(REQUEST_KEY) |
268
|
|
|
return utils.get_attachments_for(request, plan) |
269
|
|
|
|
270
|
|
|
|
271
|
|
|
@permissions_required('attachments.add_attachment') |
272
|
|
|
@rpc_method(name='TestPlan.add_attachment') |
273
|
|
|
def add_attachment(plan_id, filename, b64content, **kwargs): |
274
|
|
|
""" |
275
|
|
|
.. function:: XML-RPC TestPlan.add_attachment(plan_id, filename, b64content) |
276
|
|
|
|
277
|
|
|
Add attachment to the given TestPlan. |
278
|
|
|
|
279
|
|
|
:param plan_id: PK of TestPlan |
280
|
|
|
:type plan_id: int |
281
|
|
|
:param filename: File name of attachment, e.g. 'logs.txt' |
282
|
|
|
:type filename: str |
283
|
|
|
:param b64content: Base64 encoded content |
284
|
|
|
:type b64content: str |
285
|
|
|
:return: None |
286
|
|
|
""" |
287
|
|
|
utils.add_attachment( |
288
|
|
|
plan_id, |
289
|
|
|
'testplans.TestPlan', |
290
|
|
|
kwargs.get(REQUEST_KEY).user, |
291
|
|
|
filename, |
292
|
|
|
b64content) |
293
|
|
|
|