1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
|
|
from django.forms import EmailField, ValidationError |
4
|
|
|
|
5
|
|
|
from modernrpc.core import rpc_method, REQUEST_KEY |
6
|
|
|
|
7
|
|
|
from tcms.core.utils import form_errors_to_list |
8
|
|
|
from tcms.management.models import Tag |
9
|
|
|
from tcms.management.models import Component |
10
|
|
|
from tcms.testcases.models import TestCase |
11
|
|
|
from tcms.xmlrpc.forms import UpdateCaseForm, NewCaseForm |
12
|
|
|
|
13
|
|
|
from tcms.xmlrpc.decorators import permissions_required |
14
|
|
|
|
15
|
|
|
|
16
|
|
|
__all__ = ( |
17
|
|
|
'create', |
18
|
|
|
'update', |
19
|
|
|
'filter', |
20
|
|
|
'remove', |
21
|
|
|
|
22
|
|
|
'add_component', |
23
|
|
|
'get_components', |
24
|
|
|
'remove_component', |
25
|
|
|
|
26
|
|
|
'add_notification_cc', |
27
|
|
|
'get_notification_cc', |
28
|
|
|
'remove_notification_cc', |
29
|
|
|
|
30
|
|
|
'add_tag', |
31
|
|
|
'remove_tag', |
32
|
|
|
) |
33
|
|
|
|
34
|
|
|
|
35
|
|
|
@permissions_required('testcases.add_testcasecomponent') |
36
|
|
|
@rpc_method(name='TestCase.add_component') |
37
|
|
|
def add_component(case_id, component): |
38
|
|
|
""" |
39
|
|
|
.. function:: XML-RPC TestCase.add_component(case_id, component_id) |
40
|
|
|
|
41
|
|
|
Add component to the selected test case. |
42
|
|
|
|
43
|
|
|
:param case_id: PK of TestCase to modify |
44
|
|
|
:type case_id: int |
45
|
|
|
:param component: Name of Component to add |
46
|
|
|
:type component_id: str |
47
|
|
|
:return: None |
48
|
|
|
:raises: PermissionDenied if missing the *testcases.add_testcasecomponent* |
49
|
|
|
permission |
50
|
|
|
:raises: DoesNotExist if missing test case or component that match the |
51
|
|
|
specified PKs |
52
|
|
|
""" |
53
|
|
|
TestCase.objects.get(pk=case_id).add_component( |
54
|
|
|
Component.objects.get(name=component) |
55
|
|
|
) |
56
|
|
|
|
57
|
|
|
|
58
|
|
|
@rpc_method(name='TestCase.get_components') |
59
|
|
|
def get_components(case_id): |
60
|
|
|
""" |
61
|
|
|
.. function:: XML-RPC TestCase.get_components(case_id) |
62
|
|
|
|
63
|
|
|
Get the list of components attached to this case. |
64
|
|
|
|
65
|
|
|
:param case_id: PK if TestCase |
66
|
|
|
:type case_id: int |
67
|
|
|
:return: Serialized list of :class:`tcms.management.models.Component` objects |
68
|
|
|
:rtype: list(dict) |
69
|
|
|
:raises: TestCase.DoesNotExist if missing test case matching PK |
70
|
|
|
""" |
71
|
|
|
test_case = TestCase.objects.get(case_id=case_id) |
72
|
|
|
|
73
|
|
|
component_ids = test_case.component.values_list('id', flat=True) |
74
|
|
|
query = {'id__in': component_ids} |
75
|
|
|
return Component.to_xmlrpc(query) |
76
|
|
|
|
77
|
|
|
|
78
|
|
|
@permissions_required('testcases.delete_testcasecomponent') |
79
|
|
|
@rpc_method(name='TestCase.remove_component') |
80
|
|
|
def remove_component(case_id, component_id): |
81
|
|
|
""" |
82
|
|
|
.. function:: XML-RPC TestCase.remove_component(case_id, component_id) |
83
|
|
|
|
84
|
|
|
Remove selected component from the selected test case. |
85
|
|
|
|
86
|
|
|
:param case_id: PK of TestCase to modify |
87
|
|
|
:type case_id: int |
88
|
|
|
:param component_id: PK of Component to remove |
89
|
|
|
:type component_id: int |
90
|
|
|
:return: None |
91
|
|
|
:raises: PermissionDenied if missing the *testcases.delete_testcasecomponent* |
92
|
|
|
permission |
93
|
|
|
:raises: DoesNotExist if missing test case or component that match the |
94
|
|
|
specified PKs |
95
|
|
|
""" |
96
|
|
|
TestCase.objects.get(pk=case_id).remove_component( |
97
|
|
|
Component.objects.get(pk=component_id) |
98
|
|
|
) |
99
|
|
|
|
100
|
|
|
|
101
|
|
|
def _validate_cc_list(cc_list): |
102
|
|
|
""" |
103
|
|
|
Validate each email address given in argument. Called by |
104
|
|
|
notification RPC methods. |
105
|
|
|
|
106
|
|
|
:param cc_list: List of email addresses |
107
|
|
|
:type cc_list: list |
108
|
|
|
:return: None |
109
|
|
|
:raises: TypeError or ValidationError if addresses are not valid. |
110
|
|
|
""" |
111
|
|
|
|
112
|
|
|
if not isinstance(cc_list, list): |
113
|
|
|
raise TypeError('cc_list should be a list object.') |
114
|
|
|
|
115
|
|
|
field = EmailField(required=True) |
116
|
|
|
invalid_emails = [] |
117
|
|
|
|
118
|
|
|
for item in cc_list: |
119
|
|
|
try: |
120
|
|
|
field.clean(item) |
121
|
|
|
except ValidationError: |
122
|
|
|
invalid_emails.append(item) |
123
|
|
|
|
124
|
|
|
if invalid_emails: |
125
|
|
|
raise ValidationError( |
126
|
|
|
field.error_messages['invalid'] % { |
127
|
|
|
'value': ', '.join(invalid_emails)}) |
128
|
|
|
|
129
|
|
|
|
130
|
|
|
@permissions_required('testcases.change_testcase') |
131
|
|
|
@rpc_method(name='TestCase.add_notification_cc') |
132
|
|
|
def add_notification_cc(case_id, cc_list): |
133
|
|
|
""" |
134
|
|
|
.. function:: XML-RPC TestCase.add_notification_cc(case_id, cc_list) |
135
|
|
|
|
136
|
|
|
Add email addresses to the notification list of specified TestCase |
137
|
|
|
|
138
|
|
|
:param case_id: PK of TestCase to be modified |
139
|
|
|
:param case_id: int |
140
|
|
|
:param cc_list: List of email addresses |
141
|
|
|
:type cc_list: list(str) |
142
|
|
|
:return: None |
143
|
|
|
:raises: TypeError or ValidationError if email validation fails |
144
|
|
|
:raises: PermissionDenied if missing *testcases.change_testcase* permission |
145
|
|
|
:raises: TestCase.DoesNotExist if object with case_id doesn't exist |
146
|
|
|
""" |
147
|
|
|
|
148
|
|
|
_validate_cc_list(cc_list) |
149
|
|
|
|
150
|
|
|
test_case = TestCase.objects.get(pk=case_id) |
151
|
|
|
test_case.emailing.add_cc(cc_list) |
152
|
|
|
|
153
|
|
|
|
154
|
|
|
@permissions_required('testcases.change_testcase') |
155
|
|
|
@rpc_method(name='TestCase.remove_notification_cc') |
156
|
|
|
def remove_notification_cc(case_id, cc_list): |
157
|
|
|
""" |
158
|
|
|
.. function:: XML-RPC TestCase.remove_notification_cc(case_id, cc_list) |
159
|
|
|
|
160
|
|
|
Remove email addresses from the notification list of specified TestCase |
161
|
|
|
|
162
|
|
|
:param case_id: PK of TestCase to modify |
163
|
|
|
:type case_id: int |
164
|
|
|
:param cc_list: List of email addresses |
165
|
|
|
:type cc_list: list(str) |
166
|
|
|
:return: None |
167
|
|
|
:raises: TypeError or ValidationError if email validation fails |
168
|
|
|
:raises: PermissionDenied if missing *testcases.change_testcase* permission |
169
|
|
|
:raises: TestCase.DoesNotExist if object with case_id doesn't exist |
170
|
|
|
""" |
171
|
|
|
|
172
|
|
|
_validate_cc_list(cc_list) |
173
|
|
|
|
174
|
|
|
TestCase.objects.get(pk=case_id).emailing.remove_cc(cc_list) |
175
|
|
|
|
176
|
|
|
|
177
|
|
|
@rpc_method(name='TestCase.get_notification_cc') |
178
|
|
|
def get_notification_cc(case_id): |
179
|
|
|
""" |
180
|
|
|
.. function:: XML-RPC TestCase.get_notification_cc(case_id) |
181
|
|
|
|
182
|
|
|
Return notification list for specified TestCase |
183
|
|
|
|
184
|
|
|
:param case_id: PK of TestCase |
185
|
|
|
:type case_id: int |
186
|
|
|
:return: List of email addresses |
187
|
|
|
:rtype: list(str) |
188
|
|
|
:raises: TestCase.DoesNotExist if object with case_id doesn't exist |
189
|
|
|
""" |
190
|
|
|
return TestCase.objects.get(pk=case_id).get_cc_list() |
191
|
|
|
|
192
|
|
|
|
193
|
|
|
@permissions_required('testcases.add_testcasetag') |
194
|
|
|
@rpc_method(name='TestCase.add_tag') |
195
|
|
|
def add_tag(case_id, tag, **kwargs): |
196
|
|
|
""" |
197
|
|
|
.. function:: XML-RPC TestCase.add_tag(case_id, tag) |
198
|
|
|
|
199
|
|
|
Add one tag to the specified test case. |
200
|
|
|
|
201
|
|
|
:param case_id: PK of TestCase to modify |
202
|
|
|
:type case_id: int |
203
|
|
|
:param tag: Tag name to add |
204
|
|
|
:type tag: str |
205
|
|
|
:return: None |
206
|
|
|
:raises: PermissionDenied if missing *testcases.add_testcasetag* permission |
207
|
|
|
:raises: TestCase.DoesNotExist if object specified by PK doesn't exist |
208
|
|
|
:raises: Tag.DoesNotExist if missing *management.add_tag* permission and *tag* |
209
|
|
|
doesn't exist in the database! |
210
|
|
|
""" |
211
|
|
|
request = kwargs.get(REQUEST_KEY) |
212
|
|
|
tag, _ = Tag.get_or_create(request.user, tag) |
213
|
|
|
TestCase.objects.get(pk=case_id).add_tag(tag) |
214
|
|
|
|
215
|
|
|
|
216
|
|
|
@permissions_required('testcases.delete_testcasetag') |
217
|
|
|
@rpc_method(name='TestCase.remove_tag') |
218
|
|
|
def remove_tag(case_id, tag): |
219
|
|
|
""" |
220
|
|
|
.. function:: XML-RPC TestCase.remove_tag(case_id, tag) |
221
|
|
|
|
222
|
|
|
Remove tag from a test case. |
223
|
|
|
|
224
|
|
|
:param case_id: PK of TestCase to modify |
225
|
|
|
:type case_id: int |
226
|
|
|
:param tag: Tag name to remove |
227
|
|
|
:type tag: str |
228
|
|
|
:return: None |
229
|
|
|
:raises: PermissionDenied if missing *testcases.delete_testcasetag* permission |
230
|
|
|
:raises: DoesNotExist if objects specified don't exist |
231
|
|
|
""" |
232
|
|
|
TestCase.objects.get(pk=case_id).remove_tag( |
233
|
|
|
Tag.objects.get(name=tag) |
234
|
|
|
) |
235
|
|
|
|
236
|
|
|
|
237
|
|
|
@permissions_required('testcases.add_testcase') |
238
|
|
|
@rpc_method(name='TestCase.create') |
239
|
|
|
def create(values, **kwargs): |
240
|
|
|
""" |
241
|
|
|
.. function:: XML-RPC TestCase.create(values) |
242
|
|
|
|
243
|
|
|
Create a new TestCase object and store it in the database. |
244
|
|
|
|
245
|
|
|
:param values: Field values for :class:`tcms.testcases.models.TestCase` |
246
|
|
|
:type values: dict |
247
|
|
|
:return: Serialized :class:`tcms.testcases.models.TestCase` object |
248
|
|
|
:rtype: dict |
249
|
|
|
:raises: PermissionDenied if missing *testcases.add_testcase* permission |
250
|
|
|
|
251
|
|
|
Minimal test case parameters:: |
252
|
|
|
|
253
|
|
|
>>> values = { |
254
|
|
|
'category': 135, |
255
|
|
|
'product': 61, |
256
|
|
|
'summary': 'Testing XML-RPC', |
257
|
|
|
'priority': 1, |
258
|
|
|
} |
259
|
|
|
>>> TestCase.create(values) |
260
|
|
|
""" |
261
|
|
|
request = kwargs.get(REQUEST_KEY) |
262
|
|
|
|
263
|
|
|
if not (values.get('category') or values.get('summary')): |
264
|
|
|
raise ValueError() |
265
|
|
|
|
266
|
|
|
form = NewCaseForm(values) |
267
|
|
|
form.populate(values.get('product')) |
268
|
|
|
|
269
|
|
|
if form.is_valid(): |
270
|
|
|
# Create the case |
271
|
|
|
test_case = TestCase.create(author=request.user, values=form.cleaned_data) |
272
|
|
|
else: |
273
|
|
|
# Print the errors if the form is not passed validation. |
274
|
|
|
raise ValueError(form_errors_to_list(form)) |
275
|
|
|
|
276
|
|
|
result = test_case.serialize() |
277
|
|
|
|
278
|
|
|
return result |
279
|
|
|
|
280
|
|
|
|
281
|
|
|
@rpc_method(name='TestCase.filter') |
282
|
|
|
def filter(query): # pylint: disable=redefined-builtin |
283
|
|
|
""" |
284
|
|
|
.. function:: XML-RPC TestCase.filter(query) |
285
|
|
|
|
286
|
|
|
Perform a search and return the resulting list of test cases |
287
|
|
|
augmented with their latest ``text``. |
288
|
|
|
|
289
|
|
|
:param query: Field lookups for :class:`tcms.testcases.models.TestCase` |
290
|
|
|
:type query: dict |
291
|
|
|
:return: Serialized list of :class:`tcms.testcases.models.TestCase` objects. |
292
|
|
|
:rtype: list(dict) |
293
|
|
|
""" |
294
|
|
|
results = [] |
295
|
|
|
for case in TestCase.objects.filter(**query).distinct(): |
296
|
|
|
serialized_case = case.serialize() |
297
|
|
|
results.append(serialized_case) |
298
|
|
|
|
299
|
|
|
return results |
300
|
|
|
|
301
|
|
|
|
302
|
|
|
@permissions_required('testcases.change_testcase') |
303
|
|
|
@rpc_method(name='TestCase.update') |
304
|
|
|
def update(case_id, values, **kwargs): |
305
|
|
|
""" |
306
|
|
|
.. function:: XML-RPC TestCase.update(case_id, values) |
307
|
|
|
|
308
|
|
|
Update the fields of the selected test case. |
309
|
|
|
|
310
|
|
|
:param case_id: PK of TestCase to be modified |
311
|
|
|
:type case_id: int |
312
|
|
|
:param values: Field values for :class:`tcms.testcases.models.TestCase`. |
313
|
|
|
:type values: dict |
314
|
|
|
:return: Serialized :class:`tcms.testcases.models.TestCase` object |
315
|
|
|
:rtype: dict |
316
|
|
|
:raises: TestCase.DoesNotExist if object specified by PK doesn't exist |
317
|
|
|
:raises: PermissionDenied if missing *testcases.change_testcase* permission |
318
|
|
|
""" |
319
|
|
|
form = UpdateCaseForm(values) |
320
|
|
|
|
321
|
|
|
if values.get('category') and not values.get('product'): |
322
|
|
|
raise ValueError('Product ID is required for category') |
323
|
|
|
|
324
|
|
|
if values.get('product'): |
325
|
|
|
form.populate(product_id=values['product']) |
326
|
|
|
|
327
|
|
|
if form.is_valid(): |
328
|
|
|
test_case = TestCase.objects.get(pk=case_id) |
329
|
|
|
for key in values.keys(): |
330
|
|
|
# only modify attributes that were passed via parameters |
331
|
|
|
# skip attributes which are Many-to-Many relations |
332
|
|
|
if key not in ['component', 'tag'] and hasattr(test_case, key): |
333
|
|
|
setattr(test_case, key, form.cleaned_data[key]) |
334
|
|
|
test_case.save() |
335
|
|
|
else: |
336
|
|
|
raise ValueError(form_errors_to_list(form)) |
337
|
|
|
|
338
|
|
|
return test_case.serialize() |
339
|
|
|
|
340
|
|
|
|
341
|
|
|
@permissions_required('testcases.delete_testcase') |
342
|
|
|
@rpc_method(name='TestCase.remove') |
343
|
|
|
def remove(query): |
344
|
|
|
""" |
345
|
|
|
.. function:: XML-RPC TestCase.remove(query) |
346
|
|
|
|
347
|
|
|
Remove TestCase object(s). |
348
|
|
|
|
349
|
|
|
:param query: Field lookups for :class:`tcms.testcases.models.TestCase` |
350
|
|
|
:type query: dict |
351
|
|
|
:return: None |
352
|
|
|
:raises: PermissionDenied if missing the *testcases.delete_testcase* permission |
353
|
|
|
|
354
|
|
|
Example - removing bug from TestCase:: |
355
|
|
|
|
356
|
|
|
>>> TestCase.remove({ |
357
|
|
|
'pk__in': [1, 2, 3, 4], |
358
|
|
|
}) |
359
|
|
|
""" |
360
|
|
|
return TestCase.objects.filter(**query).delete() |
361
|
|
|
|