kiwitcms/Kiwi

View on GitHub
tcms/testcases/views.py

Summary

Maintainability
A
1 hr
Test Coverage
# -*- coding: utf-8 -*-

from django.contrib import messages
from django.contrib.auth.decorators import permission_required
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView
from django.views.generic.base import TemplateView, View
from django.views.generic.edit import CreateView, UpdateView
from guardian.decorators import permission_required as object_permission_required

from tcms.testcases.forms import (
    CaseNotifyFormSet,
    CloneCaseForm,
    SearchCaseForm,
    TestCaseForm,
)
from tcms.testcases.models import Template, TestCase
from tcms.testplans.models import TestPlan


def plan_from_request_or_none(request):  # pylint: disable=missing-permission-required
    """Get TestPlan from REQUEST

    This method relies on the existence of from_plan within REQUEST.
    """
    test_plan_id = request.POST.get("from_plan") or request.GET.get("from_plan")
    if not test_plan_id:
        return None
    return get_object_or_404(TestPlan, pk=test_plan_id)


@method_decorator(permission_required("testcases.add_testcase"), name="dispatch")
class NewCaseView(CreateView):
    model = TestCase
    form_class = TestCaseForm
    template_name = "testcases/mutable.html"

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
        # clear fields which are set dynamically via JavaScript
        form.populate(self.request.POST.get("product", -1))
        return form

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs["initial"].update(  # pylint: disable=objects-update-used
            {
                "author": self.request.user,
            }
        )

        test_plan = plan_from_request_or_none(self.request)
        if test_plan:
            kwargs["initial"]["product"] = test_plan.product_id

        return kwargs

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["test_plan"] = plan_from_request_or_none(self.request)
        context["notify_formset"] = kwargs.get("notify_formset") or CaseNotifyFormSet()
        context["templates"] = Template.objects.all()
        return context

    def form_valid(self, form):
        test_plan = plan_from_request_or_none(self.request)

        notify_formset = CaseNotifyFormSet(self.request.POST)
        if notify_formset.is_valid():
            test_case = form.save()
            if test_plan:
                test_plan.add_case(test_case)

            notify_formset.instance = test_case
            notify_formset.save()

            return HttpResponseRedirect(reverse("testcases-get", args=[test_case.pk]))

        # taken from FormMixin.form_invalid()
        return self.render_to_response(
            self.get_context_data(notify_formset=notify_formset)
        )


@method_decorator(permission_required("testcases.view_testcase"), name="dispatch")
class TestCaseSearchView(TemplateView):
    """
    Shows the search form which uses JSON RPC to fetch the results
    """

    template_name = "testcases/search.html"

    def get_context_data(self, **kwargs):
        form = SearchCaseForm(self.request.GET)
        if self.request.GET.get("product"):
            form.populate(product_id=self.request.GET["product"])
        else:
            form.populate()

        return {
            "form": form,
        }


@method_decorator(
    object_permission_required(
        "testcases.view_testcase", (TestCase, "pk", "pk"), accept_global_perms=True
    ),
    name="dispatch",
)
class TestCaseGetView(DetailView):
    model = TestCase
    template_name = "testcases/get.html"
    http_method_names = ["get"]

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["executions"] = self.object.executions.select_related(
            "run", "tested_by", "assignee", "case", "status"
        ).order_by("run__plan", "run")
        context["OBJECT_MENU_ITEMS"] = [
            (
                "...",
                [
                    (
                        _("Edit"),
                        reverse("testcases-edit", args=[self.object.pk]),
                    ),
                    (
                        _("Clone"),
                        reverse("testcases-clone") + f"?c={self.object.pk}",
                    ),
                    (
                        _("History"),
                        f"/admin/testcases/testcase/{self.object.pk}/history/",
                    ),
                    ("-", "-"),
                    (
                        _("Object permissions"),
                        reverse(
                            "admin:testcases_testcase_permissions",
                            args=[self.object.pk],
                        ),
                    ),
                    ("-", "-"),
                    (
                        _("Delete"),
                        reverse(
                            "admin:testcases_testcase_delete",
                            args=[self.object.pk],
                        ),
                    ),
                ],
            )
        ]

        return context


@method_decorator(
    object_permission_required(
        "testcases.change_testcase", (TestCase, "pk", "pk"), accept_global_perms=True
    ),
    name="dispatch",
)
class EditTestCaseView(UpdateView):
    model = TestCase
    template_name = "testcases/mutable.html"
    form_class = TestCaseForm

    def form_valid(self, form):
        notify_formset = CaseNotifyFormSet(self.request.POST, instance=self.object)
        if notify_formset.is_valid():
            notify_formset.save()
            return super().form_valid(form)

        # taken from FormMixin.form_invalid()
        return self.render_to_response(
            self.get_context_data(notify_formset=notify_formset)
        )

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["notify_formset"] = kwargs.get("notify_formset") or CaseNotifyFormSet(
            instance=self.object
        )
        return context

    def get_form(self, form_class=None):
        form = super().get_form(form_class)
        if self.request.POST.get("product"):
            form.populate(product_id=self.request.POST["product"])
        else:
            form.populate(product_id=self.object.category.product_id)
        return form

    def get_initial(self):
        default_tester = None
        if self.object.default_tester_id:
            default_tester = self.object.default_tester.email

        return {
            "product": self.object.category.product_id,
            "default_tester": default_tester,
        }


@method_decorator(permission_required("testcases.add_testcase"), name="dispatch")
class CloneTestCaseView(View):
    """Clone one case or multiple case into other plan or plans"""

    template_name = "testcases/clone.html"
    http_method_names = ["get", "post"]

    def post(self, request):
        if not self._is_request_data_valid(request):
            return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))

        # Do the clone action
        clone_form = CloneCaseForm(request.POST)
        clone_form.populate(case_ids=request.POST.getlist("case"))

        if clone_form.is_valid():
            for tc_src in clone_form.cleaned_data["case"]:
                tc_dest = tc_src.clone(request.user, clone_form.cleaned_data["plan"])

            # Detect the number of items and redirect to correct one
            if len(clone_form.cleaned_data["case"]) == 1:
                return HttpResponseRedirect(
                    reverse(
                        "testcases-get",
                        args=[
                            tc_dest.pk,
                        ],
                    )
                )

            if len(clone_form.cleaned_data["plan"]) == 1:
                test_plan = clone_form.cleaned_data["plan"][0]
                return HttpResponseRedirect(
                    reverse("test_plan_url_short", args=[test_plan.pk])
                )

            # Otherwise tell the user the clone action is successful
            messages.add_message(
                request, messages.SUCCESS, _("TestCase cloning was successful")
            )
            return HttpResponseRedirect(reverse("plans-search"))

        # invalid form
        messages.add_message(request, messages.ERROR, clone_form.errors)
        return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))

    def get(self, request):
        if not self._is_request_data_valid(request, "c"):
            return HttpResponseRedirect(request.META.get("HTTP_REFERER", "/"))

        # account for short param names in URI
        get_params = request.GET.copy()
        get_params.setlist("case", request.GET.getlist("c"))
        del get_params["c"]

        clone_form = CloneCaseForm(get_params)
        clone_form.populate(case_ids=get_params.getlist("case"))

        context = {
            "form": clone_form,
        }
        return render(request, self.template_name, context)

    @staticmethod
    def _is_request_data_valid(request, field_name="case"):
        request_data = getattr(request, request.method)

        if field_name not in request_data:
            messages.add_message(
                request, messages.ERROR, _("At least one TestCase is required")
            )
            return False

        return True