Passed
Push — master ( e9ad8a...f58c94 )
by Alexander
03:37
created

TestCaseSearchView.get_context_data()   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 2
nop 2
1
# -*- coding: utf-8 -*-
2
3
import itertools
4
5
from django.conf import settings
6
from django.contrib import messages
7
from django.test import modify_settings
8
from django.contrib.auth.decorators import permission_required
9
from django.core.exceptions import ObjectDoesNotExist
10
from django.urls import reverse
11
from django.http import HttpResponseRedirect, Http404
12
from django.shortcuts import get_object_or_404, render
13
from django.utils.decorators import method_decorator
14
from django.utils.translation import ugettext_lazy as _
15
from django.views.decorators.http import require_POST
16
from django.views.generic.base import TemplateView
17
from django.views.generic.base import View
18
19
from tcms.core.contrib.comments.utils import get_comments
20
from tcms.search import remove_from_request_path
21
from tcms.search.order import order_case_queryset
22
from tcms.testcases.models import TestCase, TestCaseStatus, \
23
    TestCasePlan
24
from tcms.management.models import Priority, Tag
25
from tcms.testplans.models import TestPlan
26
from tcms.testruns.models import TestExecution
27
from tcms.testruns.models import TestExecutionStatus
28
from tcms.testcases.forms import NewCaseForm, \
29
    SearchCaseForm, CaseNotifyForm, CloneCaseForm
30
from tcms.testcases.fields import MultipleEmailField
31
32
33
TESTCASE_OPERATION_ACTIONS = (
34
    'search', 'sort', 'update',
35
    'remove',  # including remove tag from cases
36
    'add',  # including add tag to cases
37
    'change',
38
    'delete_cases',  # unlink cases from a TestPlan
39
)
40
41
42
# _____________________________________________________________________________
43
# helper functions
44
45
46
def plan_from_request_or_none(request):  # pylint: disable=missing-permission-required
47
    """Get TestPlan from REQUEST
48
49
    This method relies on the existence of from_plan within REQUEST.
50
    """
51
    test_plan_id = request.POST.get("from_plan") or request.GET.get("from_plan")
52
    if not test_plan_id:
53
        return None
54
    return get_object_or_404(TestPlan, plan_id=test_plan_id)
55
56
57
def update_case_email_settings(test_case, n_form):
58
    """Update testcase's email settings."""
59
60
    test_case.emailing.notify_on_case_update = n_form.cleaned_data[
61
        'notify_on_case_update']
62
    test_case.emailing.notify_on_case_delete = n_form.cleaned_data[
63
        'notify_on_case_delete']
64
    test_case.emailing.auto_to_case_author = n_form.cleaned_data[
65
        'author']
66
    test_case.emailing.auto_to_case_tester = n_form.cleaned_data[
67
        'default_tester_of_case']
68
    test_case.emailing.auto_to_run_manager = n_form.cleaned_data[
69
        'managers_of_runs']
70
    test_case.emailing.auto_to_run_tester = n_form.cleaned_data[
71
        'default_testers_of_runs']
72
    test_case.emailing.auto_to_case_run_assignee = n_form.cleaned_data[
73
        'assignees_of_case_runs']
74
75
    default_tester = n_form.cleaned_data['default_tester_of_case']
76
    if (default_tester and test_case.default_tester_id):
77
        test_case.emailing.auto_to_case_tester = True
78
79
    # Continue to update CC list
80
    valid_emails = n_form.cleaned_data['cc_list']
81
    test_case.emailing.cc_list = MultipleEmailField.delimiter.join(valid_emails)
82
83
    test_case.emailing.save()
84
85
86
def group_case_bugs(bugs):
87
    """Group bugs using bug_id."""
88
    grouped_bugs = []
89
90
    for _pk, _bugs in itertools.groupby(bugs, lambda b: b.bug_id):
91
        grouped_bugs.append((_pk, list(_bugs)))
92
93
    return grouped_bugs
