Repository: Nitrate/Nitrate

Issue:
testplans/views/CaseActions needs to support GET and POST or be refactored
From original discussion at:
* https://github.com/Nitrate/Nitrate/pull/176#discussion_r111017758
* https://github.com/Nitrate/Nitrate/pull/176#discussion_r111018078

This is an inner class, defined inside the `cases` method, which allows both GET and POST. Either `delete_cases` needs to require only POST or you need to take both of them into account.

NOTE: this inner class is also similar to the ones discussed in #184 





Discussion:

Resolving patch:
diff --git a/tcms/testplans/forms.py b/tcms/testplans/forms.py
--- a/tcms/testplans/forms.py
+++ b/tcms/testplans/forms.py
@@ -638,7 +638,6 @@ class XMLRPCEditPlanForm(EditPlanForm):
 
 
 class ImportCasesViaXMLForm(forms.Form):
-    a = forms.CharField(widget=forms.HiddenInput)
     xml_file = CasePlanXMLField(
         label='Upload XML file:',
         help_text='XML file is export with TCMS or Testopia.'
diff --git a/tcms/testplans/urls/plan_urls.py b/tcms/testplans/urls/plan_urls.py
--- a/tcms/testplans/urls/plan_urls.py
+++ b/tcms/testplans/urls/plan_urls.py
@@ -14,7 +14,17 @@
     url(r'^(?P<plan_id>\d+)/edit/$', views.edit, name='plan-edit'),
     url(r'^(?P<plan_id>\d+)/attachment/$', views.attachment, name='plan-attachment'),
     url(r'^(?P<plan_id>\d+)/history/$', views.text_history, name='plan-text-history'),
-    url(r'^(?P<plan_id>\d+)/cases/$', views.cases, name='plan-cases'),
+
+    url(r'^(?P<plan_id>\d+)/reorder-cases/$', views.ReorderCasesView.as_view(),
+        name='plan-reorder-cases'),
+    url(r'^(?P<plan_id>\d+)/link-cases/$', views.LinkCasesView.as_view(),
+        name='plan-link-cases'),
+    url(r'^(?P<plan_id>\d+)/link-cases/search/$', views.LinkCasesSearchView.as_view(),
+        name='plan-search-cases-for-link'),
+    url(r'^(?P<plan_id>\d+)/import-cases/$', views.ImportCasesView.as_view(),
+        name='plan-import-cases'),
+    url(r'^(?P<plan_id>\d+)/delete-cases/$', views.DeleteCasesView.as_view(),
+        name='plan-delete-cases'),
 
     url(r'^(?P<plan_id>\d+)/runs/$', testruns_views.load_runs_of_one_plan,
         name='load_runs_of_one_plan_url'),
diff --git a/tcms/testplans/views.py b/tcms/testplans/views.py
--- a/tcms/testplans/views.py
+++ b/tcms/testplans/views.py
@@ -7,20 +7,22 @@
 from six.moves import urllib
 from json import dumps as json_dumps
 
+from django.utils.decorators import method_decorator
 from django.conf import settings
 from django.contrib.auth.decorators import permission_required
+from django.core import serializers
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.urlresolvers import reverse
-from django.core import serializers
 from django.db.models import Count
 from django.db.models import Q
-from django.http import HttpResponse, HttpResponseRedirect
 from django.http import Http404, HttpResponsePermanentRedirect
+from django.http import HttpResponse, HttpResponseRedirect
 from django.http import JsonResponse
 from django.shortcuts import get_object_or_404, render
 from django.template.loader import get_template
-from django.views.decorators.http import require_http_methods
 from django.views.decorators.http import require_GET
+from django.views.decorators.http import require_http_methods
+from django.views.generic import View
 from uuslug import slugify
 
 from tcms.core.db import SQLExecution
@@ -438,7 +440,7 @@ def get(request, plan_id, slug=None, template_name='plan/get.html'):
         'module': MODULE_NAME,
         'sub_module': SUB_MODULE_NAME,
         'test_plan': tp,
-        'xml_form': ImportCasesViaXMLForm(initial={'a': 'import_cases'}),
+        'xml_form': ImportCasesViaXMLForm(),
     }
     return render(request, template_name, context=context_data)
 
@@ -791,202 +793,198 @@ def text_history(request, plan_id, template_name='plan/history.html'):
     return render(request, template_name, context=context_data)
 
 
