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

tcms/testruns/views.py (2 issues)

1
# -*- coding: utf-8 -*-
2
3
from datetime import datetime
4
from http import HTTPStatus
5
6
from django.conf import settings
7
from django.contrib import messages
8
from django.contrib.auth.decorators import permission_required
9
from django.contrib.auth import get_user_model
10
from django.contrib.contenttypes.models import ContentType
11
from django.core.exceptions import ObjectDoesNotExist
12
from django.urls import reverse
13
from django.db.models import Count
14
from django.db.models import Q
15
from django.http import HttpResponseRedirect, Http404, JsonResponse
16
from django.shortcuts import get_object_or_404, render
17
from django.utils.decorators import method_decorator
18
from django.utils.translation import ugettext_lazy as _
19
from django.views.decorators.http import require_GET
20
from django.views.decorators.http import require_POST
21
from django.views.generic.base import TemplateView
22
from django.views.generic.base import View
23
24
from django_comments.models import Comment
25
26
from tcms.core.utils import clean_request
27
from tcms.management.models import Priority, Tag
28
from tcms.testcases.models import TestCasePlan, TestCaseStatus, BugSystem
29
from tcms.testcases.views import get_selected_testcases
30
from tcms.testplans.models import TestPlan
31
from tcms.testruns.data import get_run_bug_ids
32
from tcms.testruns.data import TestExecutionDataMixin
33
from tcms.testruns.forms import NewRunForm, SearchRunForm, BaseRunForm
34
from tcms.testruns.models import TestRun, TestExecution, TestExecutionStatus
35
from tcms.issuetracker.types import IssueTrackerType
36
37
38
User = get_user_model()  # pylint: disable=invalid-name
39
40
41
@require_POST
42
@permission_required('testruns.add_testrun')
43
def new(request):
44
    """Display the create test run page."""
45
46
    # If from_plan does not exist will redirect to plans for select a plan
47
    if not request.POST.get('from_plan'):
48
        messages.add_message(request,
49
                             messages.ERROR,
50
                             _('Creating a TestRun requires a TestPlan, select one'))
51
        return HttpResponseRedirect(reverse('plans-search'))
52
53
    plan_id = request.POST.get('from_plan')
54
    # case is required by a test run
55
    # NOTE: currently this is handled in JavaScript but in the TestRun creation
56
    # form cases can be deleted
57
    if not request.POST.get('case'):
58
        messages.add_message(request,
59
                             messages.ERROR,
60
                             _('Creating a TestRun requires at least one TestCase'))
61
        return HttpResponseRedirect(reverse('test_plan_url_short', args=[plan_id]))
62
63
    # Ready to write cases to test plan
64
    test_cases = get_selected_testcases(request)
65
    test_plan = TestPlan.objects.get(plan_id=plan_id)
66
67
    # note: ordered by case_id for test_show_create_new_run_page()
68
    tcs_values = test_cases.select_related('author',
69
                                           'case_status',
70
                                           'category',
71
                                           'priority').order_by('case_id')
72
73
    if request.POST.get('POSTING_TO_CREATE'):
74
        form = NewRunForm(request.POST)
75
        form.populate(product_id=test_plan.product_id)
76
77
        if form.is_valid():
78
            # Process the data in form.cleaned_data
79
            default_tester = form.cleaned_data['default_tester']
80
81
            test_run = TestRun.objects.create(
82
                product_version=test_plan.product_version,
83
                stop_date=None,
84
                summary=form.cleaned_data.get('summary'),
85
                notes=form.cleaned_data.get('notes'),
86
                plan=test_plan,
87
                build=form.cleaned_data['build'],
88
                manager=form.cleaned_data['manager'],
89
                default_tester=default_tester,
90
            )
91
92
            try:
93
                assignee_tester = User.objects.get(username=default_tester)
94
            except ObjectDoesNotExist:
95
                assignee_tester = None
96
97
            loop = 1
98
            for case in form.cleaned_data['case']:
99
                try:
