Repository: Nitrate/Nitrate

Issue:
Replace w3m with an equivalent one
w3m is an executable program. this is used for getting the content rendered from HTML content generated by TinyMCE. This is rely on the removal of TinyMCE. When TinyMCE is removed from Nitrate, we can remove w3m also.

Related to #1 



Discussion:
w3m is used in ``html2text`` to convert rendered HTML content to equivalent text with same text layout. so far, replacing TinyMCE is not a high priority issue, before that w3m should be replaced.

Resolving patch:
diff --git a/tcms/core/utils/html.py b/tcms/core/utils/html.py
deleted file mode 100644
--- a/tcms/core/utils/html.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- coding: utf-8 -*-
-from subprocess import Popen, PIPE
-
-
-def html2text(value):
-    """
-    Pipes given HTML string into the text browser W3M, which renders it.
-    Rendered text is grabbed from STDOUT and returned.
-    """
-    try:
-        cmd = "w3m -dump -T text/html -O ascii"
-        proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE)
-        return proc.communicate(str(value))[0]
-    except OSError:
-        # something bad happened, so just return the input
-        return value
diff --git a/tcms/testcases/models.py b/tcms/testcases/models.py
--- a/tcms/testcases/models.py
+++ b/tcms/testcases/models.py
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
+
 from datetime import datetime
+from html2text import html2text
 
 from django.conf import settings
 from django.core.urlresolvers import reverse
@@ -7,6 +9,7 @@
 from django.db.models import ObjectDoesNotExist
 from django.db.models.signals import post_save, post_delete, pre_save
 from django.contrib.contenttypes.fields import GenericRelation
+from django.utils.encoding import smart_str
 
 from tcms.core.models import TCMSActionModel
 from tcms.core.models import TCMSContentTypeBaseModel
@@ -552,14 +555,10 @@ class Meta:
         unique_together = ('case', 'case_text_version')
 
     def get_plain_text(self):
-        from tcms.core.utils.html import html2text
-        from django.utils.encoding import smart_str
-
         self.action = html2text(smart_str(self.action))
         self.effect = html2text(smart_str(self.effect))
         self.setup = html2text(smart_str(self.setup))
         self.breakdown = html2text(smart_str(self.breakdown))
-
         return self
 
 
diff --git a/tcms/testplans/models.py b/tcms/testplans/models.py
--- a/tcms/testplans/models.py
+++ b/tcms/testplans/models.py
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from datetime import datetime
+from html2text import html2text
 
 from django.conf import settings
 from django.core.exceptions import ObjectDoesNotExist
@@ -416,8 +417,6 @@ class Meta:
         unique_together = ('plan', 'plan_text_version')
 
     def get_plain_text(self):
-        from tcms.core.utils.html import html2text
-
         self.plan_text = html2text(self.plan_text)
         return self
 


