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