-@require_http_methods(['GET', 'POST'])
-def cases(request, plan_id):
-    """Process the xml with import"""
+class ReorderCasesView(View):
+    """Reorder cases"""
 
-    ajax_response = {'rc': 0, 'response': 'ok'}
-    tp = get_object_or_404(TestPlan, plan_id=plan_id)
+    http_method_names = ['post']
 
-    class CaseActions(object):
-        def __init__(self, request, tp):
-            self.__all__ = ['link_cases', 'delete_cases', 'order_cases', 'import_cases']
-            self.request = request
-            self.tp = tp
+    def post(self, request, plan_id):
+        # Current we should rewrite all of cases belong to the plan.
+        # Because the cases sortkey in database is chaos,
+        # Most of them are None.
 
-        def link_cases(self, template_name='plan/search_case.html'):
-            """Handle to form to add case to plans"""
-            SUB_MODULE_NAME = 'plans'
-            tcs = None
+        if 'case' not in request.POST:
+            return JsonResponse({
+                'rc': 1,
+                'response': 'At least one case is required to re-order.'
+            })
 
-            if request.POST.get('action') == 'add_to_plan':
-                if request.user.has_perm('testcases.add_testcaseplan'):
-                    tcs = TestCase.objects.filter(case_id__in=request.POST.getlist('case'))
-                    for tc in tcs:
-                        tp.add_case(tc)
-                else:
-                    return HttpResponse("Permission Denied")
+        plan = get_object_or_404(TestPlan, pk=int(plan_id))
 
-                return HttpResponseRedirect(
-                    reverse('plan-get', args=[plan_id]))
-
-            search_mode = request.POST.get('search_mode')
-            if request.POST.get('action') == 'search':
-
-                if search_mode == 'quick':
-                    form = quick_form = QuickSearchCaseForm(request.POST)
-                    normal_form = SearchCaseForm()
-                else:
-                    form = normal_form = SearchCaseForm(request.POST)
-                    form.populate(product_id=request.POST.get('product'))
-                    quick_form = QuickSearchCaseForm()
-
-                if form.is_valid():
-                    tcs = TestCase.list(form.cleaned_data)
-                    tcs = tcs.select_related(
-                        'author', 'default_tester', 'case_status', 'priority').only(
-                            'pk', 'summary', 'create_date', 'author__email',
-                            'default_tester__email', 'case_status__name', 'priority__value')
-                    tcs = tcs.exclude(case_id__in=tp.case.values_list('case_id', flat=True))
-            else:
-                normal_form = SearchCaseForm(initial={
-                    'product': tp.product_id,
-                    'product_version': tp.product_version_id,
-                    'case_status_id': TestCaseStatus.get_CONFIRMED()
-                })
-                quick_form = QuickSearchCaseForm()
-
-            context_data = {
-                'module': MODULE_NAME,
-                'sub_module': SUB_MODULE_NAME,
-                'test_plan': tp,
-                'test_cases': tcs,
-                'search_form': normal_form,
-                'quick_form': quick_form,
-                'search_mode': search_mode
-            }
-            return render(request, template_name, context=context_data)
+        case_ids = [int(id) for id in request.POST.getlist('case')]
+        cases = TestCase.objects.filter(pk__in=case_ids).only('pk')
 
