Passed
Push — master ( ae226f...980100 )
by Alexander
03:01
created

tcms.rpc.api.testcase   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 407
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 30
eloc 135
dl 0
loc 407
rs 10
c 0
b 0
f 0

15 Functions

Rating   Name   Duplication   Size   Complexity  
A add_attachment() 0 22 1
A filter() 0 17 2
A remove() 0 20 1
A add_notification_cc() 0 22 1
A remove_notification_cc() 0 21 1
A get_components() 0 18 1
A list_attachments() 0 17 1
A get_notification_cc() 0 14 1
A _validate_cc_list() 0 27 5
A add_component() 0 23 1
B update() 0 37 8
A create() 0 42 4
A add_tag() 0 21 1
A remove_tag() 0 18 1
A remove_component() 0 20 1
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