94
95
96
@method_decorator(permission_required('testcases.add_testcase'), name='dispatch')
97
class NewCaseView(TemplateView):
98
99
    template_name = 'testcases/mutable.html'
100
101
    def get(self, request, *args, **kwargs):
102
        test_plan = plan_from_request_or_none(request)
103
104
        default_form_parameters = {}
105
        if test_plan:
106
            default_form_parameters['product'] = test_plan.product_id
107
108
        form = NewCaseForm(initial=default_form_parameters)
109
110
        context_data = {
111
            'test_plan': test_plan,
112
            'form': form,
113
            'notify_form': CaseNotifyForm(),
114
        }
115
116
        return render(request, self.template_name, context_data)
117
118
    def post(self, request, *args, **kwargs):
119
        test_plan = plan_from_request_or_none(request)
120
121
        form = NewCaseForm(request.POST)
122
        if request.POST.get('product'):
123
            form.populate(product_id=request.POST['product'])
124
        else:
125
            form.populate()
126
127
        notify_form = CaseNotifyForm(request.POST)
128
129
        if form.is_valid() and notify_form.is_valid():
130
            test_case = self.create_test_case(form, notify_form, test_plan)
131
            return HttpResponseRedirect(reverse('testcases-get', args=[test_case.pk]))
132
133
        context_data = {
134
            'test_plan': test_plan,
135
            'form': form,
136
            'notify_form': notify_form
137
        }
138
139
        return render(request, self.template_name, context_data)
140
141
    def create_test_case(self, form, notify_form, test_plan):
142
        """Create new test case"""
143
        test_case = TestCase.create(author=self.request.user, values=form.cleaned_data)
144
145
        # Assign the case to the plan
146
        if test_plan:
147
            test_plan.add_case(test_case)
148
149
        update_case_email_settings(test_case, notify_form)
150
151
        return test_case
152
153
154
def get_testcaseplan_sortkey_pk_for_testcases(plan, tc_ids):
155
    """Get each TestCase' sortkey and related TestCasePlan's pk"""
156
    qs = TestCasePlan.objects.filter(case__in=tc_ids)
157
    if plan is not None:
158
        qs = qs.filter(plan__pk=plan.pk)
159
    qs = qs.values('pk', 'sortkey', 'case')
160
    return dict([(item['case'], {
161
        'testcaseplan_pk': item['pk'],
162
        'sortkey': item['sortkey']
163
    }) for item in qs])
164
165
166
def calculate_for_testcases(plan, testcases):
167
    """Calculate extra data for TestCases
168
169
    Attach TestCasePlan.sortkey, TestCasePlan.pk, and the number of bugs of
170
    each TestCase.
171
172
    :param plan: the TestPlan containing searched TestCases. None means testcases
173
                 are not limited to a specific TestPlan.
174
    :param testcases: a queryset of TestCases.
175
    """
176
    tc_ids = []
177
    for test_case in testcases:
178
        tc_ids.append(test_case.pk)
179
180
    sortkey_tcpkan_pks = get_testcaseplan_sortkey_pk_for_testcases(
181
        plan, tc_ids)
182
183
    for test_case in testcases:
184
        data = sortkey_tcpkan_pks.get(test_case.pk, None)
185
        if data:
186
            # todo: these properties appear to be redundant since the same
187
            # info should be available from the test_case query
188
            setattr(test_case, 'cal_sortkey', data['sortkey'])
189
            setattr(test_case, 'cal_testcaseplan_pk', data['testcaseplan_pk'])
190
        else:
191
            setattr(test_case, 'cal_sortkey', None)
192
            setattr(test_case, 'cal_testcaseplan_pk', None)
193
194
    return testcases
195
196
197
def get_case_status(template_type):
198
    """Get part or all TestCaseStatus according to template type"""
199
    confirmed_status_name = 'CONFIRMED'
200
    if template_type == 'case':
201
        d_status = TestCaseStatus.objects.filter(name=confirmed_status_name)
202
    elif template_type == 'review_case':
203
        d_status = TestCaseStatus.objects.exclude(name=confirmed_status_name)