-        def delete_cases(self):
-            if not request.POST.get('case'):
-                ajax_response['rc'] = 1
-                ajax_response['response'] = 'At least one case is required to delete.'
-                return JsonResponse(ajax_response)
-
-            tcs = get_selected_testcases(request)
-
-            # Log Action
-            tp_log = TCMSLog(model=tp)
-            for tc in tcs:
-                tp_log.make(who=request.user,
-                            action='Remove case %s from plan %s' % (tc.case_id, tp.plan_id))
-                tc.log_action(who=request.user, action='Remove from plan %s' % tp.plan_id)
-                tp.delete_case(case=tc)
-
-            return JsonResponse(ajax_response)
-
-        def order_cases(self):
-            """Resort case with new order"""
-            # Current we should rewrite all of cases belong to the plan.
-            # Because the cases sortkey in database is chaos,
-            # Most of them are None.
-
-            if not request.POST.get('case'):
-                ajax_response['rc'] = 1
-                ajax_response['response'] = 'At least one case is required to re-order.'
-                return JsonResponse(ajax_response)
-
-            tc_pks = request.POST.getlist('case')
-            tcs = TestCase.objects.filter(pk__in=tc_pks)
-
-            for tc in tcs:
-                new_sort_key = (tc_pks.index(str(tc.pk)) + 1) * 10
-                TestCasePlan.objects.filter(plan=tp, case=tc).update(sortkey=new_sort_key)
-
-            return JsonResponse(ajax_response)
-
-        def import_cases(self):
-            if request.method == 'POST':
-                # Process import case from XML action
-                if not request.user.has_perm('testcases.add_testcaseplan'):
-                    return HttpResponse(Prompt.render(
-                        request=request,
-                        info_type=Prompt.Alert,
-                        info='Permission denied',
-                        next=reverse('plan-get', args=[plan_id]),
-                    ))
-
-                xml_form = ImportCasesViaXMLForm(request.POST, request.FILES)
-
-                if xml_form.is_valid():
-                    i = 0
-                    for case in xml_form.cleaned_data['xml_file']:
-                        i += 1
-
-                        # Get the case category from the case and related to
-                        # the product of the plan
-                        try:
-                            category = TestCaseCategory.objects.get(product=tp.product,
-                                                                    name=case['category_name'])
-                        except TestCaseCategory.DoesNotExist:
-                            category = TestCaseCategory.objects.create(product=tp.product,
-                                                                       name=case['category_name'])
-
-                        # Start to create the objects
-                        tc = TestCase.objects.create(
-                            is_automated=case['is_automated'],
-                            script='',
-                            arguments='',
-                            summary=case['summary'],
-                            requirement='',
-                            alias='',
-                            estimated_time=0,
-                            case_status_id=case['case_status_id'],
-                            category_id=category.id,
-                            priority_id=case['priority_id'],
-                            author_id=case['author_id'],
-                            default_tester_id=case['default_tester_id'],
-                            notes=case['notes'],
-                        )
-                        TestCasePlan.objects.create(plan=tp, case=tc, sortkey=i * 10)
-
-                        tc.add_text(case_text_version=1,
-                                    author=case['author'],
-                                    action=case['action'],
-                                    effect=case['effect'],
-                                    setup=case['setup'],
-                                    breakdown=case['breakdown'])
-
-                        # handle tags
-                        if case['tags']:
-                            for tag in case['tags']:
-                                tc.add_tag(tag=tag)
-
-                        tc.add_to_plan(plan=tp)
-
-                    return HttpResponseRedirect(
-                        reverse('plan-get', args=[plan_id]) + '#testcases')
-                else:
-                    return HttpResponse(Prompt.render(
-                        request=request,
-                        info_type=Prompt.Alert,
-                        info=xml_form.errors,
-                        next=reverse('plan-get', args=[plan_id]) + '#testcases'
-                    ))
-            else:
-                return HttpResponseRedirect(
-                    reverse('plan-get', args=[plan_id]) + '#testcases')
+        for case in cases:
+            new_sort_key = (case_ids.index(case.pk) + 1) * 10
+            TestCasePlan.objects.filter(
+                plan=plan, case=case).update(sortkey=new_sort_key)
 
-    # tp = get_object_or_404(TestPlan, plan_id=plan_id)
-    cas = CaseActions(request, tp)
-    req_data = request.GET or request.POST
-    action = req_data.get('a')
+        return JsonResponse({'rc': 0, 'response': 'ok'})
 
-    if action not in cas.__all__:
-        if request.GET.get('format') == 'json':
-            ajax_response['rc'] = 1
-            ajax_response['response'] = 'Unrecognizable actions'
-            return HttpResponse(json_dumps(ajax_response))
 
