Passed
Push — master ( a8de6e...8a642a )
by Alexander
04:10
created

tcms.testcases.views.get()   A

Complexity

Conditions 3

Size

Total Lines 44
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 32
dl 0
loc 44
rs 9.112
c 0
b 0
f 0
cc 3
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_GET
16
from django.views.decorators.http import require_POST
17
from django.views.generic.base import TemplateView
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):
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):
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):
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):
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):
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):
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
@require_GET
448
def search(request):  # pylint: disable=missing-permission-required
449
    """
450
        Shows the search form which uses JSON RPC to fetch the resuts
451
    """
452
    form = SearchCaseForm(request.GET)
453
    if request.GET.get('product'):
454
        form.populate(product_id=request.GET['product'])
455
    else:
456
        form.populate()
457
458
    context_data = {
459
        'form': form,
460
    }
461
    return render(request, 'testcases/search.html', context_data)
462
463
464
class SimpleTestCaseView(TemplateView):  # pylint: disable=missing-permission-required
465
    """Simple read-only TestCase View used in TestPlan page"""
466
467
    template_name = 'case/get_details.html'
468
    review_mode = None
469
470
    def get(self, request, *args, **kwargs):
471
        self.review_mode = request.GET.get('review_mode')
472
        return super().get(request, *args, **kwargs)
473
474
    def get_context_data(self, **kwargs):
475
        data = super().get_context_data(**kwargs)
476
477
        case = TestCase.objects.get(pk=kwargs['case_id'])
478
        data.update({
479
            'test_case': case,
480
            'review_mode': self.review_mode,
481
            'components': case.component.only('name'),
482
            'tags': case.tag.only('name'),
483
            'case_comments': get_comments(case),
484
        })
485
486
        return data
487
488
489
class TestCaseExecutionDetailPanelView(TemplateView):  # pylint: disable=missing-permission-required
490
    """Display execution detail in run page"""
491
492
    template_name = 'case/get_details_case_run.html'
493
    caserun_id = None
494
    case_text_version = None
495
496
    def get(self, request, *args, **kwargs):
497
        try:
498
            self.caserun_id = int(request.GET.get('case_run_id'))
499
            self.case_text_version = int(request.GET.get('case_text_version'))
500
        except (TypeError, ValueError):
501
            raise Http404
502
503
        return super().get(request, *args, **kwargs)
504
505
    def get_context_data(self, **kwargs):
506
        data = super().get_context_data(**kwargs)
507
508
        case = TestCase.objects.get(pk=kwargs['case_id'])
509
        execution = TestExecution.objects.get(pk=self.caserun_id)
510
511
        # Data of TestCase
512
        test_case_text = case.get_text_with_version(self.case_text_version)
513
514
        # Data of TestExecution
515
        execution_comments = get_comments(execution)
516
517
        execution_status = TestExecutionStatus.objects.values('pk', 'name').order_by('pk')
518
        bugs = group_case_bugs(execution.case.get_bugs().order_by('bug_id'))
519
520
        data.update({
521
            'test_case': case,
522
            'test_case_text': test_case_text,
523
524
            'execution': execution,
525
            'comments_count': len(execution_comments),
526
            'execution_comments': execution_comments,
527
            'execution_logs': execution.history.all(),
528
            'execution_status': execution_status,
529
            'grouped_case_bugs': bugs,
530
        })
531
532
        return data
533
534
535
def get(request, case_id):
536
    """Get the case content"""
537
    # Get the case
538
    try:
539
        test_case = TestCase.objects.select_related(
540
            'author', 'default_tester',
541
            'category', 'category',
542
            'priority', 'case_status').get(case_id=case_id)
543
    except ObjectDoesNotExist:
544
        raise Http404
545
546
    # Get the test executions
547
    tcrs = test_case.case_run.select_related(
548
        'run', 'tested_by',
549
        'assignee', 'case',
550
        'case', 'status').order_by('run__plan', 'run')
551
552
    # Render the page
