Passed
Push — master ( 52b4e1...6824bb )
by Alexander
02:13
created

tcms.testcases.views.edit()   C

Complexity

Conditions 10

Size

Total Lines 83
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 60
dl 0
loc 83
rs 5.509
c 0
b 0
f 0
cc 10
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like tcms.testcases.views.edit() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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