100
                    tcp = TestCasePlan.objects.get(plan=test_plan, case=case)
101
                    sortkey = tcp.sortkey
102
                except ObjectDoesNotExist:
103
                    sortkey = loop * 10
104
105
                test_run.add_case_run(case=case,
106
                                      sortkey=sortkey,
107
                                      assignee=assignee_tester)
108
                loop += 1
109
110
            return HttpResponseRedirect(
111
                reverse('testruns-get', args=[test_run.run_id, ])
112
            )
113
114
    else:
115
        form = NewRunForm(initial={
116
            'summary': 'Test run for %s' % test_plan.name,
117
            'manager': test_plan.author.email,
118
            'default_tester': request.user.email,
119
            'notes': '',
120
        })
121
        form.populate(product_id=test_plan.product_id)
122
123
    context_data = {
124
        'test_plan': test_plan,
125
        'test_cases': tcs_values,
126
        'form': form,
127
    }
128
    return render(request, 'testruns/mutable.html', context_data)
129
130
131
@require_GET
132
def search(request):
133
    form = SearchRunForm(request.GET)
134
    form.populate(product_id=request.GET.get('product'))
135
136
    context_data = {
137
        'form': form,
138
    }
139
    return render(request, 'testruns/search.html', context_data)
140
141
142
def open_run_get_case_runs(request, run):
143
    """Prepare for case runs list in a TestRun page
144
145
    This is an internal method. Do not call this directly.
146
    """
147
    tcrs = run.case_run.select_related('run', 'case')
148
    tcrs = tcrs.only('run__run_id',
149
                     'run__plan',
150
                     'status',
151
                     'assignee', 'tested_by',
152
                     'case_text_version',
153
                     'sortkey',
154
                     'case__summary',
155
                     'case__is_automated',
156
                     'case__priority',
157
                     'case__category__name')
158
    # Get the bug count for each case run
159
    # 5. have to show the number of bugs of each case run
160
    tcrs = tcrs.annotate(num_bug=Count('case_run_bug', distinct=True))
161
162
    # todo: is this last distinct necessary
163
    tcrs = tcrs.distinct()
164
    # Continue to search the case runs with conditions
165
    # 4. case runs preparing for render case runs table
166
    tcrs = tcrs.filter(**clean_request(request))
167
    order_by = request.GET.get('order_by')
168
    if order_by:
169
        tcrs = tcrs.order_by(order_by)
170
    else:
171
        tcrs = tcrs.order_by('sortkey', 'pk')
172
    return tcrs
173
174
175
def open_run_get_comments_subtotal(case_run_ids):
176
    content_type = ContentType.objects.get_for_model(TestExecution)
177
    query_set = Comment.objects.filter(
178
        content_type=content_type,
179
        site_id=settings.SITE_ID,
180
        object_pk__in=case_run_ids,
181
        is_removed=False).values('object_pk').annotate(comment_count=Count('pk')).order_by(
182
            'object_pk')
183
184
    result = ((int(row['object_pk']), row['comment_count']) for row in query_set)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable row does not seem to be defined.
Loading history...
185
    return dict(result)
186
187
188
def open_run_get_users(case_runs):
189
    tester_ids = set()
190
    assignee_ids = set()
191
    for case_run in case_runs:
192
        if case_run.tested_by_id:
193
            tester_ids.add(case_run.tested_by_id)
194
        if case_run.assignee_id:
195
            assignee_ids.add(case_run.assignee_id)
196
    testers = User.objects.filter(
197
        pk__in=tester_ids).values_list('pk', 'username')
198
    assignees = User.objects.filter(
199
        pk__in=assignee_ids).values_list('pk', 'username')
200
    return (dict(testers.iterator()), dict(assignees.iterator()))
201
202
203
@require_GET
204
def get(request, run_id, template_name='run/get.html'):
205
    """Display testrun's detail"""
206
207
    # Get the test run
208
    try:
209
        # 1. get test run itself
210
        test_run = TestRun.objects.select_related().get(run_id=run_id)