204
    else:
205
        d_status = TestCaseStatus.objects.all()
206
    return d_status
207
208
209
@require_POST
210
def build_cases_search_form(request, populate=None, plan=None):
211
    """Build search form preparing for quering TestCases"""
212
    # Initial the form and template
213
    action = request.POST.get('a')
214
    if action in TESTCASE_OPERATION_ACTIONS:
215
        search_form = SearchCaseForm(request.POST)
216
        request.session['items_per_page'] = \
217
            request.POST.get('items_per_page', settings.DEFAULT_PAGE_SIZE)
218
    else:
219
        d_status = get_case_status(request.POST.get('template_type'))
220
        d_status_ids = d_status.values_list('pk', flat=True)
221
        items_per_page = request.session.get('items_per_page',
222
                                             settings.DEFAULT_PAGE_SIZE)
223
        search_form = SearchCaseForm(initial={
224
            'case_status': d_status_ids,
225
            'items_per_page': items_per_page})
226
227
    if populate:
228
        if request.POST.get('product'):
229
            search_form.populate(product_id=request.POST['product'])
230
        elif plan and plan.product_id:
231
            search_form.populate(product_id=plan.product_id)
232
        else:
233
            search_form.populate()
234
235
    return search_form
236
237
238
def paginate_testcases(request, testcases):  # pylint: disable=missing-permission-required
239
    """Paginate queried TestCases
240
241
    Arguments:
242
    - request: django's HttpRequest from which to get pagination data
243
    - testcases: an object queryset representing already queried TestCases
244
245
    Return value: return the queryset for chain call
246
    """
247
248
    page_index = int(request.POST.get('page_index', 1))
249
    page_size = int(request.POST.get(
250
        'items_per_page',
251
        request.session.get(
252
            'items_per_page', settings.DEFAULT_PAGE_SIZE
253
        )
254
    ))
255
    offset = (page_index - 1) * page_size
256
    return testcases[offset:offset + page_size]
257
258
259
def sort_queried_testcases(request, testcases):  # pylint: disable=missing-permission-required
260
    """Sort querid TestCases according to sort key
261
262
    Arguments:
263
    - request: REQUEST object
264
    - testcases: object of QuerySet containing queried TestCases
265
    """
266
    order_by = request.POST.get('order_by', 'create_date')
267
    asc = bool(request.POST.get('asc', None))
268
    tcs = order_case_queryset(testcases, order_by, asc)
269
    # default sorted by sortkey
270
    tcs = tcs.order_by('testcaseplan__sortkey')
271
    # Resort the order
272
    # if sorted by 'sortkey'(foreign key field)
273
    case_sort_by = request.POST.get('case_sort_by')
274
    if case_sort_by:
275
        if case_sort_by not in ['sortkey', '-sortkey']:
276
            tcs = tcs.order_by(case_sort_by)
277
        elif case_sort_by == 'sortkey':
278
            tcs = tcs.order_by('testcaseplan__sortkey')
279
        else:
280
            tcs = tcs.order_by('-testcaseplan__sortkey')
281
    return tcs
282
283
284
def query_testcases_from_request(request, plan=None):  # pylint: disable=missing-permission-required
285
    """Query TestCases according to criterias coming within REQUEST
286
287
    :param request: the REQUEST object.
288
    :param plan: instance of TestPlan to restrict only those TestCases belongs to
289
                 the TestPlan. Can be None. As you know, query from all TestCases.
290
    """
291
    search_form = build_cases_search_form(request, True, plan)
292
293
    action = request.POST.get('a')
294
    if action == 'initial':
295
        # todo: build_cases_search_form will also check TESTCASE_OPERATION_ACTIONS
296
        # and return slightly different values in case of initialization
297
        # move the check there and just execute the query here if the data
298
        # is valid
299
        d_status = get_case_status(request.POST.get('template_type'))
300
        tcs = TestCase.objects.filter(case_status__in=d_status)
301
    elif action in TESTCASE_OPERATION_ACTIONS and search_form.is_valid():
