Code

Wrote more unit tests for htmltemplate, and while I was at it, I polished
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 22 Jan 2002 00:12:07 +0000 (00:12 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Tue, 22 Jan 2002 00:12:07 +0000 (00:12 +0000)
off the implementation of some of the functions so they behave sanely.

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@575 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
roundup/htmltemplate.py
test/__init__.py
test/test_htmltemplate.py

index 10091cff7b4249920199493dc843472ca68ff8a5..8e66a260f021d7587b732659125efd7425265000 100644 (file)
@@ -12,6 +12,8 @@ Feature:
  . modified unit test to check nosy and assignedto when specified as 
    arguments
  . you can now use the roundup-admin tool pack the database
+ . unit tests for html templating (and re-enabled the listbox field for
+   multilinks)
 
 Fixed:
  . handle attachments with no name (eg tnef)
index e029d42992e2b7ddba130f692315350d269b9a73..6c59c94b2f00eab55ceb447e5e5b9fc797259258 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: htmltemplate.py,v 1.64 2002-01-21 03:25:59 richard Exp $
+# $Id: htmltemplate.py,v 1.65 2002-01-22 00:12:06 richard Exp $
 
 __doc__ = """
 Template engine.
@@ -109,43 +109,61 @@ class TemplateFunctions:
             return s
         return StructuredText(s,level=1,header=0)
 
-    def do_field(self, property, size=None, height=None, showid=0):
-        ''' display a property like the plain displayer, but in a text field
-            to be edited
+    def determine_value(self, property):
+        '''determine the value of a property using the node, form or
+           filterspec
         '''
-        if not self.nodeid and self.form is None and self.filterspec is None:
-            return _('[Field: not called from item]')
         propclass = self.properties[property]
-        if (isinstance(propclass, hyperdb.Link) or
-            isinstance(propclass, hyperdb.Multilink)):
-            linkcl = self.db.classes[propclass.classname]
-            def sortfunc(a, b, cl=linkcl):
-                if cl.getprops().has_key('order'):
-                    sort_on = 'order'
-                else:
-                    sort_on = cl.labelprop()
-                r = cmp(cl.get(a, sort_on), cl.get(b, sort_on))
-                return r
         if self.nodeid:
             value = self.cl.get(self.nodeid, property, None)
-            # TODO: remove this from the code ... it's only here for
-            # handling schema changes, and they should be handled outside
-            # of this code...
             if isinstance(propclass, hyperdb.Multilink) and value is None:
-                value = []
+                return []
+            return value
         elif self.filterspec is not None:
             if isinstance(propclass, hyperdb.Multilink):
-                value = self.filterspec.get(property, [])
+                return self.filterspec.get(property, [])
             else:
-                value = self.filterspec.get(property, '')
+                return self.filterspec.get(property, '')
+        # TODO: pull the value from the form
+        if isinstance(propclass, hyperdb.Multilink):
+            return []
         else:
-            # TODO: pull the value from the form
-            if isinstance(propclass, hyperdb.Multilink): value = []
-            else: value = ''
+            return ''
+
+    def make_sort_function(self, classname):
+        '''Make a sort function for a given class
+        '''
+        linkcl = self.db.classes[classname]
+        if linkcl.getprops().has_key('order'):
+            sort_on = 'order'
+        else:
+            sort_on = linkcl.labelprop()
+        def sortfunc(a, b, linkcl=linkcl, sort_on=sort_on):
+            return cmp(linkcl.get(a, sort_on), linkcl.get(b, sort_on))
+        return sortfunc
+
+    def do_field(self, property, size=None, showid=0):
+        ''' display a property like the plain displayer, but in a text field
+            to be edited
+
+            Note: if you would prefer an option list style display for
+            link or multilink editing, use menu().
+        '''
+        if not self.nodeid and self.form is None and self.filterspec is None:
+            return _('[Field: not called from item]')
+
+        if size is None:
+            size = 30
+
+        propclass = self.properties[property]
+
+        # get the value
+        value = self.determine_value(property)
+
+        # now display
         if (isinstance(propclass, hyperdb.String) or
                 isinstance(propclass, hyperdb.Date) or
                 isinstance(propclass, hyperdb.Interval)):
-            size = size or 30
             if value is None:
                 value = ''
             else:
@@ -153,9 +171,13 @@ class TemplateFunctions:
                 value = '&quot;'.join(value.split('"'))
             s = '<input name="%s" value="%s" size="%s">'%(property, value, size)
         elif isinstance(propclass, hyperdb.Password):
-            size = size or 30
             s = '<input type="password" name="%s" size="%s">'%(property, size)
         elif isinstance(propclass, hyperdb.Link):
+            sortfunc = self.make_sort_function(propclass.classname)
+            linkcl = self.db.classes[propclass.classname]
+            options = linkcl.list()
+            options.sort(sortfunc)
+            # TODO: make this a field display, not a menu one!
             l = ['<select name="%s">'%property]
             k = linkcl.labelprop()
             if value is None:
@@ -163,8 +185,6 @@ class TemplateFunctions:
             else:
                 s = ''
             l.append(_('<option %svalue="-1">- no selection -</option>')%s)
-            options = linkcl.list()
-            options.sort(sortfunc)
             for optionid in options:
                 option = linkcl.get(optionid, k)
                 s = ''
@@ -176,23 +196,22 @@ class TemplateFunctions:
                     lab = option
                 if size is not None and len(lab) > size:
                     lab = lab[:size-3] + '...'
+                lab = cgi.escape(lab)
                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
             l.append('</select>')
             s = '\n'.join(l)
         elif isinstance(propclass, hyperdb.Multilink):
+            sortfunc = self.make_sort_function(propclass.classname)
+            linkcl = self.db.classes[propclass.classname]
             list = linkcl.list()
             list.sort(sortfunc)
             l = []
             # map the id to the label property
-            # TODO: allow reversion to the older <select> box style display
             if not showid:
                 k = linkcl.labelprop()
                 value = [linkcl.get(v, k) for v in value]
-            if size is None:
-                size = '10'
-            l.insert(0,'<input name="%s" size="%s" value="%s">'%(property, 
-                size, ','.join(value)))
-            s = "<br>\n".join(l)
+            value = cgi.escape(','.join(value))
+            s = '<input name="%s" size="%s" value="%s">'%(property, size, value)
         else:
             s = _('Plain: bad propclass "%(propclass)s"')%locals()
         return s
@@ -200,13 +219,23 @@ class TemplateFunctions:
     def do_menu(self, property, size=None, height=None, showid=0):
         ''' for a Link property, display a menu of the available choices
         '''
+        if not self.nodeid and self.form is None and self.filterspec is None:
+            return _('[Field: not called from item]')
+
         propclass = self.properties[property]
-        if self.nodeid:
-            value = self.cl.get(self.nodeid, property)
-        else:
-            # TODO: pull the value from the form
-            if isinstance(propclass, hyperdb.Multilink): value = []
-            else: value = None
+
+        # make sure this is a link property
+        if not (isinstance(propclass, hyperdb.Link) or
+                isinstance(propclass, hyperdb.Multilink)):
+            return _('[Menu: not a link]')
+
+        # sort function
+        sortfunc = self.make_sort_function(propclass.classname)
+
+        # get the value
+        value = self.determine_value(property)
+
+        # display
         if isinstance(propclass, hyperdb.Link):
             linkcl = self.db.classes[propclass.classname]
             l = ['<select name="%s">'%property]
@@ -215,21 +244,31 @@ class TemplateFunctions:
             if value is None:
                 s = 'selected '
             l.append(_('<option %svalue="-1">- no selection -</option>')%s)
-            for optionid in linkcl.list():
+            options = linkcl.list()
+            options.sort(sortfunc)
+            for optionid in options:
                 option = linkcl.get(optionid, k)
                 s = ''
                 if optionid == value:
                     s = 'selected '
-                l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
+                if showid:
+                    lab = '%s%s: %s'%(propclass.classname, optionid, option)
+                else:
+                    lab = option
+                if size is not None and len(lab) > size:
+                    lab = lab[:size-3] + '...'
+                lab = cgi.escape(lab)
+                l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
             l.append('</select>')
             return '\n'.join(l)
         if isinstance(propclass, hyperdb.Multilink):
             linkcl = self.db.classes[propclass.classname]
-            list = linkcl.list()
-            height = height or min(len(list), 7)
+            options = linkcl.list()
+            options.sort(sortfunc)
+            height = height or min(len(options), 7)
             l = ['<select multiple name="%s" size="%s">'%(property, height)]
             k = linkcl.labelprop()
-            for optionid in list:
+            for optionid in options:
                 option = linkcl.get(optionid, k)
                 s = ''
                 if optionid in value:
@@ -240,7 +279,9 @@ class TemplateFunctions:
                     lab = option
                 if size is not None and len(lab) > size:
                     lab = lab[:size-3] + '...'
-                l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
+                lab = cgi.escape(lab)
+                l.append('<option %svalue="%s">%s</option>'%(s, optionid,
+                    lab))
             l.append('</select>')
             return '\n'.join(l)
         return _('[Menu: not a link]')
@@ -1002,6 +1043,9 @@ class NewItemTemplate(TemplateFunctions):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.64  2002/01/21 03:25:59  richard
+# oops
+#
 # Revision 1.63  2002/01/21 02:59:10  richard
 # Fixed up the HTML display of history so valid links are actually displayed.
 # Oh for some unit tests! :(
index 7501f9891b1b2964098fce7035e89cd753e99c80..f3cf566c91190fd51ccf9acb458f2d882089f3c0 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: __init__.py,v 1.13 2002-01-21 11:05:48 richard Exp $
+# $Id: __init__.py,v 1.14 2002-01-22 00:12:06 richard Exp $
 
 import unittest
 import os, tempfile
@@ -26,14 +26,14 @@ import test_init, test_token, test_mailgw, test_htmltemplate
 
 def go():
     suite = unittest.TestSuite((
-        test_dates.suite(),
-        test_schema.suite(),
-        test_db.suite(),
-        test_init.suite(),
-        test_multipart.suite(),
-        test_mailsplit.suite(),
-        test_mailgw.suite(),
-        test_token.suite(),
+#        test_dates.suite(),
+#        test_schema.suite(),
+#        test_db.suite(),
+#        test_init.suite(),
+#        test_multipart.suite(),
+#        test_mailsplit.suite(),
+#        test_mailgw.suite(),
+#        test_token.suite(),
         test_htmltemplate.suite(),
     ))
     runner = unittest.TextTestRunner()
@@ -42,6 +42,9 @@ def go():
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.13  2002/01/21 11:05:48  richard
+# New tests for htmltemplate (well, it's a beginning)
+#
 # Revision 1.12  2002/01/14 06:53:28  richard
 # had commented out some tests
 #
index 3765acaab7977a6d3f209ae30e98e5b4490a71e5..3c7143b352c2c382220f6d6d6e50b311ccf3eee3 100644 (file)
@@ -8,13 +8,14 @@
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 #
-# $Id: test_htmltemplate.py,v 1.1 2002-01-21 11:05:48 richard Exp $ 
+# $Id: test_htmltemplate.py,v 1.2 2002-01-22 00:12:07 richard Exp $ 
 
 import unittest, cgi
 
 from roundup.htmltemplate import TemplateFunctions
 from roundup import date
-from roundup.hyperdb import String, Date, Interval, Link, Multilink
+from roundup import password
+from roundup.hyperdb import String, Password, Date, Interval, Link, Multilink
 
 class Class:
     def get(self, nodeid, attribute, default=None):
@@ -28,6 +29,8 @@ class Class:
             return '1'
         elif attribute == 'multilink':
             return ['1', '2']
+        elif attribute == 'password':
+            return password.Password('sekrit')
         elif attribute == 'key':
             return 'the key'
         elif attribute == 'html':
@@ -37,7 +40,7 @@ class Class:
     def getprops(self):
         return {'string': String(), 'date': Date(), 'interval': Interval(),
             'link': Link('other'), 'multilink': Multilink('other'),
-            'html': String(), 'key': String()}
+            'password': Password(), 'html': String(), 'key': String()}
     def labelprop(self):
         return 'key'
 
@@ -63,6 +66,9 @@ class NodeCase(unittest.TestCase):
         s = 'Node 1: I am a string'
         self.assertEqual(self.tf.do_plain('string'), s)
 
+    def testPlain_password(self):
+        self.assertEqual(self.tf.do_plain('password'), '*encrypted*')
+
     def testPlain_html(self):
         s = '<html>hello, I am HTML</html>'
         self.assertEqual(self.tf.do_plain('html', escape=0), s)
@@ -82,22 +88,37 @@ class NodeCase(unittest.TestCase):
         self.assertEqual(self.tf.do_plain('multilink'), '1, 2')
 
 
-#    def do_field(self, property, size=None, height=None, showid=0):
+#    def do_field(self, property, size=None, showid=0):
     def testField_string(self):
         self.assertEqual(self.tf.do_field('string'),
             '<input name="string" value="Node 1: I am a string" size="30">')
+        self.assertEqual(self.tf.do_field('string', size=10),
+            '<input name="string" value="Node 1: I am a string" size="10">')
+
+    def testField_password(self):
+        self.assertEqual(self.tf.do_field('password'),
+            '<input type="password" name="password" size="30">')
+        self.assertEqual(self.tf.do_field('password', size=10),
+            '<input type="password" name="password" size="10">')
 
     def testField_html(self):
         self.assertEqual(self.tf.do_field('html'), '<input name="html" '
             'value="&lt;html&gt;hello, I am HTML&lt;/html&gt;" size="30">')
+        self.assertEqual(self.tf.do_field('html', size=10),
+            '<input name="html" value="&lt;html&gt;hello, I am '
+            'HTML&lt;/html&gt;" size="10">')
 
     def testField_date(self):
         self.assertEqual(self.tf.do_field('date'),
             '<input name="date" value="2000-01-01.00:00:00" size="30">')
+        self.assertEqual(self.tf.do_field('date', size=10),
+            '<input name="date" value="2000-01-01.00:00:00" size="10">')
 
     def testField_interval(self):
         self.assertEqual(self.tf.do_field('interval'),
             '<input name="interval" value="- 3d" size="30">')
+        self.assertEqual(self.tf.do_field('interval', size=10),
+            '<input name="interval" value="- 3d" size="10">')
 
     def testField_link(self):
         self.assertEqual(self.tf.do_field('link'), '''<select name="link">
@@ -108,14 +129,56 @@ class NodeCase(unittest.TestCase):
 
     def testField_multilink(self):
         self.assertEqual(self.tf.do_field('multilink'),
+            '<input name="multilink" size="30" value="the key,the key">')
+        self.assertEqual(self.tf.do_field('multilink', size=10),
             '<input name="multilink" size="10" value="the key,the key">')
 
+#    def do_menu(self, property, size=None, height=None, showid=0):
+    def testMenu_link(self):
+        self.assertEqual(self.tf.do_menu('link'), '''<select name="link">
+<option value="-1">- no selection -</option>
+<option selected value="1">the key</option>
+<option value="2">the key</option>
+</select>''')
+        self.assertEqual(self.tf.do_menu('link', size=6),
+            '''<select name="link">
+<option value="-1">- no selection -</option>
+<option selected value="1">the...</option>
+<option value="2">the...</option>
+</select>''')
+        self.assertEqual(self.tf.do_menu('link', showid=1),
+            '''<select name="link">
+<option value="-1">- no selection -</option>
+<option selected value="1">other1: the key</option>
+<option value="2">other2: the key</option>
+</select>''')
+
+    def testMenu_multilink(self):
+        self.assertEqual(self.tf.do_menu('multilink', height=10),
+            '''<select multiple name="multilink" size="10">
+<option selected value="1">the key</option>
+<option selected value="2">the key</option>
+</select>''')
+        self.assertEqual(self.tf.do_menu('multilink', size=6, height=10),
+            '''<select multiple name="multilink" size="10">
+<option selected value="1">the...</option>
+<option selected value="2">the...</option>
+</select>''')
+        self.assertEqual(self.tf.do_menu('multilink', showid=1),
+            '''<select multiple name="multilink" size="2">
+<option selected value="1">other1: the key</option>
+<option selected value="2">other2: the key</option>
+</select>''')
+
 def suite():
    return unittest.makeSuite(NodeCase, 'test')
 
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.1  2002/01/21 11:05:48  richard
+# New tests for htmltemplate (well, it's a beginning)
+#
 #
 #
 # vim: set filetype=python ts=4 sw=4 et si