211
    except ObjectDoesNotExist:
212
        raise Http404
213
214
    # Get the test case runs belong to the run
215
    # 2. get test run's all case runs
216
    test_case_runs = open_run_get_case_runs(request, test_run)
217
218
    status = TestExecutionStatus.objects.only('pk', 'name').order_by('pk')
219
220
    # Count the status
221
    # 3. calculate number of case runs of each status
222
    status_stats_result = test_run.stats_caseruns_status(status)
223
224
    # Get the test case run bugs summary
225
    # 6. get the number of bugs of this run
226
    test_case_run_bugs_count = test_run.get_bug_count()
227
228
    # Get tag list of testcases
229
    # 7. get tags
230
    # Get the list of testcases belong to the run
231
    test_cases = []
232
    for test_case_run in test_case_runs:
233
        test_cases.append(test_case_run.case_id)
234
    tags = Tag.objects.filter(case__in=test_cases).values_list('name', flat=True)
235
    tags = list(set(tags))
236
    tags.sort()
237
238
    def walk_case_runs():
239
        """Walking case runs for helping rendering case runs table"""
240
        priorities = dict(Priority.objects.values_list('pk', 'value'))
241
        testers, assignees = open_run_get_users(test_case_runs)
242
        test_case_run_pks = []
243
        for test_case_run in test_case_runs:
244
            test_case_run_pks.append(test_case_run.pk)
245
        comments_subtotal = open_run_get_comments_subtotal(test_case_run_pks)
246
        status = TestExecutionStatus.get_names()
247
248
        for case_run in test_case_runs:
249
            yield (case_run,
250
                   testers.get(case_run.tested_by_id, None),
251
                   assignees.get(case_run.assignee_id, None),
252
                   priorities.get(case_run.case.priority_id),
253
                   status[case_run.status_id],
254
                   comments_subtotal.get(case_run.pk, 0))
255
256
    context_data = {
257
        'test_run': test_run,
258
        'from_plan': request.GET.get('from_plan', False),
259
        'test_case_runs': walk_case_runs(),
260
        'test_case_runs_count': len(test_case_runs),
261
        'status_stats': status_stats_result,
262
        'test_case_run_bugs_count': test_case_run_bugs_count,
263
        'test_status': status,
264
        'priorities': Priority.objects.filter(is_active=True),
265
        'case_own_tags': tags,
266
        'bug_trackers': BugSystem.objects.all(),
267
    }
268
    return render(request, template_name, context_data)
269
270
271
@permission_required('testruns.change_testrun')
272
def edit(request, run_id):
273
    """Edit test plan view"""
274
275
    try:
276
        test_run = TestRun.objects.select_related().get(run_id=run_id)
277
    except ObjectDoesNotExist:
278
        raise Http404
279
280
    # If the form is submitted
281
    if request.method == "POST":
282
        form = BaseRunForm(request.POST)
283
        form.populate(product_id=test_run.plan.product_id)
284
285
        # FIXME: Error handler
286
        if form.is_valid():
287
            test_run.summary = form.cleaned_data['summary']
288
            # Permission hack
289
            if test_run.manager == request.user or test_run.plan.author == request.user:
290
                test_run.manager = form.cleaned_data['manager']
291
            test_run.default_tester = form.cleaned_data['default_tester']
292
            test_run.build = form.cleaned_data['build']
293
            test_run.product_version = test_run.plan.product_version
294
            test_run.notes = form.cleaned_data['notes']
295
            test_run.save()
296
297
            return HttpResponseRedirect(reverse('testruns-get', args=[run_id, ]))
298
    else:
299
        # Generate a blank form
300
        form = BaseRunForm(initial={
301
            'summary': test_run.summary,
302
            'manager': test_run.manager.email,
303
            'default_tester': (test_run.default_tester and
304
                               test_run.default_tester.email or None),
305
            'version': test_run.product_version_id,
306
            'build': test_run.build_id,
307
            'notes': test_run.notes,
308
        })