553
    context_data = {
554
        'test_case': test_case,
555
        'test_case_runs': tcrs,
556
    }
557
558
    with modify_settings(
559
            MENU_ITEMS={'append': [
560
                ('...', [
561
                    (
562
                        _('Edit'),
563
                        reverse('testcases-edit', args=[test_case.pk])
564
                    ),
565
                    (
566
                        _('Clone'),
567
                        reverse('testcases-clone') + "?case=%d" % test_case.pk
568
                    ),
569
                    (
570
                        _('History'),
571
                        "/admin/testcases/testcase/%d/history/" % test_case.pk
572
                    ),
573
                    ('-', '-'),
574
                    (
575
                        _('Delete'),
576
                        reverse('admin:testcases_testcase_delete', args=[test_case.pk])
577
                    )])]}):
578
        return render(request, 'testcases/get.html', context_data)
579
580
581
@require_POST
582
def printable(request, template_name='case/printable.html'):
583
    """
584
        Create the printable copy for plan/case.
585
        Only CONFIRMED TestCases are printed when printing a TestPlan!
586
    """
587
    # fixme: remove when TestPlan and TestCase templates have been converted to Patternfly
588
    # instead of generating the print values on the backend we can use CSS to do
589
    # this in the browser
590
    # search only by case PK. Used when printing selected cases
591
    case_ids = request.POST.getlist('case')
592
    case_filter = {'pk__in': case_ids}
593
594
    test_plan = None
595
    # plan_pk is passed from the TestPlan.printable function
596
    # but it doesn't pass IDs of individual cases to be printed
597
    if not case_ids:
598
        plan_pk = request.POST.get('plan', 0)
599
        try:
600
            test_plan = TestPlan.objects.get(pk=plan_pk)
601
            # search cases from a TestPlan, used when printing entire plan
602
            case_filter = {
603
                'pk__in': test_plan.case.all(),
604
                'case_status': TestCaseStatus.objects.get(name='CONFIRMED').pk,
605
            }
606
        except (ValueError, TestPlan.DoesNotExist):
607
            test_plan = None
608
609
    tcs = TestCase.objects.filter(**case_filter).values(
610
        'case_id', 'summary', 'text'
611
    ).order_by('case_id')
612
613
    context_data = {
614
        'test_plan': test_plan,
615
        'test_cases': tcs,
616
    }
617
    return render(request, template_name, context_data)
618
619
620
def update_testcase(request, test_case, tc_form):
621
    """Updating information of specific TestCase
622
623
    This is called by views.edit internally. Don't call this directly.
624
625
    Arguments:
626
    - test_case: instance of a TestCase being updated
627
    - tc_form: instance of django.forms.Form, holding validated data.
628
    """
629
630
    # TODO: this entire function doesn't seem very useful
631
    # part if it was logging the changes but now this is
632
    # done by simple_history. Should we remove it ???
633
    # Modify the contents
634
    fields = ['summary',
635
              'case_status',
636
              'category',
637
              'priority',
638
              'notes',
639
              'text',
640
              'is_automated',
641
              'script',
642
              'arguments',
643
              'extra_link',
644
              'requirement']
645
646
    for field in fields:
647
        if getattr(test_case, field) != tc_form.cleaned_data[field]:
648
            setattr(test_case, field, tc_form.cleaned_data[field])
649
    try:
650
        if test_case.default_tester != tc_form.cleaned_data['default_tester']:
651
            test_case.default_tester = tc_form.cleaned_data['default_tester']
652
    except ObjectDoesNotExist:
653
        pass
654
655
    test_case.save()
656
657
658
@permission_required('testcases.change_testcase')
659
def edit(request, case_id):
660
    """Edit case detail"""
661
    try:
662
        test_case = TestCase.objects.select_related().get(case_id=case_id)
663
    except ObjectDoesNotExist:
664
        raise Http404
665
666
    test_plan = plan_from_request_or_none(request)
667
668
    if request.method == "POST":
669
        form = NewCaseForm(request.POST)