302
        tcs = TestCase.list(search_form.cleaned_data, plan)
303
    else:
304
        tcs = TestCase.objects.none()
305
306
    # Search the relationship
307
    if plan:
308
        tcs = tcs.filter(plan=plan)
309
310
    tcs = tcs.select_related('author',
311
                             'default_tester',
312
                             'case_status',
313
                             'priority',
314
                             'category',
315
                             'reviewer')
316
    return tcs, search_form
317
318
319
def get_selected_testcases(request):  # pylint: disable=missing-permission-required
320
    """Get selected TestCases from client side
321
322
    TestCases are selected in two cases. One is user selects part of displayed
323
    TestCases, where there should be at least one variable named case, whose
324
    value is the TestCase Id. Another one is user selects all TestCases based
325
    on previous filter criterias even through there are non-displayed ones. In
326
    this case, another variable selectAll appears in the REQUEST. Whatever its
327
    value is.
328
329
    If neither variables mentioned exists, empty query result is returned.
330
331
    Arguments:
332
    - request: REQUEST object.
333
    """
334
    method = request.POST or request.GET
335
    if method.get('selectAll', None):
336
        plan = plan_from_request_or_none(request)
337
        cases, _search_form = query_testcases_from_request(request, plan)
338
        return cases
339
340
    return TestCase.objects.filter(pk__in=method.getlist('case'))
341
342
343
def load_more_cases(request,  # pylint: disable=missing-permission-required
344
                    template_name='plan/cases_rows.html'):
345
    """Loading more TestCases"""
346
    plan = plan_from_request_or_none(request)
347
    cases = []
348
    selected_case_ids = []
349
    if plan is not None:
350
        cases, _search_form = query_testcases_from_request(request, plan)
351
        cases = sort_queried_testcases(request, cases)
352
        cases = paginate_testcases(request, cases)
353
        cases = calculate_for_testcases(plan, cases)
354
        selected_case_ids = []
355
        for test_case in cases:
356
            selected_case_ids.append(test_case.pk)
357
    context_data = {
358
        'test_plan': plan,
359
        'test_cases': cases,
360
        'selected_case_ids': selected_case_ids,
361
        'case_status': TestCaseStatus.objects.all(),
362
    }
363
    return render(request, template_name, context_data)
364
365
366
def get_tags_from_cases(case_ids, plan=None):
367
    """Get all tags from test cases
368
369
    @param cases: an iterable object containing test cases' ids
370
    @type cases: list, tuple
371
372
    @param plan: TestPlan object
373
374
    @return: a list containing all found tags with id and name
375
    @rtype: list
376
    """
377
    query = Tag.objects.filter(case__in=case_ids).distinct().order_by('name')
378
    if plan:
379
        query = query.filter(case__plan=plan)
380
381
    return query
382
383
384
@require_POST
385
def list_all(request):  # pylint: disable=missing-permission-required
386
    """
387
    Generate the TestCase list for the UI tabs in TestPlan page view.
388
    """
389
    # Intial the plan in plan details page
390
    test_plan = plan_from_request_or_none(request)
391
    if not test_plan:
392
        messages.add_message(request,
393
                             messages.ERROR,
394
                             _('TestPlan not specified or does not exist'))
395
        return HttpResponseRedirect(reverse('core-views-index'))
396
397
    tcs, search_form = query_testcases_from_request(request, test_plan)
398
    tcs = sort_queried_testcases(request, tcs)
399
    total_cases_count = tcs.count()
400
401
    # Get the tags own by the cases
402
    ttags = get_tags_from_cases((case.pk for case in tcs), test_plan)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable case does not seem to be defined.
Loading history...
403
404
    tcs = paginate_testcases(request, tcs)
405
406
    # There are several extra information related to each TestCase to be shown
407
    # also. This step must be the very final one, because the calculation of
408
    # related data requires related TestCases' IDs, that is the queryset of
409
    # TestCases should be evaluated in advance.
410
    tcs = calculate_for_testcases(test_plan, tcs)