-        return HttpResponse(Prompt.render(
-            request=request,
-            info_type=Prompt.Alert,
-            info='Unrecognizable actions',
-            next=reverse('plan-get', args=[plan_id]),
-        ))
+class LinkCasesView(View):
+    """Link cases to plan"""
+
+    @method_decorator(permission_required('testcases.add_testcaseplan'))
+    def post(self, request, plan_id):
+        plan = get_object_or_404(TestPlan.objects.only('pk'), pk=int(plan_id))
+        case_ids = [int(id) for id in request.POST.getlist('case')]
+        cases = TestCase.objects.filter(case_id__in=case_ids).only('pk')
+        for case in cases:
+            plan.add_case(case)
+        return HttpResponseRedirect(reverse('plan-get', args=[plan_id]))
+
+
+class LinkCasesSearchView(View):
+    """Search cases for linking to plan"""
+
+    template_name = 'plan/search_case.html'
+    SUB_MODULE_NAME = 'plans'
+
+    def get(self, request, plan_id):
+        plan = get_object_or_404(TestPlan, pk=int(plan_id))
+
+        normal_form = SearchCaseForm(initial={
+            'product': plan.product_id,
+            'product_version': plan.product_version_id,
+            'case_status_id': TestCaseStatus.get_CONFIRMED()
+        })
+        quick_form = QuickSearchCaseForm()
+        return render(self.request, self.template_name, {
+            'module': MODULE_NAME,
+            'sub_module': self.SUB_MODULE_NAME,
+            'search_form': normal_form,
+            'quick_form': quick_form,
+            'test_plan': plan,
+        })
+
+    def post(self, request, plan_id):
+        plan = get_object_or_404(TestPlan, pk=int(plan_id))
+
+        search_mode = request.POST.get('search_mode')
+        if search_mode == 'quick':
+            form = quick_form = QuickSearchCaseForm(request.POST)
+            normal_form = SearchCaseForm()
+        else:
+            form = normal_form = SearchCaseForm(request.POST)
+            form.populate(product_id=request.POST.get('product'))
+            quick_form = QuickSearchCaseForm()
+
+        if form.is_valid():
+            cases = TestCase.list(form.cleaned_data)
+            cases = cases.select_related(
+                'author', 'default_tester', 'case_status', 'priority'
+            ).only(
+                'pk', 'summary', 'create_date', 'author__email',
+                'default_tester__email', 'case_status__name',
+                'priority__value'
+            ).exclude(
+                case_id__in=plan.case.values_list('case_id', flat=True))
+
+        context = {
+            'module': MODULE_NAME,
+            'sub_module': self.SUB_MODULE_NAME,
+            'test_plan': plan,
+            'test_cases': cases,
+            'search_form': normal_form,
+            'quick_form': quick_form,
+            'search_mode': search_mode
+        }
+        return render(request, self.template_name, context=context)
+
+
+class ImportCasesView(View):
+    """Import cases to a plan"""
+
+    @method_decorator(permission_required('testcases.add_testcaseplan'))
+    def post(self, request, plan_id):
+        plan = get_object_or_404(TestPlan.objects.only('pk'), pk=int(plan_id))
+
+        # Process import case from XML action
+        xml_form = ImportCasesViaXMLForm(request.POST, request.FILES)
+
+        if xml_form.is_valid():
+            i = 0
+            for case in xml_form.cleaned_data['xml_file']:
+                i += 1
+
+                # Get the case category from the case and related to
+                # the product of the plan
+                try:
+                    category = TestCaseCategory.objects.get(
+                        product=plan.product, name=case['category_name'])
+                except TestCaseCategory.DoesNotExist:
+                    category = TestCaseCategory.objects.create(
+                        product=plan.product, name=case['category_name'])
+
+                # Start to create the objects
+                tc = TestCase.objects.create(
+                    is_automated=case['is_automated'],
+                    script='',
+                    arguments='',
+                    summary=case['summary'],
+                    requirement='',
+                    alias='',
+                    estimated_time=0,
+                    case_status_id=case['case_status_id'],
+                    category_id=category.id,
+                    priority_id=case['priority_id'],
+                    author_id=case['author_id'],
+                    default_tester_id=case['default_tester_id'],
+                    notes=case['notes'],
+                )
+                TestCasePlan.objects.create(plan=plan, case=tc, sortkey=i * 10)
+
+                tc.add_text(case_text_version=1,
+                            author=case['author'],
+                            action=case['action'],
+                            effect=case['effect'],
+                            setup=case['setup'],
+                            breakdown=case['breakdown'])
+
+                # handle tags
+                if case['tags']:
+                    for tag in case['tags']:
+                        tc.add_tag(tag=tag)
+
+                tc.add_to_plan(plan=plan)
+
+            return HttpResponseRedirect(
+                reverse('plan-get', args=[plan_id]) + '#testcases')
+        else:
+            return HttpResponse(Prompt.render(
+                request=request,
+                info_type=Prompt.Alert,
+                info=xml_form.errors,
+                next=reverse('plan-get', args=[plan_id]) + '#testcases'
+            ))
+
+
+class DeleteCasesView(View):
+    """Delete selected cases from plan"""
+
+    def post(self, request, plan_id):
+        plan = get_object_or_404(TestPlan.objects.only('pk'), pk=int(plan_id))
+
+        if 'case' not in request.POST:
+            return JsonResponse({
+                'rc': 1,
+                'response': 'At least one case is required to delete.'
+            })
+
+        cases = get_selected_testcases(request).only('pk')
+
+        # Log Action
+        plan_log = TCMSLog(model=plan)
+        for case in cases:
+            plan_log.make(
+                who=request.user,
+                action='Remove case {} from plan {}'.format(case.pk, plan.pk))
+            case.log_action(who=request.user,
+                            action='Remove from plan {}'.format(plan.pk))
+            plan.delete_case(case=case)
 