670
        if request.POST.get('product'):
671
            form.populate(product_id=request.POST['product'])
672
        elif test_plan:
673
            form.populate(product_id=test_plan.product_id)
674
        else:
675
            form.populate()
676
677
        n_form = CaseNotifyForm(request.POST)
678
679
        if form.is_valid() and n_form.is_valid():
680
            update_testcase(request, test_case, form)
681
            update_case_email_settings(test_case, n_form)
682
683
            return HttpResponseRedirect(
684
                reverse('testcases-get', args=[case_id, ])
685
            )
686
687
    else:
688
        # Notification form initial
689
        n_form = CaseNotifyForm(initial={
690
            'notify_on_case_update': test_case.emailing.notify_on_case_update,
691
            'notify_on_case_delete': test_case.emailing.notify_on_case_delete,
692
            'author': test_case.emailing.auto_to_case_author,
693
            'default_tester_of_case': test_case.emailing.auto_to_case_tester,
694
            'managers_of_runs': test_case.emailing.auto_to_run_manager,
695
            'default_testers_of_runs': test_case.emailing.auto_to_run_tester,
696
            'assignees_of_case_runs': test_case.emailing.auto_to_case_run_assignee,
697
            'cc_list': MultipleEmailField.delimiter.join(
698
                test_case.emailing.get_cc_list()),
699
        })
700
701
        components = []
702
        for component in test_case.component.all():
703
            components.append(component.pk)
704
705
        default_tester = None
706
        if test_case.default_tester_id:
707
            default_tester = test_case.default_tester.email
708
709
        form = NewCaseForm(initial={
710
            'summary': test_case.summary,
711
            'default_tester': default_tester,
712
            'requirement': test_case.requirement,
713
            'is_automated': test_case.is_automated,
714
            'script': test_case.script,
715
            'arguments': test_case.arguments,
716
            'extra_link': test_case.extra_link,
717
            'case_status': test_case.case_status_id,
718
            'priority': test_case.priority_id,
719
            'product': test_case.category.product_id,
720
            'category': test_case.category_id,
721
            'notes': test_case.notes,
722
            'text': test_case.text,
723
        })
724
725
        form.populate(product_id=test_case.category.product_id)
726
727
    context_data = {
728
        'test_case': test_case,
729
        'test_plan': test_plan,
730
        'form': form,
731
        'notify_form': n_form,
732
    }
733
    return render(request, 'testcases/mutable.html', context_data)
734
735
736
@permission_required('testcases.add_testcase')
737
def clone(request, template_name='testcases/clone.html'):
738
    """Clone one case or multiple case into other plan or plans"""
739
740
    request_data = getattr(request, request.method)
741
742
    if 'case' not in request_data:
743
        messages.add_message(request,
744
                             messages.ERROR,
745
                             _('At least one TestCase is required'))
746
        # redirect back where we came from
747
        return HttpResponseRedirect(request.META.get('HTTP_REFERER', '/'))
748
749
    # Do the clone action
750
    if request.method == 'POST':
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 and plans_count == 1:
816
                return HttpResponseRedirect(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...
817
818
            if cases_count == 1:
819
                return HttpResponseRedirect(
820
                    reverse('testcases-get', args=[tc_dest.pk, ])
821
                )
822
823
            if plans_count == 1:
824
                return HttpResponseRedirect(
825
                    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...
826
                )
827
828
            # Otherwise it will prompt to user the clone action is successful.
829
            messages.add_message(request,
830
                                 messages.SUCCESS,
831
                                 _('TestCase cloning was successful'))
832
            return HttpResponseRedirect(reverse('plans-search'))
833
    else:
834
        selected_cases = get_selected_testcases(request)
835
        # Initial the clone case form
836
        clone_form = CloneCaseForm(initial={
837
            'case': selected_cases,
838
        })
839
        clone_form.populate(case_ids=selected_cases)
840
841
    context = {
842
        'form': clone_form,
843
    }
844
    return render(request, template_name, context)
845