Passed
Push — master ( 05a640...f19445 )
by Alexander
03:06
created

tcms.testcases.views.get()   B

Complexity

Conditions 4

Size

Total Lines 55
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

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