411
412
    # generating a query_url with order options
413
    #
414
    # FIXME: query_url is always equivlant to None&asc=True whatever what
415
    # criterias specified in filter form, or just with default filter
416
    # conditions during loading TestPlan page.
417
    query_url = remove_from_request_path(request, 'order_by')
418
    asc = bool(request.POST.get('asc', None))
419
    if asc:
420
        query_url = remove_from_request_path(query_url, 'asc')
421
    else:
422
        query_url = '%s&asc=True' % query_url
423
424
    selected_case_ids = []
425
    for test_case in get_selected_testcases(request):
426
        selected_case_ids.append(test_case.pk)
427
428
    context_data = {
429
        'test_cases': tcs,
430
        'test_plan': test_plan,
431
        'search_form': search_form,
432
        # selected_case_ids is used in template to decide whether or not this TestCase is selected
433
        'selected_case_ids': selected_case_ids,
434
        'case_status': TestCaseStatus.objects.all(),
435
        'priorities': Priority.objects.filter(is_active=True),
436
        'case_own_tags': ttags,
437
        'query_url': query_url,
438
439
        # Load more is a POST request, so POST parameters are required only.
440
        # Remember this for loading more cases with the same as criterias.
441
        'search_criterias': request.body.decode(),
442
        'total_cases_count': total_cases_count,
443
    }
444
    return render(request, 'plan/get_cases.html', context_data)
445
446
447
class TestCaseSearchView(TemplateView):  # pylint: disable=missing-permission-required
448
    """
449
        Shows the search form which uses JSON RPC to fetch the results
450
    """
451
452
    template_name = 'testcases/search.html'
453
454
    def get_context_data(self, **kwargs):
455
        form = SearchCaseForm(self.request.GET)
456
        if self.request.GET.get('product'):
457
            form.populate(product_id=self.request.GET['product'])
458
        else:
459
            form.populate()
460
461
        return {
462
            'form': form,
463
        }
464
465
466
class SimpleTestCaseView(TemplateView):  # pylint: disable=missing-permission-required
467
    """Simple read-only TestCase View used in TestPlan page"""
468
469
    template_name = 'case/get_details.html'
470
    review_mode = None
471
472
    def get(self, request, *args, **kwargs):
473
        self.review_mode = request.GET.get('review_mode')
474
        return super().get(request, *args, **kwargs)
475
476
    def get_context_data(self, **kwargs):
477
        data = super().get_context_data(**kwargs)
478
479
        case = TestCase.objects.get(pk=kwargs['case_id'])
480
        data.update({
481
            'test_case': case,
482
            'review_mode': self.review_mode,
483
            'components': case.component.only('name'),
484
            'tags': case.tag.only('name'),
485
            'case_comments': get_comments(case),
486
        })
487
488
        return data
489
490
491
class TestCaseExecutionDetailPanelView(TemplateView):  # pylint: disable=missing-permission-required
492
    """Display execution detail in run page"""
493
494
    template_name = 'case/get_details_case_run.html'
495
    caserun_id = None
496
    case_text_version = None
497
498
    def get(self, request, *args, **kwargs):
499
        try:
500
            self.caserun_id = int(request.GET.get('case_run_id'))
501
            self.case_text_version = int(request.GET.get('case_text_version'))
502
        except (TypeError, ValueError):
503
            raise Http404
504
505
        return super().get(request, *args, **kwargs)
506
507
    def get_context_data(self, **kwargs):
508
        data = super().get_context_data(**kwargs)
509
510
        case = TestCase.objects.get(pk=kwargs['case_id'])
511
        execution = TestExecution.objects.get(pk=self.caserun_id)
512
513
        # Data of TestCase
514
        test_case_text = case.get_text_with_version(self.case_text_version)
515
516
        # Data of TestExecution
517
        execution_comments = get_comments(execution)
518
519
        execution_status = TestExecutionStatus.objects.values('pk', 'name').order_by('pk')
520
        bugs = group_case_bugs(execution.case.get_bugs().order_by('bug_id'))