309
        form.populate(test_run.plan.product_id)
310
311
    context_data = {
312
        'test_run': test_run,
313
        'test_plan': test_run.plan,
314
        'form': form,
315
    }
316
    return render(request, 'testruns/mutable.html', context_data)
317
318
319
class TestRunReportView(TemplateView, TestExecutionDataMixin):
320
    """Test Run report"""
321
322
    template_name = 'run/report.html'
323
324
    def get_context_data(self, **kwargs):
325
        """Generate report for specific TestRun
326
327
        There are four data source to generate this report.
328
        1. TestRun
329
        2. Test case runs included in the TestRun
330
        3. Comments associated with each test case run
331
        4. Statistics
332
        5. bugs
333
        """
334
        run = TestRun.objects.select_related('manager', 'plan').get(pk=kwargs['run_id'])
335
336
        case_runs = TestExecution.objects.filter(
337
            run=run
338
        ).select_related(
339
            'status', 'case', 'tested_by'
340
        ).only(
341
            'close_date',
342
            'status__name',
343
            'case__category__name',
344
            'case__summary', 'case__is_automated',
345
            'tested_by__username'
346
        )
347
        mode_stats = self.stats_mode_case_runs(case_runs)
348
        summary_stats = self.get_summary_stats(case_runs)
349
350
        test_case_run_bugs = []
351
        bug_system_types = {}
352
        for _bug in get_run_bug_ids(run.pk):
353
            # format the bug URLs based on DB settings
354
            test_case_run_bugs.append((
355
                _bug['bug_id'],
356
                _bug['bug_system__url_reg_exp'] % _bug['bug_id'],
357
            ))
358
            # find out all unique bug tracking systems which were used to record
359
            # bugs in this particular test run. we use this data for reporting
360
            if _bug['bug_system'] not in bug_system_types:
361
                # store a tracker type object for producing the report URL
362
                tracker_class = IssueTrackerType.from_name(_bug['bug_system__tracker_type'])
363
                bug_system = BugSystem.objects.get(pk=_bug['bug_system'])
364
                tracker = tracker_class(bug_system)
365
                bug_system_types[_bug['bug_system']] = (tracker, [])
366
367
            # store the list of bugs as well
368
            bug_system_types[_bug['bug_system']][1].append(_bug['bug_id'])
369
370
        # list of URLs which opens all bugs reported to every different
371
        # issue tracker used in this test run
372
        report_urls = []
373
        for (issue_tracker, ids) in bug_system_types.values():
374
            report_url = issue_tracker.all_issues_link(ids)
375
            # if IT doesn't support this feature or report url is not configured
376
            # the above method will return None
377
            if report_url:
378
                report_urls.append((issue_tracker.tracker.name, report_url))
379
380
        case_run_bugs = self.get_case_runs_bugs(run.pk)
381
        comments = self.get_case_runs_comments(run.pk)
382
383
        for case_run in case_runs:
384
            case_run.bugs = case_run_bugs.get(case_run.pk, ())
385
            case_run.user_comments = comments.get(case_run.pk, [])
386
387
        context = super().get_context_data(**kwargs)
388
        context.update({
389
            'test_run': run,
390
            'test_case_runs': case_runs,
391
            'test_case_runs_count': len(case_runs),
392
            'test_case_run_bugs': test_case_run_bugs,
393
            'mode_stats': mode_stats,
394
            'summary_stats': summary_stats,
395
            'report_urls': report_urls,
396
        })
397
398
        return context
399
400
401
@require_POST
402
def clone(request, run_id):
403
    """Clone cases from filter caserun"""
404
405
    test_run = get_object_or_404(TestRun, run_id=run_id)
406
    confirmed_case_status = TestCaseStatus.get_confirmed()
407
    disabled_cases = 0
408
409
    if request.POST.get('case_run'):
410
        test_cases = []
411
        for test_case_run in test_run.case_run.filter(pk__in=request.POST.getlist('case_run')):
412
            if test_case_run.case.case_status == confirmed_case_status:
