Passed
Push — master ( cf1d40...440998 )
by Alexander
02:54
created

TestCaseGetView.render_to_response()   A

Complexity

Conditions 1

Size

Total Lines 23
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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