View on GitHub


35 mins
Test Coverage
# Copyright (c) 2019-2021 Alexander Todorov <>

# Licensed under the GPL 2.0:

from django.contrib.auth.decorators import permission_required
from django.http import HttpResponseRedirect
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.bugs.forms import BugCommentForm, NewBugForm
from tcms.bugs.models import Bug
from tcms.core.helpers.comments import add_comment
from import Component

        "bugs.view_bug", (Bug, "pk", "pk"), accept_global_perms=True
class Get(DetailView):
    model = Bug
    template_name = "bugs/get.html"
    http_method_names = ["get"]

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["comment_form"] = BugCommentForm()
        context["executions"] = self.object.executions.all()
        context["OBJECT_MENU_ITEMS"] = [
                    (_("Edit"), reverse("bugs-edit", args=[])),
                    ("-", "-"),
                        _("Object permissions"),
                        reverse("admin:bugs_bug_permissions", args=[]),
                    ("-", "-"),
                        reverse("admin:bugs_bug_delete", args=[]),

        return context

@method_decorator(permission_required("bugs.add_bug"), name="dispatch")
class New(CreateView):
    model = Bug
    form_class = NewBugForm
    template_name = "bugs/mutable.html"

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["page_title"] = _("New Bug")
        context["form_post_url"] = reverse("bugs-new")
        return context

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

    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 form_valid(self, form):
        response = super().form_valid(form)

        if not self.object.assignee:
            self.object.assignee = New.find_assignee(self.request.POST)
        add_comment([self.object], form.cleaned_data["text"], self.request.user)

        return response

    def assignee_from_components(components):
        Return the first owner which is assigned to any of the
        components. This is as best as we can to automatically figure
        out who should be assigned to this bug.
        for component in components:
            if component.initial_owner:
                return component.initial_owner

        return None

    def find_assignee(data):
        Try to automatically find an assignee for Bug by first scanning
        TestCase components (if present) and then components for the
        assignee = None
        if "_execution" in data:
            assignee = New.assignee_from_components(
            del data["_execution"]

        if not assignee:
            assignee = New.assignee_from_components(

        return assignee

    def create_bug(data):
        Helper method used within Issue Tracker integration.

        :param data: Untrusted input, usually via HTTP request
        :type data: dict
        :return: bug
        :rtype: Model
        bug = None

        if "assignee" not in data or not data["assignee"]:
            data["assignee"] = New.find_assignee(data)

        text = data["text"]
        del data["text"]

        bug = Bug.objects.create(**data)
        add_comment([bug], text, bug.reporter)

        return bug

        "bugs.change_bug", (Bug, "pk", "pk"), accept_global_perms=True
class Edit(UpdateView):
    model = Bug
    form_class = NewBugForm
    template_name = "bugs/mutable.html"
    _values_before_update = {}

    def _record_changes(self, new_data):
        result = ""
        for field in self.model._meta.fields:
            field =
            if (
                field in new_data
                and self._values_before_update[field] != new_data[field]
                _before_update = self._values_before_update[field]
                _after_update = new_data[field]
                result += f"{field.title()}: {_before_update} -> {_after_update}\n"
        if result:
            add_comment([self.object], result, self.request.user)

    def get_initial(self):
        return {
            "assignee": self.object.assignee,

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context["page_title"] = _("Edit bug")
        context["form_post_url"] = reverse(
        return context

    def get_object(self, queryset=None):
        obj = super().get_object(queryset)
        for field in self.model._meta.fields:
            self._values_before_update[] = getattr(obj,
        return obj

    def form_valid(self, form):
        return super().form_valid(form)

@method_decorator(permission_required("django_comments.add_comment"), name="dispatch")
class AddComment(View):
    http_methods = ["post"]

    def post(self, request):
        form = BugCommentForm(request.POST)
        request_action = request.POST.get("action")

        if form.is_valid():
            bug = form.cleaned_data["bug"]
            if form.cleaned_data["text"]:
                add_comment([bug], form.cleaned_data["text"], request.user)

            if request_action == "close":
                bug.status = False
                add_comment([bug], _("*bug closed*"), request.user)

            if request_action == "reopen":
                bug.status = True
                add_comment([bug], _("*bug reopened*"), request.user)

        return HttpResponseRedirect(reverse("bugs-get", args=[]))

@method_decorator(permission_required("bugs.view_bug"), name="dispatch")
class Search(TemplateView):
    template_name = "bugs/search.html"

    def get_context_data(self, **kwargs):
        form = NewBugForm(self.request.GET)
        form.populate(self.request.GET.get("product", -1))

        return {"form": form}