Passed
Push — master ( 3727b7...72c322 )
by Alexander
02:28
created

tcms/testcases/views.py (1 issue)

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