Passed
Push — master ( 85e1bb...09f4b0 )
by Alexander
02:22
created

tcms.testcases.views.get()   A

Complexity

Conditions 4

Size

Total Lines 51
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 36
dl 0
loc 51
rs 9.016
c 0
b 0
f 0
cc 4
nop 2

How to fix   Long Method   

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:

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