521
522
        data.update({
523
            'test_case': case,
524
            'test_case_text': test_case_text,
525
526
            'execution': execution,
527
            'comments_count': len(execution_comments),
528
            'execution_comments': execution_comments,
529
            'execution_logs': execution.history.all(),
530
            'execution_status': execution_status,
531
            'grouped_case_bugs': bugs,
532
        })
533
534
        return data
535
536
537
def get(request, case_id):  # pylint: disable=missing-permission-required
538
    """Get the case content"""
539
    # Get the case
540
    try:
541
        test_case = TestCase.objects.select_related(
542
            'author', 'default_tester',
543
            'category', 'category',
544
            'priority', 'case_status').get(case_id=case_id)
545
    except ObjectDoesNotExist:
546
        raise Http404
547
548
    # Get the test executions
549
    tcrs = test_case.case_run.select_related(
550
        'run', 'tested_by',
551
        'assignee', 'case',
552
        'case', 'status').order_by('run__plan', 'run')
553
554
    # Render the page
555
    context_data = {
556
        'test_case': test_case,
557
        'executions': tcrs,
558
    }
559
560
    with modify_settings(
561
            MENU_ITEMS={'append': [
562
                ('...', [
563
                    (
564
                        _('Edit'),
565
                        reverse('testcases-edit', args=[test_case.pk])
566
                    ),
567
                    (
568
                        _('Clone'),
569
                        reverse('testcases-clone') + "?case=%d" % test_case.pk
570
                    ),
571
                    (
572
                        _('History'),
573
                        "/admin/testcases/testcase/%d/history/" % test_case.pk
574
                    ),
575
                    ('-', '-'),
576
                    (
577
                        _('Delete'),
578
                        reverse('admin:testcases_testcase_delete', args=[test_case.pk])
579
                    )])]}):
580
        return render(request, 'testcases/get.html', context_data)
581
582
583
@require_POST
584
def printable(request,  # pylint: disable=missing-permission-required
585
              template_name='case/printable.html'):
586
    """
587
        Create the printable copy for plan/case.
588
        Only CONFIRMED TestCases are printed when printing a TestPlan!
589
    """
590
    # fixme: remove when TestPlan and TestCase templates have been converted to Patternfly
591
    # instead of generating the print values on the backend we can use CSS to do
592
    # this in the browser
593
    # search only by case PK. Used when printing selected cases
594
    case_ids = request.POST.getlist('case')
595
    case_filter = {'pk__in': case_ids}
596
597
    test_plan = None
598
    # plan_pk is passed from the TestPlan.printable function
599
    # but it doesn't pass IDs of individual cases to be printed
600
    if not case_ids:
601
        plan_pk = request.POST.get('plan', 0)
602
        try:
603
            test_plan = TestPlan.objects.get(pk=plan_pk)
604
            # search cases from a TestPlan, used when printing entire plan
605
            case_filter = {
606
                'pk__in': test_plan.case.all(),
607
                'case_status': TestCaseStatus.objects.get(name='CONFIRMED').pk,
608
            }
609
        except (ValueError, TestPlan.DoesNotExist):
610
            test_plan = None
611
612
    tcs = TestCase.objects.filter(**case_filter).values(
613
        'case_id', 'summary', 'text'
614
    ).order_by('case_id')
615
616
    context_data = {
617
        'test_plan': test_plan,
618
        'test_cases': tcs,
619
    }
620
    return render(request, template_name, context_data)
621
622
623
def update_testcase(request, test_case, tc_form):
624
    """Updating information of specific TestCase
625
626
    This is called by views.edit internally. Don't call this directly.
627
628
    Arguments:
629
    - test_case: instance of a TestCase being updated
630
    - tc_form: instance of django.forms.Form, holding validated data.
631
    """
632
633
    # TODO: this entire function doesn't seem very useful
634
    # part if it was logging the changes but now this is
635
    # done by simple_history. Should we remove it ???
636
    # Modify the contents
