Skip to content

Commit

Permalink
Make TestCaseStatus customizeable. Fix #1932
Browse files Browse the repository at this point in the history
- new DB migrations
- update docs
- add admin page to edit these statuses
- Remove get_confirmed(), get_proposed() methods and update
  tests to match
- Because there could be more than 1 confirmed statuses update
  API, views and JavaScript to take that into account
  • Loading branch information
atodorov committed Nov 20, 2020
1 parent 9441fbb commit f49bf30
Show file tree
Hide file tree
Showing 19 changed files with 134 additions and 60 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
10 changes: 4 additions & 6 deletions tcms/telemetry/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from modernrpc.auth.basic import http_basic_auth_login_required
from modernrpc.core import rpc_method

from tcms.testcases.models import TestCase, TestCaseStatus
from tcms.testcases.models import TestCase
from tcms.testruns.models import TestExecution, TestExecutionStatus


Expand Down Expand Up @@ -45,20 +45,18 @@ def breakdown(query=None):


def _get_field_count_map(test_cases, expression, field):
confirmed = TestCaseStatus.get_confirmed()

query_set_confirmed = test_cases.filter(
case_status=confirmed
case_status__is_confirmed=True
).values(field).annotate(
count=Count(expression)
)
query_set_not_confirmed = test_cases.exclude(
case_status=confirmed
case_status__is_confirmed=True
).values(field).annotate(
count=Count(expression)
)
return {
confirmed.name: _map_query_set(query_set_confirmed, field),
str(_('CONFIRMED')): _map_query_set(query_set_confirmed, field),
str(_('OTHER')): _map_query_set(query_set_not_confirmed, field)
}

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),
]
23 changes: 5 additions & 18 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,28 +17,15 @@
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'


# register model for DB translations
vinaigrette.register(TestCaseStatus, ['name'])
Expand Down Expand Up @@ -234,7 +221,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/testcases/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def setUpTestData(cls):
super().setUpTestData()
user_should_have_perm(cls.tester, 'testcases.view_testcase')

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

case = TestCaseFactory(
author=cls.tester,
Expand Down
35 changes: 20 additions & 15 deletions tcms/testplans/static/testplans/js/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ var expandedTestCaseIds = [],
const allTestCases = {},
autocomplete_cache = {};

let testCaseStatusConfirmedPK = null;
let confirmedStatuses = [];


$(document).ready(function() {
const testPlanDataElement = $('#test_plan_pk');
testCaseStatusConfirmedPK = Number(testPlanDataElement.data('testcasestatus-confirmed-pk'));

const testPlanId = testPlanDataElement.data('testplan-pk');

const permissions = {
Expand All @@ -25,19 +23,26 @@ $(document).ready(function() {
const perm_remove_tag = testPlanDataElement.data('perm-remove-tag') === 'True';
tagsCard('TestPlan', testPlanId, {plan: testPlanId}, perm_remove_tag);

jsonRPC('TestCase.sortkeys', {'plan': testPlanId}, function(sortkeys) {
jsonRPC('TestCase.filter', {'plan': testPlanId}, function(data) {
for (var i = 0; i < data.length; i++) {
var testCase = data[i];
jsonRPC('TestCaseStatus.filter', {is_confirmed: true}, function(statuses) {
// save for later use
for (let i = 0; i < statuses.length; i++) {
confirmedStatuses.push(statuses[i].id);
}

testCase.sortkey = sortkeys[testCase.id];
allTestCases[testCase.id] = testCase;
}
sortTestCases(Object.values(allTestCases), testPlanId, permissions, 'sortkey');
jsonRPC('TestCase.sortkeys', {'plan': testPlanId}, function(sortkeys) {
jsonRPC('TestCase.filter', {'plan': testPlanId}, function(data) {
for (var i = 0; i < data.length; i++) {
var testCase = data[i];

// drag & reorder needs the initial order of test cases and
// they may not be fully loaded when sortable() is initialized!
toolbarEvents(testPlanId, permissions);
testCase.sortkey = sortkeys[testCase.id];
allTestCases[testCase.id] = testCase;
}
sortTestCases(Object.values(allTestCases), testPlanId, permissions, 'sortkey');

// drag & reorder needs the initial order of test cases and
// they may not be fully loaded when sortable() is initialized!
toolbarEvents(testPlanId, permissions);
});
});
});

Expand Down Expand Up @@ -622,7 +627,7 @@ function toolbarEvents(testPlanId, permissions) {
}

function isTestCaseConfirmed(status) {
return Number(status) === testCaseStatusConfirmedPK;
return confirmedStatuses.indexOf(Number(status)) > -1 ;
}

// on dropdown change update the label of the button and set new selected list item
Expand Down
1 change: 0 additions & 1 deletion tcms/testplans/templates/testplans/get.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ <h1 class="col-md-12" style="margin-top: 0">
{% if not object.is_active %}<s>{% endif %}
<span id="test_plan_pk"
data-testplan-pk="{{ object.pk }}"
data-testcasestatus-confirmed-pk="{{ testcasestatus_confirmed.pk }}"
data-perm-remove-tag="{{ perms.testplans.delete_testplantag }}"
data-perm-change-testcase="{{ perms.testcases.change_testcase }}"
data-perm-remove-testcase="{{ perms.testcases.delete_testcaseplan }}"
Expand Down
1 change: 0 additions & 1 deletion tcms/testplans/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ 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()
return context


Expand Down
4 changes: 2 additions & 2 deletions tcms/testruns/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from tcms.core.forms.fields import UserField
from tcms.core.utils import string_to_list
from tcms.management.models import Build, Product, Version
from tcms.testcases.models import TestCase, TestCaseStatus
from tcms.testcases.models import TestCase
from .models import TestRun

User = get_user_model() # pylint: disable=invalid-name
Expand Down Expand Up @@ -44,7 +44,7 @@ def populate(self, plan_id):
product_id=self.fields['plan'].queryset.first().product_id,
is_active=True)
self.fields['case'].queryset = TestCase.objects.filter(
case_status=TestCaseStatus.get_confirmed()).all()
case_status__is_confirmed=True).all()


class SearchRunForm(forms.Form):
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
Loading

0 comments on commit f49bf30

Please sign in to comment.