413
                test_cases.append(test_case_run.case)
414
            else:
415
                disabled_cases += 1
416
    else:
417
        test_cases = None
418
419
    if not test_cases:
420
        messages.add_message(request,
421
                             messages.ERROR,
422
                             _('At least one TestCase is required'))
423
        return HttpResponseRedirect(reverse('testruns-get', args=[run_id]))
424
425
    form = NewRunForm(initial={
426
        'summary': _('Clone of ') + test_run.summary,
427
        'notes': test_run.notes,
428
        'manager': test_run.manager,
429
        'build': test_run.build_id,
430
        'default_tester': test_run.default_tester,
431
    })
432
    form.populate(product_id=test_run.plan.product_id)
433
434
    context_data = {
435
        'is_cloning': True,
436
        'disabled_cases': disabled_cases,
437
        'test_plan': test_run.plan,
438
        'test_cases': test_cases,
439
        'form': form,
440
    }
441
    return render(request, 'testruns/mutable.html', context_data)
442
443
444
@permission_required('testruns.change_testrun')
445
def change_status(request, run_id):
446
    """Change test run finished or running"""
447
    test_run = get_object_or_404(TestRun, run_id=run_id)
448
449
    test_run.update_completion_status(request.GET.get('finished') == '1')
450
    test_run.save()
451
452
    return HttpResponseRedirect(reverse('testruns-get', args=[run_id, ]))
453
454
455
@require_POST
456
@permission_required('testruns.delete_testexecution')
457
def remove_case_run(request, run_id):
458
    """Remove specific case run from the run"""
459
460
    # Ignore invalid case run ids
461
    case_run_ids = []
462
    for item in request.POST.getlist('case_run'):
463
        try:
464
            case_run_ids.append(int(item))
465
        except (ValueError, TypeError):
466
            pass
467
468
    # If no case run to remove, no further operation is required, just return
469
    # back to run page immediately.
470
    if not case_run_ids:
471
        return HttpResponseRedirect(reverse('testruns-get',
472
                                            args=[run_id, ]))
473
474
    run = get_object_or_404(TestRun.objects.only('pk'), pk=run_id)
475
476
    # Restrict to delete those case runs that belongs to run
477
    TestExecution.objects.filter(run_id=run.pk, pk__in=case_run_ids).delete()
478
479
    caseruns_exist = TestExecution.objects.filter(run_id=run.pk).exists()
480
    if caseruns_exist:
481
        redirect_to = 'testruns-get'
482
    else:
483
        redirect_to = 'add-cases-to-run'
484
485
    return HttpResponseRedirect(reverse(redirect_to, args=[run_id, ]))
486
487
488
@method_decorator(permission_required('testruns.add_testexecution'), name='dispatch')
489
class AddCasesToRunView(View):
490
    """Add cases to a TestRun"""
491
492
    def post(self, request, run_id):
493
        # Selected cases' ids to add to run
494
        test_cases_ids = request.POST.getlist('case')
495
        if not test_cases_ids:
496
            # user clicked Update button without selecting new Test Cases
497
            # to be dded to TestRun
498
            messages.add_message(request,
499
                                 messages.ERROR,
500
                                 _('At least one TestCase is required'))
501
            return HttpResponseRedirect(reverse('add-cases-to-run', args=[run_id]))
502
503
        try:
504
            test_cases_ids = list(map(int, test_cases_ids))
505
        except (ValueError, TypeError):
506
            # this will happen only on malicious requests
507
            messages.add_message(request,
508
                                 messages.ERROR,
509
                                 _('TestCase ID is not a valid integer'))
510
            return HttpResponseRedirect(reverse('add-cases-to-run', args=[run_id]))
511
512
        try:
513
            test_run = TestRun.objects.select_related('plan').only('plan__plan_id').get(
514
                run_id=run_id)
515
        except ObjectDoesNotExist:
516
            raise Http404
517
518
        test_case_runs_ids = test_run.case_run.values_list('case', flat=True)