-    func = getattr(cas, action)
-    return func()
+        return JsonResponse({'rc': 0, 'response': 'ok'})
 
 
 @require_GET


Test patch:
diff --git a/tcms/testplans/tests.py b/tcms/testplans/tests.py
--- a/tcms/testplans/tests.py
+++ b/tcms/testplans/tests.py
@@ -85,22 +85,6 @@ def test_plan_details(self):
         response = self.c.get(location, follow=True)
         self.assertEquals(response.status_code, http_client.OK)
 
-    def test_plan_cases(self):
-        location = reverse('plan-cases', args=[self.plan_id])
-        response = self.c.get(location)
-        self.assertEquals(response.status_code, http_client.OK)
-
-    def test_plan_importcase(self):
-        location = reverse('plan-cases', args=[self.plan_id])
-        filename = os.path.join(TCMS_ROOT_PATH, 'fixtures', 'cases-to-import.xml')
-        with open(filename, 'r') as fin:
-            response = self.c.post(location, {'a': 'import_cases', 'xml_file': fin}, follow=True)
-        self.assertEquals(response.status_code, http_client.OK)
-
-        summary = 'Remove this case from a test plan'
-        has_case = TestCase.objects.filter(summary=summary).exists()
-        self.assertTrue(has_case)
-
     def test_plan_delete(self):
         tp_pk = self.test_plan.pk
 
@@ -175,20 +159,36 @@ def test_plan_delete(self):
 # ### Test cases for view methods ### #
 
 
-class TestUnknownActionOnCases(BasePlanCase):
-    """Test case for unknown action on a plan's cases"""
+class TestImportCasesToPlan(BasePlanCase):
+    """Test import cases to a plan"""
+
+    @classmethod
+    def setUpTestData(cls):
+        super(TestImportCasesToPlan, cls).setUpTestData()
 
-    def setUp(self):
-        self.cases_url = reverse('plan-cases', args=[self.plan.pk])
+        cls.plan_tester = User.objects.create_user(
+            username='plan_tester',
+            email='admin@example.com',
+            password='password')
 
-    def test_ajax_request(self):
-        response = self.client.get(self.cases_url, {'a': 'unknown action', 'format': 'json'})
-        data = json_loads(response.content)
-        self.assertEqual('Unrecognizable actions', data['response'])
+        user_should_have_perm(cls.plan_tester, 'testcases.add_testcaseplan')
+
+    def test_import_cases(self):
+        self.client.login(username=self.plan_tester.username,
+                          password='password')
+
+        location = reverse('plan-import-cases', args=[self.plan.pk])
+        filename = os.path.join(TCMS_ROOT_PATH, 'fixtures', 'cases-to-import.xml')
+        with open(filename, 'r') as fin:
+            response = self.client.post(location, {'xml_file': fin})
+        self.assertRedirects(
+            response,
+            reverse('plan-get', args=[self.plan.pk]) + '#testcases',
+            target_status_code=301)
 
-    def test_request_from_webui(self):
-        response = self.client.get(self.cases_url, {'a': 'unknown action'})
-        self.assertContains(response, 'Unrecognizable actions')
+        summary = 'Remove this case from a test plan'
+        has_case = TestCase.objects.filter(summary=summary).exists()
+        self.assertTrue(has_case)
 
 
 class TestDeleteCasesFromPlan(BasePlanCase):