Test patch:
diff --git a/tcms/testcases/tests/__init__.py b/tcms/testcases/tests/__init__.py
new file mode 100644
diff --git a/tcms/testcases/tests/test_models.py b/tcms/testcases/tests/test_models.py
new file mode 100644
--- /dev/null
+++ b/tcms/testcases/tests/test_models.py
@@ -0,0 +1,199 @@
+# -*- coding: utf-8 -*-
+
+from __future__ import absolute_import
+
+from django.contrib.auth.models import User
+
+from ..models import TestCaseBugSystem
+from ..models import TestCaseText
+from tcms.core.utils.checksum import checksum
+from tcms.tests.factories import ComponentFactory
+from tcms.tests.factories import TestBuildFactory
+from tcms.tests.factories import TestCaseComponentFactory
+from tcms.tests.factories import TestCaseFactory
+from tcms.tests.factories import TestCaseRunFactory
+from tcms.tests.factories import TestCaseTagFactory
+from tcms.tests.factories import TestRunFactory
+from tcms.tests.factories import TestTagFactory
+from tcms.tests import BasePlanCase
+
+
+class TestCaseRemoveBug(BasePlanCase):
+    """Test TestCase.remove_bug"""
+
+    @classmethod
+    def setUpTestData(cls):
+        super(TestCaseRemoveBug, cls).setUpTestData()
+        cls.build = TestBuildFactory(product=cls.product)
+        cls.test_run = TestRunFactory(product_version=cls.version, plan=cls.plan,
+                                      manager=cls.tester, default_tester=cls.tester)
+        cls.case_run = TestCaseRunFactory(assignee=cls.tester, tested_by=cls.tester,
+                                          case=cls.case, run=cls.test_run, build=cls.build)
+        cls.bug_system = TestCaseBugSystem.objects.get(name='Bugzilla')
+
+    def setUp(self):
+        self.bug_id_1 = '12345678'
+        self.case.add_bug(self.bug_id_1, self.bug_system.pk,
+                          summary='error when add a bug to a case')
+        self.bug_id_2 = '10000'
+        self.case.add_bug(self.bug_id_2, self.bug_system.pk, case_run=self.case_run)
+
+    def tearDown(self):
+        self.case.case_bug.all().delete()
+
+    def test_remove_case_bug(self):
+        self.case.remove_bug(self.bug_id_1)
+
+        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_1).exists()
+        self.assertFalse(bug_found)
+
+        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_2).exists()
+        self.assertTrue(bug_found,
+                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_2))
+
+    def test_case_bug_not_removed_by_passing_case_run(self):
+        self.case.remove_bug(self.bug_id_1, run_id=self.case_run.pk)
+
+        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_1).exists()
+        self.assertTrue(bug_found,
+                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_1))
+
+        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_2).exists()
+        self.assertTrue(bug_found,
+                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_2))
+
+    def test_remove_case_run_bug(self):
+        self.case.remove_bug(self.bug_id_2, run_id=self.case_run.pk)
+
+        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_2).exists()
+        self.assertFalse(bug_found)
+
+        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_1).exists()
+        self.assertTrue(bug_found,
+                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_1))
+
+    def test_case_run_bug_not_removed_by_missing_case_run(self):
+        self.case.remove_bug(self.bug_id_2)
+
+        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_1).exists()
+        self.assertTrue(bug_found,
+                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_1))
+
+        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_2).exists()
+        self.assertTrue(bug_found,
+                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_2))
+
+
+class TestCaseRemoveComponent(BasePlanCase):
+    """Test TestCase.remove_component"""
+
+    @classmethod
+    def setUpTestData(cls):
+        super(TestCaseRemoveComponent, cls).setUpTestData()
+
+        cls.component_1 = ComponentFactory(name='Application',
+                                           product=cls.product,
+                                           initial_owner=cls.tester,
+                                           initial_qa_contact=cls.tester)
+        cls.component_2 = ComponentFactory(name='Database',
+                                           product=cls.product,
+                                           initial_owner=cls.tester,
+                                           initial_qa_contact=cls.tester)
+
+        cls.cc_rel_1 = TestCaseComponentFactory(case=cls.case,
+                                                component=cls.component_1)
+        cls.cc_rel_2 = TestCaseComponentFactory(case=cls.case,
+                                                component=cls.component_2)
+
+    def test_remove_a_component(self):
+        self.case.remove_component(self.component_1)
+
+        found = self.case.component.filter(pk=self.component_1.pk).exists()
+        self.assertFalse(
+            found,
+            'Component {0} exists. But, it should be removed.'.format(
+                self.component_1.pk))
+        found = self.case.component.filter(pk=self.component_2.pk).exists()
+        self.assertTrue(
+            found,
+            'Component {0} does not exist. It should not be removed.'.format(
+                self.component_2.pk))
+
+
+class TestCaseRemovePlan(BasePlanCase):
+    """Test TestCase.remove_plan"""
+
+    @classmethod
+    def setUpTestData(cls):
+        super(TestCaseRemovePlan, cls).setUpTestData()
+
+        cls.new_case = TestCaseFactory(author=cls.tester, default_tester=None, reviewer=cls.tester,
+                                       plan=[cls.plan])
+
+    def test_remove_plan(self):
+        self.new_case.remove_plan(self.plan)
+
+        found = self.plan.case.filter(pk=self.new_case.pk).exists()
+        self.assertFalse(
+            found, 'Case {0} should has no relationship to plan {0} now.'.format(self.new_case.pk,
+                                                                                 self.plan.pk))
+
+
+class TestCaseRemoveTag(BasePlanCase):
+    """Test TestCase.remove_tag"""
+
+    @classmethod
+    def setUpTestData(cls):
+        super(TestCaseRemoveTag, cls).setUpTestData()
+
+        cls.tag_rhel = TestTagFactory(name='rhel')
+        cls.tag_fedora = TestTagFactory(name='fedora')
+        TestCaseTagFactory(case=cls.case, tag=cls.tag_rhel, user=cls.tester.pk)
+        TestCaseTagFactory(case=cls.case, tag=cls.tag_fedora, user=cls.tester.pk)
+
+    def test_remove_tag(self):
+        self.case.remove_tag(self.tag_rhel)
+
+        tag_pks = list(self.case.tag.all().values_list('pk', flat=True))
+        self.assertEqual([self.tag_fedora.pk], tag_pks)
+
+
+class TestGetPlainText(BasePlanCase):
+    """Test TestCaseText.get_plain_text"""
+
+    @classmethod
+    def setUpTestData(cls):
+        super(TestGetPlainText, cls).setUpTestData()
+
+        cls.action = '<p>First step:</p>'
+        cls.effect = '''<ul>
+    <li>effect 1</li>
+    <li>effect 2</li>
+</ul>'''
+        cls.setup = '<p><a href="/setup_guide">setup</a></p>'
+        cls.breakdown = '<span>breakdown</span>'
+
+        cls.text_author = User.objects.create_user(username='author',
+                                                   email='my@example.com')
+        TestCaseText.objects.create(
+            case=cls.case,
+            case_text_version=1,
+            author=cls.text_author,
+            action=cls.action,
+            effect=cls.effect,
+            setup=cls.setup,
+            breakdown=cls.breakdown,
+            action_checksum=checksum(cls.action),
+            effect_checksum=checksum(cls.effect),
+            setup_checksum=checksum(cls.setup),
+            breakdown_checksum=checksum(cls.breakdown))
+
+    def test_get_plain_text(self):
+        case_text = TestCaseText.objects.all()[0]
+        plain_text = case_text.get_plain_text()
+
+        # These expected values were converted from html2text.
+        self.assertEqual('First step:\n\n', plain_text.action)
+        self.assertEqual('  * effect 1\n  * effect 2\n\n', plain_text.effect)
+        self.assertEqual('[setup](/setup_guide)\n\n', plain_text.setup)
+        self.assertEqual('breakdown\n\n', plain_text.breakdown)
diff --git a/tcms/testcases/tests.py b/tcms/testcases/tests/test_views.py
similarity index 85%
rename from tcms/testcases/tests.py
rename to tcms/testcases/tests/test_views.py
--- a/tcms/testcases/tests.py
+++ b/tcms/testcases/tests/test_views.py
@@ -17,8 +17,8 @@
 
 from uuslug import slugify
 