637
    fields = ['summary',
638
              'case_status',
639
              'category',
640
              'priority',
641
              'notes',
642
              'text',
643
              'is_automated',
644
              'script',
645
              'arguments',
646
              'extra_link',
647
              'requirement']
648
649
    for field in fields:
650
        if getattr(test_case, field) != tc_form.cleaned_data[field]:
651
            setattr(test_case, field, tc_form.cleaned_data[field])
652
    try:
653
        if test_case.default_tester != tc_form.cleaned_data['default_tester']:
654
            test_case.default_tester = tc_form.cleaned_data['default_tester']
655
    except ObjectDoesNotExist:
656
        pass
657
658
    test_case.save()
659
660
661
@permission_required('testcases.change_testcase')
662
def edit(request, case_id):
663
    """Edit case detail"""
664
    try:
665
        test_case = TestCase.objects.select_related().get(case_id=case_id)
666
    except ObjectDoesNotExist:
667
        raise Http404
668
669
    test_plan = plan_from_request_or_none(request)
670
671
    if request.method == "POST":
672
        form = NewCaseForm(request.POST)
673
        if request.POST.get('product'):
674
            form.populate(product_id=request.POST['product'])
675
        elif test_plan:
676
            form.populate(product_id=test_plan.product_id)
677
        else:
678
            form.populate()
679
680
        n_form = CaseNotifyForm(request.POST)
681
682
        if form.is_valid() and n_form.is_valid():
683
            update_testcase(request, test_case, form)
684
            update_case_email_settings(test_case, n_form)
685
686
            return HttpResponseRedirect(
687
                reverse('testcases-get', args=[case_id, ])
688
            )
689
690
    else:
691
        # Notification form initial
692
        n_form = CaseNotifyForm(initial={
693
            'notify_on_case_update': test_case.emailing.notify_on_case_update,
694
            'notify_on_case_delete': test_case.emailing.notify_on_case_delete,
695
            'author': test_case.emailing.auto_to_case_author,
696
            'default_tester_of_case': test_case.emailing.auto_to_case_tester,
697
            'managers_of_runs': test_case.emailing.auto_to_run_manager,
698
            'default_testers_of_runs': test_case.emailing.auto_to_run_tester,
699
            'assignees_of_case_runs': test_case.emailing.auto_to_case_run_assignee,
700
            'cc_list': MultipleEmailField.delimiter.join(
701
                test_case.emailing.get_cc_list()),
702
        })
703
704
        components = []
705
        for component in test_case.component.all():
706
            components.append(component.pk)
707
708
        default_tester = None
709
        if test_case.default_tester_id:
710
            default_tester = test_case.default_tester.email
711
712
        form = NewCaseForm(initial={
713
            'summary': test_case.summary,
714
            'default_tester': default_tester,
715
            'requirement': test_case.requirement,
716
            'is_automated': test_case.is_automated,
717
            'script': test_case.script,
718
            'arguments': test_case.arguments,
719
            'extra_link': test_case.extra_link,
720
            'case_status': test_case.case_status_id,
721
            'priority': test_case.priority_id,
722
            'product': test_case.category.product_id,
723
            'category': test_case.category_id,
724
            'notes': test_case.notes,
725
            'text': test_case.text,
726
        })
727
728
        form.populate(product_id=test_case.category.product_id)
729
730
    context_data = {
731
        'test_case': test_case,
732
        'test_plan': test_plan,
733
        'form': form,
734
        'notify_form': n_form,
735
    }
736
    return render(request, 'testcases/mutable.html', context_data)
737
738
739
@method_decorator(permission_required('testcases.add_testcase'), name='dispatch')
740
class CloneTestCaseView(View):
741
    """Clone one case or multiple case into other plan or plans"""
742
743
    template_name = 'testcases/clone.html'
744
    http_method_names = ['get', 'post']
745
746
    def post(self, request):
747
        if not self._is_request_data_valid(request):
748
            return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
749
750
        # Do the clone action
751
        clone_form = CloneCaseForm(request.POST)
752
        clone_form.populate(case_ids=request.POST.getlist('case'))
