Skip to content

Commit

Permalink
WIP: Make TestCaseStatus customizeable. Fix #1932
Browse files Browse the repository at this point in the history
  • Loading branch information
atodorov committed Nov 20, 2020
1 parent 9441fbb commit 26b4334
Show file tree
Hide file tree
Showing 14 changed files with 109 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ check: flake8 test
.PHONY: pylint
pylint:
pylint -d missing-docstring *.py kiwi_lint/
PYTHONPATH=.:./tcms/ DJANGO_SETTINGS_MODULE=$(DJANGO_SETTINGS_MODULE) pylint --load-plugins=pylint_django.checkers.migrations -d missing-docstring -d duplicate-code --module-naming-style=any tcms/*/migrations/*
PYTHONPATH=.:./tcms/ DJANGO_SETTINGS_MODULE=$(DJANGO_SETTINGS_MODULE) pylint --load-plugins=pylint_django.checkers.migrations -d missing-docstring -d duplicate-code -d new-db-field-with-default --module-naming-style=any tcms/*/migrations/*
PYTHONPATH=.:./tcms/ DJANGO_SETTINGS_MODULE=$(DJANGO_SETTINGS_MODULE) pylint --load-plugins=pylint_django --load-plugins=kiwi_lint --load-plugins=pylint.extensions.docparams -d missing-docstring -d duplicate-code tcms/ tcms_settings_dir/

.PHONY: pylint_site_packages
Expand Down
30 changes: 30 additions & 0 deletions docs/source/admin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,36 @@ information may also be conveyed with tags, properties or in other way
so it is up to you to decide how you want to organize your testing workflow!


Test Case statuses
~~~~~~~~~~~~~~~~~~

.. versionadded:: 8.9

Kiwi TCMS installs several pre-configured statuses by default. Starting with
v8.9 you can fully customize them!

.. important::

Confirmed statuses indicate that a test case is ready for execution
and can be added to a test run. Kiwi TCMS doesn't implement any
additional behavior wrt status names.

.. warning::

Make sure to always have at least 1 confirmed and 1 unconfirmed status,
e.g. ``CONFIRMED``, ``NEEDS_UPDATE``. If you delete all statuses within
a certain group Kiwi TCMS will crash!

.. note::

For statuses shipped with Kiwi TCMS the names may appear translated
into local language! If you change these default names they will
appear untranslated!

Translation of non-default names is currently not straight forward,
see https://github.com/ecometrica/django-vinaigrette/issues/45.


Test Execution statuses
~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion tcms/core/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def test_admin_display(self):
self.assertContains(response, 'Bug trackers')
self.assertContains(response, 'Test case categories')

self.assertNotContains(response, 'Test case status')
self.assertContains(response, _('Test case statuses'))
self.assertContains(response, 'Testcases')
self.assertContains(response, 'Test cases')

Expand Down
4 changes: 2 additions & 2 deletions tcms/rpc/api/testrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from tcms.management.models import Tag
from tcms.rpc.api.forms.testrun import UpdateForm
from tcms.rpc.decorators import permissions_required
from tcms.testcases.models import TestCase, TestCaseStatus
from tcms.testcases.models import TestCase
from tcms.testruns.forms import NewRunForm
from tcms.testruns.models import TestExecution, TestRun

Expand Down Expand Up @@ -47,7 +47,7 @@ def add_case(run_id, case_id):
if run.case_run.filter(case=case).exists():
return run.case_run.filter(case=case).first().serialize()

if case.case_status != TestCaseStatus.get_confirmed():
if not case.case_status.is_confirmed:
raise RuntimeError("TC-%d status is not confirmed" % case.pk)

execution = run.create_execution(case=case)
Expand Down
4 changes: 2 additions & 2 deletions tcms/rpc/tests/test_testrun.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def _fixture_setup(self):
self.plan = TestPlanFactory(author=self.api_user)

self.test_case = TestCaseFactory()
self.test_case.case_status = TestCaseStatus.get_confirmed()
self.test_case.case_status = TestCaseStatus.objects.filter(is_confirmed=True).first()
self.test_case.save() # generate history object
self.plan.add_case(self.test_case)

Expand Down Expand Up @@ -113,7 +113,7 @@ def _fixture_setup(self):
super()._fixture_setup()

self.test_case = TestCaseFactory()
self.test_case.case_status = TestCaseStatus.get_confirmed()
self.test_case.case_status = TestCaseStatus.objects.filter(is_confirmed=True).first()
self.test_case.save()
self.test_run = TestRunFactory()

Expand Down
37 changes: 35 additions & 2 deletions tcms/testcases/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,46 @@

from django import forms
from django.conf import settings
from django.contrib import admin
from django.contrib import admin, messages
from django.forms.widgets import Select
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from tcms.core.admin import ObjectPermissionsAdminMixin
from tcms.core.history import ReadOnlyHistoryAdmin
from tcms.testcases.models import BugSystem, Category, TestCase
from tcms.testcases.models import BugSystem, Category, TestCase, TestCaseStatus


class TestCaseStatusAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'is_confirmed')
ordering = ['-is_confirmed', 'id']
fieldsets = [
('', {
'fields': ('name', 'description', 'is_confirmed'),
'description': "<h1>%s</h1>" %
_("""For more information about customizing test case statuses see
<a href="https://kiwitcms.readthedocs.io/en/latest/admin.html#test-case-statuses">
the documentation</a>!"""),
}),
]

@admin.options.csrf_protect_m
def delete_view(self, request, object_id, extra_context=None):
obj = self.model.objects.get(pk=object_id)

if not self.model.objects.filter(
is_confirmed=obj.is_confirmed
).exclude(pk=object_id).exists():
messages.add_message(
request,
messages.ERROR,
_('1 confirmed & 1 uncomfirmed status required!'),
)

return HttpResponseRedirect(reverse('admin:testcases_testcasestatus_changelist'))

return super().delete_view(request, object_id, extra_context)


class TestCaseAdmin(ObjectPermissionsAdminMixin, ReadOnlyHistoryAdmin):
Expand Down Expand Up @@ -100,3 +132,4 @@ class BugSystemAdmin(admin.ModelAdmin):
admin.site.register(BugSystem, BugSystemAdmin)
admin.site.register(Category, CategoryAdmin)
admin.site.register(TestCase, TestCaseAdmin)
admin.site.register(TestCaseStatus, TestCaseStatusAdmin)
24 changes: 24 additions & 0 deletions tcms/testcases/migrations/0016_testcasestatus_is_confirmed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from django.db import migrations, models


def forwards(apps, schema_editor):
test_case_status_model = apps.get_model('testcases', 'TestCaseStatus')
test_case_status_model.objects.filter(
name='CONFIRMED'
).update(is_confirmed=True)


class Migration(migrations.Migration):

dependencies = [
('testcases', '0015_add_summary_db_index'),
]

operations = [
migrations.AddField(
model_name='testcasestatus',
name='is_confirmed',
field=models.BooleanField(db_index=True, default=False),
),
migrations.RunPython(forwards, migrations.RunPython.noop),
]
21 changes: 6 additions & 15 deletions tcms/testcases/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.db.models import ObjectDoesNotExist
from django.db.models import Q
from django.urls import reverse
from django.utils.translation import override
from django.utils.translation import gettext_lazy as _

from tcms.core.history import KiwiHistoricalRecords
from tcms.core.models import TCMSActionModel
Expand All @@ -17,27 +17,18 @@
class TestCaseStatus(TCMSActionModel):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
is_confirmed = models.BooleanField(db_index=True, default=False)

class Meta:
verbose_name = "Test case status"
verbose_name_plural = "Test case statuses"
verbose_name = _('Test case status')
verbose_name_plural = _('Test case statuses')

def __str__(self):
return self.name

@classmethod
def get_proposed(cls):
with override('en'):
return cls.objects.get(name='PROPOSED')

@classmethod
def get_confirmed(cls):
with override('en'):
return cls.objects.get(name='CONFIRMED')

def is_confirmed(self):
with override('en'):
return self.name == 'CONFIRMED'
return cls.objects.filter(is_confirmed=True).first()


# register model for DB translations
Expand Down Expand Up @@ -234,7 +225,7 @@ def clone(self, new_author, test_plans):
extra_link=self.extra_link,
summary=self.summary,
requirement=self.requirement,
case_status=TestCaseStatus.get_proposed(),
case_status=TestCaseStatus.objects.filter(is_confirmed=False).first(),
category=self.category,
priority=self.priority,
notes=self.notes,
Expand Down
2 changes: 1 addition & 1 deletion tcms/testcases/templates/testcases/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
<div class="col-md-3 col-lg-3">
<select class="form-control selectpicker" id="id_status" multiple>
{% for option in form.case_status.field.queryset %}
<option value="{{ option.pk }}" {% if form.case_status.value.0|add:'0' == option.pk %}selected{% endif %}>{{ option.name }}</option>
<option value="{{ option.pk }}" {% if option.pk|stringformat:"i" in form.case_status.value %}selected{% endif %}>{{ option.name }}</option>
{% endfor %}
</select>
</div>
Expand Down
2 changes: 1 addition & 1 deletion tcms/testplans/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def get_context_data(self, **kwargs):
context['statues'] = TestCaseStatus.objects.all()
context['priorities'] = Priority.objects.filter(is_active=True)
context['comment_form'] = SimpleCommentForm()
context['testcasestatus_confirmed'] = TestCaseStatus.get_confirmed()
context['confirmed_statuses'] = TestCaseStatus.objects.filter(is_confirmed=True)
return context


Expand Down
3 changes: 1 addition & 2 deletions tcms/testruns/static/testruns/js/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ $(document).ready(() => {
testExecutionSelectors.each((_index, te) => { te.checked = isChecked })
})

const confirmedPK = Number($('#test_run_pk').data('testcasestatus-confirmed-pk'));
quickSearchAndAddTestCase(testRunId, addTestCaseToRun, autocomplete_cache, {case_status: confirmedPK})
quickSearchAndAddTestCase(testRunId, addTestCaseToRun, autocomplete_cache, {case_status__is_confirmed: true})
$('#btn-search-cases').click(function () {
return advancedSearchAndAddTestCases(
testRunId, 'TestRun.add_case', $(this).attr('href'),
Expand Down
4 changes: 2 additions & 2 deletions tcms/testruns/templates/testruns/get.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
<h1 class="col-md-12" style="margin-top: 0">
<span id="test_run_pk"
data-pk="{{ object.pk }}"
data-testcasestatus-confirmed-pk="{{ testcasestatus_confirmed.pk }}"
data-perm-remove-tag="{{ perms.testruns.delete_testruntag }}"
data-perm-add-comment="{{ perms.django_comments.add_comment }}"
data-perm-remove-comment="{{ perms.django_comments.delete_comment }}"
Expand Down Expand Up @@ -234,7 +233,8 @@ <h2 class="card-pf-title">
<span class="fa fa-plus"></span>
</button>

<button class="btn btn-default" href="{% url 'testcases-search' %}?product={{ object.plan.product_id }}&case_status={{ testcasestatus_confirmed.pk }}"
<button class="btn btn-default"
href="{% url 'testcases-search' %}?product={{ object.plan.product_id }}{% for status in confirmed_statuses %}&case_status={{ status.pk }}{% endfor %}"
id="btn-search-cases" title="{% trans 'Advanced search' %}">
<span class="fa fa-search"></span>
</button>
Expand Down
2 changes: 1 addition & 1 deletion tcms/testruns/tests/test_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def setUpTestData(cls):
cls.post_data['manager'] = cls.tester.email
cls.post_data['default_tester'] = cls.tester.email

case_status_confirmed = TestCaseStatus.get_confirmed()
case_status_confirmed = TestCaseStatus.objects.filter(is_confirmed=True).first()

cls.case_1 = factories.TestCaseFactory(
author=cls.tester,
Expand Down
4 changes: 2 additions & 2 deletions tcms/testruns/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class GetTestRunView(DetailView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['execution_statuses'] = TestExecutionStatus.objects.order_by('-weight', 'name')
context['testcasestatus_confirmed'] = TestCaseStatus.get_confirmed()
context['confirmed_statuses'] = TestCaseStatus.objects.filter(is_confirmed=True)
context['link_form'] = LinkReferenceForm()
context['bug_trackers'] = BugSystem.objects.all()
context['comment_form'] = SimpleCommentForm()
Expand Down Expand Up @@ -241,4 +241,4 @@ def get(self, request, pk):


def get_disabled_test_cases_count(test_cases):
return test_cases.exclude(case_status=TestCaseStatus.get_confirmed()).count()
return test_cases.filter(case_status__is_confirmed=False).count()

0 comments on commit 26b4334

Please sign in to comment.