-from .fields import MultipleEmailField
-from .forms import CaseTagForm
+from ..fields import MultipleEmailField
+from ..forms import CaseTagForm
 from tcms.management.models import TestTag
 from tcms.testcases.models import TestCase
 from tcms.testcases.models import TestCaseTag
@@ -27,14 +27,11 @@
 from tcms.testcases.models import TestCasePlan
 from tcms.testcases.views import ajax_response
 from tcms.tests.factories import ComponentFactory
-from tcms.tests.factories import TestBuildFactory
 from tcms.tests.factories import TestCaseCategoryFactory
 from tcms.tests.factories import TestCaseComponentFactory
 from tcms.tests.factories import TestCaseFactory
-from tcms.tests.factories import TestCaseRunFactory
 from tcms.tests.factories import TestCaseTagFactory
 from tcms.tests.factories import TestPlanFactory
-from tcms.tests.factories import TestRunFactory
 from tcms.tests.factories import TestTagFactory
 from tcms.tests import BasePlanCase
 from tcms.tests import remove_perm_from_user
@@ -89,146 +86,6 @@ def test_clean(self):
         self.assertEqual(data, [])
 
 
-class TestCaseRemoveBug(BasePlanCase):
-    """Test TestCase.remove_bug"""
-
-    @classmethod
-    def setUpTestData(cls):
-        super(TestCaseRemoveBug, cls).setUpTestData()
-        cls.build = TestBuildFactory(product=cls.product)
-        cls.test_run = TestRunFactory(product_version=cls.version, plan=cls.plan,
-                                      manager=cls.tester, default_tester=cls.tester)
-        cls.case_run = TestCaseRunFactory(assignee=cls.tester, tested_by=cls.tester,
-                                          case=cls.case, run=cls.test_run, build=cls.build)
-        cls.bug_system = TestCaseBugSystem.objects.get(name='Bugzilla')
-
-    def setUp(self):
-        self.bug_id_1 = '12345678'
-        self.case.add_bug(self.bug_id_1, self.bug_system.pk,
-                          summary='error when add a bug to a case')
-        self.bug_id_2 = '10000'
-        self.case.add_bug(self.bug_id_2, self.bug_system.pk, case_run=self.case_run)
-
-    def tearDown(self):
-        self.case.case_bug.all().delete()
-
-    def test_remove_case_bug(self):
-        self.case.remove_bug(self.bug_id_1)
-
-        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_1).exists()
-        self.assertFalse(bug_found)
-
-        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_2).exists()
-        self.assertTrue(bug_found,
-                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_2))
-
-    def test_case_bug_not_removed_by_passing_case_run(self):
-        self.case.remove_bug(self.bug_id_1, run_id=self.case_run.pk)
-
-        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_1).exists()
-        self.assertTrue(bug_found,
-                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_1))
-
-        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_2).exists()
-        self.assertTrue(bug_found,
-                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_2))
-
-    def test_remove_case_run_bug(self):
-        self.case.remove_bug(self.bug_id_2, run_id=self.case_run.pk)
-
-        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_2).exists()
-        self.assertFalse(bug_found)
-
-        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_1).exists()
-        self.assertTrue(bug_found,
-                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_1))
-
-    def test_case_run_bug_not_removed_by_missing_case_run(self):
-        self.case.remove_bug(self.bug_id_2)
-
-        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_1).exists()
-        self.assertTrue(bug_found,
-                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_1))
-
-        bug_found = self.case.case_bug.filter(bug_id=self.bug_id_2).exists()
-        self.assertTrue(bug_found,
-                        'Bug {0} does not exist. It should not be deleted.'.format(self.bug_id_2))
-
-
-class TestCaseRemoveComponent(BasePlanCase):
-    """Test TestCase.remove_component"""
-
-    @classmethod
-    def setUpTestData(cls):
-        super(TestCaseRemoveComponent, cls).setUpTestData()
-
-        cls.component_1 = ComponentFactory(name='Application',
-                                           product=cls.product,
-                                           initial_owner=cls.tester,
-                                           initial_qa_contact=cls.tester)
-        cls.component_2 = ComponentFactory(name='Database',
-                                           product=cls.product,
-                                           initial_owner=cls.tester,
-                                           initial_qa_contact=cls.tester)
-
-        cls.cc_rel_1 = TestCaseComponentFactory(case=cls.case,
-                                                component=cls.component_1)
-        cls.cc_rel_2 = TestCaseComponentFactory(case=cls.case,
-                                                component=cls.component_2)
-
-    def test_remove_a_component(self):
-        self.case.remove_component(self.component_1)
-
-        found = self.case.component.filter(pk=self.component_1.pk).exists()
-        self.assertFalse(
-            found,
-            'Component {0} exists. But, it should be removed.'.format(
-                self.component_1.pk))
-        found = self.case.component.filter(pk=self.component_2.pk).exists()
-        self.assertTrue(
-            found,
-            'Component {0} does not exist. It should not be removed.'.format(
-                self.component_2.pk))
-
-
-class TestCaseRemovePlan(BasePlanCase):
-    """Test TestCase.remove_plan"""
-
-    @classmethod
-    def setUpTestData(cls):
-        super(TestCaseRemovePlan, cls).setUpTestData()
-
-        cls.new_case = TestCaseFactory(author=cls.tester, default_tester=None, reviewer=cls.tester,
-                                       plan=[cls.plan])
-
-    def test_remove_plan(self):
-        self.new_case.remove_plan(self.plan)
-
-        found = self.plan.case.filter(pk=self.new_case.pk).exists()
-        self.assertFalse(
-            found, 'Case {0} should has no relationship to plan {0} now.'.format(self.new_case.pk,
-                                                                                 self.plan.pk))
-
-
-class TestCaseRemoveTag(BasePlanCase):
-    """Test TestCase.remove_tag"""
-
-    @classmethod
-    def setUpTestData(cls):
-        super(TestCaseRemoveTag, cls).setUpTestData()
-
-        cls.tag_rhel = TestTagFactory(name='rhel')
-        cls.tag_fedora = TestTagFactory(name='fedora')
-        TestCaseTagFactory(case=cls.case, tag=cls.tag_rhel, user=cls.tester.pk)
-        TestCaseTagFactory(case=cls.case, tag=cls.tag_fedora, user=cls.tester.pk)
-
-    def test_remove_tag(self):
-        self.case.remove_tag(self.tag_rhel)
-
-        tag_pks = list(self.case.tag.all().values_list('pk', flat=True))
-        self.assertEqual([self.tag_fedora.pk], tag_pks)
-
-
 class CaseTagFormTest(test.TestCase):
 
     @classmethod