753
754
        if clone_form.is_valid():
755
            tcs_src = clone_form.cleaned_data['case']
756
            for tc_src in tcs_src:
757
                tc_dest = TestCase.objects.create(
758
                    is_automated=tc_src.is_automated,
759
                    script=tc_src.script,
760
                    arguments=tc_src.arguments,
761
                    extra_link=tc_src.extra_link,
762
                    summary=tc_src.summary,
763
                    requirement=tc_src.requirement,
764
                    case_status=TestCaseStatus.get_proposed(),
765
                    category=tc_src.category,
766
                    priority=tc_src.priority,
767
                    notes=tc_src.notes,
768
                    text=tc_src.text,
769
                    author=request.user,
770
                    default_tester=tc_src.default_tester,
771
                )
772
773
                # apply tags as well
774
                for tag in tc_src.tag.all():
775
                    tc_dest.add_tag(tag=tag)
776
777
                for test_plan in clone_form.cleaned_data['plan']:
778
                    # add new TC to selected TP
779
                    sortkey = test_plan.get_case_sortkey()
780
                    test_plan.add_case(tc_dest, sortkey)
781
782
                    # clone TC category b/c we may be cloning a 'linked'
783
                    # TC which has a different Product that doesn't have the
784
                    # same categories yet
785
                    try:
786
                        tc_category = test_plan.product.category.get(
787
                            name=tc_src.category.name
788
                        )
789
                    except ObjectDoesNotExist:
790
                        tc_category = test_plan.product.category.create(
791
                            name=tc_src.category.name,
792
                            description=tc_src.category.description,
793
                        )
794
                    tc_dest.category = tc_category
795
                    tc_dest.save()
796
797
                    # clone TC components b/c we may be cloning a 'linked'
798
                    # TC which has a different Product that doesn't have the
799
                    # same components yet
800
                    for component in tc_src.component.all():
801
                        try:
802
                            new_c = test_plan.product.component.get(name=component.name)
803
                        except ObjectDoesNotExist:
804
                            new_c = test_plan.product.component.create(
805
                                name=component.name,
806
                                initial_owner=request.user,
807
                                description=component.description,
808
                            )
809
                        tc_dest.add_component(new_c)
810
811
            # Detect the number of items and redirect to correct one
812
            cases_count = len(clone_form.cleaned_data['case'])
813
            plans_count = len(clone_form.cleaned_data['plan'])
814
815
            if cases_count == 1:
816
                return HttpResponseRedirect(
817
                    reverse('testcases-get', args=[tc_dest.pk, ])
0 ignored issues
show
introduced by
The variable tc_dest does not seem to be defined in case the for loop on line 756 is not entered. Are you sure this can never be the case?
Loading history...
818
                )
819
820
            if plans_count == 1:
821
                return HttpResponseRedirect(
822
                    reverse('test_plan_url_short', args=[test_plan.pk, ])
0 ignored issues
show
introduced by
The variable test_plan does not seem to be defined for all execution paths.
Loading history...
823
                )
824
825
            # Otherwise it will prompt to user the clone action is successful.
826
            messages.add_message(request,
827
                                 messages.SUCCESS,
828
                                 _('TestCase cloning was successful'))
829
            return HttpResponseRedirect(reverse('plans-search'))
830
831
    def get(self, request):
832
        if not self._is_request_data_valid(request):
833
            return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
834
835
        selected_cases = get_selected_testcases(request)
836
        # Initial the clone case form
837
        clone_form = CloneCaseForm(initial={
838
            'case': selected_cases,
839
        })
840
        clone_form.populate(case_ids=selected_cases)
841
842
        context = {
843
            'form': clone_form,
844
        }
845
        return render(request, self.template_name, context)
846
847
    def _is_request_data_valid(self, request):
848
        request_data = getattr(request, request.method)
849
850
        if 'case' not in request_data:
851
            messages.add_message(request,
852
                                 messages.ERROR,
853
                                 _('At least one TestCase is required'))
854
            return False
855
856
        return True
857