@@ -201,26 +201,30 @@ def setUpTestData(cls):
         cls.plan_tester.set_password('password')
         cls.plan_tester.save()
 
-        cls.cases_url = reverse('plan-cases', args=[cls.plan.pk])
+        cls.cases_url = reverse('plan-delete-cases', args=[cls.plan.pk])
 
     def test_missing_cases_ids(self):
-        self.client.login(username=self.plan_tester.username, password='password')
+        self.client.login(username=self.plan_tester.username,
+                          password='password')
 
-        response = self.client.post(self.cases_url, {'a': 'delete_cases'})
+        response = self.client.post(self.cases_url, {})
         data = json_loads(response.content)
         self.assertEqual(1, data['rc'])
-        self.assertEqual('At least one case is required to delete.', data['response'])
+        self.assertEqual('At least one case is required to delete.',
+                         data['response'])
 
     def test_delete_cases(self):
-        self.client.login(username=self.plan_tester.username, password='password')
+        self.client.login(username=self.plan_tester.username,
+                          password='password')
 
-        post_data = {'a': 'delete_cases', 'case': [self.case_1.pk, self.case_3.pk]}
+        post_data = {'case': [self.case_1.pk, self.case_3.pk]}
         response = self.client.post(self.cases_url, post_data)
         data = json_loads(response.content)
 
         self.assertEqual(0, data['rc'])
         self.assertEqual('ok', data['response'])
-        self.assertFalse(self.plan.case.filter(pk__in=[self.case_1.pk, self.case_3.pk]).exists())
+        self.assertFalse(self.plan.case.filter(
+            pk__in=[self.case_1.pk, self.case_3.pk]).exists())
 
         # Assert action logs are recorded for plan and case correctly
 
@@ -229,9 +233,12 @@ def test_delete_cases(self):
             log = TCMSLogModel.get_logs_for_model(TestCase, pk)[0]
             self.assertEqual(expected_log, log.action)
 
-        for plan_pk, case_pk in ((self.plan.pk, self.case_1.pk), (self.plan.pk, self.case_3.pk)):
-            expected_log = 'Remove case {} from plan {}'.format(case_pk, plan_pk)
-            self.assertTrue(TCMSLogModel.objects.filter(action=expected_log).exists())
+        for plan_pk, case_pk in ((self.plan.pk, self.case_1.pk),
+                                 (self.plan.pk, self.case_3.pk)):
+            expected_log = 'Remove case {} from plan {}'.format(
+                case_pk, plan_pk)
+            self.assertTrue(
+                TCMSLogModel.objects.filter(action=expected_log).exists())
 
 
 class TestSortCases(BasePlanCase):
@@ -244,12 +251,12 @@ def setUpTestData(cls):
         cls.plan_tester.set_password('password')
         cls.plan_tester.save()
 
-        cls.cases_url = reverse('plan-cases', args=[cls.plan.pk])
+        cls.cases_url = reverse('plan-reorder-cases', args=[cls.plan.pk])
 
     def test_missing_cases_ids(self):
         self.client.login(username=self.plan_tester.username, password='password')
 
-        response = self.client.post(self.cases_url, {'a': 'order_cases'})
+        response = self.client.post(self.cases_url, {})
         data = json_loads(response.content)
         self.assertEqual(1, data['rc'])
         self.assertEqual('At least one case is required to re-order.', data['response'])
@@ -257,17 +264,16 @@ def test_missing_cases_ids(self):
     def test_order_cases(self):
         self.client.login(username=self.plan_tester.username, password='password')
 
-        post_data = {'a': 'order_cases', 'case': [self.case_1.pk, self.case_3.pk]}
+        post_data = {'case': [self.case_3.pk, self.case_1.pk]}
         response = self.client.post(self.cases_url, post_data)
         data = json_loads(response.content)
 
-        self.assertEqual(0, data['rc'])
-        self.assertEqual('ok', data['response'])
+        self.assertEqual({'rc': 0, 'response': 'ok'}, data)
 
-        case_plan_rel = TestCasePlan.objects.get(plan=self.plan, case=self.case_1)
+        case_plan_rel = TestCasePlan.objects.get(plan=self.plan, case=self.case_3)
         self.assertEqual(10, case_plan_rel.sortkey)
 