519
520
        # avoid add cases that are already in current run with pk run_id
521
        test_cases_ids = set(test_cases_ids) - set(test_case_runs_ids)
522
523
        test_plan = test_run.plan
524
        test_cases = test_run.plan.case.filter(case_status__name='CONFIRMED').select_related(
525
            'default_tester').only('default_tester__id').filter(
526
                case_id__in=test_cases_ids)
527
528
        if request.POST.get('_use_plan_sortkey'):
529
            test_case_pks = (test_case.pk for test_case in test_cases)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable test_case does not seem to be defined.
Loading history...
530
            query_set = TestCasePlan.objects.filter(
531
                plan=test_plan, case__in=test_case_pks).values('case', 'sortkey')
532
            sort_keys_in_plan = dict((row['case'], row['sortkey']) for row in query_set.iterator())
533
            for test_case in test_cases:
534
                sort_key = sort_keys_in_plan.get(test_case.pk, 0)
535
                test_run.add_case_run(case=test_case, sortkey=sort_key)
536
        else:
537
            for test_case in test_cases:
538
                test_run.add_case_run(case=test_case)
539
540
        return HttpResponseRedirect(reverse('testruns-get',
541
                                            args=[test_run.run_id, ]))
542
543
    def get(self, request, run_id):
544
        # information about TestRun, used in the page header
545
        test_run = TestRun.objects.select_related(
546
            'plan', 'manager', 'build'
547
        ).only(
548
            'plan', 'plan__name',
549
            'manager__email', 'build__name'
550
        ).get(run_id=run_id)
551
552
        # select all CONFIRMED cases from the TestPlan that is a parent
553
        # of this particular TestRun
554
        rows = TestCasePlan.objects.values(
555
            'case',
556
            'case__create_date', 'case__summary',
557
            'case__category__name',
558
            'case__priority__value',
559
            'case__author__username'
560
        ).filter(
561
            plan_id=test_run.plan,
562
            case__case_status=TestCaseStatus.objects.filter(name='CONFIRMED').first().pk
563
        ).order_by('case')  # order b/c of PostgreSQL
564
565
        # also grab a list of all TestCase IDs which are already present in the
566
        # current TestRun so we can mark them as disabled and not allow them to
567
        # be selected
568
        test_case_runs = TestExecution.objects.filter(run=run_id).values_list('case', flat=True)
569
570
        data = {
571
            'test_run': test_run,
572
            'confirmed_cases': rows,
573
            'confirmed_cases_count': rows.count(),
574
            'test_case_runs_count': len(test_case_runs),
575
            'exist_case_run_ids': test_case_runs,
576
        }
577
578
        return render(request, 'run/assign_case.html', data)
579
580
581
@require_GET
582
def cc(request, run_id):  # pylint: disable=invalid-name
583
    """Add or remove cc from a test run"""
584
585
    test_run = get_object_or_404(TestRun, run_id=run_id)
586
    action = request.GET.get('do')
587
    username_or_email = request.GET.get('user')
588
    context_data = {'test_run': test_run, 'is_ajax': True}
589
590
    if action:
591
        if not username_or_email:
592
            context_data['message'] = 'User name or email is required by this operation'
593
        else:
594
            try:
595
                user = User.objects.get(
596
                    Q(username=username_or_email) |
597
                    Q(email=username_or_email)
598
                )
599
            except ObjectDoesNotExist:
600
                context_data['message'] = 'The user you typed does not exist in database'
601
            else:
602
                if action == 'add':
603
                    test_run.add_cc(user=user)
604
605
                if action == 'remove':
606
                    test_run.remove_cc(user=user)
607
608
    return render(request, 'run/get_cc.html', context_data)
609
610
611
@require_POST
612
@permission_required('testruns.change_testexecution')
613
def update_case_run_text(request, run_id):
614
    """Update the IDLE cases to newest text"""
615
616
    test_run = get_object_or_404(TestRun, run_id=run_id)
617
618
    if request.POST.get('case_run'):