-        case_plan_rel = TestCasePlan.objects.get(plan=self.plan, case=self.case_3)
+        case_plan_rel = TestCasePlan.objects.get(plan=self.plan, case=self.case_1)
         self.assertEqual(20, case_plan_rel.sortkey)
 
 
@@ -300,7 +306,9 @@ def setUpTestData(cls):
         cls.plan_tester.set_password('password')
         cls.plan_tester.save()
 
-        cls.cases_url = reverse('plan-cases', args=[cls.plan.pk])
+        cls.search_cases_for_link_url = reverse('plan-search-cases-for-link',
+                                                args=[cls.plan.pk])
+        cls.link_cases_url = reverse('plan-link-cases', args=[cls.plan.pk])
 
     def tearDown(self):
         # Ensure permission is removed whenever it was added during tests
@@ -323,7 +331,7 @@ def assert_normal_search_is_shown(self, response):
     def test_show_quick_search_by_default(self):
         self.client.login(username=self.plan_tester.username, password='password')
 
-        response = self.client.post(self.cases_url, {'a': 'link_cases'})
+        response = self.client.post(self.search_cases_for_link_url, {})
         self.assert_quick_search_is_shown(response)
 
     def assert_search_result(self, response):
@@ -345,9 +353,12 @@ def assert_search_result(self, response):
     def test_quick_search(self):
         self.client.login(username=self.plan_tester.username, password='password')
 
-        post_data = {'a': 'link_cases', 'action': 'search', 'search_mode': 'quick',
-                     'case_id_set': ','.join(map(str, [self.case_1.pk, self.another_case_2.pk]))}
-        response = self.client.post(self.cases_url, post_data)
+        post_data = {
+            'search_mode': 'quick',
+            'case_id_set': ','.join(
+                map(str, [self.case_1.pk, self.another_case_2.pk]))
+        }
+        response = self.client.post(self.search_cases_for_link_url, post_data)
 
         self.assert_quick_search_is_shown(response)
         self.assert_search_result(response)
@@ -355,29 +366,25 @@ def test_quick_search(self):
     def test_normal_search(self):
         self.client.login(username=self.plan_tester.username, password='password')
 
-        post_data = {'a': 'link_cases', 'action': 'search', 'search_mode': 'normal',
-                     'case_id_set': ','.join(map(str, [self.case_1.pk, self.another_case_2.pk]))}
-        response = self.client.post(self.cases_url, post_data)
+        post_data = {
+            'search_mode': 'normal',
+            'case_id_set': ','.join(
+                map(str, [self.case_1.pk, self.another_case_2.pk]))
+        }
+        response = self.client.post(self.search_cases_for_link_url, post_data)
 
         self.assert_normal_search_is_shown(response)
         self.assert_search_result(response)
 
-    def test_missing_permission_to_link_cases(self):
-        self.client.login(username=self.plan_tester.username, password='password')
-
-        post_data = {'a': 'link_cases', 'action': 'add_to_plan',
-                     'case': [self.another_case_1.pk, self.another_case_2.pk]}
-        response = self.client.post(self.cases_url, post_data)
-        self.assertContains(response, 'Permission Denied')
-
     def test_link_cases(self):
         self.client.login(username=self.plan_tester.username, password='password')
 
         user_should_have_perm(self.plan_tester, 'testcases.add_testcaseplan')
 
-        post_data = {'a': 'link_cases', 'action': 'add_to_plan',
-                     'case': [self.another_case_1.pk, self.another_case_2.pk]}
-        response = self.client.post(self.cases_url, post_data)
+        post_data = {
+            'case': [self.another_case_1.pk, self.another_case_2.pk]
+        }
+        response = self.client.post(self.link_cases_url, post_data)
 
         self.assertRedirects(
             response,
@@ -385,9 +392,11 @@ def test_link_cases(self):
             target_status_code=http_client.MOVED_PERMANENTLY)
 
         self.assertTrue(
-            TestCasePlan.objects.filter(plan=self.plan, case=self.another_case_1).exists())
+            TestCasePlan.objects.filter(
+                plan=self.plan, case=self.another_case_1).exists())
         self.assertTrue(
-            TestCasePlan.objects.filter(plan=self.plan, case=self.another_case_2).exists())
+            TestCasePlan.objects.filter(
+                plan=self.plan, case=self.another_case_2).exists())
 
 
 class TestCloneView(BasePlanCase):