619
        test_case_runs = test_run.case_run.filter(pk__in=request.POST.getlist('case_run'))
620
    else:
621
        test_case_runs = test_run.case_run.all()
622
623
    test_case_runs = test_case_runs.filter(status__name='IDLE')
624
625
    count = 0
626
    updated_test_case_runs = ''
627
    for test_case_run in test_case_runs:
628
        latest_version = test_case_run.case.history.latest().history_id
629
        if test_case_run.case_text_version != latest_version:
630
            count += 1
631
            updated_test_case_runs += '<li>%s: %s -> %s</li>' % (
632
                test_case_run.case.summary, test_case_run.case_text_version, latest_version
633
            )
634
            test_case_run.case_text_version = latest_version
635
            test_case_run.save()
636
637
    info = "<p>%s</p><ul>%s</ul>" % (_("%d CaseRun(s) updated:") % count, updated_test_case_runs)
638
    message_level = messages.INFO
639
    if count:
640
        message_level = messages.SUCCESS
641
642
    messages.add_message(request, message_level, info)
643
    return HttpResponseRedirect(reverse('testruns-get', args=[run_id]))
644
645
646
def get_caseruns_of_runs(runs, kwargs=None):
647
    """
648
    Filtering argument -
649
        priority
650
        tester
651
        plan tag
652
    """
653
654
    if kwargs is None:
655
        kwargs = {}
656
    plan_tag = kwargs.get('plan_tag', None)
657
    if plan_tag:
658
        runs = runs.filter(plan__tag__name=plan_tag)
659
    caseruns = TestExecution.objects.filter(run__in=runs)
660
    priority = kwargs.get('priority', None)
661
    if priority:
662
        caseruns = caseruns.filter(case__priority__pk=priority)
663
    tester = kwargs.get('tester', None)
664
    if not tester:
665
        caseruns = caseruns.filter(tested_by=None)
666
    if tester:
667
        caseruns = caseruns.filter(tested_by__pk=tester)
668
    status = kwargs.get('status', None)
669
    if status:
670
        caseruns = caseruns.filter(status__name__iexact=status)
671
    return caseruns
672
673
674
@method_decorator(permission_required('testruns.change_testexecution'), name='dispatch')
675
class UpdateAssigneeView(View):
676
    """Updates TestCaseRun.assignee. Called from the front-end."""
677
678
    http_method_names = ['post']
679
680
    def post(self, request):
681
        assignee = request.POST.get('assignee')
682
        try:
683
            user = User.objects.get(username=assignee)
684
        except User.DoesNotExist:
685
            try:
686
                user = User.objects.get(email=assignee)
687
            except User.DoesNotExist:
688
                return JsonResponse({'rc': 1,
689
                                     'response': _('User %s not found!' % assignee)},
690
                                    status=HTTPStatus.NOT_FOUND)
691
692
        object_ids = request.POST.getlist('ids[]')
693
694
        for caserun_pk in object_ids:
695
            test_case_run = get_object_or_404(TestExecution, pk=int(caserun_pk))
696
            test_case_run.assignee = user
697
            test_case_run.save()
698
699
        return JsonResponse({'rc': 0, 'response': 'ok'})
700
701
702
@method_decorator(permission_required('testruns.change_testexecution'), name='dispatch')
703
class UpdateCaseRunStatusView(View):
704
    """Updates TestCaseRun.status_id. Called from the front-end."""
705
706
    http_method_names = ['post']
707
708
    def post(self, request):
709
        status_id = int(request.POST.get('status_id'))
710
        object_ids = request.POST.getlist('object_pk[]')
711
712
        for caserun_pk in object_ids:
713
            test_case_run = get_object_or_404(TestExecution, pk=int(caserun_pk))
714
            test_case_run.status_id = status_id
715
            test_case_run.tested_by = request.user
716
            test_case_run.close_date = datetime.now()
717
            test_case_run.save()
718
719
        return JsonResponse({'rc': 0, 'response': 'ok'})
720