From python-checkins at python.org Wed Nov 1 00:00:36 2006 From: python-checkins at python.org (brett.cannon) Date: Wed, 1 Nov 2006 00:00:36 +0100 (CET) Subject: [Python-checkins] r52567 - sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061031230036.0B1601E400E@bag.python.org> Author: brett.cannon Date: Wed Nov 1 00:00:36 2006 New Revision: 52567 Modified: sandbox/trunk/import_in_py/test_importer.py Log: Flesh out two tests. Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Wed Nov 1 00:00:36 2006 @@ -281,14 +281,23 @@ # Should be able to read a file. source = self.loader.read_data(self.source_path, False) self.failUnlessEqual(source, self.source) - # XXX Binary reads. + test_text = '1\r\n2' + try: + with open(test_support.TESTFN, 'wb') as test_file: + test_file.write(test_text) + result = self.loader.read_data(test_support.TESTFN, binary=True) + self.failUnlessEqual(result, test_text) + result = self.loader.read_data(test_support.TESTFN, binary=False) + self.failUnlessEqual(result, test_text.replace('\r', '')) + finally: + os.remove(test_support.TESTFN) def test_write_data(self): # Should be able to write a file. self.loader.write_data(self.source, self.source_path, False) read_source = self.loader.read_data(self.source_path, False) self.failUnlessEqual(read_source, self.source) - # XXX Binary writes. + # XXX How to test data written with 'b'? class PyPycHandlerSupportingMethodTests(PyPycFileHelper): @@ -323,7 +332,14 @@ # Split up data from .pyc file for the magic number, timestamp, # and bytecode. pass - # XXX + # XXX Can't fully test until unmarshaling longs is available. + with open(self.bytecode_path, 'rb') as bytecode_file: + pyc = bytecode_file.read() + magic, timestamp, bytecode = self.handler.parse_pyc(pyc) + code = marshal.loads(bytecode) + module = imp.new_module(self.module) + exec code in module.__dict__ + self.verify_module(module) def test_check_magic(self): # Compare a number against the magic number. From python-checkins at python.org Wed Nov 1 00:35:02 2006 From: python-checkins at python.org (brett.cannon) Date: Wed, 1 Nov 2006 00:35:02 +0100 (CET) Subject: [Python-checkins] r52568 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061031233502.D774B1E4006@bag.python.org> Author: brett.cannon Date: Wed Nov 1 00:35:02 2006 New Revision: 52568 Modified: sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/test_importer.py Log: Add preliminary code to handle what module to return based on fromlist. Tests are not done as more package support is needed. Also broke out code that handles actual import into another method so that __call__ can be for handling relative/absolute resolution. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Wed Nov 1 00:35:02 2006 @@ -30,6 +30,9 @@ * __path__ is not inherited in any way [introspection]. * sys.path_importer_cache has an entry for each package directory that was successfully imported [introspection]. +* When importing pre-requisite modules/packages for a dotted module name, + calls are handled internally [introspection]. + + sys.modules is checked, then find/load is done. * Packages take precedence over modules with the same name in the same sys.path entry [package essay]. @@ -577,14 +580,32 @@ return loader else: raise ImportError("%s not found on sys.path" % name) + + def import(self, name): + """Import a module.""" + try: + # Attempt to get a cached version of the module from sys.modules. + return sys.modules[name] + except KeyError: + pass + try: + # Attempt to find a loader on sys.meta_path. + loader = self.search_meta_path(name) + except ImportError: + # sys.meta_path search failed. Attempt to find a loader on + # sys.path. If this fails then module cannot be found. + loader = self.search_sys_path(name) + # A loader was found. It is the loader's responsibility to have put an + # entry in sys.modules. + return loader.load_module(name) def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1): - """Import a module. + """Import a module after resolving relative/absolute import issues. 'name' is the dotted name of the module/package to import. 'globals'and 'locals' are the global and local namespace dictionaries of the caller (only 'globals' is used to introspect the __path__ attribute of the calling - module). fromlist is any specific objects that are to eventually be put + module). fromlist lists any specific objects that are to eventually be put into the namespace (e.g., ``from for.bar import baz`` would have baz in the fromlist). 'level' is set to -1 if both relative and absolute imports are supported, 0 if only for absolute, and positive values represent the number @@ -593,10 +614,11 @@ When 'name' is a dotted name, there are two different situations to consider. One is when the fromlist is empty. In this situation the import imports and returns the name up to the first dot. All subsequent names are - imported but set at attributes as needed. When fromlist is not empty then + imported but set as attributes as needed. When fromlist is not empty then the module represented by the full dotted name is returned. """ + # XXX Ignores fromlist. # XXX Does not handle packages yet, which means no absolute/relative imports # or fromlist worries. @@ -607,15 +629,12 @@ # Import submodules; short-circuits search if module is already # in sys.modules. # XXX - - - # Try meta_path entries. - try: - # Attempt to find a loader on sys.meta_path. - loader = self.search_meta_path(name) - except ImportError: - # sys.meta_path search failed. Attempt to find a loader on - # sys.path. If this fails then module cannot be found. - loader = self.search_sys_path(name) - # A loader was found. - return loader.load_module(name) + module = self.import(name) + # When fromlist is not specified, return the root module (i.e., module + # up to first dot). + if not fromlist: + return sys.modules[name.partition('.')[0]] + # When fromlist is not empty, return the actual module specified in + # the import. + else: + return module \ No newline at end of file Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Wed Nov 1 00:35:02 2006 @@ -581,6 +581,19 @@ sys.path_importer_cache.clear() self.failUnlessRaises(ImportError, self.import_.search_sys_path, 'sys') self.failUnless(sys.path_importer_cache[path_entry] is None) + + +class PackageImportTests(unittest.TestCase): + + """Test cases involving package semantics.""" + + def test_empty_fromlist(self): + # An empty fromlist means that the root module is returned. + pass + + def test_nonempty_fromlist(self): + # A fromlist with values should return the specified module. + pass def test_main(): From python-checkins at python.org Wed Nov 1 00:35:34 2006 From: python-checkins at python.org (brett.cannon) Date: Wed, 1 Nov 2006 00:35:34 +0100 (CET) Subject: [Python-checkins] r52569 - sandbox/trunk/import_in_py/importer.py Message-ID: <20061031233534.91E561E4006@bag.python.org> Author: brett.cannon Date: Wed Nov 1 00:35:34 2006 New Revision: 52569 Modified: sandbox/trunk/import_in_py/importer.py Log: Fix naming conflict. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Wed Nov 1 00:35:34 2006 @@ -581,7 +581,7 @@ else: raise ImportError("%s not found on sys.path" % name) - def import(self, name): + def import_(self, name): """Import a module.""" try: # Attempt to get a cached version of the module from sys.modules. @@ -629,7 +629,7 @@ # Import submodules; short-circuits search if module is already # in sys.modules. # XXX - module = self.import(name) + module = self.import_(name) # When fromlist is not specified, return the root module (i.e., module # up to first dot). if not fromlist: From python-checkins at python.org Wed Nov 1 02:04:39 2006 From: python-checkins at python.org (brett.cannon) Date: Wed, 1 Nov 2006 02:04:39 +0100 (CET) Subject: [Python-checkins] r52570 - sandbox/trunk/import_in_py/importer.py Message-ID: <20061101010439.8E4201E4002@bag.python.org> Author: brett.cannon Date: Wed Nov 1 02:04:38 2006 New Revision: 52570 Modified: sandbox/trunk/import_in_py/importer.py Log: Add basic outline of how to tackle packages. Also tweak some comments. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Wed Nov 1 02:04:38 2006 @@ -25,6 +25,8 @@ Package notes ============= +Semantics +--------- * __path__ is set to the deepest package directory, not the top package directory [introspection]. * __path__ is not inherited in any way [introspection]. @@ -60,6 +62,18 @@ this means that the usual path_hooks/path_importer_cache dance is done for entries in __path__ instead of sys.path (meta_path is still searched regardless of the value of __path__) [PEP 302]. + +Implementing +------------ +1. Imports of top-level package. + + __path__ set. +2. Importing within package. + + Root package already imported. + + Root package not imported yet. +3. fromlist semantics. +4. Relative imports. + + Py3K semantics. + + 2.x semantics. Things to be exposed at the Python level @@ -618,17 +632,14 @@ the module represented by the full dotted name is returned. """ - # XXX Ignores fromlist. # XXX Does not handle packages yet, which means no absolute/relative imports # or fromlist worries. - # Check for a relative import; if it is one make it absolute to try to import that, + # XXX Check for a relative import; if it is one make it absolute to try to import that, # otherwise import as a top-level module. - # XXX - # Import submodules; short-circuits search if module is already + # XXX Import submodules; short-circuits search if module is already # in sys.modules. - # XXX module = self.import_(name) # When fromlist is not specified, return the root module (i.e., module # up to first dot). From python-checkins at python.org Wed Nov 1 03:15:23 2006 From: python-checkins at python.org (brett.cannon) Date: Wed, 1 Nov 2006 03:15:23 +0100 (CET) Subject: [Python-checkins] r52571 - sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061101021523.436801E4002@bag.python.org> Author: brett.cannon Date: Wed Nov 1 03:15:22 2006 New Revision: 52571 Modified: sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Log: Added an __init__ the the filesystem mock object. Also reorganize tests for importer.Importer. Modified: sandbox/trunk/import_in_py/mock_importer.py ============================================================================== --- sandbox/trunk/import_in_py/mock_importer.py (original) +++ sandbox/trunk/import_in_py/mock_importer.py Wed Nov 1 03:15:22 2006 @@ -19,11 +19,19 @@ the base path and another string representing the type. """ + + def __init__(self, file_path, handler, package=None): + """Store arguments passed into the constructor for verification.""" + self.init_file_path = file_path + self.init_handler = handler + self.init_package = package - def __init__(self, good_magic=True, good_timestamp=True, pyc_exists=True, + @classmethod + def setup(cls, good_magic=True, good_timestamp=True, pyc_exists=True, py_exists=True): """Set up the mock loader based on possible scenarios of source and bytecode combinations/issues.""" + self = cls('', '') self.module_name = 'test_module' self.py_ext = "source" if py_exists else None self.pyc_ext = "bytecode" if pyc_exists else None @@ -45,6 +53,7 @@ # Needed for read_data on .pyc path. self.pyc = pyc + bytecode self.log = [] + return self def _create_handler(self, handler): if self.py_ext: @@ -189,6 +198,7 @@ def load_module(self, fullname, path=None): self.load_request = fullname, path + sys.modules[fullname] = self.module return self.module @classmethod Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Wed Nov 1 03:15:22 2006 @@ -375,7 +375,7 @@ """Test PyPycHandler.handle_code().""" def test_good_pyc_w_py(self): - loader = mock_importer.MockPyPycLoader() + loader = mock_importer.MockPyPycLoader.setup() handler = loader._create_handler(importer.PyPycHandler) module = loader._handle_pyc(handler) loader._verify_module(module) @@ -401,7 +401,7 @@ pass def test_py_no_pyc(self): - loader = mock_importer.MockPyPycLoader(pyc_exists=False) + loader = mock_importer.MockPyPycLoader.setup(pyc_exists=False) handler = loader._create_handler(importer.PyPycHandler) module = loader._handle_py(handler) loader._verify_module(module) @@ -410,7 +410,7 @@ self.failUnlessEqual(loader.log.count('read_data'), 1) def test_py_w_pyc(self): - loader = mock_importer.MockPyPycLoader() + loader = mock_importer.MockPyPycLoader.setup() handler = loader._create_handler(importer.PyPycHandler) module = loader._handle_py(handler) loader._verify_module(module) @@ -447,19 +447,24 @@ if not attr.startswith('_'))) -class SimpleImportTests(unittest.TestCase): +class ImporterHelper(unittest.TestCase): - """Test Importer class with only direct module imports; no packages.""" + """Common helper methods for testing the Importer class.""" def setUp(self): """Store a copy of the 'sys' attribute pertaining to imports.""" - # Don't backup sys.modules since dict is cached and losing the cache - # is not that severe. + # Don't backup sys.modules since dict is cached. + # Don't clear sys.modules as it can wreak havoc + # (e.g., losing __builtin__). self.old_sys_modules = sys.modules.copy() self.old_meta_path = sys.meta_path[:] + sys.meta_path = [] self.old_sys_path = sys.path[:] + sys.path = [] self.old_path_hooks = sys.path_hooks[:] + sys.path_hooks = [] self.old_path_importer_cache = sys.path_importer_cache.copy() + sys.path_importer_cache.clear() self.import_ = importer.Importer() def tearDown(self): @@ -470,38 +475,37 @@ sys.path = self.old_sys_path sys.path_hooks = self.old_path_hooks sys.path_importer_cache = self.old_path_importer_cache - - def test_default_importer_factory(self): - # Make sure that the object passed in during initialization is used - # when sys.path_importer_cache has a value of None. - succeed_importer = mock_importer.SucceedImporter() - import_ = importer.Importer(succeed_importer, ()) - sys.meta_path = [] - sys.path = [''] - sys.path_importer_cache[''] = None - module = import_('sys') - self.failUnlessEqual(succeed_importer.find_request, ('sys', None)) - self.failUnless(module is mock_importer.SucceedImporter.module) - - def test_extended_meta_path(self): - # Default meta_path entries set during initialization should be - # queried after sys.meta_path. - pass_importer = mock_importer.PassImporter() - sys.meta_path = [pass_importer] - succeed_importer = mock_importer.SucceedImporter() - import_ = importer.Importer(extended_meta_path=(succeed_importer,)) - module = import_('sys') - for meta_importer in (pass_importer, succeed_importer): - self.failUnlessEqual(meta_importer.find_request, ('sys', None)) - self.failUnless(module is mock_importer.SucceedImporter.module) - + + def clear_sys_module(*modules): + for module in modules: + try: + del sys.modules[module] + except KeyError: + pass + + +class ImporterMiscTests(ImporterHelper): + + """Test miscellaneous parts of the Importer class.""" + + def test_sys_module_return(self): + # A module found in sys.modules should be returned immediately. + sys.path_importer_cache.clear() + sys.path = [] + test_module = '' + sys.modules[test_module] = test_module + module = self.import_('') + self.failUnlessEqual(module, test_module) + def test_default_init(self): # The default initialization should work with a None entry for every # sys.path entry in sys.path_importer_cache. It should also lead to # built-in, frozen, extension, .pyc, and .py files being imported if # desired. sys.path_importer_cache = dict((entry, None) for entry in sys.path) - sys.modules = {} + self.clear_sys_module('sys', '__hello__', 'time', 'token') + # Restore sys.path for 'time' import. + sys.path = self.old_sys_path # Built-ins. module = self.import_('sys') self.failUnlessEqual(module.__name__, 'sys') @@ -521,10 +525,26 @@ module = self.import_('token') self.failUnlessEqual(module.__name__, 'token') self.failUnless(hasattr(module, 'ISTERMINAL')) - + + def test_empty_fromlist(self): + # An empty fromlist means that the root module is returned. + # XXX + pass + + def test_nonempty_fromlist(self): + # A fromlist with values should return the specified module. + # XXX + pass + + +class ImporterMetaPathTests(ImporterHelper): + + """Test meta_path usage.""" + def test_search_meta_path(self): # Test search method of sys.meta_path. # Should raise ImportError on error. + self.clear_sys_module('sys') import_ = importer.Importer(extended_meta_path=()) sys.meta_path = [] self.failUnlessRaises(ImportError, import_.search_meta_path, @@ -538,9 +558,40 @@ self.failUnlessEqual(entry.find_request, ('sys', None)) self.failUnless(loader is meta_path[-1]) + def test_extended_meta_path(self): + # Default meta_path entries set during initialization should be + # queried after sys.meta_path. + self.clear_sys_module('sys') + pass_importer = mock_importer.PassImporter() + sys.meta_path = [pass_importer] + succeed_importer = mock_importer.SucceedImporter() + import_ = importer.Importer(extended_meta_path=(succeed_importer,)) + module = import_('sys') + for meta_importer in (pass_importer, succeed_importer): + self.failUnlessEqual(meta_importer.find_request, ('sys', None)) + self.failUnless(module is mock_importer.SucceedImporter.module) + + +class ImporterSysPathTests(ImporterHelper): + + """Test sys.path usage.""" + + def test_default_importer_factory(self): + # Make sure that the object passed in during initialization is used + # when sys.path_importer_cache has a value of None. + self.clear_sys_module('sys') + succeed_importer = mock_importer.SucceedImporter() + import_ = importer.Importer(succeed_importer, ()) + sys.meta_path = [] + sys.path = [''] + sys.path_importer_cache[''] = None + module = import_('sys') + self.failUnlessEqual(succeed_importer.find_request, ('sys', None)) + self.failUnless(module is mock_importer.SucceedImporter.module) + def test_search_sys_path(self): # Test sys.path searching for a loader. - sys.meta_path = [] + self.clear_sys_module('sys') import_ = importer.Importer(extended_meta_path=()) sys.path = [] sys_path = (mock_importer.PassImporter.set_on_sys_path(), @@ -553,6 +604,7 @@ def test_importer_cache_preexisting(self): # A pre-existing importer should be returned if it exists in # sys.path_importer_cache. + self.clear_sys_module('sys') sys.path = [] succeed_importer = mock_importer.SucceedImporter.set_on_sys_path() loader = self.import_.search_sys_path('sys') @@ -562,6 +614,7 @@ # If an entry does not exist for a sys.path entry in the importer cache # then sys.path_hooks should be searched and if one is found then cache # it. + self.clear_sys_module('sys') path_entry = '' succeed_importer = mock_importer.SucceedImporter() sys.path = [path_entry] @@ -575,25 +628,13 @@ def test_importer_cache_no_path_hooks(self): # If an entry does not exist for a sys.path entry in the importer cache # and sys.path_hooks has nothing for the entry, None should be set. + self.clear_sys_module('sys') path_entry = '' sys.path = [path_entry] sys.path_hooks = [] sys.path_importer_cache.clear() self.failUnlessRaises(ImportError, self.import_.search_sys_path, 'sys') self.failUnless(sys.path_importer_cache[path_entry] is None) - - -class PackageImportTests(unittest.TestCase): - - """Test cases involving package semantics.""" - - def test_empty_fromlist(self): - # An empty fromlist means that the root module is returned. - pass - - def test_nonempty_fromlist(self): - # A fromlist with values should return the specified module. - pass def test_main(): From python-checkins at python.org Wed Nov 1 08:04:57 2006 From: python-checkins at python.org (anthony.baxter) Date: Wed, 1 Nov 2006 08:04:57 +0100 (CET) Subject: [Python-checkins] r52572 - in python/branches/release23-maint: Doc/commontex/boilerplate.tex Include/patchlevel.h Lib/idlelib/NEWS.txt Lib/idlelib/idlever.py Misc/NEWS Misc/RPM/python-2.3.spec Message-ID: <20061101070457.740EE1E4016@bag.python.org> Author: anthony.baxter Date: Wed Nov 1 08:04:56 2006 New Revision: 52572 Modified: python/branches/release23-maint/Doc/commontex/boilerplate.tex python/branches/release23-maint/Include/patchlevel.h python/branches/release23-maint/Lib/idlelib/NEWS.txt python/branches/release23-maint/Lib/idlelib/idlever.py python/branches/release23-maint/Misc/NEWS python/branches/release23-maint/Misc/RPM/python-2.3.spec Log: updates for 2.3.6 final Modified: python/branches/release23-maint/Doc/commontex/boilerplate.tex ============================================================================== --- python/branches/release23-maint/Doc/commontex/boilerplate.tex (original) +++ python/branches/release23-maint/Doc/commontex/boilerplate.tex Wed Nov 1 08:04:56 2006 @@ -6,5 +6,5 @@ } %\date{\today} -\date{October 25, 2006} % XXX update before final release! +\date{November 1, 2006} % XXX update before final release! \input{patchlevel} % include Python version information Modified: python/branches/release23-maint/Include/patchlevel.h ============================================================================== --- python/branches/release23-maint/Include/patchlevel.h (original) +++ python/branches/release23-maint/Include/patchlevel.h Wed Nov 1 08:04:56 2006 @@ -22,8 +22,8 @@ #define PY_MAJOR_VERSION 2 #define PY_MINOR_VERSION 3 #define PY_MICRO_VERSION 6 -#define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_GAMMA -#define PY_RELEASE_SERIAL 1 +#define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_FINAL +#define PY_RELEASE_SERIAL 0 /* Version as a string */ #define PY_VERSION "2.3.6c1" Modified: python/branches/release23-maint/Lib/idlelib/NEWS.txt ============================================================================== --- python/branches/release23-maint/Lib/idlelib/NEWS.txt (original) +++ python/branches/release23-maint/Lib/idlelib/NEWS.txt Wed Nov 1 08:04:56 2006 @@ -1,3 +1,8 @@ +What's New in IDLE 1.0.6? +========================= + +*Release date: 01-Nov-2006* + What's New in IDLE 1.0.6c1? =========================== Modified: python/branches/release23-maint/Lib/idlelib/idlever.py ============================================================================== --- python/branches/release23-maint/Lib/idlelib/idlever.py (original) +++ python/branches/release23-maint/Lib/idlelib/idlever.py Wed Nov 1 08:04:56 2006 @@ -1 +1 @@ -IDLE_VERSION = "1.0.6c1" +IDLE_VERSION = "1.0.6" Modified: python/branches/release23-maint/Misc/NEWS ============================================================================== --- python/branches/release23-maint/Misc/NEWS (original) +++ python/branches/release23-maint/Misc/NEWS Wed Nov 1 08:04:56 2006 @@ -4,6 +4,11 @@ (editors: check NEWS.help for information about editing NEWS using ReST.) +What's New in Python 2.3.6? +=========================== + +*Release date: 01-NOV-2006* + What's New in Python 2.3.6c1? ============================= Modified: python/branches/release23-maint/Misc/RPM/python-2.3.spec ============================================================================== --- python/branches/release23-maint/Misc/RPM/python-2.3.spec (original) +++ python/branches/release23-maint/Misc/RPM/python-2.3.spec Wed Nov 1 08:04:56 2006 @@ -35,7 +35,7 @@ %define name python %define version 2.3.6 %define libvers 2.3 -%define release 4pydotorg +%define release 1pydotorg %define __prefix /usr # kludge to get around rpm define weirdness From python-checkins at python.org Wed Nov 1 08:07:27 2006 From: python-checkins at python.org (anthony.baxter) Date: Wed, 1 Nov 2006 08:07:27 +0100 (CET) Subject: [Python-checkins] r52573 - python/branches/release23-maint/Include/patchlevel.h Message-ID: <20061101070727.B06661E4006@bag.python.org> Author: anthony.baxter Date: Wed Nov 1 08:07:27 2006 New Revision: 52573 Modified: python/branches/release23-maint/Include/patchlevel.h Log: update patchlevel.h correctly, this time Modified: python/branches/release23-maint/Include/patchlevel.h ============================================================================== --- python/branches/release23-maint/Include/patchlevel.h (original) +++ python/branches/release23-maint/Include/patchlevel.h Wed Nov 1 08:07:27 2006 @@ -26,7 +26,7 @@ #define PY_RELEASE_SERIAL 0 /* Version as a string */ -#define PY_VERSION "2.3.6c1" +#define PY_VERSION "2.3.6" /* Version as a single 4-byte hex number, e.g. 0x010502B2 == 1.5.2b2. Use this for numeric comparisons, e.g. #if PY_VERSION_HEX >= ... */ From python-checkins at python.org Wed Nov 1 08:09:04 2006 From: python-checkins at python.org (anthony.baxter) Date: Wed, 1 Nov 2006 08:09:04 +0100 (CET) Subject: [Python-checkins] r52574 - python/tags/r236 Message-ID: <20061101070904.4DE811E4002@bag.python.org> Author: anthony.baxter Date: Wed Nov 1 08:09:04 2006 New Revision: 52574 Added: python/tags/r236/ - copied from r52573, python/branches/release23-maint/ Log: Tagging for release of Python 2.3.6 From python-checkins at python.org Wed Nov 1 23:57:04 2006 From: python-checkins at python.org (brett.cannon) Date: Wed, 1 Nov 2006 23:57:04 +0100 (CET) Subject: [Python-checkins] r52575 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061101225704.D006C1E4003@bag.python.org> Author: brett.cannon Date: Wed Nov 1 23:57:03 2006 New Revision: 52575 Modified: sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Log: Refactor unit tests to use mock objects. Tests now run faster and are more isolated. Also begin initial work on package support (mostly untested). Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Wed Nov 1 23:57:03 2006 @@ -255,6 +255,7 @@ def __init__(self, path_entry, *handlers): self.path_entry = path_entry self.handlers = handlers + self.loader = FileSystemLoader def find_module(self, fullname, path=None): """Determine if this path entry can handle this import, and if so, @@ -262,15 +263,19 @@ registered.""" # XXX Ignores 'path' at the moment. # XXX Does not worry about case-insensitive filesystems. - try: - for handler in self.handlers: - for file_ext in handler.handles: - file_name = fullname + file_ext - file_path = os.path.join(self.path_entry, file_name) - if os.path.isfile(file_path): - raise StopIteration("file found") - except StopIteration: - return FileSystemLoader(file_path, handler) + for handler in self.handlers: + for file_ext in handler.handles: + # XXX Backwards-incompatible to use extension modules for __init__? + package_entry = os.path.join(self.path_entry, fullname) + package_name = os.path.join(package_entry, + '__init__'+file_ext) + file_path = os.path.join(self.path_entry, package_name) + if os.path.isfile(file_path): + return FileSystemLoader(file_path, handler, package_entry) + file_name = fullname + file_ext + file_path = os.path.join(self.path_entry, file_name) + if os.path.isfile(file_path): + return self.loader(file_path, handler) else: return None @@ -283,10 +288,11 @@ """ - def __init__(self, file_path, handler): + def __init__(self, file_path, handler, package=None): """Store arguments on to the instance.""" self.file_path = file_path self.handler = handler + self.package = package def load_module(self, fullname, path=None): """Load the module from self.path using self.handler. @@ -305,6 +311,8 @@ if fullname in sys.modules: del sys.modules[fullname] raise + if self.package is not None: + module.__path__ = [self.package] return module def mod_time(self, path): Modified: sandbox/trunk/import_in_py/mock_importer.py ============================================================================== --- sandbox/trunk/import_in_py/mock_importer.py (original) +++ sandbox/trunk/import_in_py/mock_importer.py Wed Nov 1 23:57:03 2006 @@ -11,6 +11,19 @@ return log_and_call +class MockHandler(object): + + """Mock a handler.""" + + def __init__(self, *handles): + self.handles = handles + + def handle_code(self, loader, mod_name, path): + """Mock implementation of a handler.""" + called_with = loader, mod_name, path + sys.modules[mod_name] = called_with + return called_with + class MockPyPycLoader(object): """Mock loader object for testing importer.PyPycHandler. @@ -22,9 +35,9 @@ def __init__(self, file_path, handler, package=None): """Store arguments passed into the constructor for verification.""" - self.init_file_path = file_path - self.init_handler = handler - self.init_package = package + self.file_path = file_path + self.handler = handler + self.package = package @classmethod def setup(cls, good_magic=True, good_timestamp=True, pyc_exists=True, @@ -89,7 +102,8 @@ return True def load_module(self, fullname, path=None): - raise NotImplementedError + """Return what the method was called with.""" + return fullname, path @log_call def split_path(self, path): @@ -149,9 +163,11 @@ return None +# Mock Importers (with optional path_hooks support). + class ErrorImporter(object): - """Helper class to have a guaranteed error point.""" + """Mock importer to have a guaranteed error point.""" def find_module(self, fullname, path=None): self.find_request = fullname, path @@ -168,7 +184,11 @@ class PassImporter(object): - """Always pass on importing a module.""" + """Mock importer that always pass on importing a module.""" + + def __call__(self, path_entry): + """Always pass when asked to create an importer.""" + raise ImportError def find_module(self, fullname, path=None): self.find_request = fullname, path @@ -185,7 +205,7 @@ class SucceedImporter(object): - """Always succeed by returning 'self'.""" + """Mock importer that always succeed by returning 'self'.""" module = 42 Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Wed Nov 1 23:57:03 2006 @@ -1,3 +1,4 @@ +"""XXX separate medium/large tests into a single test class.""" from __future__ import with_statement import importer @@ -166,23 +167,37 @@ self.failUnlessEqual(getattr(module, self.attr_name), self.attr_value) -class FileSystemImporterTests(PyPycFileHelper): +class FileSystemImporterTests(unittest.TestCase): """Test the filesystem importer.""" def setUp(self): """Create a basic importer.""" - super(FileSystemImporterTests, self).setUp() - source_handler = importer.PyPycHandler(bytecode_handles=tuple()) + self.directory = tempfile.gettempdir() + self.module_name = 'importer_test' + self.file_ext = '.test' + self.file_path = os.path.join(self.directory, + self.module_name + self.file_ext) + with open(self.file_path, 'w') as test_file: + test_file.write("A file for testing the 'importer' module.") + mock_handler = mock_importer.MockHandler(self.file_ext) self.importer = importer.FileSystemImporter(self.directory, - source_handler) + mock_handler) + self.importer.loader = mock_importer.MockPyPycLoader + + def tearDown(self): + """Clean up the created file.""" + try: + os.remove(self.file_path) + except OSError: + pass def test_find_module_single_handler(self): # Having a single handler should work without issue. - loader = self.importer.find_module(self.module) - self.failUnless(isinstance(loader, importer.FileSystemLoader)) - self.failUnlessEqual(loader.file_path, self.source_path) - self.failUnless(isinstance(loader.handler, importer.PyPycHandler)) + loader = self.importer.find_module(self.module_name) + self.failUnless(isinstance(loader, mock_importer.MockPyPycLoader)) + self.failUnlessEqual(loader.file_path, self.file_path) + self.failUnless(isinstance(loader.handler, mock_importer.MockHandler)) def test_find_module_cannot_find(self): # Should return None if it can't find the module. @@ -191,48 +206,58 @@ def test_find_module_multiple_handlers(self): # Modules should be found based on the order of the handlers. - source_handler = importer.PyPycHandler(bytecode_handles=tuple()) - bytecode_handler = importer.PyPycHandler(source_handles=tuple()) + # mock + first_handler = mock_importer.MockHandler('.not_found') + second_handler = mock_importer.MockHandler(self.file_ext) fs_importer = importer.FileSystemImporter(self.directory, - bytecode_handler, source_handler) - loader = fs_importer.find_module(self.module) - self.failUnless(isinstance(loader, importer.FileSystemLoader)) - self.failUnlessEqual(loader.file_path, self.bytecode_path) - self.failUnless(isinstance(loader.handler, importer.PyPycHandler)) - - def test_find_to_load(self): - # Make sure that one can go from find_module() to getting a module - # imported. - loader = self.importer.find_module(self.module) - self.failUnless(loader) - module = loader.load_module(self.module) - self.verify_module(module, self.source_path) - self.failUnlessEqual(module, sys.modules[self.module]) - + first_handler, + second_handler) + fs_importer.loader = mock_importer.MockPyPycLoader + loader = fs_importer.find_module(self.module_name) + self.failUnless(isinstance(loader, mock_importer.MockPyPycLoader)) + self.failUnlessEqual(loader.file_path, self.file_path) + self.failUnless(isinstance(loader.handler, mock_importer.MockHandler)) -class FileSystemLoaderBlackBoxTests(PyPycFileHelper): - """Test the filesystem loader's PEP 302 API compliance.""" +class FileSystemLoaderMockEnv(unittest.TestCase): + + """Helper for settting up mock environment for testing + importer.FileSystemLoader.""" def setUp(self): """Create a fresh loader per run.""" - super(FileSystemLoaderBlackBoxTests, self).setUp() - source_handler = importer.PyPycHandler(bytecode_handles=tuple()) - self.loader = importer.FileSystemLoader(self.source_path, - source_handler) + # XXX mock + mock_handler = mock_importer.MockHandler() + self.test_path = "" + self.module_name = "test_module_name" + self.loader = importer.FileSystemLoader(self.test_path, + mock_handler) + + def tearDown(self): + """Make sure that there is no straggling modules in sys.modules.""" + try: + del sys.modules[self.module_name] + except KeyError: + pass + + +class FileSystemLoaderBlackBoxTests(FileSystemLoaderMockEnv): + + """Test the filesystem loader's PEP 302 API compliance.""" def test_load_module_fresh(self): # Test a basic module load where there is no sys.modules entry. # PyPycFileHelper.setUp() clears sys.modules for us. - new_module = self.loader.load_module(self.module) - self.verify_module(new_module, self.source_path) + new_module = self.loader.load_module(self.module_name) + expected = self.loader, self.module_name, self.test_path + self.failUnlessEqual(new_module, expected) def test_load_module_sys_modules(self): # Make sure that the loader returns the module from sys.modules if it # is there. - sys.modules[self.module] = self.module_object - loaded_module = self.loader.load_module(self.module) - self.failUnless(loaded_module is self.module_object) + sys.modules[self.module_name] = self.module_name + loaded_module = self.loader.load_module(self.module_name) + self.failUnless(loaded_module is self.module_name) def test_sys_module_cleared_on_error(self): # Any entry made for module into sys.modules should be cleared upon error. @@ -240,64 +265,77 @@ def handle_code(*args): raise ImportError - loader = importer.FileSystemLoader(self.source_path, RaiseErrorHandler()) + loader = importer.FileSystemLoader(self.test_path, RaiseErrorHandler()) try: - loader.load_module(self.module) + loader.load_module(self.module_name) except ImportError: - self.failUnless(self.module not in sys.modules) + self.failUnless(self.module_name not in sys.modules) + + def test_package_init(self): + # Test that instantiating a loader for handling a package works + # properly. + # XXX + pass -class FileSystemLoaderWhiteBoxTests(PyPycFileHelper): +class FileSystemLoaderWhiteBoxTests(FileSystemLoaderMockEnv): """Test the filesystem loader's non-PEP 302 API.""" def setUp(self): - """Create a loader.""" + """Set up a test file.""" super(FileSystemLoaderWhiteBoxTests, self).setUp() - source_handler = importer.PyPycHandler(bytecode_handles=tuple()) - self.loader = importer.FileSystemLoader(self.source_path, - source_handler) + self.directory = tempfile.gettempdir() + self.file_ext = ".test" + self.module_name = "import_test" + self.file_name = self.module_name + self.file_ext + self.file_path = os.path.join(self.directory, self.file_name) + self.data = "File used for testing 'importer' module.\r\n42" + with open(self.file_path, 'w') as test_file: + test_file.write(self.data) + + def tearDown(self): + """Delete the test file.""" + super(FileSystemLoaderWhiteBoxTests, self).tearDown() + try: + os.remove(self.file_path) + except OSError: + pass def test_mod_time(self): # Modification time for the passed-in path should match the file. - true_mod_time = os.stat(self.source_path).st_mtime - mod_time = self.loader.mod_time(self.source_path) + true_mod_time = os.stat(self.file_path).st_mtime + mod_time = self.loader.mod_time(self.file_path) self.failUnlessEqual(mod_time, true_mod_time) def test_split_path(self): # Should split a path just like os.path.splitext(). - true_split_path = os.path.splitext(self.source_path) - split_path = self.loader.split_path(self.source_path) + true_split_path = os.path.splitext(self.file_path) + split_path = self.loader.split_path(self.file_path) self.failUnlessEqual(split_path, true_split_path) def test_create_path(self): # Should work like concatenating two strings. - true_path = ''.join(self.loader.split_path(self.source_path)) + true_path = self.file_path path = self.loader.create_path(*self.loader.split_path( - self.source_path)) + self.file_path)) self.failUnlessEqual(path, true_path) def test_read_data(self): # Should be able to read a file. - source = self.loader.read_data(self.source_path, False) - self.failUnlessEqual(source, self.source) + data = self.loader.read_data(self.file_path, False) + self.failUnlessEqual(data, self.data.replace('\r', '')) + data = self.loader.read_data(self.file_path, True) + self.failUnlessEqual(data, self.data) test_text = '1\r\n2' - try: - with open(test_support.TESTFN, 'wb') as test_file: - test_file.write(test_text) - result = self.loader.read_data(test_support.TESTFN, binary=True) - self.failUnlessEqual(result, test_text) - result = self.loader.read_data(test_support.TESTFN, binary=False) - self.failUnlessEqual(result, test_text.replace('\r', '')) - finally: - os.remove(test_support.TESTFN) def test_write_data(self): # Should be able to write a file. - self.loader.write_data(self.source, self.source_path, False) - read_source = self.loader.read_data(self.source_path, False) - self.failUnlessEqual(read_source, self.source) - # XXX How to test data written with 'b'? + self.loader.write_data(self.data, self.file_path, True) + with open(self.file_path, 'rb') as test_file: + data = test_file.read() + self.failUnlessEqual(data, self.data) + # XXX How to test data written with/without 'b'? class PyPycHandlerSupportingMethodTests(PyPycFileHelper): @@ -436,7 +474,7 @@ raise test_support.TestSkipped("not extension modules found") self.ext_path = os.path.join(entry, ext_paths[0]) self.module_name = os.path.splitext(os.path.split(self.ext_path)[1])[0] - self.loader = importer.FileSystemLoader(self.ext_path, self.handler) + self.loader = mock_importer.MockHandler() def test_handle_code(self): # Make sure an extension module can be loaded. @@ -465,7 +503,8 @@ sys.path_hooks = [] self.old_path_importer_cache = sys.path_importer_cache.copy() sys.path_importer_cache.clear() - self.import_ = importer.Importer() + self.default_importer = mock_importer.PassImporter() + self.importer = importer.Importer(self.default_importer, tuple()) def tearDown(self): """Restore backup of import-related attributes in 'sys'.""" @@ -486,46 +525,21 @@ class ImporterMiscTests(ImporterHelper): - """Test miscellaneous parts of the Importer class.""" + """Test miscellaneous parts of the Importer class. + + Tests of the default values for __init__ are handled in the integration + tests. + + """ def test_sys_module_return(self): # A module found in sys.modules should be returned immediately. - sys.path_importer_cache.clear() - sys.path = [] test_module = '' sys.modules[test_module] = test_module - module = self.import_('') + module = self.importer.import_('') + del sys.modules[test_module] self.failUnlessEqual(module, test_module) - - def test_default_init(self): - # The default initialization should work with a None entry for every - # sys.path entry in sys.path_importer_cache. It should also lead to - # built-in, frozen, extension, .pyc, and .py files being imported if - # desired. - sys.path_importer_cache = dict((entry, None) for entry in sys.path) - self.clear_sys_module('sys', '__hello__', 'time', 'token') - # Restore sys.path for 'time' import. - sys.path = self.old_sys_path - # Built-ins. - module = self.import_('sys') - self.failUnlessEqual(module.__name__, 'sys') - self.failUnless(hasattr(sys, 'version')) - # Frozen modules. - try: - sys.stdout = StringIO.StringIO() - module = self.import_('__hello__') - finally: - sys.stdout = sys.__stdout__ - self.failUnlessEqual(module.__name__, '__hello__') - # Extension modules. - module = self.import_('time') - self.failUnlessEqual(module.__name__, 'time') - self.failUnless(hasattr(module, 'sleep')) - # .py/.pyc files. - module = self.import_('token') - self.failUnlessEqual(module.__name__, 'token') - self.failUnless(hasattr(module, 'ISTERMINAL')) - + def test_empty_fromlist(self): # An empty fromlist means that the root module is returned. # XXX @@ -545,15 +559,13 @@ # Test search method of sys.meta_path. # Should raise ImportError on error. self.clear_sys_module('sys') - import_ = importer.Importer(extended_meta_path=()) - sys.meta_path = [] - self.failUnlessRaises(ImportError, import_.search_meta_path, + self.failUnlessRaises(ImportError, self.importer.search_meta_path, 'sys') # Verify call order. meta_path = (mock_importer.PassImporter(), mock_importer.SucceedImporter()) sys.meta_path = meta_path - loader = import_.search_meta_path('sys') + loader = self.importer.search_meta_path('sys') for entry in meta_path: self.failUnlessEqual(entry.find_request, ('sys', None)) self.failUnless(loader is meta_path[-1]) @@ -565,8 +577,8 @@ pass_importer = mock_importer.PassImporter() sys.meta_path = [pass_importer] succeed_importer = mock_importer.SucceedImporter() - import_ = importer.Importer(extended_meta_path=(succeed_importer,)) - module = import_('sys') + importer_ = importer.Importer(extended_meta_path=(succeed_importer,)) + module = importer_.import_('sys') for meta_importer in (pass_importer, succeed_importer): self.failUnlessEqual(meta_importer.find_request, ('sys', None)) self.failUnless(module is mock_importer.SucceedImporter.module) @@ -581,22 +593,22 @@ # when sys.path_importer_cache has a value of None. self.clear_sys_module('sys') succeed_importer = mock_importer.SucceedImporter() - import_ = importer.Importer(succeed_importer, ()) + importer_ = importer.Importer(succeed_importer, tuple()) sys.meta_path = [] sys.path = [''] sys.path_importer_cache[''] = None - module = import_('sys') + module = importer_.import_('sys') self.failUnlessEqual(succeed_importer.find_request, ('sys', None)) self.failUnless(module is mock_importer.SucceedImporter.module) def test_search_sys_path(self): # Test sys.path searching for a loader. self.clear_sys_module('sys') - import_ = importer.Importer(extended_meta_path=()) + importer_ = importer.Importer(extended_meta_path=()) sys.path = [] sys_path = (mock_importer.PassImporter.set_on_sys_path(), mock_importer.SucceedImporter.set_on_sys_path()) - module = import_('token') + module = importer_.import_('token') for entry in sys_path: self.failUnlessEqual(entry.find_request, ('token', None)) self.failUnless(module is mock_importer.SucceedImporter.module) @@ -607,7 +619,7 @@ self.clear_sys_module('sys') sys.path = [] succeed_importer = mock_importer.SucceedImporter.set_on_sys_path() - loader = self.import_.search_sys_path('sys') + loader = self.importer.search_sys_path('sys') self.failUnless(loader is succeed_importer) def test_importer_cache_from_path_hooks(self): @@ -620,7 +632,7 @@ sys.path = [path_entry] sys.path_importer_cache.clear() sys.path_hooks = [succeed_importer] - loader = self.import_.search_sys_path('sys') + loader = self.importer.search_sys_path('sys') self.failUnless(loader is succeed_importer) self.failUnless(sys.path_importer_cache[path_entry] is succeed_importer) @@ -633,8 +645,52 @@ sys.path = [path_entry] sys.path_hooks = [] sys.path_importer_cache.clear() - self.failUnlessRaises(ImportError, self.import_.search_sys_path, 'sys') + self.failUnlessRaises(ImportError, self.importer.search_sys_path, 'sys') self.failUnless(sys.path_importer_cache[path_entry] is None) + + +class IntegrationTests(unittest.TestCase): + + """Hold tests that involve multiple classes from 'importer' and verify + integration semantics. + + XXX + * file loader <-> py/pyc handler + * file loader <-> extension handler + * file importer -> file loader + * file importer -> file loader <-> py/pyc handler + * Importer -> importer -> loader + + """ + def XXX_test_default_import(self): + # The default initialization should work with a None entry for every + # sys.path entry in sys.path_importer_cache. It should also lead to + # built-in, frozen, extension, .pyc, and .py files being imported if + # desired. + sys.path_importer_cache = dict((entry, None) for entry in sys.path) + self.clear_sys_module('sys', '__hello__', 'time', 'token') + # Restore sys.path for 'time' import. + sys.path = self.old_sys_path + import_ = importer.Importer() + # Built-ins. + module = import_('sys') + self.failUnlessEqual(module.__name__, 'sys') + self.failUnless(hasattr(sys, 'version')) + # Frozen modules. + try: + sys.stdout = StringIO.StringIO() + module = import_('__hello__') + finally: + sys.stdout = sys.__stdout__ + self.failUnlessEqual(module.__name__, '__hello__') + # Extension modules. + module = import_('time') + self.failUnlessEqual(module.__name__, 'time') + self.failUnless(hasattr(module, 'sleep')) + # .py/.pyc files. + module = import_('token') + self.failUnlessEqual(module.__name__, 'token') + self.failUnless(hasattr(module, 'ISTERMINAL')) def test_main(): From python-checkins at python.org Thu Nov 2 00:12:42 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 00:12:42 +0100 (CET) Subject: [Python-checkins] r52576 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061101231242.E4FF51E4002@bag.python.org> Author: brett.cannon Date: Thu Nov 2 00:12:42 2006 New Revision: 52576 Modified: sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/test_importer.py Log: Change the name of the Importer class to Import. This is to disambiguate the class from actual importer classes. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Thu Nov 2 00:12:42 2006 @@ -507,7 +507,7 @@ return imp.load_dynamic(mod_name, extension_path) -class Importer(object): +class Import(object): """Class that re-implements __import__. Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Thu Nov 2 00:12:42 2006 @@ -504,7 +504,7 @@ self.old_path_importer_cache = sys.path_importer_cache.copy() sys.path_importer_cache.clear() self.default_importer = mock_importer.PassImporter() - self.importer = importer.Importer(self.default_importer, tuple()) + self.importer = importer.Import(self.default_importer, tuple()) def tearDown(self): """Restore backup of import-related attributes in 'sys'.""" @@ -525,7 +525,7 @@ class ImporterMiscTests(ImporterHelper): - """Test miscellaneous parts of the Importer class. + """Test miscellaneous parts of the Import class. Tests of the default values for __init__ are handled in the integration tests. @@ -577,7 +577,7 @@ pass_importer = mock_importer.PassImporter() sys.meta_path = [pass_importer] succeed_importer = mock_importer.SucceedImporter() - importer_ = importer.Importer(extended_meta_path=(succeed_importer,)) + importer_ = importer.Import(extended_meta_path=(succeed_importer,)) module = importer_.import_('sys') for meta_importer in (pass_importer, succeed_importer): self.failUnlessEqual(meta_importer.find_request, ('sys', None)) @@ -593,7 +593,7 @@ # when sys.path_importer_cache has a value of None. self.clear_sys_module('sys') succeed_importer = mock_importer.SucceedImporter() - importer_ = importer.Importer(succeed_importer, tuple()) + importer_ = importer.Import(succeed_importer, tuple()) sys.meta_path = [] sys.path = [''] sys.path_importer_cache[''] = None @@ -604,7 +604,7 @@ def test_search_sys_path(self): # Test sys.path searching for a loader. self.clear_sys_module('sys') - importer_ = importer.Importer(extended_meta_path=()) + importer_ = importer.Import(extended_meta_path=()) sys.path = [] sys_path = (mock_importer.PassImporter.set_on_sys_path(), mock_importer.SucceedImporter.set_on_sys_path()) @@ -671,7 +671,7 @@ self.clear_sys_module('sys', '__hello__', 'time', 'token') # Restore sys.path for 'time' import. sys.path = self.old_sys_path - import_ = importer.Importer() + import_ = importer.Import() # Built-ins. module = import_('sys') self.failUnlessEqual(module.__name__, 'sys') From python-checkins at python.org Thu Nov 2 00:18:05 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 00:18:05 +0100 (CET) Subject: [Python-checkins] r52577 - sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061101231805.28C2C1E4002@bag.python.org> Author: brett.cannon Date: Thu Nov 2 00:18:04 2006 New Revision: 52577 Modified: sandbox/trunk/import_in_py/test_importer.py Log: More cleanup from changing the name of the Import class. Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Thu Nov 2 00:18:04 2006 @@ -485,7 +485,7 @@ if not attr.startswith('_'))) -class ImporterHelper(unittest.TestCase): +class ImportHelper(unittest.TestCase): """Common helper methods for testing the Importer class.""" @@ -523,7 +523,7 @@ pass -class ImporterMiscTests(ImporterHelper): +class ImportMiscTests(ImportHelper): """Test miscellaneous parts of the Import class. @@ -551,7 +551,7 @@ pass -class ImporterMetaPathTests(ImporterHelper): +class ImportMetaPathTests(ImportHelper): """Test meta_path usage.""" @@ -584,7 +584,7 @@ self.failUnless(module is mock_importer.SucceedImporter.module) -class ImporterSysPathTests(ImporterHelper): +class ImportSysPathTests(ImportHelper): """Test sys.path usage.""" From python-checkins at python.org Thu Nov 2 00:40:11 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 00:40:11 +0100 (CET) Subject: [Python-checkins] r52578 - sandbox/trunk/import_in_py/importer.py Message-ID: <20061101234011.666051E4002@bag.python.org> Author: brett.cannon Date: Thu Nov 2 00:40:10 2006 New Revision: 52578 Modified: sandbox/trunk/import_in_py/importer.py Log: Clarify a comment about __init__ files. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Thu Nov 2 00:40:10 2006 @@ -265,7 +265,8 @@ # XXX Does not worry about case-insensitive filesystems. for handler in self.handlers: for file_ext in handler.handles: - # XXX Backwards-incompatible to use extension modules for __init__? + # XXX Backwards-incompatible to use anything but .py/.pyc + # files for __init__? package_entry = os.path.join(self.path_entry, fullname) package_name = os.path.join(package_entry, '__init__'+file_ext) From python-checkins at python.org Thu Nov 2 00:40:31 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 00:40:31 +0100 (CET) Subject: [Python-checkins] r52579 - sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061101234031.F05E11E4002@bag.python.org> Author: brett.cannon Date: Thu Nov 2 00:40:31 2006 New Revision: 52579 Modified: sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Log: Add tests for the filesystem importer and top-level packages. Modified: sandbox/trunk/import_in_py/mock_importer.py ============================================================================== --- sandbox/trunk/import_in_py/mock_importer.py (original) +++ sandbox/trunk/import_in_py/mock_importer.py Thu Nov 2 00:40:31 2006 @@ -19,10 +19,17 @@ self.handles = handles def handle_code(self, loader, mod_name, path): - """Mock implementation of a handler.""" - called_with = loader, mod_name, path - sys.modules[mod_name] = called_with - return called_with + """Mock implementation of a handler. + + An object that can have arbitrary attributes attached to it must be + returned. + + """ + self.loader = loader + self.module_name = mod_name + self.path = path + sys.modules[mod_name] = self + return self class MockPyPycLoader(object): Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Thu Nov 2 00:40:31 2006 @@ -12,6 +12,7 @@ import new import os import py_compile +import shutil import StringIO import sys import tempfile @@ -180,17 +181,22 @@ self.module_name + self.file_ext) with open(self.file_path, 'w') as test_file: test_file.write("A file for testing the 'importer' module.") - mock_handler = mock_importer.MockHandler(self.file_ext) + self.pkg_name = 'test_pkg' + self.pkg_path = os.path.join(self.directory, self.pkg_name) + os.mkdir(os.path.join(self.directory, self.pkg_name)) + self.pkg_init_path = os.path.join(self.pkg_path, + '__init__'+self.file_ext) + with open(self.pkg_init_path, 'w') as init_file: + init_file.write("This is a file for testing packages.") + self.handler = mock_importer.MockHandler(self.file_ext) self.importer = importer.FileSystemImporter(self.directory, - mock_handler) + self.handler) self.importer.loader = mock_importer.MockPyPycLoader def tearDown(self): """Clean up the created file.""" - try: - os.remove(self.file_path) - except OSError: - pass + os.remove(self.file_path) + shutil.rmtree(self.pkg_path) def test_find_module_single_handler(self): # Having a single handler should work without issue. @@ -198,6 +204,7 @@ self.failUnless(isinstance(loader, mock_importer.MockPyPycLoader)) self.failUnlessEqual(loader.file_path, self.file_path) self.failUnless(isinstance(loader.handler, mock_importer.MockHandler)) + self.failUnless(loader.package is None) def test_find_module_cannot_find(self): # Should return None if it can't find the module. @@ -217,6 +224,14 @@ self.failUnless(isinstance(loader, mock_importer.MockPyPycLoader)) self.failUnlessEqual(loader.file_path, self.file_path) self.failUnless(isinstance(loader.handler, mock_importer.MockHandler)) + + def test_pkg_discovery(self): + # If a module name refers to a directory with an __init__ file it + # should be recognized as a package. + loader = self.importer.find_module(self.pkg_name) + self.failUnlessEqual(loader.file_path, self.pkg_init_path) + self.failUnlessEqual(loader.handler, self.handler) + self.failUnlessEqual(loader.package, self.pkg_path) class FileSystemLoaderMockEnv(unittest.TestCase): @@ -226,7 +241,6 @@ def setUp(self): """Create a fresh loader per run.""" - # XXX mock mock_handler = mock_importer.MockHandler() self.test_path = "" self.module_name = "test_module_name" @@ -249,8 +263,9 @@ # Test a basic module load where there is no sys.modules entry. # PyPycFileHelper.setUp() clears sys.modules for us. new_module = self.loader.load_module(self.module_name) - expected = self.loader, self.module_name, self.test_path - self.failUnlessEqual(new_module, expected) + self.failUnlessEqual(new_module.loader, self.loader) + self.failUnlessEqual(new_module.module_name, self.module_name) + self.failUnlessEqual(new_module.path, self.test_path) def test_load_module_sys_modules(self): # Make sure that the loader returns the module from sys.modules if it From python-checkins at python.org Thu Nov 2 00:49:35 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 00:49:35 +0100 (CET) Subject: [Python-checkins] r52580 - sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061101234935.D91B51E4002@bag.python.org> Author: brett.cannon Date: Thu Nov 2 00:49:35 2006 New Revision: 52580 Modified: sandbox/trunk/import_in_py/test_importer.py Log: Add tests for top-level package support for filesystem loaders. Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Thu Nov 2 00:49:35 2006 @@ -243,7 +243,10 @@ """Create a fresh loader per run.""" mock_handler = mock_importer.MockHandler() self.test_path = "" + self.pkg_path = "" + self.pkg_init_path = os.path.join(self.pkg_path, '__init__.test') self.module_name = "test_module_name" + self.pkg_name = "test_pkg" self.loader = importer.FileSystemLoader(self.test_path, mock_handler) @@ -289,8 +292,15 @@ def test_package_init(self): # Test that instantiating a loader for handling a package works # properly. - # XXX - pass + pkg_loader = importer.FileSystemLoader(self.pkg_init_path, + mock_importer.MockHandler(), + self.pkg_path) + module = pkg_loader.load_module(self.pkg_name) + self.failUnlessEqual(module.loader, pkg_loader) + self.failUnlessEqual(module.path, self.pkg_init_path) + self.failUnlessEqual(module.module_name, self.pkg_name) + self.failUnless(hasattr(module, '__path__')) + self.failUnlessEqual(module.__path__, [self.pkg_path]) class FileSystemLoaderWhiteBoxTests(FileSystemLoaderMockEnv): From python-checkins at python.org Thu Nov 2 00:50:55 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 00:50:55 +0100 (CET) Subject: [Python-checkins] r52581 - sandbox/trunk/import_in_py/importer.py Message-ID: <20061101235055.0F7FF1E4002@bag.python.org> Author: brett.cannon Date: Thu Nov 2 00:50:54 2006 New Revision: 52581 Modified: sandbox/trunk/import_in_py/importer.py Log: Update comment on implementation strategy for packages. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Thu Nov 2 00:50:54 2006 @@ -65,13 +65,12 @@ Implementing ------------ -1. Imports of top-level package. - + __path__ set. -2. Importing within package. +#. Importing module within a package. +#. Importing within package. + Root package already imported. + Root package not imported yet. -3. fromlist semantics. -4. Relative imports. +#. fromlist semantics. +#. Relative imports. + Py3K semantics. + 2.x semantics. From python-checkins at python.org Thu Nov 2 20:48:24 2006 From: python-checkins at python.org (thomas.heller) Date: Thu, 2 Nov 2006 20:48:24 +0100 (CET) Subject: [Python-checkins] r52588 - python/trunk/Doc/lib/libctypes.tex Message-ID: <20061102194824.6A7651E400F@bag.python.org> Author: thomas.heller Date: Thu Nov 2 20:48:24 2006 New Revision: 52588 Modified: python/trunk/Doc/lib/libctypes.tex Log: Replace the XXX marker in the 'Arrays and pointers' reference manual section with a link to the tutorial sections. Will backport to release25-maint. Modified: python/trunk/Doc/lib/libctypes.tex ============================================================================== --- python/trunk/Doc/lib/libctypes.tex (original) +++ python/trunk/Doc/lib/libctypes.tex Thu Nov 2 20:48:24 2006 @@ -2440,5 +2440,6 @@ \subsubsection{Arrays and pointers\label{ctypes-arrays-pointers}} -XXX +Not yet written - please see section~\ref{ctypes-pointers}, pointers and +section~\ref{ctypes-arrays}, arrays in the tutorial. From python-checkins at python.org Thu Nov 2 20:52:44 2006 From: python-checkins at python.org (thomas.heller) Date: Thu, 2 Nov 2006 20:52:44 +0100 (CET) Subject: [Python-checkins] r52589 - python/branches/release25-maint/Doc/lib/libctypes.tex Message-ID: <20061102195244.26AC41E4003@bag.python.org> Author: thomas.heller Date: Thu Nov 2 20:52:43 2006 New Revision: 52589 Modified: python/branches/release25-maint/Doc/lib/libctypes.tex Log: Replace the XXX marker in the 'Arrays and pointers' reference manual section with a link to the tutorial sections. Backported from trunk. Modified: python/branches/release25-maint/Doc/lib/libctypes.tex ============================================================================== --- python/branches/release25-maint/Doc/lib/libctypes.tex (original) +++ python/branches/release25-maint/Doc/lib/libctypes.tex Thu Nov 2 20:52:43 2006 @@ -2440,5 +2440,6 @@ \subsubsection{Arrays and pointers\label{ctypes-arrays-pointers}} -XXX +Not yet written - please see section~\ref{ctypes-pointers}, pointers and +section~\ref{ctypes-arrays}, arrays in the tutorial. From python-checkins at python.org Thu Nov 2 21:22:29 2006 From: python-checkins at python.org (thomas.heller) Date: Thu, 2 Nov 2006 21:22:29 +0100 (CET) Subject: [Python-checkins] r52592 - python/trunk/Doc/lib/libctypes.tex Message-ID: <20061102202229.AFB7A1E400F@bag.python.org> Author: thomas.heller Date: Thu Nov 2 21:22:29 2006 New Revision: 52592 Modified: python/trunk/Doc/lib/libctypes.tex Log: Fix a code example by adding a missing import. Fixes #1557890. Will backport to release25-maint. Modified: python/trunk/Doc/lib/libctypes.tex ============================================================================== --- python/trunk/Doc/lib/libctypes.tex (original) +++ python/trunk/Doc/lib/libctypes.tex Thu Nov 2 21:22:29 2006 @@ -1848,7 +1848,7 @@ Here is the wrapping with \code{ctypes}: \begin{quote} -\begin{verbatim}>>> from ctypes import POINTER, WINFUNCTYPE, windll +\begin{verbatim}>>> from ctypes import POINTER, WINFUNCTYPE, windll, WinError >>> from ctypes.wintypes import BOOL, HWND, RECT >>> prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT)) >>> paramflags = (1, "hwnd"), (2, "lprect") From python-checkins at python.org Thu Nov 2 21:24:26 2006 From: python-checkins at python.org (thomas.heller) Date: Thu, 2 Nov 2006 21:24:26 +0100 (CET) Subject: [Python-checkins] r52593 - python/branches/release25-maint/Doc/lib/libctypes.tex Message-ID: <20061102202426.D86AF1E4003@bag.python.org> Author: thomas.heller Date: Thu Nov 2 21:24:26 2006 New Revision: 52593 Modified: python/branches/release25-maint/Doc/lib/libctypes.tex Log: Fix code example by adding a missing import. Fixes #1557890. Backported from trunk. Modified: python/branches/release25-maint/Doc/lib/libctypes.tex ============================================================================== --- python/branches/release25-maint/Doc/lib/libctypes.tex (original) +++ python/branches/release25-maint/Doc/lib/libctypes.tex Thu Nov 2 21:24:26 2006 @@ -1848,7 +1848,7 @@ Here is the wrapping with \code{ctypes}: \begin{quote} -\begin{verbatim}>>> from ctypes import POINTER, WINFUNCTYPE, windll +\begin{verbatim}>>> from ctypes import POINTER, WINFUNCTYPE, windll, WinError >>> from ctypes.wintypes import BOOL, HWND, RECT >>> prototype = WINFUNCTYPE(BOOL, HWND, POINTER(RECT)) >>> paramflags = (1, "hwnd"), (2, "lprect") From python-checkins at python.org Thu Nov 2 22:43:18 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 22:43:18 +0100 (CET) Subject: [Python-checkins] r52594 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061102214318.115751E4003@bag.python.org> Author: brett.cannon Date: Thu Nov 2 22:43:17 2006 New Revision: 52594 Modified: sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Log: Add support in the Import class for handling modules contained within a top-level package. It is untested whether this will work for arbitrarily nested packages. Also no support yet in filesystem importer. Still need to look at what __path__ is used for when found in globals argument to __import__. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Thu Nov 2 22:43:17 2006 @@ -62,10 +62,18 @@ this means that the usual path_hooks/path_importer_cache dance is done for entries in __path__ instead of sys.path (meta_path is still searched regardless of the value of __path__) [PEP 302]. +* Only meta_path entries have the 'path' argument for find_module and + load_module set. path_importer_cache entries (which covers sys.path and + entries for __path__ for package) do not have 'path' set to anything (but + still have the full name of the module passed in) + [pep 302 and introspection(Python/import.c:1278)]. Implementing ------------ #. Importing module within a package. + + One level deep. + + Arbitrarily deep. + + Search __path__ and not sys.path. #. Importing within package. + Root package already imported. + Root package not imported yet. @@ -533,7 +541,7 @@ handlers = ExtensionFileHandler(), PyPycHandler() self.default_path_hook = FileSystemFactory(*handlers) - def search_meta_path(self, name): + def search_meta_path(self, name, path=None): """Check the importers on sys.meta_path for a loader along with the extended meta path sequence stored within this instance. @@ -542,17 +550,17 @@ """ for entry in (tuple(sys.meta_path) + self.extended_meta_path): - loader = entry.find_module(name) + loader = entry.find_module(name, path) if loader: return loader else: - raise ImportError("%s not found on meta path" % name) + raise ImportError("%s not found on sys.meta_path" % name) def sys_path_importer(self, path_entry): - """Return the importer for the entry on sys.path. + """Return the importer for the specified path. - If an entry on sys.path has None stored in sys.path_importer_cache - then use the default path hook. + If None is stored in sys.path_importer_cache then use the default path + hook. """ try: @@ -590,9 +598,14 @@ sys.path_importer_cache[path_entry] = None raise ImportError("no importer found for %s" % path_entry) - def search_sys_path(self, name): - """Check sys.path for the module and return a loader if found.""" - for entry in sys.path: + def search_std_path(self, name, path=None): + """Check sys.path or 'path' (depending if 'path' is set) for the + named module and return its loader.""" + if path: + search_paths = path + else: + search_paths = sys.path + for entry in search_paths: try: importer = self.sys_path_importer(entry) except ImportError: @@ -603,7 +616,7 @@ else: raise ImportError("%s not found on sys.path" % name) - def import_(self, name): + def import_(self, name, path=None): """Import a module.""" try: # Attempt to get a cached version of the module from sys.modules. @@ -612,14 +625,14 @@ pass try: # Attempt to find a loader on sys.meta_path. - loader = self.search_meta_path(name) + loader = self.search_meta_path(name, path) except ImportError: # sys.meta_path search failed. Attempt to find a loader on # sys.path. If this fails then module cannot be found. - loader = self.search_sys_path(name) + loader = self.search_std_path(name, path) # A loader was found. It is the loader's responsibility to have put an # entry in sys.modules. - return loader.load_module(name) + return loader.load_module(name, path) def __call__(self, name, globals={}, locals={}, fromlist=[], level=-1): """Import a module after resolving relative/absolute import issues. @@ -646,9 +659,28 @@ # XXX Check for a relative import; if it is one make it absolute to try to import that, # otherwise import as a top-level module. - # XXX Import submodules; short-circuits search if module is already - # in sys.modules. - module = self.import_(name) + # Import the module (importing its parent modules first). + name_parts = name.split('.') + current_name_parts = [] + parent_module = None + path_list = None + # XXX Any perk to being optimistic and assuming parent modules were + # imported already, and thus search in the reverse order for the first module + # to be found? + for name_part in name_parts: + current_name_parts.append(name_part) + # Recreating name every time around the loop is sub-optimal, but + # does away with base case of a non-dotted name. + current_name = ".".join(current_name_parts) + if parent_module: + try: + path_list = parent_module.__path__ + except AttributeError: + pass + module = self.import_(current_name, path_list) + if parent_module: + setattr(parent_module, current_name, module) + parent_module = module # When fromlist is not specified, return the root module (i.e., module # up to first dot). if not fromlist: @@ -656,4 +688,4 @@ # When fromlist is not empty, return the actual module specified in # the import. else: - return module \ No newline at end of file + return sys.modules[name] \ No newline at end of file Modified: sandbox/trunk/import_in_py/mock_importer.py ============================================================================== --- sandbox/trunk/import_in_py/mock_importer.py (original) +++ sandbox/trunk/import_in_py/mock_importer.py Thu Nov 2 22:43:17 2006 @@ -215,8 +215,12 @@ """Mock importer that always succeed by returning 'self'.""" module = 42 + + def __init__(self): + self.path_entries = [] def __call__(self, path_entry): + self.path_entries.append(path_entry) return self def find_module(self, fullname, path=None): Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Thu Nov 2 22:43:17 2006 @@ -540,7 +540,7 @@ sys.path_hooks = self.old_path_hooks sys.path_importer_cache = self.old_path_importer_cache - def clear_sys_module(*modules): + def clear_sys_modules(*modules): for module in modules: try: del sys.modules[module] @@ -564,7 +564,12 @@ module = self.importer.import_('') del sys.modules[test_module] self.failUnlessEqual(module, test_module) - + + def test_parent_missing(self): + # An import should fail if a parent module cannot be found. + sys.modules['a.b'] = 'a.b' + self.failUnlessRaises(ImportError, self.importer, 'a.b') + def test_empty_fromlist(self): # An empty fromlist means that the root module is returned. # XXX @@ -583,7 +588,7 @@ def test_search_meta_path(self): # Test search method of sys.meta_path. # Should raise ImportError on error. - self.clear_sys_module('sys') + self.clear_sys_modules('sys') self.failUnlessRaises(ImportError, self.importer.search_meta_path, 'sys') # Verify call order. @@ -598,7 +603,7 @@ def test_extended_meta_path(self): # Default meta_path entries set during initialization should be # queried after sys.meta_path. - self.clear_sys_module('sys') + self.clear_sys_modules('sys') pass_importer = mock_importer.PassImporter() sys.meta_path = [pass_importer] succeed_importer = mock_importer.SucceedImporter() @@ -607,57 +612,78 @@ for meta_importer in (pass_importer, succeed_importer): self.failUnlessEqual(meta_importer.find_request, ('sys', None)) self.failUnless(module is mock_importer.SucceedImporter.module) + + def test_parent_path(self): + # If a parent module has __path__ defined it should be passed as an + # argument during importing. + test_path = [''] + pkg_module = imp.new_module('_test_pkg') + pkg_module.__path__ = test_path + sys.modules['_test_pkg'] = pkg_module + succeed_importer = mock_importer.SucceedImporter() + sys.meta_path.append(succeed_importer) + module_name = '_test_pkg.module' + lookup_args = (module_name, test_path) + self.importer(module_name) + self.failUnless(module_name in sys.modules) + self.failUnlessEqual(succeed_importer.find_request, lookup_args) + self.failUnlessEqual(succeed_importer.load_request, lookup_args) -class ImportSysPathTests(ImportHelper): +class ImportStdPathTests(ImportHelper): """Test sys.path usage.""" def test_default_importer_factory(self): # Make sure that the object passed in during initialization is used # when sys.path_importer_cache has a value of None. - self.clear_sys_module('sys') + module_name = '' + self.clear_sys_modules(module_name) succeed_importer = mock_importer.SucceedImporter() importer_ = importer.Import(succeed_importer, tuple()) sys.meta_path = [] sys.path = [''] sys.path_importer_cache[''] = None - module = importer_.import_('sys') - self.failUnlessEqual(succeed_importer.find_request, ('sys', None)) + module = importer_.import_(module_name) + self.failUnlessEqual(succeed_importer.find_request, + (module_name, None)) self.failUnless(module is mock_importer.SucceedImporter.module) - def test_search_sys_path(self): + def test_search_std_path(self): # Test sys.path searching for a loader. - self.clear_sys_module('sys') + module_name = '' + self.clear_sys_modules(module_name) importer_ = importer.Import(extended_meta_path=()) sys.path = [] sys_path = (mock_importer.PassImporter.set_on_sys_path(), mock_importer.SucceedImporter.set_on_sys_path()) - module = importer_.import_('token') + module = importer_.import_(module_name) for entry in sys_path: - self.failUnlessEqual(entry.find_request, ('token', None)) + self.failUnlessEqual(entry.find_request, (module_name, None)) self.failUnless(module is mock_importer.SucceedImporter.module) def test_importer_cache_preexisting(self): # A pre-existing importer should be returned if it exists in # sys.path_importer_cache. - self.clear_sys_module('sys') + module_name = '' + self.clear_sys_modules(module_name) sys.path = [] succeed_importer = mock_importer.SucceedImporter.set_on_sys_path() - loader = self.importer.search_sys_path('sys') + loader = self.importer.search_std_path(module_name) self.failUnless(loader is succeed_importer) def test_importer_cache_from_path_hooks(self): # If an entry does not exist for a sys.path entry in the importer cache # then sys.path_hooks should be searched and if one is found then cache # it. - self.clear_sys_module('sys') + module_name = '' + self.clear_sys_modules(module_name) path_entry = '' succeed_importer = mock_importer.SucceedImporter() sys.path = [path_entry] sys.path_importer_cache.clear() sys.path_hooks = [succeed_importer] - loader = self.importer.search_sys_path('sys') + loader = self.importer.search_std_path(module_name) self.failUnless(loader is succeed_importer) self.failUnless(sys.path_importer_cache[path_entry] is succeed_importer) @@ -665,14 +691,34 @@ def test_importer_cache_no_path_hooks(self): # If an entry does not exist for a sys.path entry in the importer cache # and sys.path_hooks has nothing for the entry, None should be set. - self.clear_sys_module('sys') + module_name = '' + self.clear_sys_modules(module_name) path_entry = '' sys.path = [path_entry] sys.path_hooks = [] sys.path_importer_cache.clear() - self.failUnlessRaises(ImportError, self.importer.search_sys_path, 'sys') + self.failUnlessRaises(ImportError, self.importer.search_std_path, + module_name) self.failUnless(sys.path_importer_cache[path_entry] is None) + def test_searching_package_path(self): + # If importing in a package then search path is the package's __path__ + # value; otherwise it is sys.path. + succeed_importer = mock_importer.SucceedImporter() + sys.path_hooks.append(succeed_importer) + search_paths = ['test path'] + module_name = '.' + loader = self.importer.search_std_path(module_name, search_paths) + self.failUnless(loader is succeed_importer) + self.failUnless(search_paths[0] in succeed_importer.path_entries) + self.failUnlessEqual(succeed_importer.find_request, + (module_name, None)) + self.clear_sys_modules(module_name) + del sys.path_importer_cache[search_paths[0]] + succeed_importer.path_entries = [] + self.importer.import_(module_name, search_paths) + self.failUnless(search_paths[0] in succeed_importer.path_entries) + class IntegrationTests(unittest.TestCase): @@ -693,7 +739,7 @@ # built-in, frozen, extension, .pyc, and .py files being imported if # desired. sys.path_importer_cache = dict((entry, None) for entry in sys.path) - self.clear_sys_module('sys', '__hello__', 'time', 'token') + self.clear_sys_modules('sys', '__hello__', 'time', 'token') # Restore sys.path for 'time' import. sys.path = self.old_sys_path import_ = importer.Import() From python-checkins at python.org Thu Nov 2 23:27:32 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 23:27:32 +0100 (CET) Subject: [Python-checkins] r52595 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061102222732.15F0F1E4003@bag.python.org> Author: brett.cannon Date: Thu Nov 2 23:27:31 2006 New Revision: 52595 Modified: sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Log: Fix bug where __file__, __path__, and __loader__ were not being set early enough by the filesystem handler (must be before any code is executed). Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Thu Nov 2 23:27:31 2006 @@ -314,13 +314,12 @@ return sys.modules[fullname] except KeyError: try: - module = self.handler.handle_code(self, fullname, self.file_path) + module = self.handler.handle_code(self, fullname, + self.file_path, self.package) except: if fullname in sys.modules: del sys.modules[fullname] raise - if self.package is not None: - module.__path__ = [self.package] return module def mod_time(self, path): @@ -413,7 +412,7 @@ data += marshal.dumps(bytecode) return data - def handle_code(self, loader, mod_name, path): + def handle_code(self, loader, mod_name, path, package=None): """Handle creating a new module object that is initialized from Python bytecode or source. @@ -444,7 +443,12 @@ source_timstamp = None bytecode_path = None module = self.new_module(mod_name) + # __file__, __path__, and __loader__ *must* be set on the module before + # any code is executed by the import. module.__loader__ = loader + module.__file__ = path + if package is not None: + module.__path__ = [package] sys.modules[mod_name] = module base_path, type_ = loader.split_path(path) if type_ in self.bytecode_handles: Modified: sandbox/trunk/import_in_py/mock_importer.py ============================================================================== --- sandbox/trunk/import_in_py/mock_importer.py (original) +++ sandbox/trunk/import_in_py/mock_importer.py Thu Nov 2 23:27:31 2006 @@ -18,7 +18,7 @@ def __init__(self, *handles): self.handles = handles - def handle_code(self, loader, mod_name, path): + def handle_code(self, loader, mod_name, path, package=None): """Mock implementation of a handler. An object that can have arbitrary attributes attached to it must be @@ -28,6 +28,7 @@ self.loader = loader self.module_name = mod_name self.path = path + self.package = package sys.modules[mod_name] = self return self @@ -56,6 +57,8 @@ self.py_ext = "source" if py_exists else None self.pyc_ext = "bytecode" if pyc_exists else None self.base_path = "base path" + self.py_path = (self.base_path, self.py_ext) + self.pyc_path = (self.base_path, self.pyc_ext) self.modification_time = 1 # Needed for read_data on source path. self.source = "test_attr = None" @@ -87,12 +90,10 @@ return handler(py_ext, pyc_ext) def _handle_py(self, handler): - return handler.handle_code(self, self.module_name, - (self.base_path, self.py_ext)) + return handler.handle_code(self, self.module_name, self.py_path) def _handle_pyc(self, handler): - return handler.handle_code(self, self.module_name, - (self.base_path, self.pyc_ext)) + return handler.handle_code(self, self.module_name, self.pyc_path) def _verify_module(self, module, test_metadata=True): if not hasattr(module, 'test_attr'): Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Thu Nov 2 23:27:31 2006 @@ -299,8 +299,7 @@ self.failUnlessEqual(module.loader, pkg_loader) self.failUnlessEqual(module.path, self.pkg_init_path) self.failUnlessEqual(module.module_name, self.pkg_name) - self.failUnless(hasattr(module, '__path__')) - self.failUnlessEqual(module.__path__, [self.pkg_path]) + self.failUnlessEqual(module.package, self.pkg_path) class FileSystemLoaderWhiteBoxTests(FileSystemLoaderMockEnv): From python-checkins at python.org Thu Nov 2 23:41:26 2006 From: python-checkins at python.org (brett.cannon) Date: Thu, 2 Nov 2006 23:41:26 +0100 (CET) Subject: [Python-checkins] r52596 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061102224126.90E091E4003@bag.python.org> Author: brett.cannon Date: Thu Nov 2 23:41:25 2006 New Revision: 52596 Modified: sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Log: Add tests for setting __path__ by the py/pyc handler using new method parameter lists. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Thu Nov 2 23:41:25 2006 @@ -21,6 +21,9 @@ * Raise ImportError when load_module() fails to load a module without raising an exception. * Is module returned by load_module() actually used for anything? + + Can always just return from sys.modules. +* PEP 302 does not mention if __name__ must be set before any code is executed + or not. Package notes @@ -74,13 +77,12 @@ + One level deep. + Arbitrarily deep. + Search __path__ and not sys.path. -#. Importing within package. - + Root package already imported. - + Root package not imported yet. + + Parent modules not already imported. #. fromlist semantics. #. Relative imports. + Py3K semantics. + 2.x semantics. + + From within a package's __init__ module. Things to be exposed at the Python level Modified: sandbox/trunk/import_in_py/mock_importer.py ============================================================================== --- sandbox/trunk/import_in_py/mock_importer.py (original) +++ sandbox/trunk/import_in_py/mock_importer.py Thu Nov 2 23:41:25 2006 @@ -89,12 +89,18 @@ pyc_ext = tuple() return handler(py_ext, pyc_ext) - def _handle_py(self, handler): - return handler.handle_code(self, self.module_name, self.py_path) - - def _handle_pyc(self, handler): - return handler.handle_code(self, self.module_name, self.pyc_path) - + def _handle_py(self, handler, package=None): + args = [self, self.module_name, self.py_path] + if package is not None: + args.append(package) + return handler.handle_code(*args) + + def _handle_pyc(self, handler, package=None): + args = [self, self.module_name, self.pyc_path] + if package is not None: + args.append(package) + return handler.handle_code(*args) + def _verify_module(self, module, test_metadata=True): if not hasattr(module, 'test_attr'): raise test_support.TestFailed("test_attr attribute missing") Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Thu Nov 2 23:41:25 2006 @@ -437,6 +437,7 @@ """Test PyPycHandler.handle_code().""" def test_good_pyc_w_py(self): + # Test using a .pyc file that is valid and a .py file exists. loader = mock_importer.MockPyPycLoader.setup() handler = loader._create_handler(importer.PyPycHandler) module = loader._handle_pyc(handler) @@ -463,6 +464,7 @@ pass def test_py_no_pyc(self): + # Test importing a .py file where no .pyc path is available. loader = mock_importer.MockPyPycLoader.setup(pyc_exists=False) handler = loader._create_handler(importer.PyPycHandler) module = loader._handle_py(handler) @@ -472,11 +474,21 @@ self.failUnlessEqual(loader.log.count('read_data'), 1) def test_py_w_pyc(self): + # Test importing a .py file and a .pyc path is available. loader = mock_importer.MockPyPycLoader.setup() handler = loader._create_handler(importer.PyPycHandler) module = loader._handle_py(handler) loader._verify_module(module) self.failUnless('write_data' in loader.log) + + def test_package_init(self): + # Handling a package should set __path__ properly. + loader = mock_importer.MockPyPycLoader.setup(pyc_exists=False) + handler = loader._create_handler(importer.PyPycHandler) + pkg_path = 'pkg path' + module = loader._handle_py(handler, pkg_path) + loader._verify_module(module) + self.failUnlessEqual(module.__path__, [pkg_path]) class ExtensionHandlerTests(unittest.TestCase): From python-checkins at python.org Fri Nov 3 00:18:04 2006 From: python-checkins at python.org (brett.cannon) Date: Fri, 3 Nov 2006 00:18:04 +0100 (CET) Subject: [Python-checkins] r52597 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061102231804.741C11E4003@bag.python.org> Author: brett.cannon Date: Fri Nov 3 00:18:03 2006 New Revision: 52597 Modified: sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Log: Complete implementing importing of a module from a top-level package. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Fri Nov 3 00:18:03 2006 @@ -71,10 +71,9 @@ still have the full name of the module passed in) [pep 302 and introspection(Python/import.c:1278)]. -Implementing ------------- +XXX Need to Implement +--------------------- #. Importing module within a package. - + One level deep. + Arbitrarily deep. + Search __path__ and not sys.path. + Parent modules not already imported. @@ -685,7 +684,7 @@ pass module = self.import_(current_name, path_list) if parent_module: - setattr(parent_module, current_name, module) + setattr(parent_module, name_part, module) parent_module = module # When fromlist is not specified, return the root module (i.e., module # up to first dot). Modified: sandbox/trunk/import_in_py/mock_importer.py ============================================================================== --- sandbox/trunk/import_in_py/mock_importer.py (original) +++ sandbox/trunk/import_in_py/mock_importer.py Fri Nov 3 00:18:03 2006 @@ -10,6 +10,18 @@ return method(self, *args, **kwargs) return log_and_call +class MockModule(object): + + """A mock module.""" + + def __init__(self, name=None, file_path=None, pkg_list=None): + if name is not None: + self.__name__ = name + if file_path is not None: + self.__file__ = file_path + if pkg_list is not None: + self.__path__ = pkg_list + class MockHandler(object): @@ -220,11 +232,10 @@ class SucceedImporter(object): """Mock importer that always succeed by returning 'self'.""" - - module = 42 def __init__(self): self.path_entries = [] + self.loaded_modules = [] def __call__(self, path_entry): self.path_entries.append(path_entry) @@ -236,8 +247,10 @@ def load_module(self, fullname, path=None): self.load_request = fullname, path - sys.modules[fullname] = self.module - return self.module + module = MockModule(fullname, '', path) + self.loaded_modules.append(module) + sys.modules[fullname] = module + return module @classmethod def set_on_sys_path(cls): Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Fri Nov 3 00:18:03 2006 @@ -580,6 +580,38 @@ # An import should fail if a parent module cannot be found. sys.modules['a.b'] = 'a.b' self.failUnlessRaises(ImportError, self.importer, 'a.b') + + def test_parent_imported(self): + # If a parent module is already imported, importing a child module + # should work (along with setting the attribute on the parent for the + # child module). + succeed_importer = mock_importer.SucceedImporter() + sys.meta_path.append(succeed_importer) + parent_name = '' + parent_module = mock_importer.MockModule(parent_name) + sys.modules[parent_name] = parent_module + child_name = 'module' + full_child_name = parent_name + '.' + child_name + self.importer(full_child_name) + self.failUnless(hasattr(parent_module, child_name)) + self.failUnlessEqual(getattr(parent_module, child_name), + sys.modules[full_child_name]) + + def test_parent_not_imported(self): + # If a parent module is not imported yet, it should be imported. + # The attribute on the parent module for the child module should also + # be set. + succeed_importer = mock_importer.SucceedImporter() + sys.meta_path.append(succeed_importer) + parent_name = '' + child_name = '' + full_child_name = '.'.join([parent_name, child_name]) + self.importer(full_child_name) + self.failUnless(parent_name in sys.modules) + self.failUnless(full_child_name in sys.modules) + self.failUnless(hasattr(sys.modules[parent_name], child_name)) + self.failUnlessEqual(getattr(sys.modules[parent_name], child_name), + sys.modules[full_child_name]) def test_empty_fromlist(self): # An empty fromlist means that the root module is returned. @@ -622,7 +654,7 @@ module = importer_.import_('sys') for meta_importer in (pass_importer, succeed_importer): self.failUnlessEqual(meta_importer.find_request, ('sys', None)) - self.failUnless(module is mock_importer.SucceedImporter.module) + self.failUnless(module in succeed_importer.loaded_modules) def test_parent_path(self): # If a parent module has __path__ defined it should be passed as an @@ -658,7 +690,7 @@ module = importer_.import_(module_name) self.failUnlessEqual(succeed_importer.find_request, (module_name, None)) - self.failUnless(module is mock_importer.SucceedImporter.module) + self.failUnless(module in succeed_importer.loaded_modules) def test_search_std_path(self): # Test sys.path searching for a loader. @@ -666,12 +698,13 @@ self.clear_sys_modules(module_name) importer_ = importer.Import(extended_meta_path=()) sys.path = [] - sys_path = (mock_importer.PassImporter.set_on_sys_path(), - mock_importer.SucceedImporter.set_on_sys_path()) + pass_importer = mock_importer.PassImporter.set_on_sys_path() + succeed_importer = mock_importer.SucceedImporter.set_on_sys_path() + sys_path = (pass_importer, succeed_importer) module = importer_.import_(module_name) for entry in sys_path: self.failUnlessEqual(entry.find_request, (module_name, None)) - self.failUnless(module is mock_importer.SucceedImporter.module) + self.failUnless(module in succeed_importer.loaded_modules) def test_importer_cache_preexisting(self): # A pre-existing importer should be returned if it exists in From python-checkins at python.org Fri Nov 3 03:32:48 2006 From: python-checkins at python.org (tim.peters) Date: Fri, 3 Nov 2006 03:32:48 +0100 (CET) Subject: [Python-checkins] r52598 - in python/trunk/Lib: smtplib.py test/test_MimeWriter.py test/test___future__.py test/test_bufio.py test/test_codecs.py test/test_grammar.py test/test_mailbox.py test/test_math.py test/test_mmap.py test/test_poll.py test/test_scope.py test/test_structmembers.py Message-ID: <20061103023248.BC5EF1E4003@bag.python.org> Author: tim.peters Date: Fri Nov 3 03:32:46 2006 New Revision: 52598 Modified: python/trunk/Lib/smtplib.py python/trunk/Lib/test/test_MimeWriter.py python/trunk/Lib/test/test___future__.py python/trunk/Lib/test/test_bufio.py python/trunk/Lib/test/test_codecs.py python/trunk/Lib/test/test_grammar.py python/trunk/Lib/test/test_mailbox.py python/trunk/Lib/test/test_math.py python/trunk/Lib/test/test_mmap.py python/trunk/Lib/test/test_poll.py python/trunk/Lib/test/test_scope.py python/trunk/Lib/test/test_structmembers.py Log: Whitespace normalization. Modified: python/trunk/Lib/smtplib.py ============================================================================== --- python/trunk/Lib/smtplib.py (original) +++ python/trunk/Lib/smtplib.py Fri Nov 3 03:32:46 2006 @@ -729,7 +729,7 @@ support). If host is not specified, '' (the local host) is used. If port is omitted, the standard SMTP-over-SSL port (465) is used. keyfile and certfile are also optional - they can contain a PEM formatted private key and - certificate chain file for the SSL connection. + certificate chain file for the SSL connection. """ def __init__(self, host = '', port = 0, local_hostname = None, keyfile = None, certfile = None): Modified: python/trunk/Lib/test/test_MimeWriter.py ============================================================================== --- python/trunk/Lib/test/test_MimeWriter.py (original) +++ python/trunk/Lib/test/test_MimeWriter.py Fri Nov 3 03:32:46 2006 @@ -192,7 +192,7 @@ ''' class MimewriterTest(unittest.TestCase): - + def test(self): buf = StringIO.StringIO() Modified: python/trunk/Lib/test/test___future__.py ============================================================================== --- python/trunk/Lib/test/test___future__.py (original) +++ python/trunk/Lib/test/test___future__.py Fri Nov 3 03:32:46 2006 @@ -50,7 +50,7 @@ check(mandatory, "mandatory") a(optional < mandatory, "optional not less than mandatory, and mandatory not None") - + a(hasattr(value, "compiler_flag"), "feature is missing a .compiler_flag attr") a(isinstance(getattr(value, "compiler_flag"), int), Modified: python/trunk/Lib/test/test_bufio.py ============================================================================== --- python/trunk/Lib/test/test_bufio.py (original) +++ python/trunk/Lib/test/test_bufio.py Fri Nov 3 03:32:46 2006 @@ -36,7 +36,7 @@ os.unlink(test_support.TESTFN) except: pass - + def drive_one(self, pattern): for length in lengths: # Repeat string 'pattern' as often as needed to reach total length Modified: python/trunk/Lib/test/test_codecs.py ============================================================================== --- python/trunk/Lib/test/test_codecs.py (original) +++ python/trunk/Lib/test/test_codecs.py Fri Nov 3 03:32:46 2006 @@ -911,7 +911,7 @@ self.assertEquals(f.readlines(), [u'\ud55c\n', u'\uae00']) class EncodedFileTest(unittest.TestCase): - + def test_basic(self): f = StringIO.StringIO('\xed\x95\x9c\n\xea\xb8\x80') ef = codecs.EncodedFile(f, 'utf-16-le', 'utf-8') @@ -1172,7 +1172,7 @@ decoder = codecs.getincrementaldecoder(encoding)("ignore") decodedresult = u"".join(decoder.decode(c) for c in encodedresult) self.assertEqual(decodedresult, s, "%r != %r (encoding=%r)" % (decodedresult, s, encoding)) - + encodedresult = "".join(cencoder.encode(c) for c in s) cdecoder = _testcapi.codec_incrementaldecoder(encoding, "ignore") decodedresult = u"".join(cdecoder.decode(c) for c in encodedresult) Modified: python/trunk/Lib/test/test_grammar.py ============================================================================== --- python/trunk/Lib/test/test_grammar.py (original) +++ python/trunk/Lib/test/test_grammar.py Fri Nov 3 03:32:46 2006 @@ -123,10 +123,10 @@ # single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE # XXX can't test in a script -- this rule is only used when interactive - + # file_input: (NEWLINE | stmt)* ENDMARKER # Being tested as this very moment this very module - + # expr_input: testlist NEWLINE # XXX Hard to test -- used only in calls to input() @@ -329,7 +329,7 @@ # which is not available in unittest. save_stdout = sys.stdout sys.stdout = StringIO.StringIO() - + print 1, 2, 3 print 1, 2, 3, print @@ -563,7 +563,7 @@ elif 0: pass elif 0: pass else: pass - + def testWhile(self): # 'while' test ':' suite ['else' ':' suite] while 0: pass @@ -696,7 +696,7 @@ def testSelectors(self): ### trailer: '(' [testlist] ')' | '[' subscript ']' | '.' NAME ### subscript: expr | [expr] ':' [expr] - + import sys, time c = sys.path[0] x = time.time() Modified: python/trunk/Lib/test/test_mailbox.py ============================================================================== --- python/trunk/Lib/test/test_mailbox.py (original) +++ python/trunk/Lib/test/test_mailbox.py Fri Nov 3 03:32:46 2006 @@ -754,15 +754,15 @@ key1 = self._box.add(msg) self._box.flush() self._box.close() - + self._box = self._factory(self._path) self._box.lock() key2 = self._box.add(msg) self._box.flush() self.assert_(self._box._locked) self._box.close() - - + + class TestMbox(_TestMboxMMDF): Modified: python/trunk/Lib/test/test_math.py ============================================================================== --- python/trunk/Lib/test/test_math.py (original) +++ python/trunk/Lib/test/test_math.py Fri Nov 3 03:32:46 2006 @@ -152,7 +152,7 @@ testmodf('modf(1.5)', math.modf(1.5), (0.5, 1.0)) testmodf('modf(-1.5)', math.modf(-1.5), (-0.5, -1.0)) - + def testPow(self): self.assertRaises(TypeError, math.pow) self.ftest('pow(0,1)', math.pow(0,1), 0) Modified: python/trunk/Lib/test/test_mmap.py ============================================================================== --- python/trunk/Lib/test/test_mmap.py (original) +++ python/trunk/Lib/test/test_mmap.py Fri Nov 3 03:32:46 2006 @@ -218,7 +218,7 @@ # Ensuring copy-on-write maps cannot be resized self.assertRaises(TypeError, m.resize, 2*mapsize) del m, f - + # Ensuring invalid access parameter raises exception f = open(TESTFN, "r+b") self.assertRaises(ValueError, mmap.mmap, f.fileno(), mapsize, access=4) Modified: python/trunk/Lib/test/test_poll.py ============================================================================== --- python/trunk/Lib/test/test_poll.py (original) +++ python/trunk/Lib/test/test_poll.py Fri Nov 3 03:32:46 2006 @@ -21,7 +21,7 @@ def test_poll1(self): # Basic functional test of poll object # Create a bunch of pipe and test that poll works with them. - + p = select.poll() NUM_PIPES = 12 Modified: python/trunk/Lib/test/test_scope.py ============================================================================== --- python/trunk/Lib/test/test_scope.py (original) +++ python/trunk/Lib/test/test_scope.py Fri Nov 3 03:32:46 2006 @@ -8,7 +8,7 @@ class ScopeTests(unittest.TestCase): def testSimpleNesting(self): - + def make_adder(x): def adder(y): return x + y Modified: python/trunk/Lib/test/test_structmembers.py ============================================================================== --- python/trunk/Lib/test/test_structmembers.py (original) +++ python/trunk/Lib/test/test_structmembers.py Fri Nov 3 03:32:46 2006 @@ -43,11 +43,11 @@ def has_warned(self): self.assertEqual(test.test_warnings.msg.category, exceptions.RuntimeWarning.__name__) - + def test_byte_max(self): ts.T_BYTE=CHAR_MAX+1 self.has_warned() - + def test_byte_min(self): ts.T_BYTE=CHAR_MIN-1 self.has_warned() @@ -68,7 +68,7 @@ ts.T_USHORT=USHRT_MAX+1 self.has_warned() - + def test_main(verbose=None): test_support.run_unittest( From buildbot at python.org Fri Nov 3 04:25:46 2006 From: buildbot at python.org (buildbot at python.org) Date: Fri, 03 Nov 2006 03:25:46 +0000 Subject: [Python-checkins] buildbot warnings in alpha Tru64 5.1 trunk Message-ID: <20061103032546.E32D91E4006@bag.python.org> The Buildbot has detected a new failure of alpha Tru64 5.1 trunk. Full details are available at: http://www.python.org/dev/buildbot/all/alpha%2520Tru64%25205.1%2520trunk/builds/1261 Buildbot URL: http://www.python.org/dev/buildbot/all/ Build Reason: Build Source Stamp: [branch trunk] HEAD Blamelist: thomas.heller,tim.peters Build had warnings: warnings test Excerpt from the test logfile: 1 test failed: test_socket ====================================================================== FAIL: testInterruptedTimeout (test.test_socket.TCPTimeoutTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "/net/ringneck/scratch1/nnorwitz/buildbot/trunk.norwitz-tru64/build/Lib/test/test_socket.py", line 847, in testInterruptedTimeout self.fail("got Alarm in wrong place") AssertionError: got Alarm in wrong place sincerely, -The Buildbot From python-checkins at python.org Sat Nov 4 01:25:51 2006 From: python-checkins at python.org (brett.cannon) Date: Sat, 4 Nov 2006 01:25:51 +0100 (CET) Subject: [Python-checkins] r52618 - sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Message-ID: <20061104002551.E97F01E4004@bag.python.org> Author: brett.cannon Date: Sat Nov 4 01:25:51 2006 New Revision: 52618 Modified: sandbox/trunk/import_in_py/importer.py sandbox/trunk/import_in_py/mock_importer.py sandbox/trunk/import_in_py/test_importer.py Log: Fix parameter lists for handler to accept 'package' argument to be used to set __path__. Also add notes on integration tests to write along with some cleanup of some support code. Modified: sandbox/trunk/import_in_py/importer.py ============================================================================== --- sandbox/trunk/import_in_py/importer.py (original) +++ sandbox/trunk/import_in_py/importer.py Sat Nov 4 01:25:51 2006 @@ -318,7 +318,7 @@ module = self.handler.handle_code(self, fullname, self.file_path, self.package) except: - if fullname in sys.modules: + if fullname in sys.modules: del sys.modules[fullname] raise return module @@ -414,8 +414,9 @@ return data def handle_code(self, loader, mod_name, path, package=None): - """Handle creating a new module object that is initialized from Python - bytecode or source. + """Handle creating a new module object for the module mod_name that is + to be initialized with data from 'path' and, if a package, has a + package location of 'package'. The loader needs to implement several methods in order to this handler to be able to load needed data. A key point with some of these methods @@ -439,7 +440,6 @@ written as binary or textual data. """ - # XXX Does not worry about packages. source_path = None source_timstamp = None bytecode_path = None @@ -515,9 +515,12 @@ self.handles = tuple(suffix[0] for suffix in imp.get_suffixes() if suffix[2] == imp.C_EXTENSION) - def handle_code(self, loader, mod_name, extension_path): + def handle_code(self, loader, mod_name, extension_path, package=None): """Import an extension module.""" - return imp.load_dynamic(mod_name, extension_path) + module = imp.load_dynamic(mod_name, extension_path) + if package is not None: + module.__path__ = [package] + return module class Import(object): @@ -693,4 +696,4 @@ # When fromlist is not empty, return the actual module specified in # the import. else: - return sys.modules[name] \ No newline at end of file + return sys.modules[name] Modified: sandbox/trunk/import_in_py/mock_importer.py ============================================================================== --- sandbox/trunk/import_in_py/mock_importer.py (original) +++ sandbox/trunk/import_in_py/mock_importer.py Sat Nov 4 01:25:51 2006 @@ -181,6 +181,7 @@ assert binary # XXX Assert proper magic number # XXX Assert time stamp + # XXX mock module = imp.new_module(self.module_name) code = marshal.loads(data[8:]) exec code in module.__dict__ Modified: sandbox/trunk/import_in_py/test_importer.py ============================================================================== --- sandbox/trunk/import_in_py/test_importer.py (original) +++ sandbox/trunk/import_in_py/test_importer.py Sat Nov 4 01:25:51 2006 @@ -1,4 +1,3 @@ -"""XXX separate medium/large tests into a single test class.""" from __future__ import with_statement import importer @@ -120,7 +119,15 @@ class PyPycFileHelper(unittest.TestCase): - """Base class to help in generating a fresh source and bytecode file.""" + """Base class to help in generating a fresh source and bytecode file. + + XXX refactor: + * better attribute names. + * file names that cannot be imported normally. + * Can easily be used by other classes that also need to generate a package. + * Ditch need for separate methods. + + """ def setUp(self): """Generate the path to a temporary file to test with.""" @@ -399,6 +406,7 @@ pyc = bytecode_file.read() magic, timestamp, bytecode = self.handler.parse_pyc(pyc) code = marshal.loads(bytecode) + # XXX mock module = imp.new_module(self.module) exec code in module.__dict__ self.verify_module(module) @@ -660,6 +668,7 @@ # If a parent module has __path__ defined it should be passed as an # argument during importing. test_path = [''] + # XXX mock pkg_module = imp.new_module('_test_pkg') pkg_module.__path__ = test_path sys.modules['_test_pkg'] = pkg_module @@ -766,46 +775,58 @@ class IntegrationTests(unittest.TestCase): - """Hold tests that involve multiple classes from 'importer' and verify - integration semantics. - - XXX - * file loader <-> py/pyc handler - * file loader <-> extension handler - * file importer -> file loader - * file importer -> file loader <-> py/pyc handler - * Importer -> importer -> loader - + """Tests that verify the default semantics are what is expected. + + Tests should verify that: + * The proper module was returned. + * All expected modules were added to sys.modules. + * All modules imported by the call have the proper attributes. + """ - def XXX_test_default_import(self): - # The default initialization should work with a None entry for every - # sys.path entry in sys.path_importer_cache. It should also lead to - # built-in, frozen, extension, .pyc, and .py files being imported if - # desired. - sys.path_importer_cache = dict((entry, None) for entry in sys.path) - self.clear_sys_modules('sys', '__hello__', 'time', 'token') - # Restore sys.path for 'time' import. - sys.path = self.old_sys_path - import_ = importer.Import() - # Built-ins. - module = import_('sys') - self.failUnlessEqual(module.__name__, 'sys') - self.failUnless(hasattr(sys, 'version')) - # Frozen modules. - try: - sys.stdout = StringIO.StringIO() - module = import_('__hello__') - finally: - sys.stdout = sys.__stdout__ - self.failUnlessEqual(module.__name__, '__hello__') - # Extension modules. - module = import_('time') - self.failUnlessEqual(module.__name__, 'time') - self.failUnless(hasattr(module, 'sleep')) - # .py/.pyc files. - module = import_('token') - self.failUnlessEqual(module.__name__, 'token') - self.failUnless(hasattr(module, 'ISTERMINAL')) + + def setUp(self): + """Create a test environment: + * .py file + * .pyc file + * package + + __init__.py + + .py file + + """ + pass + + def tearDown(self): + """Remove all test files.""" + pass + + def XXX_test_builtin(self): + # Test importing a built-in module. + pass + + def XXX_test_frozen(self): + # Importing a frozen module should work. + pass + + def XXX_test_pyc_w_py(self): + # Should be able to import a .pyc file when a .py is also present. + pass + + def XXX_test_pyc_wo_py(self): + # Importing just a .pyc file (w/ no .py) should be okay. + pass + + def XXX_test_py(self): + # Importing a .py file should work and generate a .pyc file. + pass + + def XXX_test_top_level_package(self): + # Should be able to import a top-level package. + pass + + def XXX_test_package_module(self): + # A module within a top-level package should work with the package not + # already imported. + pass def test_main(): From python-checkins at python.org Sat Nov 4 19:14:07 2006 From: python-checkins at python.org (martin.v.loewis) Date: Sat, 4 Nov 2006 19:14:07 +0100 (CET) Subject: [Python-checkins] r52619 - in python/trunk: Lib/distutils/command/bdist_rpm.py Misc/NEWS Message-ID: <20061104181407.71DB31E4004@bag.python.org> Author: martin.v.loewis Date: Sat Nov 4 19:14:06 2006 New Revision: 52619 Modified: python/trunk/Lib/distutils/command/bdist_rpm.py python/trunk/Misc/NEWS Log: - Patch #1060577: Extract list of RPM files from spec file in bdist_rpm Will backport to 2.5. Modified: python/trunk/Lib/distutils/command/bdist_rpm.py ============================================================================== --- python/trunk/Lib/distutils/command/bdist_rpm.py (original) +++ python/trunk/Lib/distutils/command/bdist_rpm.py Sat Nov 4 19:14:06 2006 @@ -337,37 +337,47 @@ if not self.keep_temp: rpm_cmd.append('--clean') rpm_cmd.append(spec_path) + # Determine the binary rpm names that should be built out of this spec + # file + # Note that some of these may not be really built (if the file + # list is empty) + nvr_string = "%{name}-%{version}-%{release}" + src_rpm = nvr_string + ".src.rpm" + non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm" + q_cmd = r"rpm -q --qf '%s %s\n' --specfile '%s'" % ( + src_rpm, non_src_rpm, spec_path) + + out = os.popen(q_cmd) + binary_rpms = [] + source_rpm = None + while 1: + line = out.readline() + if not line: + break + l = string.split(string.strip(line)) + assert(len(l) == 2) + binary_rpms.append(l[1]) + # The source rpm is named after the first entry in the spec file + if source_rpm is None: + source_rpm = l[0] + + status = out.close() + if status: + raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd)) + self.spawn(rpm_cmd) - # XXX this is a nasty hack -- we really should have a proper way to - # find out the names of the RPM files created; also, this assumes - # that RPM creates exactly one source and one binary RPM. if not self.dry_run: if not self.binary_only: - srpms = glob.glob(os.path.join(rpm_dir['SRPMS'], "*.rpm")) - assert len(srpms) == 1, \ - "unexpected number of SRPM files found: %s" % srpms - dist_file = ('bdist_rpm', 'any', - self._dist_path(srpms[0])) - self.distribution.dist_files.append(dist_file) - self.move_file(srpms[0], self.dist_dir) + srpm = os.path.join(rpm_dir['SRPMS'], source_rpm) + assert(os.path.exists(srpm)) + self.move_file(srpm, self.dist_dir) if not self.source_only: - rpms = glob.glob(os.path.join(rpm_dir['RPMS'], "*/*.rpm")) - debuginfo = glob.glob(os.path.join(rpm_dir['RPMS'], - "*/*debuginfo*.rpm")) - if debuginfo: - rpms.remove(debuginfo[0]) - assert len(rpms) == 1, \ - "unexpected number of RPM files found: %s" % rpms - dist_file = ('bdist_rpm', get_python_version(), - self._dist_path(rpms[0])) - self.distribution.dist_files.append(dist_file) - self.move_file(rpms[0], self.dist_dir) - if debuginfo: - dist_file = ('bdist_rpm', get_python_version(), - self._dist_path(debuginfo[0])) - self.move_file(debuginfo[0], self.dist_dir) + for rpm in binary_rpms: + rpm = os.path.join(rpm_dir['RPMS'], rpm) + if os.path.exists(rpm): + self.move_file(rpm, self.dist_dir) # run() def _dist_path(self, path): Modified: python/trunk/Misc/NEWS ============================================================================== --- python/trunk/Misc/NEWS (original) +++ python/trunk/Misc/NEWS Sat Nov 4 19:14:06 2006 @@ -92,6 +92,9 @@ Library ------- +- Patch #1060577: Extract list of RPM files from spec file in + bdist_rpm + - Bug #1586613: fix zlib and bz2 codecs' incremental en/decoders. - Patch #1583880: fix tarfile's problems with long names and posix/ From python-checkins at python.org Sat Nov 4 19:14:23 2006 From: python-checkins at python.org (martin.v.loewis) Date: Sat, 4 Nov 2006 19:14:23 +0100 (CET) Subject: [Python-checkins] r52620 - in python/branches/release25-maint: Lib/distutils/command/bdist_rpm.py Misc/NEWS Message-ID: <20061104181423.136591E4004@bag.python.org> Author: martin.v.loewis Date: Sat Nov 4 19:14:22 2006 New Revision: 52620 Modified: python/branches/release25-maint/Lib/distutils/command/bdist_rpm.py python/branches/release25-maint/Misc/NEWS Log: Patch #1060577: Extract list of RPM files from spec file in bdist_rpm Modified: python/branches/release25-maint/Lib/distutils/command/bdist_rpm.py ============================================================================== --- python/branches/release25-maint/Lib/distutils/command/bdist_rpm.py (original) +++ python/branches/release25-maint/Lib/distutils/command/bdist_rpm.py Sat Nov 4 19:14:22 2006 @@ -337,37 +337,47 @@ if not self.keep_temp: rpm_cmd.append('--clean') rpm_cmd.append(spec_path) + # Determine the binary rpm names that should be built out of this spec + # file + # Note that some of these may not be really built (if the file + # list is empty) + nvr_string = "%{name}-%{version}-%{release}" + src_rpm = nvr_string + ".src.rpm" + non_src_rpm = "%{arch}/" + nvr_string + ".%{arch}.rpm" + q_cmd = r"rpm -q --qf '%s %s\n' --specfile '%s'" % ( + src_rpm, non_src_rpm, spec_path) + + out = os.popen(q_cmd) + binary_rpms = [] + source_rpm = None + while 1: + line = out.readline() + if not line: + break + l = string.split(string.strip(line)) + assert(len(l) == 2) + binary_rpms.append(l[1]) + # The source rpm is named after the first entry in the spec file + if source_rpm is None: + source_rpm = l[0] + + status = out.close() + if status: + raise DistutilsExecError("Failed to execute: %s" % repr(q_cmd)) + self.spawn(rpm_cmd) - # XXX this is a nasty hack -- we really should have a proper way to - # find out the names of the RPM files created; also, this assumes - # that RPM creates exactly one source and one binary RPM. if not self.dry_run: if not self.binary_only: - srpms = glob.glob(os.path.join(rpm_dir['SRPMS'], "*.rpm")) - assert len(srpms) == 1, \ - "unexpected number of SRPM files found: %s" % srpms - dist_file = ('bdist_rpm', 'any', - self._dist_path(srpms[0])) - self.distribution.dist_files.append(dist_file) - self.move_file(srpms[0], self.dist_dir) + srpm = os.path.join(rpm_dir['SRPMS'], source_rpm) + assert(os.path.exists(srpm)) + self.move_file(srpm, self.dist_dir) if not self.source_only: - rpms = glob.glob(os.path.join(rpm_dir['RPMS'], "*/*.rpm")) - debuginfo = glob.glob(os.path.join(rpm_dir['RPMS'], - "*/*debuginfo*.rpm")) - if debuginfo: - rpms.remove(debuginfo[0]) - assert len(rpms) == 1, \ - "unexpected number of RPM files found: %s" % rpms - dist_file = ('bdist_rpm', get_python_version(), - self._dist_path(rpms[0])) - self.distribution.dist_files.append(dist_file) - self.move_file(rpms[0], self.dist_dir) - if debuginfo: - dist_file = ('bdist_rpm', get_python_version(), - self._dist_path(debuginfo[0])) - self.move_file(debuginfo[0], self.dist_dir) + for rpm in binary_rpms: + rpm = os.path.join(rpm_dir['RPMS'], rpm) + if os.path.exists(rpm): + self.move_file(rpm, self.dist_dir) # run() def _dist_path(self, path): Modified: python/branches/release25-maint/Misc/NEWS ============================================================================== --- python/branches/release25-maint/Misc/NEWS (original) +++ python/branches/release25-maint/Misc/NEWS Sat Nov 4 19:14:22 2006 @@ -93,6 +93,9 @@ Library ------- +- Patch #1060577: Extract list of RPM files from spec file in + bdist_rpm + - Bug #1586613: fix zlib and bz2 codecs' incremental en/decoders. - Patch #1583880: fix tarfile's problems with long names and posix/ From python-checkins at python.org Sat Nov 4 20:25:22 2006 From: python-checkins at python.org (neal.norwitz) Date: Sat, 4 Nov 2006 20:25:22 +0100 (CET) Subject: [Python-checkins] r52621 - in python/trunk: Lib/test/test_grammar.py Misc/NEWS Python/ast.c Message-ID: <20061104192522.EDD621E4004@bag.python.org> Author: neal.norwitz Date: Sat Nov 4 20:25:22 2006 New Revision: 52621 Modified: python/trunk/Lib/test/test_grammar.py python/trunk/Misc/NEWS python/trunk/Python/ast.c Log: Bug #1588287: fix invalid assertion for `1,2` in debug builds. Will backport Modified: python/trunk/Lib/test/test_grammar.py ============================================================================== --- python/trunk/Lib/test/test_grammar.py (original) +++ python/trunk/Lib/test/test_grammar.py Sat Nov 4 20:25:22 2006 @@ -747,6 +747,8 @@ x = `x` x = `1 or 2 or 3` + self.assertEqual(`1,2`, '(1, 2)') + x = x x = 'x' x = 123 Modified: python/trunk/Misc/NEWS ============================================================================== --- python/trunk/Misc/NEWS (original) +++ python/trunk/Misc/NEWS Sat Nov 4 20:25:22 2006 @@ -12,6 +12,8 @@ Core and builtins ----------------- +- Bug #1588287: fix invalid assertion for `1,2` in debug builds. + - Bug #1576657: when setting a KeyError for a tuple key, make sure that the tuple isn't used as the "exception arguments tuple". Modified: python/trunk/Python/ast.c ============================================================================== --- python/trunk/Python/ast.c (original) +++ python/trunk/Python/ast.c Sat Nov 4 20:25:22 2006 @@ -532,6 +532,7 @@ || TYPE(n) == listmaker || TYPE(n) == testlist_gexp || TYPE(n) == testlist_safe + || TYPE(n) == testlist1 ); seq = asdl_seq_new((NCH(n) + 1) / 2, c->c_arena); From python-checkins at python.org Sat Nov 4 20:32:56 2006 From: python-checkins at python.org (neal.norwitz) Date: Sat, 4 Nov 2006 20:32:56 +0100 (CET) Subject: [Python-checkins] r52622 - in python/branches/release25-maint: Lib/test/test_grammar.py Misc/NEWS Python/ast.c Message-ID: <20061104193256.E0F911E4004@bag.python.org> Author: neal.norwitz Date: Sat Nov 4 20:32:54 2006 New Revision: 52622 Modified: python/branches/release25-maint/Lib/test/test_grammar.py python/branches/release25-maint/Misc/NEWS python/branches/release25-maint/Python/ast.c Log: Backport 52621: Bug #1588287: fix invalid assertion for `1,2` in debug builds. Modified: python/branches/release25-maint/Lib/test/test_grammar.py ============================================================================== --- python/branches/release25-maint/Lib/test/test_grammar.py (original) +++ python/branches/release25-maint/Lib/test/test_grammar.py Sat Nov 4 20:32:54 2006 @@ -709,6 +709,7 @@ x = `x` x = `1 or 2 or 3` +x = `1,2` x = x x = 'x' x = 123 Modified: python/branches/release25-maint/Misc/NEWS ============================================================================== --- python/branches/release25-maint/Misc/NEWS (original) +++ python/branches/release25-maint/Misc/NEWS Sat Nov 4 20:32:54 2006 @@ -12,6 +12,8 @@ Core and builtins ----------------- +- Bug #1588287: fix invalid assertion for `1,2` in debug builds. + - Bug #1576657: when setting a KeyError for a tuple key, make sure that the tuple isn't used as the "exception arguments tuple". Modified: python/branches/release25-maint/Python/ast.c ============================================================================== --- python/branches/release25-maint/Python/ast.c (original) +++ python/branches/release25-maint/Python/ast.c Sat Nov 4 20:32:54 2006 @@ -538,6 +538,7 @@ || TYPE(n) == listmaker || TYPE(n) == testlist_gexp || TYPE(n) == testlist_safe + || TYPE(n) == testlist1 ); seq = asdl_seq_new((NCH(n) + 1) / 2, c->c_arena); From buildbot at python.org Sat Nov 4 22:00:00 2006 From: buildbot at python.org (buildbot at python.org) Date: Sat, 04 Nov 2006 21:00:00 +0000 Subject: [Python-checkins] buildbot warnings in x86 Ubuntu edgy (icc) 2.5 Message-ID: <20061104210000.6BA2E1E4004@bag.python.org> The Buildbot has detected a new failure of x86 Ubuntu edgy (icc) 2.5. Full details are available at: http://www.python.org/dev/buildbot/all/x86%2520Ubuntu%2520edgy%2520%2528icc%2529%25202.5/builds/89 Buildbot URL: http://www.python.org/dev/buildbot/all/ Build Reason: Build Source Stamp: [branch branches/release25-maint] HEAD Blamelist: martin.v.loewis,thomas.heller Build had warnings: warnings test Excerpt from the test logfile: 1 test failed: test_timeout sincerely, -The Buildbot From buildbot at python.org Sat Nov 4 23:08:27 2006 From: buildbot at python.org (buildbot at python.org) Date: Sat, 04 Nov 2006 22:08:27 +0000 Subject: [Python-checkins] buildbot warnings in x86 Ubuntu edgy (icc) trunk Message-ID: <20061104220827.9EB071E4004@bag.python.org> The Buildbot has detected a new failure of x86 Ubuntu edgy (icc) trunk. Full details are available at: http://www.python.org/dev/buildbot/all/x86%2520Ubuntu%2520edgy%2520%2528icc%2529%2520trunk/builds/1052 Buildbot URL: http://www.python.org/dev/buildbot/all/ Build Reason: Build Source Stamp: [branch trunk] HEAD Blamelist: neal.norwitz Build had warnings: warnings test Excerpt from the test logfile: Traceback (most recent call last): File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/threading.py", line 460, in __bootstrap self.run() File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/threading.py", line 440, in run self.__target(*self.__args, **self.__kwargs) File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/bsddb/test/test_thread.py", line 281, in readerThread rec = dbutils.DeadlockWrap(c.next, max_retries=10) File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/bsddb/dbutils.py", line 62, in DeadlockWrap return function(*_args, **_kwargs) DBLockDeadlockError: (-30995, 'DB_LOCK_DEADLOCK: Locker killed to resolve a deadlock') Traceback (most recent call last): File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/threading.py", line 460, in __bootstrap self.run() File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/threading.py", line 440, in run self.__target(*self.__args, **self.__kwargs) File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/bsddb/test/test_thread.py", line 281, in readerThread rec = dbutils.DeadlockWrap(c.next, max_retries=10) File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/bsddb/dbutils.py", line 62, in DeadlockWrap return function(*_args, **_kwargs) DBLockDeadlockError: (-30995, 'DB_LOCK_DEADLOCK: Locker killed to resolve a deadlock') 1 test failed: test_timeout ====================================================================== FAIL: testConnectTimeout (test.test_timeout.TimeoutTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/buildbot/Buildbot/trunk.baxter-ubuntu/build/Lib/test/test_timeout.py", line 128, in testConnectTimeout %(_delta, self.fuzz, _timeout)) AssertionError: timeout (2.56139) is more than 2 seconds more than expected (0.001) sincerely, -The Buildbot From buildbot at python.org Sat Nov 4 23:08:53 2006 From: buildbot at python.org (buildbot at python.org) Date: Sat, 04 Nov 2006 22:08:53 +0000 Subject: [Python-checkins] buildbot warnings in hppa Ubuntu dapper trunk Message-ID: <20061104220853.7BE0C1E4004@bag.python.org> The Buildbot has detected a new failure of hppa Ubuntu dapper trunk. Full details are available at: http://www.python.org/dev/buildbot/all/hppa%2520Ubuntu%2520dapper%2520trunk/builds/193 Buildbot URL: http://www.python.org/dev/buildbot/all/ Build Reason: Build Source Stamp: [branch trunk] HEAD Blamelist: neal.norwitz Build had warnings: warnings test Excerpt from the test logfile: sincerely, -The Buildbot From buildbot at python.org Sat Nov 4 23:23:49 2006 From: buildbot at python.org (buildbot at python.org) Date: Sat, 04 Nov 2006 22:23:49 +0000 Subject: [Python-checkins] buildbot warnings in hppa Ubuntu dapper 2.5 Message-ID: <20061104222349.8C3E51E4004@bag.python.org> The Buildbot has detected a new failure of hppa Ubuntu dapper 2.5. Full details are available at: http://www.python.org/dev/buildbot/all/hppa%2520Ubuntu%2520dapper%25202.5/builds/90 Buildbot URL: http://www.python.org/dev/buildbot/all/ Build Reason: Build Source Stamp: [branch branches/release25-maint] HEAD Blamelist: neal.norwitz Build had warnings: warnings test Excerpt from the test logfile: sincerely, -The Buildbot From python-checkins at python.org Sun Nov 5 20:41:03 2006 From: python-checkins at python.org (erik.forsberg) Date: Sun, 5 Nov 2006 20:41:03 +0100 (CET) Subject: [Python-checkins] r52623 - tracker Message-ID: <20061105194103.378B61E4012@bag.python.org> Author: erik.forsberg Date: Sun Nov 5 20:41:02 2006 New Revision: 52623 Added: tracker/ Log: Creating top-level directory for roundup-based python-dev tracker From python-checkins at python.org Sun Nov 5 21:18:32 2006 From: python-checkins at python.org (erik.forsberg) Date: Sun, 5 Nov 2006 21:18:32 +0100 (CET) Subject: [Python-checkins] r52624 - in tracker/vendor: roundup roundup/current Message-ID: <20061105201832.75B531E4007@bag.python.org> Author: erik.forsberg Date: Sun Nov 5 21:18:29 2006 New Revision: 52624 Added: tracker/vendor/ tracker/vendor/roundup/ tracker/vendor/roundup/current/ Log: Creating directory structure for vendor drop of roundup. From python-checkins at python.org Sun Nov 5 21:30:53 2006 From: python-checkins at python.org (erik.forsberg) Date: Sun, 5 Nov 2006 21:30:53 +0100 (CET) Subject: [Python-checkins] r52625 - in tracker/vendor/roundup/current: .cvsignore BUILD.txt CHANGES.txt COPYING.txt ChangeLog MANIFEST.in README.txt cgi-bin cgi-bin/roundup.cgi demo.py detectors detectors/creator_resolution.py detectors/emailauditor.py detectors/newissuecopy.py doc doc/.cvsignore doc/FAQ.txt doc/Makefile doc/ZPL.txt doc/admin_guide.txt doc/announcement.txt doc/customizing.txt doc/debugging.txt doc/default.css doc/design.txt doc/developers.txt doc/features.txt doc/glossary.txt doc/images doc/images/edit.png doc/images/hyperdb.png doc/images/logo-acl-medium.png doc/images/logo-codesourcery-medium.png doc/images/logo-software-carpentry-standard.png doc/images/roundup-1.png doc/images/roundup.png doc/implementation.txt doc/index.txt doc/installation.txt doc/mysql.txt doc/original_overview.html doc/overview.txt doc/postgresql.txt doc/roundup-admin.1 doc/roundup-demo.1 doc/roundup-favicon.ico doc/roundup-mailgw.1 doc/roundup-server.1 doc/roundup-server.ini.example doc/spec.html doc/tracker_templates.txt doc/upgrading.txt doc/user_guide.txt doc/whatsnew-0.7.txt doc/whatsnew-0.8.txt frontends frontends/README.txt frontends/ZRoundup frontends/ZRoundup/.cvsignore frontends/ZRoundup/ZRoundup.py frontends/ZRoundup/__init__.py frontends/ZRoundup/dtml frontends/ZRoundup/dtml/manage_addZRoundupForm.dtml frontends/ZRoundup/icons frontends/ZRoundup/icons/tick_symbol.gif frontends/ZRoundup/refresh.txt locale locale/.cvsignore locale/GNUmakefile locale/de.po locale/en.po locale/es_AR.po locale/fr.po locale/lt.po locale/roundup.pot locale/ru.po locale/zh_CN.po locale/zh_TW.po patches patches/20020205.alternate_auth roundup roundup/.cvsignore roundup/__init__.py roundup/admin.py roundup/backends roundup/backends/.cvsignore roundup/backends/__init__.py roundup/backends/back_anydbm.py roundup/backends/back_metakit.py roundup/backends/back_mysql.py roundup/backends/back_postgresql.py roundup/backends/back_sqlite.py roundup/backends/back_tsearch2.py roundup/backends/blobfiles.py roundup/backends/indexer_common.py roundup/backends/indexer_dbm.py roundup/backends/indexer_rdbms.py roundup/backends/indexer_xapian.py roundup/backends/locking.py roundup/backends/portalocker.py roundup/backends/rdbms_common.py roundup/backends/sessions_dbm.py roundup/backends/sessions_rdbms.py roundup/backends/tsearch2_setup.py roundup/cgi roundup/cgi/.cvsignore roundup/cgi/MultiMapping.py roundup/cgi/PageTemplates roundup/cgi/PageTemplates/.cvsignore roundup/cgi/PageTemplates/Expressions.py roundup/cgi/PageTemplates/GlobalTranslationService.py roundup/cgi/PageTemplates/MultiMapping.py roundup/cgi/PageTemplates/PageTemplate.py roundup/cgi/PageTemplates/PathIterator.py roundup/cgi/PageTemplates/PythonExpr.py roundup/cgi/PageTemplates/README.txt roundup/cgi/PageTemplates/TALES.py roundup/cgi/PageTemplates/__init__.py roundup/cgi/TAL roundup/cgi/TAL/.cvsignore roundup/cgi/TAL/DummyEngine.py roundup/cgi/TAL/HTMLParser.py roundup/cgi/TAL/HTMLTALParser.py roundup/cgi/TAL/README.txt roundup/cgi/TAL/TALDefs.py roundup/cgi/TAL/TALGenerator.py roundup/cgi/TAL/TALInterpreter.py roundup/cgi/TAL/TALParser.py roundup/cgi/TAL/TranslationContext.py roundup/cgi/TAL/XMLParser.py roundup/cgi/TAL/__init__.py roundup/cgi/TAL/markupbase.py roundup/cgi/TAL/talgettext.py roundup/cgi/TranslationService.py roundup/cgi/ZTUtils roundup/cgi/ZTUtils/.cvsignore roundup/cgi/ZTUtils/Batch.py roundup/cgi/ZTUtils/Iterator.py roundup/cgi/ZTUtils/__init__.py roundup/cgi/__init__.py roundup/cgi/accept_language.py roundup/cgi/actions.py roundup/cgi/apache.py roundup/cgi/cgitb.py roundup/cgi/client.py roundup/cgi/exceptions.py roundup/cgi/form_parser.py roundup/cgi/templating.py roundup/cgi/zLOG.py roundup/configuration.py roundup/date.py roundup/exceptions.py roundup/hyperdb.py roundup/i18n.py roundup/init.py roundup/install_util.py roundup/instance.py roundup/mailer.py roundup/mailgw.py roundup/msgfmt.py roundup/password.py roundup/rfc2822.py roundup/roundupdb.py roundup/scripts roundup/scripts/.cvsignore roundup/scripts/__init__.py roundup/scripts/roundup_admin.py roundup/scripts/roundup_demo.py roundup/scripts/roundup_gettext.py roundup/scripts/roundup_mailgw.py roundup/scripts/roundup_server.py roundup/security.py roundup/support.py roundup/token.py roundup/version_check.py run_tests.py scripts scripts/README.txt scripts/add-issue scripts/copy-user.py scripts/hyperdb_example.py scripts/imapServer.py scripts/import_sf.py scripts/roundup-reminder scripts/roundup.rc-debian scripts/schema_diagram.py scripts/server-ctl setup.py templates templates/classic templates/classic/.cvsignore templates/classic/TEMPLATE-INFO.txt templates/classic/detectors templates/classic/detectors/.cvsignore templates/classic/detectors/messagesummary.py templates/classic/detectors/nosyreaction.py templates/classic/detectors/statusauditor.py templates/classic/detectors/userauditor.py templates/classic/extensions templates/classic/extensions/README.txt templates/classic/html templates/classic/html/_generic.calendar.html templates/classic/html/_generic.collision.html templates/classic/html/_generic.help.html templates/classic/html/_generic.index.html templates/classic/html/_generic.item.html templates/classic/html/file.index.html templates/classic/html/file.item.html templates/classic/html/help_controls.js templates/classic/html/home.classlist.html templates/classic/html/home.html templates/classic/html/issue.index.html templates/classic/html/issue.item.html templates/classic/html/issue.search.html templates/classic/html/keyword.item.html templates/classic/html/msg.index.html templates/classic/html/msg.item.html templates/classic/html/page.html templates/classic/html/query.edit.html templates/classic/html/query.item.html templates/classic/html/style.css templates/classic/html/user.forgotten.html templates/classic/html/user.index.html templates/classic/html/user.item.html templates/classic/html/user.register.html templates/classic/html/user.rego_progress.html templates/classic/initial_data.py templates/classic/schema.py templates/minimal templates/minimal/.cvsignore templates/minimal/TEMPLATE-INFO.txt templates/minimal/detectors templates/minimal/detectors/.cvsignore templates/minimal/detectors/userauditor.py templates/minimal/extensions templates/minimal/extensions/README.txt templates/minimal/html templates/minimal/html/_generic.calendar.html templates/minimal/html/_generic.collision.html templates/minimal/html/_generic.help.html templates/minimal/html/_generic.index.html templates/minimal/html/_generic.item.html templates/minimal/html/help_controls.js templates/minimal/html/home.classlist.html templates/minimal/html/home.html templates/minimal/html/page.html templates/minimal/html/style.css templates/minimal/html/user.index.html templates/minimal/html/user.item.html templates/minimal/html/user.register.html templates/minimal/html/user.rego_progress.html templates/minimal/initial_data.py templates/minimal/schema.py test test/.cvsignore test/README.txt test/__init__.py test/benchmark.py test/db_test_base.py test/mocknull.py test/session_common.py test/test_actions.py test/test_anydbm.py test/test_cgi.py test/test_dates.py test/test_hyperdbvals.py test/test_indexer.py test/test_locking.py test/test_mailgw.py test/test_mailsplit.py test/test_metakit.py test/test_multipart.py test/test_mysql.py test/test_postgresql.py test/test_rfc2822.py test/test_schema.py test/test_security.py test/test_sqlite.py test/test_templating.py test/test_token.py test/test_tsearch2.py tools tools/.cvsignore tools/base64 tools/fixroles.py tools/load_tracker.py tools/migrate-queries.py tools/pygettext.py Message-ID: <20061105203053.418E11E4008@bag.python.org> Author: erik.forsberg Date: Sun Nov 5 21:30:25 2006 New Revision: 52625 Added: tracker/vendor/roundup/current/.cvsignore tracker/vendor/roundup/current/BUILD.txt tracker/vendor/roundup/current/CHANGES.txt tracker/vendor/roundup/current/COPYING.txt tracker/vendor/roundup/current/ChangeLog tracker/vendor/roundup/current/MANIFEST.in tracker/vendor/roundup/current/README.txt tracker/vendor/roundup/current/cgi-bin/ tracker/vendor/roundup/current/cgi-bin/roundup.cgi (contents, props changed) tracker/vendor/roundup/current/demo.py tracker/vendor/roundup/current/detectors/ tracker/vendor/roundup/current/detectors/creator_resolution.py tracker/vendor/roundup/current/detectors/emailauditor.py tracker/vendor/roundup/current/detectors/newissuecopy.py tracker/vendor/roundup/current/doc/ tracker/vendor/roundup/current/doc/.cvsignore tracker/vendor/roundup/current/doc/FAQ.txt tracker/vendor/roundup/current/doc/Makefile tracker/vendor/roundup/current/doc/ZPL.txt tracker/vendor/roundup/current/doc/admin_guide.txt tracker/vendor/roundup/current/doc/announcement.txt tracker/vendor/roundup/current/doc/customizing.txt tracker/vendor/roundup/current/doc/debugging.txt tracker/vendor/roundup/current/doc/default.css tracker/vendor/roundup/current/doc/design.txt tracker/vendor/roundup/current/doc/developers.txt tracker/vendor/roundup/current/doc/features.txt tracker/vendor/roundup/current/doc/glossary.txt tracker/vendor/roundup/current/doc/images/ tracker/vendor/roundup/current/doc/images/edit.png (contents, props changed) tracker/vendor/roundup/current/doc/images/hyperdb.png (contents, props changed) tracker/vendor/roundup/current/doc/images/logo-acl-medium.png (contents, props changed) tracker/vendor/roundup/current/doc/images/logo-codesourcery-medium.png (contents, props changed) tracker/vendor/roundup/current/doc/images/logo-software-carpentry-standard.png (contents, props changed) tracker/vendor/roundup/current/doc/images/roundup-1.png (contents, props changed) tracker/vendor/roundup/current/doc/images/roundup.png (contents, props changed) tracker/vendor/roundup/current/doc/implementation.txt tracker/vendor/roundup/current/doc/index.txt tracker/vendor/roundup/current/doc/installation.txt tracker/vendor/roundup/current/doc/mysql.txt tracker/vendor/roundup/current/doc/original_overview.html tracker/vendor/roundup/current/doc/overview.txt tracker/vendor/roundup/current/doc/postgresql.txt tracker/vendor/roundup/current/doc/roundup-admin.1 tracker/vendor/roundup/current/doc/roundup-demo.1 tracker/vendor/roundup/current/doc/roundup-favicon.ico (contents, props changed) tracker/vendor/roundup/current/doc/roundup-mailgw.1 tracker/vendor/roundup/current/doc/roundup-server.1 tracker/vendor/roundup/current/doc/roundup-server.ini.example tracker/vendor/roundup/current/doc/spec.html tracker/vendor/roundup/current/doc/tracker_templates.txt tracker/vendor/roundup/current/doc/upgrading.txt tracker/vendor/roundup/current/doc/user_guide.txt tracker/vendor/roundup/current/doc/whatsnew-0.7.txt tracker/vendor/roundup/current/doc/whatsnew-0.8.txt tracker/vendor/roundup/current/frontends/ tracker/vendor/roundup/current/frontends/README.txt tracker/vendor/roundup/current/frontends/ZRoundup/ tracker/vendor/roundup/current/frontends/ZRoundup/.cvsignore tracker/vendor/roundup/current/frontends/ZRoundup/ZRoundup.py tracker/vendor/roundup/current/frontends/ZRoundup/__init__.py tracker/vendor/roundup/current/frontends/ZRoundup/dtml/ tracker/vendor/roundup/current/frontends/ZRoundup/dtml/manage_addZRoundupForm.dtml tracker/vendor/roundup/current/frontends/ZRoundup/icons/ tracker/vendor/roundup/current/frontends/ZRoundup/icons/tick_symbol.gif (contents, props changed) tracker/vendor/roundup/current/frontends/ZRoundup/refresh.txt tracker/vendor/roundup/current/locale/ tracker/vendor/roundup/current/locale/.cvsignore tracker/vendor/roundup/current/locale/GNUmakefile tracker/vendor/roundup/current/locale/de.po tracker/vendor/roundup/current/locale/en.po tracker/vendor/roundup/current/locale/es_AR.po (contents, props changed) tracker/vendor/roundup/current/locale/fr.po tracker/vendor/roundup/current/locale/lt.po (contents, props changed) tracker/vendor/roundup/current/locale/roundup.pot tracker/vendor/roundup/current/locale/ru.po tracker/vendor/roundup/current/locale/zh_CN.po tracker/vendor/roundup/current/locale/zh_TW.po (contents, props changed) tracker/vendor/roundup/current/patches/ tracker/vendor/roundup/current/patches/20020205.alternate_auth tracker/vendor/roundup/current/roundup/ tracker/vendor/roundup/current/roundup/.cvsignore tracker/vendor/roundup/current/roundup/__init__.py tracker/vendor/roundup/current/roundup/admin.py tracker/vendor/roundup/current/roundup/backends/ tracker/vendor/roundup/current/roundup/backends/.cvsignore tracker/vendor/roundup/current/roundup/backends/__init__.py tracker/vendor/roundup/current/roundup/backends/back_anydbm.py tracker/vendor/roundup/current/roundup/backends/back_metakit.py (contents, props changed) tracker/vendor/roundup/current/roundup/backends/back_mysql.py tracker/vendor/roundup/current/roundup/backends/back_postgresql.py tracker/vendor/roundup/current/roundup/backends/back_sqlite.py tracker/vendor/roundup/current/roundup/backends/back_tsearch2.py tracker/vendor/roundup/current/roundup/backends/blobfiles.py tracker/vendor/roundup/current/roundup/backends/indexer_common.py tracker/vendor/roundup/current/roundup/backends/indexer_dbm.py tracker/vendor/roundup/current/roundup/backends/indexer_rdbms.py tracker/vendor/roundup/current/roundup/backends/indexer_xapian.py tracker/vendor/roundup/current/roundup/backends/locking.py tracker/vendor/roundup/current/roundup/backends/portalocker.py tracker/vendor/roundup/current/roundup/backends/rdbms_common.py tracker/vendor/roundup/current/roundup/backends/sessions_dbm.py tracker/vendor/roundup/current/roundup/backends/sessions_rdbms.py tracker/vendor/roundup/current/roundup/backends/tsearch2_setup.py tracker/vendor/roundup/current/roundup/cgi/ tracker/vendor/roundup/current/roundup/cgi/.cvsignore tracker/vendor/roundup/current/roundup/cgi/MultiMapping.py tracker/vendor/roundup/current/roundup/cgi/PageTemplates/ tracker/vendor/roundup/current/roundup/cgi/PageTemplates/.cvsignore tracker/vendor/roundup/current/roundup/cgi/PageTemplates/Expressions.py tracker/vendor/roundup/current/roundup/cgi/PageTemplates/GlobalTranslationService.py tracker/vendor/roundup/current/roundup/cgi/PageTemplates/MultiMapping.py tracker/vendor/roundup/current/roundup/cgi/PageTemplates/PageTemplate.py (contents, props changed) tracker/vendor/roundup/current/roundup/cgi/PageTemplates/PathIterator.py tracker/vendor/roundup/current/roundup/cgi/PageTemplates/PythonExpr.py tracker/vendor/roundup/current/roundup/cgi/PageTemplates/README.txt tracker/vendor/roundup/current/roundup/cgi/PageTemplates/TALES.py tracker/vendor/roundup/current/roundup/cgi/PageTemplates/__init__.py tracker/vendor/roundup/current/roundup/cgi/TAL/ tracker/vendor/roundup/current/roundup/cgi/TAL/.cvsignore tracker/vendor/roundup/current/roundup/cgi/TAL/DummyEngine.py tracker/vendor/roundup/current/roundup/cgi/TAL/HTMLParser.py tracker/vendor/roundup/current/roundup/cgi/TAL/HTMLTALParser.py tracker/vendor/roundup/current/roundup/cgi/TAL/README.txt tracker/vendor/roundup/current/roundup/cgi/TAL/TALDefs.py tracker/vendor/roundup/current/roundup/cgi/TAL/TALGenerator.py tracker/vendor/roundup/current/roundup/cgi/TAL/TALInterpreter.py tracker/vendor/roundup/current/roundup/cgi/TAL/TALParser.py tracker/vendor/roundup/current/roundup/cgi/TAL/TranslationContext.py tracker/vendor/roundup/current/roundup/cgi/TAL/XMLParser.py tracker/vendor/roundup/current/roundup/cgi/TAL/__init__.py tracker/vendor/roundup/current/roundup/cgi/TAL/markupbase.py tracker/vendor/roundup/current/roundup/cgi/TAL/talgettext.py tracker/vendor/roundup/current/roundup/cgi/TranslationService.py tracker/vendor/roundup/current/roundup/cgi/ZTUtils/ tracker/vendor/roundup/current/roundup/cgi/ZTUtils/.cvsignore tracker/vendor/roundup/current/roundup/cgi/ZTUtils/Batch.py tracker/vendor/roundup/current/roundup/cgi/ZTUtils/Iterator.py tracker/vendor/roundup/current/roundup/cgi/ZTUtils/__init__.py tracker/vendor/roundup/current/roundup/cgi/__init__.py tracker/vendor/roundup/current/roundup/cgi/accept_language.py (contents, props changed) tracker/vendor/roundup/current/roundup/cgi/actions.py (contents, props changed) tracker/vendor/roundup/current/roundup/cgi/apache.py tracker/vendor/roundup/current/roundup/cgi/cgitb.py tracker/vendor/roundup/current/roundup/cgi/client.py tracker/vendor/roundup/current/roundup/cgi/exceptions.py (contents, props changed) tracker/vendor/roundup/current/roundup/cgi/form_parser.py (contents, props changed) tracker/vendor/roundup/current/roundup/cgi/templating.py tracker/vendor/roundup/current/roundup/cgi/zLOG.py tracker/vendor/roundup/current/roundup/configuration.py tracker/vendor/roundup/current/roundup/date.py tracker/vendor/roundup/current/roundup/exceptions.py tracker/vendor/roundup/current/roundup/hyperdb.py tracker/vendor/roundup/current/roundup/i18n.py tracker/vendor/roundup/current/roundup/init.py tracker/vendor/roundup/current/roundup/install_util.py tracker/vendor/roundup/current/roundup/instance.py tracker/vendor/roundup/current/roundup/mailer.py tracker/vendor/roundup/current/roundup/mailgw.py tracker/vendor/roundup/current/roundup/msgfmt.py tracker/vendor/roundup/current/roundup/password.py tracker/vendor/roundup/current/roundup/rfc2822.py tracker/vendor/roundup/current/roundup/roundupdb.py tracker/vendor/roundup/current/roundup/scripts/ tracker/vendor/roundup/current/roundup/scripts/.cvsignore tracker/vendor/roundup/current/roundup/scripts/__init__.py tracker/vendor/roundup/current/roundup/scripts/roundup_admin.py tracker/vendor/roundup/current/roundup/scripts/roundup_demo.py tracker/vendor/roundup/current/roundup/scripts/roundup_gettext.py tracker/vendor/roundup/current/roundup/scripts/roundup_mailgw.py tracker/vendor/roundup/current/roundup/scripts/roundup_server.py tracker/vendor/roundup/current/roundup/security.py tracker/vendor/roundup/current/roundup/support.py tracker/vendor/roundup/current/roundup/token.py tracker/vendor/roundup/current/roundup/version_check.py tracker/vendor/roundup/current/run_tests.py tracker/vendor/roundup/current/scripts/ tracker/vendor/roundup/current/scripts/README.txt tracker/vendor/roundup/current/scripts/add-issue (contents, props changed) tracker/vendor/roundup/current/scripts/copy-user.py (contents, props changed) tracker/vendor/roundup/current/scripts/hyperdb_example.py tracker/vendor/roundup/current/scripts/imapServer.py tracker/vendor/roundup/current/scripts/import_sf.py tracker/vendor/roundup/current/scripts/roundup-reminder (contents, props changed) tracker/vendor/roundup/current/scripts/roundup.rc-debian tracker/vendor/roundup/current/scripts/schema_diagram.py tracker/vendor/roundup/current/scripts/server-ctl (contents, props changed) tracker/vendor/roundup/current/setup.py tracker/vendor/roundup/current/templates/ tracker/vendor/roundup/current/templates/classic/ tracker/vendor/roundup/current/templates/classic/.cvsignore tracker/vendor/roundup/current/templates/classic/TEMPLATE-INFO.txt tracker/vendor/roundup/current/templates/classic/detectors/ tracker/vendor/roundup/current/templates/classic/detectors/.cvsignore tracker/vendor/roundup/current/templates/classic/detectors/messagesummary.py tracker/vendor/roundup/current/templates/classic/detectors/nosyreaction.py tracker/vendor/roundup/current/templates/classic/detectors/statusauditor.py tracker/vendor/roundup/current/templates/classic/detectors/userauditor.py tracker/vendor/roundup/current/templates/classic/extensions/ tracker/vendor/roundup/current/templates/classic/extensions/README.txt tracker/vendor/roundup/current/templates/classic/html/ tracker/vendor/roundup/current/templates/classic/html/_generic.calendar.html tracker/vendor/roundup/current/templates/classic/html/_generic.collision.html tracker/vendor/roundup/current/templates/classic/html/_generic.help.html tracker/vendor/roundup/current/templates/classic/html/_generic.index.html tracker/vendor/roundup/current/templates/classic/html/_generic.item.html tracker/vendor/roundup/current/templates/classic/html/file.index.html tracker/vendor/roundup/current/templates/classic/html/file.item.html tracker/vendor/roundup/current/templates/classic/html/help_controls.js tracker/vendor/roundup/current/templates/classic/html/home.classlist.html tracker/vendor/roundup/current/templates/classic/html/home.html tracker/vendor/roundup/current/templates/classic/html/issue.index.html tracker/vendor/roundup/current/templates/classic/html/issue.item.html tracker/vendor/roundup/current/templates/classic/html/issue.search.html tracker/vendor/roundup/current/templates/classic/html/keyword.item.html tracker/vendor/roundup/current/templates/classic/html/msg.index.html tracker/vendor/roundup/current/templates/classic/html/msg.item.html tracker/vendor/roundup/current/templates/classic/html/page.html tracker/vendor/roundup/current/templates/classic/html/query.edit.html tracker/vendor/roundup/current/templates/classic/html/query.item.html tracker/vendor/roundup/current/templates/classic/html/style.css tracker/vendor/roundup/current/templates/classic/html/user.forgotten.html tracker/vendor/roundup/current/templates/classic/html/user.index.html tracker/vendor/roundup/current/templates/classic/html/user.item.html tracker/vendor/roundup/current/templates/classic/html/user.register.html tracker/vendor/roundup/current/templates/classic/html/user.rego_progress.html tracker/vendor/roundup/current/templates/classic/initial_data.py tracker/vendor/roundup/current/templates/classic/schema.py tracker/vendor/roundup/current/templates/minimal/ tracker/vendor/roundup/current/templates/minimal/.cvsignore tracker/vendor/roundup/current/templates/minimal/TEMPLATE-INFO.txt tracker/vendor/roundup/current/templates/minimal/detectors/ tracker/vendor/roundup/current/templates/minimal/detectors/.cvsignore tracker/vendor/roundup/current/templates/minimal/detectors/userauditor.py tracker/vendor/roundup/current/templates/minimal/extensions/ tracker/vendor/roundup/current/templates/minimal/extensions/README.txt tracker/vendor/roundup/current/templates/minimal/html/ tracker/vendor/roundup/current/templates/minimal/html/_generic.calendar.html tracker/vendor/roundup/current/templates/minimal/html/_generic.collision.html tracker/vendor/roundup/current/templates/minimal/html/_generic.help.html tracker/vendor/roundup/current/templates/minimal/html/_generic.index.html tracker/vendor/roundup/current/templates/minimal/html/_generic.item.html tracker/vendor/roundup/current/templates/minimal/html/help_controls.js tracker/vendor/roundup/current/templates/minimal/html/home.classlist.html tracker/vendor/roundup/current/templates/minimal/html/home.html tracker/vendor/roundup/current/templates/minimal/html/page.html tracker/vendor/roundup/current/templates/minimal/html/style.css tracker/vendor/roundup/current/templates/minimal/html/user.index.html tracker/vendor/roundup/current/templates/minimal/html/user.item.html tracker/vendor/roundup/current/templates/minimal/html/user.register.html tracker/vendor/roundup/current/templates/minimal/html/user.rego_progress.html tracker/vendor/roundup/current/templates/minimal/initial_data.py tracker/vendor/roundup/current/templates/minimal/schema.py tracker/vendor/roundup/current/test/ tracker/vendor/roundup/current/test/.cvsignore tracker/vendor/roundup/current/test/README.txt tracker/vendor/roundup/current/test/__init__.py tracker/vendor/roundup/current/test/benchmark.py tracker/vendor/roundup/current/test/db_test_base.py tracker/vendor/roundup/current/test/mocknull.py tracker/vendor/roundup/current/test/session_common.py tracker/vendor/roundup/current/test/test_actions.py (contents, props changed) tracker/vendor/roundup/current/test/test_anydbm.py tracker/vendor/roundup/current/test/test_cgi.py tracker/vendor/roundup/current/test/test_dates.py tracker/vendor/roundup/current/test/test_hyperdbvals.py tracker/vendor/roundup/current/test/test_indexer.py tracker/vendor/roundup/current/test/test_locking.py tracker/vendor/roundup/current/test/test_mailgw.py tracker/vendor/roundup/current/test/test_mailsplit.py tracker/vendor/roundup/current/test/test_metakit.py tracker/vendor/roundup/current/test/test_multipart.py tracker/vendor/roundup/current/test/test_mysql.py tracker/vendor/roundup/current/test/test_postgresql.py tracker/vendor/roundup/current/test/test_rfc2822.py tracker/vendor/roundup/current/test/test_schema.py tracker/vendor/roundup/current/test/test_security.py tracker/vendor/roundup/current/test/test_sqlite.py tracker/vendor/roundup/current/test/test_templating.py tracker/vendor/roundup/current/test/test_token.py tracker/vendor/roundup/current/test/test_tsearch2.py tracker/vendor/roundup/current/tools/ tracker/vendor/roundup/current/tools/.cvsignore tracker/vendor/roundup/current/tools/base64 (contents, props changed) tracker/vendor/roundup/current/tools/fixroles.py tracker/vendor/roundup/current/tools/load_tracker.py (contents, props changed) tracker/vendor/roundup/current/tools/migrate-queries.py tracker/vendor/roundup/current/tools/pygettext.py Log: Importing roundup 1.1.2. Added: tracker/vendor/roundup/current/.cvsignore ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/.cvsignore Sun Nov 5 21:30:25 2006 @@ -0,0 +1,9 @@ +*.pyc +*.pyo +localconfig.py +build +demo +dist +MANIFEST +_test_* +*.cover Added: tracker/vendor/roundup/current/BUILD.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/BUILD.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,51 @@ +Building Releases +================= + +Roundup is currently a source-only release - it has no binary components. I +want it to stay that way, too. This document describes how to build a +source release. Users of Roundup should read the doc/installation.txt file +to find out how to install this software. + +Building and distributing a release of Roundup is done by running: + +1. Make sure the unit tests run! "./run_tests.py" +2. Tag the CVS for the release, eg. "cvs tag -R release-0-6-3" +3. Edit roundup/__init__.py and doc/announcement.txt to reflect the new + version and appropriate announcements. Add truncated announcement to + setup.py description field. +4. Clean out all *.orig, *.rej, .#* files from the source. +5. python setup.py clean --all +6. Edit setup.py to ensure that all information therein (version, contact + information etc) is correct. +7. python setup.py sdist --manifest-only +8. Check the MANIFEST to make sure that any new files are included. If + they are not, edit MANIFEST.in to include them. "Documentation" for + MANIFEST.in may be found in disutils.filelist._parse_template_line. +9. python setup.py sdist + (if you find sdist a little verbose, add "--quiet" to the end of the + command) +10. Unpack the new dist file in /tmp then a) run_test.py and b) demo.py + with all available Python versions. +11. Generate gpg signature with "gpg -a --detach-sign" +12. python setup.py bdist_rpm +13. python setup.py bdist_wininst +14. Send doc/announcement.txt to python-announce at python.org +15. Notify any other news services as appropriate... + + http://freshmeat.net/projects/roundup/ + + +So, those commands in a nice, cut'n'pasteable form:: + + find . -name '*.orig' -exec rm {} \; + find . -name '*.rej' -exec rm {} \; + find . -name '.#*' -exec rm {} \; + python setup.py clean --all + python setup.py sdist --manifest-only + python setup.py sdist --quiet + python setup.py bdist_rpm + python setup.py bdist_wininst + python setup.py register + python2.5 setup.py sdist upload --sign + + Added: tracker/vendor/roundup/current/CHANGES.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/CHANGES.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,1873 @@ +This file contains the changes to the Roundup system over time. The entries +are given with the most recent entry first. + +2006-04-27 1.1.2 +Feature: +- server-ctl script uses server configuration file (sf bug 1443805) + +Fixed: +- progress display in roundup-admin reindex +- bug in menu() permission filter (sf bug 1444440) +- indexing may be turned off for FileClass "content" now + ("content" and "type" properties are now automatically included in the + FileClass schema where previously the "content" property was faked and + "type" was optional) +- verbose output during import is optional now (sf bug 1475624) +- escape *all* uses of "schema" in mysql backend (sf bug 1472120) +- responses to user rego email (sf bug 1470254) +- dangling connections in session handling (sf bug 1463359) +- reduced frequency of session timestamp update +- classhelp popup pagination forgot about "type" (sf bug 1465836) +- umask is now configurable (with the same 0002 default) +- sorting of entries in classhelp popup (sf bug 1449000) +- allow single digit seconds in date spec (sf bug 1447141) +- prevent generation of new single-digit seconds dates (sf bug 1429390) +- implement close() on all indexers (sf bug 1242477) + + +2006-03-03 1.1.1 +Fixed: +- failure with browsers not sending "Accept-Language" header + (sf bugs 1429646 and 1435335) +- translate class name in "required property not supplied" error message + (sf bug 1429669) +- error in link property lookups with numeric-alike key values (sf bug 1424550) +- ignore UTF-8 BOM in .po files +- add permission filter to menu() implementations (sf bug 1431188) +- lithuanian translation updated by Nerijus Baliunas (sf patch 1411175) +- incompatibility with python2.3 in the mailer module (sf bug 1432602) +- typo in SMTP TLS option name: "MAIL_TLS_CERFILE" (sf bug 1435452) +- email obfuscation code in html templating is more robust +- blank-title subject line handling (sf bug 1442121) +- "All users may only view and edit issues, files and messages they + create" example in docs (sf bug 1439086) +- saving of queries (sf bug 1436169) +- "Adding a new constrained field to the classic schema" example in docs + (sf bug 1433118) +- security check in mailgw (sf bug 1442145) +- "clear this message" (sf bug 1429367) +- escape all uses of "schema" in mysql backend (sf bug 1397569) +- date spec wasn't allowing week intervals + + +2006-02-10 1.1.0 +Feature: +- trackers may configure custom stop-words for the full-text indexer +- login may now be for a single session (and this is the default) +- trackers may hide exceptions from web users (they will be mailed to the + tracker admin) (hiding is the default) +- include "clear this message" link in the "ok" message bar + +Fixed: +- fixes in scripts/import_sf.py +- fix some unicode bugs in roundup-admin import +- Xapian indexer wasn't actually being used and its reindexing of existing + data was busted to boot +- roundup-admin import wasn't indexing message content +- allow dispname to be passed to renderWith (sf bug 1424587) +- rename dispname to @dispname to avoid name clashes in the future +- fixed schema migration problem when Class keys were removed + + +2006-02-03 1.0.1 +Feature: +- scripts/import_sf.py will import a tracker from Sourceforge.NET +- added hasRole() to HTMLUser + +Fixed: +- SQL generation for sort/group by separate Link properties (sf bug + 1417565) +- fix timezone offsetting in email Date: header +- fix security check for hasPermission('Permission', None) + + +2006-01-27 1.0 +Feature: +- Lithuanian translation by Aiste Kesminaite +- Web User Interface language selection by form variable @language, + browser cookie or HTTP header Accept-Language (sf patch 1360321) +- initial values for configuration options may be passed on + 'roundup-admin install' command line (based on sf patch 1237110) +- favicon.ico image may be changed with server config option (sf patch 1355661) +- Password objects initialized from plaintext remember plaintext value + (sf rfe 1379447) +- Roundup installation document includes configuration example + for Exim Internet Mailer (sf bug 1393860) +- enable registration confirmation by web only (sf bug 1381675) +- allow preselection of values in templating menu()s (sf patch 1396085) +- display the query name in the header (sf feature 1298535 / patch 1349387) +- classhelp works with Link properties now (sf bug 1410290) +- added setorderprop() and setlabelprop() to Class (sf features 1379534, + 1379490) +- CSV encoding support (sf bug 1240848) +- fields rendered with StructuredText are hyperlinked by default +- additional attributes for input element may be passed to the 'field' + method of a property wrapper +- added "copy_url" method to generate a URL for copying an item + +Fixed: +- MySQL now creates String columns using the TEXT column type +- password.crypt won't work with md5 passwords (sf bug 1372253) +- use quoted printable encoding for nosy attachments that have MIME + type 'text/plain' but contain 8-bit characters (sf bug 1381559) +- login name and email address fields in the classic template + are highlighted as required fields (sf bug 1392364) +- french translation updated by Patrick Decat (sf patch 1397059) +- HTTP authorization takes precedence over session cookie (sf bug 1396134) +- enforce correct encoding of PostgreSQL backend (sf bug 1374235) +- grouping/sorting on link to same class fixed (sf bug 1404930) +- all backends implement the retired check in getnodeids (sf bug 1290560) +- fix detection of "missing" existing values in CGI form parser (sf bug + 1414149) +- ZRoundup works again (sf bug 1263842) +- default user template does not display password fields and submit button + when editing is not allowed +- fix StructuredText import in cgi.templating +- have "System Messages" be marked as such again (sf bug 1281907) +- enable editing of public queries (sf bug 966144) + + +2005-10-07 0.9.0b1 +Feature: +- added "imapServer.py" script (sf patch 934567) +- added date selection popup windows (thanks Marcus Priesch) +- added Xapian indexer; replaces standard indexers if Xapian is available +- mailgw subject parsing has configurable levels of strictness +- nosy messages may be sent individually to all recipients +- remember where we came from when logging in (sf patch 1312889) + + +2006-??-?? 0.8.6 +Fixed: +- french translation updated by Patrick Decat (sf patch 1397059) +- tighten up Date parsing to not allow 'M/D/YY' (or 'D/M/YY) (sf bug + 1290550) +- handle "schema" being reserved word in MySQL 5+ (sf bug 1397569) +- fixed documentation of filter() in the case of multiple values in a + String search (sf bug 1373396) +- fix comma-separated ID filter spec in web requests (sf bug 1396278) +- fix Date: header generation to be LOCALE-agnostic (sf bug 1352624) +- fix admin doc description of roundup-server config file +- fix redirect after instant registration (sf bug 1381676) +- fix permission checks in cgi interface (sf bug 1289557) +- fix permission check on RetireAction (sf bug 1407342) +- timezone now applied to date for pretty-format (sf bug 1406861) +- fix mangling of "_" in mail Subject class name (sf bug 1413852) +- catch bad classname in URL (related to sf bug 1240541) +- clean up digested_file_types (sf bug 1268303) +- fix permission checks in mailgw (sf bug 1263655) +- fix encoding of subject in generated error message (sf bug 1414465) + + +2005-10-07 0.8.5 +Feature: +- Argentinian Spanish translation by Ramiro Morales + +Fixed: +- Display of Multilinks where linked Class labelprop values are None +- Fix references to the old * Registration Permissions +- Fix missing merge of fix to sf bug 1177057 +- Fix RDBMS indexer indexing UTF-8 words that encode to > 30 chars +- Handle invalidly-specified charsets in incoming email + + +2005-07-18 0.8.4 +Fixed: +- extra CRs in CSV export files on Windows platform (sf bug 1195742) +- activity RDBMS columns were being reported in changes +- fix name collision in roundup.cgi script (sf bug 1203795) +- fix handling of invalid interval input +- search locale files relative ro roundup installation path (sf bug 1219689) +- use translation for boolean property rendering (sf bug 1225152) +- enabled disabling of REMOTE_USER for when it's not a valid username (sf + bug 1190187) +- fix invocation of hasPermission from templating code (sf bug 1224172) +- have 'roundup-admin security' display property restrictions (sf bug + 1222135) +- fixed templating menu() sort_on handling (sf bug 1221936) +- allow specification of pagesize, sorting and filtering in "classhelp" + popups (sf bug 1211800) +- handle dropped properies in rdbms/metakit journal export (sf bug 1203569) +- handle missing Subject lines better (sf bug 1198729) +- sort/group by missing values correctly (sf bugs 1198623, 1176897) +- discard, don't bounce messages to the mailgw when the messages's sender + is invalid (ie. when we try to bounce, we get a 550 "unknown user + account" response from the SMTP server) (sf bug 1190906) +- removed debugging code from cgi/actions.py +- refactored hyperdb.rawToHyperdb, allowing a number of improvements + (thanks Ralf Schlatterbeck) +- don't try to set a timeout for IMAPS (thanks Paul Jimenez) +- present Reject exception messages to web users (sf bug 1237685) + + +2005-05-02 0.8.3 +Feature: +- chinese translation by limodou + +Fixed: +- fix reference to The Zope Book in Roundup FAQ +- disabled file logging in Roundup test suite (sf bug 1155649) +- return original string if message issue xref isn't valid +- fix nosyreaction.py to stop it setting the nosy list unnecessarily + (see doc/upgrading.txt for how to fix in your trackers) +- after logout, always display tracker home page +- web forms don't create new items if no item properties are set from UI +- item creation failed if multilink fields had invalid entries (sf bug + 1177602) +- fix bdist_rpm (sf bug 1164328) +- fix checking of "Email Access" for Anonymous email registration (sf bug + 1177057) +- disable "Email Access" for Anonymous by default to stop spam regsitering + users on public trackers +- send errors in the web interface to a logfile by default. Use the + "debug" multiprocess mode (roundup-server) or the DEBUG_TO_CLIENT var + (roundup.cgi) to have the errors appear in your browser +- fix setgid typo (sf bug 1171346) +- fix faulty find_template filename facility (sf bug 1163629) +- fix roundup-admin "export" so it creates the target dir if needed +- "fix" roundup-admin "import" to not use "universal newline support" since + the csv module appears to have its own ideas about such things (sf bug + 1163890) +- fix installation docs referring to old-style configuration variables +- fix roundup-admin "find" for searching Multilinks (sf bug 1189465) + + +2005-03-03 0.8.2 +Feature: +- roundup-server automatically redirects from trackers list + to the tracker page if there is only one tracker + +Fixed: +- added content to ZRoundup refresh.txt file (sf bug 1147622) +- fix invalid reference to csv.colon_separated +- correct URL to What's New in setup.py meta-data +- change AUTOCOMMIT=OFF to AUTOCOMMIT=0 for MySQL (sf bug 1143707) +- compile message objects in 'setup.py build' +- use backend datatype for journal timestamps in RDBMSes +- fixes to the "Using an external password validation source" + customisation example (sf bugs 1153640 and 1155108) + + +2005-02-17 0.8.1 +Fixed: +- replaced MutlilinkIterator with multilinkGenerator (thanks Bob Ippolito) +- fixed broken csv import in roundup.admin module +- fixed braino in HTMLClass.filter() (sf bug 1124213) +- change ZTUtils Iterator to always iter() its sequence argument + + +2005-01-16 0.8.0 +Fixed: +- fix roundup-server log and PID file paths to be absolute +- fix initialisation of roundup-server in daemon mode so initialisation + errors are visible +- fix: 'Logout' link was enabled on issue index page only +- have Permissions only test the check function if itemid is suppled +- modify cgi templating system to check item-level permissions in listings +- enable batching in message and file listings +- more documentation of security mechanisms (incl. sf patches 1117932, + 1117860) +- better unit tests for security mechanisms +- code cleanup (sf patch 1115329 and additional) +- issue search page allows setting of no sorting / grouping (sf bug + 1119475) +- better edit conflict handling (sf bug 1118790) +- consistent text searching behaviour (AND everywhere) (sf bug 1101036) +- fix handling of invalid date input (sf bug 1102165) +- retain Boolean selections in edit error handling (sf bug 1101492) +- fix initialisation of logging module from config file (sf bug 1108577) +- removed rlog module (py 2.3 is minimum version now) +- fixed class "help" listing paging (sf bug 1106329) +- nicer error looking up values of None (response to sf bug 1108697) +- fallback for (list) popups if javascript disabled (sf patch 1101626) + + +2005-01-13 0.8.0b2 +Fixed: +- note about how to run roundup demo in Windows (sf bug 1082090) +- fix API for templating utils extensions - remove "utils" arg (sf bug 1081981) +- back_sqlite.py is missing "import time" (sf bug 1081959) +- fix (list) popup (sf bug 1083570) +- fix some security assertions (sf bug 1085481) +- 'roundup-server -S' always writes [trackers] section heading (sf bug 1088878) +- fix port number as int in mysql connection info (sf bug 1082530) +- fix setup.py to work with (thanks Roch'e Compaan) +- TAL expressions like 'request/show/whatever' return True + if the request does not contain explicit @columns list +- NumberHTMLProperty should return '' not "None" if not set (thanks + William) +- ensure multilink ordering in RDBMS backends (thanks Marcus Priesch, sf + bug 950963) +- always honor indexme property on Strings (sf patch 1063711) +- make hyperdb value parsing errors readable in mailgw errors +- make anydbm journal export handle removed properties +- allow use of XML templates again + + +2004-10-15 0.7.8 +Fixed: +- Clean out sessions / otks tables when migrating + + +2004-10-11 0.7.7 +Fixed: +- ZRoundup's search interface works now (sf bug 994957) +- fixed history display when "ascending" +- removed references to py2.3+ boolean values (sf bug 995682) +- fix static file path normalisation in security check (thanks David Linke) +- less specific messages for login failures (thanks Chris Withers) +- Reject raised against email messages should result in email rejection, not + discarding of the message +- mailgw can override the MAIL_DEFAULT_CLASS +- handle Py2.3+ datetime objects as Date specs (sf bug 971300) +- use row locking in MySQL newid() (sf bug 1034211) +- add sanity check for sort and group on same property (sf bug 1033477) +- extend OTK and session table value cols to TEXT (sf bug 1031271) +- fix lookup of REMOTE_USER (sf bug 1002923) +- new Interval props weren't created properly in rdbms +- date.Interval() now accepts an Interval as a spec (sf bug 1041266) +- handle deleted properties in RDBMS history +- apply timezone in correct direction in user input (sf bug 1013097) +- more efficient find() in RDBMS (sf bug 1012781) + + +2004-07-21 0.7.6 +Fixed: +- rdbms backend full text search failure after import (sf bug 980314) +- rdbms backends not filtering correctly on link=None +- fix anydbm journal import (sf bug 983166) +- handle postgresql bug in SQL generation (sf bug 984591) +- fix dates-from-Dates (sf bug 984604) +- fix messageid generated when msgid is None for send_message (sf bug 987933) +- make user permissions check more sane (fix search page for anonymous) +- fixed RDBMS filter() for no matches from full-text search (sf bug 990778) +- fixed DateHTMLProperty for invalid date entry (sf bug 986538) +- fixed external password source example (sf bug 986601) +- document the STATIC_FILES config var +- implement the HTTP HEAD command (sf bug 992544) +- fix journal export of files to remove content from CSV files +- API clarification. Previously, the anydbm/bsddb/metakit filter() methods + had required exact matches to Multilink argument lists. The RDBMS + backends treated Multilink matches like all other data types - matching + any of the Multilink argument list is good enough. The latter behaviour + is implemented across the board now. +- fix metakit handling of filter on Link==None + + +2004-06-24 0.7.5 +Fixed: +- force lookup of journal props in anydbm filtering +- fixed lookup of "missing" Link values for new props in anydbm backend +- allow list of values for id, Number and Boolean filtering in anydbm + backend +- fixed some more mysql 0.6->0.7 upgrade bugs (sf bug 950410) +- fixed Boolean values in postgresql (sf bugs 972546 and 972600) +- fixed -g arg to roundup-server (sf bug 973946) +- better roundup-server usage string (sf bug 973352) +- include "context" always, as documented (sf bug 965447) +- fixed REMOTE_USER (external HTTP Basic auth) (sf bug 977309) +- fixed roundup-admin "find" to use better value parsing +- fixed RDBMS Class.find() to handle None value in multiple find +- export now stores file "content" in separate files in export directory + + +2004-06-10 0.7.4 +Fixed: +- re-acquire the OTK manager when we re-open the database +- mailgw handler can close the database on us +- fixed grouping by a NULL Link value +- fixed anydbm import/export (sf bugs 965216, 964457, 964450) +- fix python 2.3.3 strftime deprecation warning (sf patch 968398) +- fix some column datatypes in postgresql and mysql (sf bugs 962611, + 959177 and 964231) +- fixed RDBMS journal packing (sf bug 959177) +- fixed filtering by floats in anydbm (sf bug 963584) + + +2004-05-28 0.7.3 +Fixed: +- add "checked" to truth values for Boolean input +- fixed import in metakit backend +- fix SearchAction use of Class.filter(), and clarify API docs for same +- ensure static files may only be served out of the tracker's "static + files" directory + + +2004-05-17 0.7.2 +Fixed: +- anydbm sorting with None values (sf bug 952853) +- roundup-server -g option not recognised (sf bug 952310) +- HTML templating isset() inverted (sf bug 951779) +- otks manager missing (sf bug 952931) +- mention DEFAULT_TIMEZONE requirement in upgrading doc (sf bug 952932) +- fix DateHTMLProperty so local() can override user timezone (sf bug + 953678) +- fix anydbm sort/group direction handling, and make RDBMS sort/group use + Link'ed "order" properties (sf bug 953148) +- fix Interval editing (sf bug 954891) + + +2004-05-10 0.7.1 +Fixed: +- several temp files made it into the source distribution (sf bug 949243) +- typo in roundup/instance.py +- missing CRLF var in rfc822.py (sf patch 949471) +- fix user creation page +- have roundup server pass though the cause of a "403 Forbidden" response +- fix schema mutation in sqlite backends (thanks Tamer Fahmy) +- make popup Javascript IE 5.0 friendly (thanks Marlon van den Berg) +- fix RDBMS import (thanks Tamer Fahmy) + + +2004-05-06 0.7.0 +Fixed: +- sqlite migration drops some journal information (thanks David Linke) +- user editing Role entry help text always appears +- disable forking server when os.fork() not available (sf bug 938586) +- removed Boolean from source to make py <2.3 happy (sf bug 938790) +- fix nested scope bug in rdbms multilink sorting +- re-seed the random number generator for each request +- postgresql backend altered to not use popen (thanks Georges Martin) +- fixed journal marshalling in RDBMS backends (sf bug 943627) +- fixed handling of key values starting with numbers (sf bug 941363) +- fixed journal "param" column size in RDBMS backends +- fixed static file serving +- fixed rego from email address (sf bug 947414) +- fixed sqlite journal ordering issue +- fixed mysql date range filtering + + +2004-04-18 0.7.0b3 +Feature: +- added a favicon +- added url_quote and html_quote methods to the utils object +- added isset method to HTMLProperty +- database export now exports full journals too +- tracker name at end of page title (sf rfe 926840) +- roundup-server now uses the ForkingMixin +- added another sample detector "creator_resolution" +- added search_checkboxes as an option for the search form +- added IMAP support to mail gateway (sf rfe 934000) +- check MANIFEST against the files actually unpacked +- roundupdb nosymessage() takes an optional bcc list + +Fixed: +- mysql and postgresql schema mutation now handle added Multilinks +- web CSV export was busted (as was any action returning a result) +- MultiMapping deviated from the Zope C implementation in a number of + places (thanks Toby Sargeant) +- MySQL and Postgresql use BOOL/BOOLEAN for Boolean types +- OTK generation was busted (thanks Stuart D. Gathman) +- export and import now include journals (incompatible with export < 0.7) +- added "download_url" method to generate a correctly quoted URL for file + download links (sf bug 927745) +- all uses of TRACKER_WEB now ensure it ends with a '/' +- roundup-admin install checks for existing tracker in target home +- grouping (and sorting) by multilink in RDBMS backends (sf bug 655702) +- roundup scripts may now be asked for their version (sf rfe 798657) +- sqlite backend had stopped using the global lock +- better check for anonymous viewing of user items (sf bug 933510) +- stop Interval from displaying an empty string (sf bug 934022) +- fixed storage of some datatypes in some RDBMS backends + + +2004-03-27 0.7.0b2 +Feature: +- added CSV export to index pages +- added emailauditor.py which works around a bug in IE. See + "detectors/emailauditor.py" for more info. +- added dispatcher functionality - see upgrading.txt for more info +- added Reject exception which may be raised by auditors. This is trapped + by mailgw and may be used to veto creation of file attachments or + messages. (sf bug 700265) +- queries on a per-user basis, and public queries (sf "bug" 891798 :) +- added DEFAULT_TIMEZONE (sf rfe 895139) +- added HTML page template to the templating context as "template" +- added is_retired to HTMLItems in templating + +Fixed: +- Boolean, Date and Link HTML templating was broken +- fix reporting of test inclusion in postgresql test +- EditAction was confused about who "self" was +- edit collision detection was broken for index-page edits +- sqlite backend wasn't migrating multilink tables correctly +- use SimpleCookie instead of Cookie (is an alias for the evil SmartCookie) +- handle older sessions in session dbm +- make presetunread more resilient to status Class changes +- HTMLDatabase classes() was broken + + +2004-03-24 0.7.0b1 +Major new features: +- added postgresql backend (originally from sf patch 761740, many changes + since) +- added new "actor" automatic property (indicates user who cause the last + "activity") +- RDBMS backends implement their session and one-time-key stores and + full-text indexers; thus they are now performing their own locking + internally +- all RDBMS backends now have indexes on several columns +- support confirming registration by replying to the email (sf bug 763668) +- all HTML templating methods now automatically check for permissions + (either view or edit as appropriate), greatly simplifying templates + +Other new features: +- simple support for collision detection (sf rfe 648763) +- support setgid and running on port < 1024 (sf patch 777528) +- using Zope3's test runner now, allowing GC checks, nicer controls and + coverage analysis +- change nosymessage and send_message to accept msgid=None (RFE #707235) +- handle Resent-From: headers (sf bug 841151) +- always sort MultilinkHTMLProperty in the correct order, usually + alphabetically (sf feature 790512) +- added script for copying user(s) ("scripts/copy-user.py") from tracker + to tracker (sf patch 828963) +- ignore incoming email with "Precedence: bulk" (sf patch 843489) +- use HTTP 'Content-Length' header (modified sf patch 844577) +- HTML generated is now HTML4 (or optionally XHTML) compliant (sf feature + 814314 and sf patch 834620) +- default stylesheet turns off sidebar when printing +- allow direct supply of filter() arguments in templating (thanks Godefroid + Chapelle) +- improved body_title slot in HTML templating (sf patch 873502) +- HTMLLinkProperty field() method renders as a field now (thanks darryl) +- cgi Action handlers may now return the actual content to be sent back to + the user (rather than using some template) +- date.Date now handles fractional seconds + +Fixed: +- mysql documentation fixed to note requirement of 4.0+ and InnoDB +- added testing of schema mutation, fixed rdbms backends handling of a + couple of cases +- HTML 4.01 validation on the 'classic' backend +- messages to the mailgw can be about classes other than issues now. +- signature matching is more precise (sf bug 827775). +- anonymous user can no longer edit or view itself (sf bug 828901). +- corrected typo in installation.html (sf bug 822967). +- clarified listTemplates docstring. +- print a nicer error message when the address is already in use + (sf bug 798659). +- remove empty lines before sending strings off to the csv parser + (sf bug 821364). +- centralised conversion of user-input data to hyperdb values (sf bug + 802405, sf bug 817217, sf rfe 816994) +- recalculate SHA on template files when installed tracker used as + template (sf bug 827510) +- fixed ZRoundup (sf bug 624380) +- the mail gateway now searches recursively for the text/plain and the + attachments of a message (sf bug 841241). +- fixed display of feedback messages in some situations (sf bug 739545) +- fixed ability to edit "content" property (sf bug 914062) + +Cleanup: +- replace curuserid attribute on Database with the extended getuid() method +- extract a new 'mailer' module for sending mail +- extract a '_send_mail' method for testing mail sending +- simplify backend importing +- use roundup_server in demo.py +- implement newItemAction using editItemAction +- use FormError in client.py, moving the handling up to inner_main() +- implemented semantic comparison of Message objects in test_mailgw +- tidied up forms in default stylesheet +- force textareas to use monospace fonts, lessening surprise on the user +- moved out parts of client.py to new modules: + * actions.py - the xxxAction and xxxPermission functions refactored into + Action classes + * exceptions.py - all exceptions + * form_parser.py - parsePropsFromForm & extractFormList in a FormParser + class + + +2004-05-17 0.6.10 +Fixed: +- mysql backend wasn't locking tracker +- ensure static files may only be served out of the tracker's "static + files" directory + + +2004-04-18 0.6.9 +Fixed: +- paging in classhelp popup was broken +- socket timeout error logging can fail +- hyperlink designators in message display (sf bug 931828) +- don't match retired items in RDBMS stringFind + + +2004-04-01 0.6.8 +Fixed: +- existing trackers (ie. live ones) may be used as templates for new + trackers - the TEMPLATE-INFO.txt name entry has the tracker's dir name + appended (so the demo tracker's template name is "classic-demo") +- handle bad multilink input at item creation time better (sf bug 917834) +- make sure email signature starts on a newline (sf bug 919759) +- add line to rego email to help URL detection (sf bug 906247) +- look harder for text/plain in email +- fixed fallback excel writer in rcsv so it has a delimiter +- fixed setup.py's use of listTemplates (!) +- make rdbms serialise() less trusting +- handle Boolean values in history HTML display + + +2004-03-01 0.6.7 +Fixed: +- be more backward-compatible when asking for EMAIL_CHARSET +- made error on create consistent with edit when user enters invalid data + for Multilink and Link form fields (sf bug 904072) +- made errors from bad input in the quick "Show issue:" form more + user-friendly (sf bug 904064) +- don't add a query to a user's list if it's already there +- nicer invalid property error in HTML templating +- use EMAIL_CHARSET for message body too (still sf bug 900046) + + +2004-02-25 0.6.6 +Fixed: +- don't insert spaces into designators, it just confuses users (sf bug + 898087) +- Eudora can't handle utf-8 headers. We love Eudora. (sf bug 900046) +- fixed bug in args to new DateHTMLProperty in the local() method (sf bug + 901444) +- fixed registration (sf bug 903283) +- also changed rego to not use a 302 during confirmation, as this seems to + confuse some email clients or browsers. + + +2004-02-16 0.6.5 +Fixed: +- mailgw handling of subject-line errors +- allow serving of FileClass file content when the class isn't called + "file" (eg. messages and other FileClasses) +- allowed negative ids (ie. new item markers) in HTMLClass.getItem, + allowing "db/file_with_status/-1/status/menu" to generate a useful + widget +- fixed content-type when templates are serving up xml (thanks Godefroid + Chapelle) +- fixed IE double-submit when it shouldn't (sf bug 842254) +- fixed check for JS pop()/push() to make more general (sf bug 877504) +- fix re-enabling queries (sf bug 861940) +- use supplied content-type on file uploads before trying filename) +- fixed roundup-reminder script to use default schema (thanks Klamer Schutte) +- fixed edit action / parsePropsFromForm to handle index-page edits better +- safer logging from HTTP server (sf bug 896917) + + +2003-12-17 0.6.4 +Fixed: +- fixed date arithmetic to not allow day-of-month == 0 (sf bug 853306) +- fixed date arithmetic to limit hours-per-day to 24, not 60 +- hard-coded python2.3-ism (socket.timeout) fixed +- fixed activity displaying as future because of Date arithmetic fix in 0.6.3 + (sf bug 842027). +- fix Windows service mode for roundup-server (sf bug 819890) +- fixed #white in cgitb (thanks Henrik Levkowetz) + + +2003-11-14 0.6.3 +Fixed: +- fixed detectors fix incorrectly fixed in bugfix release 0.6.2 +- added note to upgrading doc for detectors fix in 0.6.2 +- added script to help migrating queries from pre-0.6 trackers +- fixed "documentation" of getnodeids in roundup.hyperdb +- added flush() to DevNull (sf bug 835365) +- fixed javascript for help window for only one checkbox case +- date arithmetic was utterly broken, and has been for a long time. + Date +/- Interval now works, and Date - Date also works (produces + an Interval. +- handle socket timeout exception (thanks Marcus Priesch) +- fixed retirement of items in rdbms imports (sf bug 841355) +- fixed bug in looking up journal of newly-created items in *dbm backends + + +2003-09-29 0.6.2 +Fixed: +- cleaned up, clarified internal caching API in *dbm backends +- stopped pyc writing to current directory! yay! (patch 800718 with changes) +- fixed file leak in detector initialisation (patch 800715) +- commented out example tracker homes (patch 800720) +- added note about hidden :template var in user.item (bug 799842) +- fixed Apply Error that was raised, when property was deleted from class and + we are trying to edit an instance + + +2003-08-31 0.6.1 +Fixed: +- Add note about installing cgi-bin with a different interpreter +- Importing wasn't setting None values explicitly when it should have been +- Fixed import warning regarding 0xffff0000 literal, finally, really this + time. Checked on win2k. (sf bug 786711) +- fix CGI editCSV action to handle metakit's integer itemids +- apply fix for "remove" links from Klamer Schutte +- added permission check on "remove" link while I was there.. +- applied CSV fix for python2.3 (sf bug 790363) +- fixed form padding in LHS menu (sf bug 790502) +- fixed upgrading docs for timezones (sf bug 790498) +- set the content type on page templates (can have XML templates now) +- various cosmetic fixes (thanks James Kew for being persistent :) +- applied patch 739314 (sorry John!) + + +2003-08-08 0.6.0 +- Fixed editing attributes on FileClass nodes. +- Query editing now works correctly (sf bug 621248) +- roundup-server now logs IP addresses by default (sf bug 778795) +- logfile must be specified if pidfile is (sf bug 772820) +- timelog editing via csv interface crashes (sf bug 699837) +- sort multilinks a little better for grouping (sf bug 772935) +- batch the (list) listings at 500 entries per page (sf bug 759906) +- don't have RDBMS backends list retired nodes (sf bug 767319) +- fix file downloading +- add action attribute to issue.item form tag + + +2003-07-29 0.6.0b4 +- plugged cross-site-scripting hole (thanks Jeff Epler) +- handle deprecation of FCNTL in python2.2+ (sf bug 756756) +- handle missing Subject: line (sf bug 755331) +- fix New User creation (sf bug 754510) +- fix hackish message escaping (sf bug 757128) +- fix :required ordering problem (sf bug 740214) +- audit some user properties for valid values (roles, address) (sf bugs + 742968 and 739653) +- fix HTML file detection (hence history xref linking) (sf bug 741478) +- session database caches it's type, rather than calling whichdb each time + around. +- changed rdbms_common to fix sql backends for new Boolean types under Py2.3 + + +2003-06-10 0.6.0b3 +Fixed: +- cgi client was broken during b2 fixing + + +2003-06-09 0.6.0b2 +Feature: +- added the start/stop/restart/condstart/status roundup-server control + script + +Fixed: +- handle non-existant demo dir (thanks Ollie Rutherfurd) +- strip whitespace from Role names so "User, Admin" will work +- fixed template searching on Windows (thanks J Vickroy) + + +2003-05-09 0.6.0b1 +Removed: +- having served its purpose as a template for other relational database + implementations, the gadfly backend has now been removed from the Roundup + distribution. + +Feature: +- new instant-gratification Demo Mode +- support setting of properties on message and file through web and + email interface (thanks John Rouillard) +- allow additional control over the roundupdb email sending (explicit + cc addresses, different from address and different nosy list property) + (thanks John Rouillard) +- applied patch for nicer history display (sf feature 638280) +- cleaning old unused sessions only once per hour, not on every cgi + request. It is greatly improves web interface performance, especially + on trackers under high load +- added mysql backend (see doc/mysql.txt for details) +- switch metakit to use "compressed" multilink journal change representation +- metakit now handles "unset" for most types (not Number and Boolean) +- fixed bug in metakit search-by-ID +- added ability to display localized dates in web interface. User input is + convered to GMT (see doc/upgrading.txt). +- added a form to show a specific issue +- more proper sorting/grouping on mulitilink properties. Sorting is performed + not only by number of links, but also by links itself. This makes usable + grouping e.g. by topic multilink +- add "ago" to intervals in the past (sf bug 679232) +- included UN*X manual pages from Bastian Kleineidam +- implemented extension to form parsing to allow editing of multiple items + and creation of multiple items (but only one per class) +- the colon ":" special form variable designator may now be any of : + @ +- trackers' templates directory can contain subdirectories with static files + (e.g. images). They are accessible naturally: _file/images/img.gif +- altered Class.create() and FileClass.create() methods to make "content" + property available in auditors +- can now configure CC to author only for messages creating issues (sf + feature 625808) +- registration is now a two-step process, with confirmation from the email + address supplied in the registration form +- added password reset feature for forgotten password / login +- added support for last-modified and if-modified-since headers for static + file serving +- added Node.get() method +- nicer page titles (sf feature 65197) +- relaxed CVS importing (sf feature 693277) +- added support for searching on ranges of dates and intervals (see + doc/user_guide.txt in chapter "Searching Page" for details) (closes sf + feature 700178) +- role names made case insensitive +- added ability to restore retired nodes +- more lenient date input and addition Interval input support (sf bug 677764) +- roundup mailgw now handles apop +- implemented ability to search for multilink properties with no value +- Class.find() may now find unset Links (sf bug 700620) +- more flexibility in classhelp link labelling (sf feature 608204) +- added command-line functionality for roundup-admin (sf feature 687664) +- added nicer popup windows for topic, nosy, etc (has add/remove buttons) + thanks Gus Gollings +- HTML templating files now have a .html extension +- Roundup templates are now distributed much more sanely, allowing for + 3rd-party templates. +- extended date syntax to make range searches even more useful +- SMTP login and TLS support added (sf bug 710853 with extras ;) + Note: requires python 2.2+ +- added Windows Service mode for roundup-server when daemonification is + attempted on Windows. +- sort HTMLClass.properties results by name (sf feature 724738) +- nicer index navigation (sf feature 676866) + +Fixed: +- applied unicode patch. All data is stored in utf-8. Incoming messages + converted from any encoding to utf-8, outgoing messages are encoded + according to rfc2822 (sf bug 568873) +- fixed layout issues with forms in sidebar +- fixed timelog example so it handles new issues (sf bug 678908) +- handle missing os.fork() (sf bug 681046) +- added warning filter for "FutureWarning: hex/oct constants > sys.maxint will + return positive values..." (literal 0xffff0000 in portalocker.py) +- fixed ZPT code generating SyntaxWarning for assignment to None +- open static files using binary mode (sf bug 693208) +- fixed deja-vu bug 692910 +- don't display "Editing" on read-only pages (sf bug 651967) +- re-worked detectors initialisation - woohoo, no more cross-importing! +- fixed export/import of retired nodes (sf bug 685273) +- remember the display template specified during edit (sf bug 701815) +- added example HTML tempating for vacation flag (sf bug 701722) +- finally, tables autosize columns (sf bug 609070) +- added creation to index columns (sf bug 708247) +- fixed missing (pre-commit) journal entries in *dbm backends (sf bug 679217) +- URL cited in roundup email confusing dumb Email clients (sf bug 716585) +- set title on issues even when the email body is empty (sf bug 727430) +- under the heading of "questionable whether it's a fix or not" + (sf "bug" 621226 for the users of the "standards compliant" browser IE) + + +2003-05-08 0.5.7 +- fixed Interval maths (sf bug 665357) +- fixed sqlite rollback/caching bug (sf bug 689383) +- fixed rdbms table update detection logic (sf bug 703297) +- fixed detection of bad date specs (sf bug 691439) +- required String properties not being flagged (thanks Ajit George) +- only look for CSV files when importing (thanks Dan Grassi) +- can now unset values in CSV editing (sf bug 704788) +- fixed rdbms email address lookup (case insensitivity) +- email file attachments added to issue files list (sf bug 711501) +- added socket timeout to attempt to prevent stuck processes (sf bug 665487) +- email registered users shouldn't be able to log in (sf bug 714673) +- handle missing addresses on users (sf bug 724537) + + +2003-02-27 0.5.6 +- fixed templating filter function arguments (sf bug 678911) +- fixed multiselect in searching (sf bug 676874) +- fixed parsing of content-disposition filenames (sf bug 675116) +- added 'h' to roundup-server optarg list (sf bug 674070) +- fixed doc for db.history in anydbm and rdbms_common (sf bug 679221) +- fixed roundup-reminder (sf bug 681042) +- fixed int assumptions about Number values (sf bug 677762) +- clarified licensing +- another attempt to fix cookie misbehaviour - customise cookie name using + tracker name +- fixed error in indexargs_url (thanks Patrick Ohly) +- fixed getnode (sf bug 684531) +- fixed args to some date templating methods (sf bug 689670) +- fixed database corruption in rdbms property mutation + + +2003-01-24 0.5.5 +- fixed rdbms searching by ID (sf bug 666615) +- fixed metakit searching by ID +- detect corrupted index and raise semi-useful exception (sf bug 666767) +- open server logfile unbuffered +- revert StringHTMLProperty to not hyperlink text by default +- fixes to CGI form handling +- fix unlink bug in metakit backend +- fixed hyperlinking ambiguity (sf bug 669777) +- fixed cookie path to use TRACKER_WEB (sf bug 667020) (thanks Nathaniel Smith + for helping chase it down and Luke Opperman for confirming fix) + + +2003-01-10 0.5.4 +- key the templates cache off full path, not filename +- implemented whole-database locking +- hyperlinking of special text (url, email, item designator) in messages +- fixed time default in date.py +- fixed error in cgi/templates.py (sf bug 652089) +- fixed handling of missing password (sf bug 655632) +- applied patches for handling Outlook quirks (thanks Andrey Lebedev) + (multipart/alternative, "fw" and content-type "name") +- fire auditors and reactors in rdbms retire (thanks Sheila King) +- better match for mailgw help "command" text +- handle :add: better in cgi form parsing (sf bug 663235) +- handle all-whitespace multilink values in forms (sf bug 663855) +- fixed searching on date / interval fields (sf bug 658157) +- fixed form elements names in search form to allow grouping and sorting + on "creation" field +- display of saved queries is now performed correctly + + +2002-12-11 0.5.3 +- added mention of how to give users multiple Roles +- mention needed trailing "/" in TRACKER_WEB +- fixed upgrading doc to have CGI changes in the correct order +- fixed double-close of anydbm backend (sf bug 639030) +- removed use of string/strop from TAL/TALInterpreter +- handle KeyboardInterrupt nicely +- fixed Date and Interval form value handling +- fixed Date.local() +- email quoted text stripping is controllable again (sf bug 650742) +- extract attachment name from content-disposition if name is missing (sf + bug 637278) +- removed FILTER_POSITION from bundled configs +- reverse message listing in issue display (reversion of recent change) +- bad entries for multilink editing in cgi don't traceback now (sf bug 640310) +- detect and break email loops (sf bug 640854) +- finished of handling of retired flag in filter() (sf bug 635260) +- allow StringHTMLProperty in MultilinkHTMLProperty test to work +- don't set explicit None Link properties in web create +- fixed nasty sorting bug that was lowercasing properties +- allow multiple :remove and :add elements per property being edited +- added date header to emails (sf bug 651358) + + +2002-11-07 0.5.2 +- added quotes around python interpreter in windows bat (sf bug 623963) +- fixed link at end of installation doc (sf bug 623957) +- handle "classname" URL path errors cleaner (generate a 404) +- added CGI :remove: and :add: which specify item ids to + remove / add in multilink. +- bugfix in boolean templating +- remember the change note on bad submissions (sf bug 625989) +- highlight required form fields (sf bug 625989) +- force non-word boundary to match re: in subject (sf bug 626303) +- handle sqlite bug (<2.7.2) (sf bug 630828) +- handle missing props in anydbm stringFind +- updated email package address formatting (deprecation) +- copied email address quoting from email v2.4.3 so we're consistent with 2.2 +- email summary extraction now takes the first whole sentence or line - + whichever is longer +- documented dependency on Active State (sf bug 623959) +- ensured there's no zero-length files in source (sf bug 633622) +- added ID to the search page (sf bug 631601) +- fixed filtering by id in anydbm +- show issue ID in the headings (sf bug 631598) +- show entire messages by default in issues (sf bug 625995) +- fixed journalling to save old values instead of new (sorry it took so long GM) +- handle missing REQUEST_URI for cgi-bin users (sf bug 620163) + + +2002-10-16 0.5.1 +- highlight rows in groups of three +- metakit cleanups +- nicer "navigation" style in index views +- handle missing Link values in anydbm backend set() operation +- fixed filter() with no sort/group (sf bug 618614) +- fixed register with no session (sf bug 618611) +- fixed log / pid file path handling in roundup-server (sf bug 617981) +- fixed old gadfly compatibiltiy problem, for sure this time (sf bug 612873) +- https URLs from config now recognised as valid (sf bug 619829) +- nicer display of tracker list in roundup-server (sf bug 619769) +- fixed some missed renaming instance -> tracker (sf bug 619769) +- allow blank passwords again (sf bug 619714) +- expose the tracker config as a variable for templating +- homogenise newlines in CGI text submissions (sf bug 614072) +- merged Zope Collector #372 fix from ZPT CVS trunk +- fixed history to display username instead of userid +- shipped templates didn't import all hyperdb types in dbinit.py +- fixed bug in Interval serialisation +- handle "unset" status in status auditor (sf bug 621250) +- issues in 'done-cbb' are now also moved to 'chatting' on new messages +- implemented the missing Interval.__add__ +- added ability to implement new templating utility methods +- expose the Date.pretty method to templating +- made form table cell alignment consistent (sf bug 621887) +- include stylesheet in docs (sf bug 623183) +- store PIPE messages so we can re-send them on errors (sf bug 623082) +- implemented "retire" cgi action, added to user index (sf bug 618612) +- included doc ideas from Bernhard Reiter (sf feature 621941) + + +2002-10-02 0.5.0 +- fixed style for alternating rows in user lists +- fixed query edit form so it doesn't barf +- #617133 ] 0.5.0pr1 uses nonexistent renderTemplate +- merged Zope Collector #539 fix from ZPT CVS trunk + + +2002-09-27 0.5.0 pr1 +- handling of None for Date/Interval/Password values in export/import +- handling of journal values in export/import +- password edit now has a confirmation field +- registration error punts back to register page +- gadfly backend now handles changes to the schema - but only one property + at a time +- cgi.client base URL is now obtained from the config TRACKER_WEB +- request.url has gone away - there's too much magic in trying to figure + what it should be +- cgi-bin script redirects to https now if the request was https +- FileClass "content" property wasn't being returned by getprops() in most + backends +- we now verify instance attributes on instance open and throw a useful error + if they're not all there +- sf 611217 ] menu() has problems when labelprop==None +- verify contents of tracker module when the tracker is opened +- many performance improvements in *dbm and sql backends +- mailgw was missing an "import sys" +- setup now installs scripts with python -O flag, doubling performance in some + cases (there's a lot of __debug__ use) +- fix :required for Link menus +- import wasn't setting the ID to maxid+1 +- added getItem to HTMLClass so you can access arbitrary items in templates +- index filtering form values may now be key values too +- replaced the content() callback ickiness with Page Template macro usage +- changed the default CSS style to be less offensive to some ;) +- better handling of Page Template compilation errors +- handle multiple unrelated indexed classes +- #614188 ] Exception in mailgw.py +- #613310 ] traceback on onexistant items +- #613291 ] typos in nosy list +- handle stupid mailers that QUOTE their Re; 'Re: "[issue1] bla blah"' +- giving a user a Role that doesn't exist doesn't break stuff any more +- revamped user guide, customisation guide, added maintenance guide +- merge Zope Collector #538 fix from ZPT CVS trunk (path expressions with a + non-path final alternate no longer try to call a value returned by that + alternate) +- merge Zope Collector #573 fix from ZPT CVS trunk +- merge Zope Collector #580 fix from ZPT CVS trunk +- added "crypt" password encoding and ability to set password with + already encrypted password through roundup-admin +- fixed the mailgw so that anonymous users may still access it +- add hook to allow external password verification, overridable in the + tracker interfaces module +- fixed login attempt by user that doesn't exist + + +2002-09-13 0.5.0 beta2 +- all backends now have a .close() method, and it's used everywhere +- fixed bug in detectors __init__ +- switched the default issue item display to only show issue summary + (added instructions to doc to make it display entire content) +- MANIFEST.in was missing a lot of template files +- added generic item editing +- much nicer layout of template rendering errors +- added context/is_edit_ok and context/is_view_ok convenience methods and + implemented use of them in the classic template + + +2002-09-11 0.5.0 beta1 +Fixed: +- #576086 ] dumb copying mistake (frontends/ZRoundup.py) +- installation instructions now mention "python2" in "testing your python". +- made the unit tests run again - they were quite b0rken +- #571170 ] gdbm deadlock +- #576241 ] MultiLink problems in parsePropsFromForm +- fixed the date module so that Date(". - 2d") works +- web forms may now unset Link values (like assignedto) +- cleanup: moved roundup.templatebuilder to roundup.templates.builder +- instance __init__ no longer silently traps dbinit import errors + +Feature: +- new backend for metakit (thanks Gordon McMillan) +- new backend for gadfly (it's as done as it's going to get) +- further split the dbm backends from the core code, allowing easier + non-dict-like backends (eg metakit, RDB) +- implemented and used the new access control mechanisms (Permissions, Roles) + (see doc/security.txt) +- switched templating to use Zope's PageTemplates (yay!) +- switched to sessions for web authentication +- added Boolean and Number types +- fixed the journal bloat +- updated design document for new access controls +- updated customisation document, including more examples +- entire database export and import (incl files) +- better mailgw help message (feature request #558562) +- re-enabled link backrefs from messages (feature request #568714) +- the page layout is now templatable +- re-worked cgi interface to abstract out the explicit "issue" interface +- have index page handle mid-page errors better so header and footer are + still visible +- we handle "not found", access and item page render errors better +- fixed double-submit by having new-item-submit redirect at end +- daemonify roundup-server (fork, logfile, pidfile) +- modify cgitb to display PageTemplate errors better +- rename to "instance" to "tracker" +- have roundup.cgi pick up tracker config from the environment +- revamped look and feel in web interface +- cleaned up stylesheet usage +- several bug fixes and documentation fixes +- added is_retired test to hyperdb.Class +- added capability to save queries: + - a query Class with name, klass (to search) and url (query string) + properties + - a Multilink to query on user called queries + - html templates for query, and a list of queries in user.item + - search form has Save button & name input + - saved queries put in menu in pagehead + - for migration, none of the above is required and old behavior preserved. + - showquery translates search form <-> query string +- cleaned up the indexer code: + - it splits more words out + - removed code we'll never use (roundup.roundup_indexer has the full + implementation, and replaces roundup.indexer) + - only index text/plain and rfc822/message (ideas for other text formats to + index are welcome) + - added simple unit test for indexer. Needs more tests for regression. + - all String properties may now be indexed too. Currently there's a bit of + "issue" specific code in the actual searching which needs to be + addressed. In a nutshell: + + pass 'indexme="yes"' as a String() property initialisation arg, eg: + file = FileClass(db, "file", name=String(), type=String(), + comment=String(indexme="yes")) + + the comment will then be indexed and be searchable, with the results + related back to the issue that the file is linked to + - as a result of this work, the FileClass has a default MIME type that may + be overridden in a subclass, or by the use of a "type" property as is + done in the default templates. + - the regeneration of the indexes (if necessary) is done once the schema is + set up in the dbinit. + - new "reindex" command in roundup-admin used to force regeneration of the + index +- added email display function - mangles email addrs so they're not so easily + scraped from the web +- switched to using a session-based web login +- made mailgw handle set and modify operations on multilinks (bug #579094) +- fixed the journal bloat from multilink changes - we just log the add or + remove operations, not the whole list + + +2002-06-24 0.4.2 +Fixed: +- Cleaned up the hyperdb unit tests. +- Applied patch from Andrew W. Nosenko to give nicer Unauthorised message + when anonymous user tries to edit. Should've been applied in 0.4.2pr1. Oops. +- Added more detailed note to MIGRATION regarding the detectors changes. + + +2002-06-19 0.4.2pr1 +Feature: +- added a "detectors" directory for people to put their useful auditors and + reactors in. Note - the roundupdb.IssueClass.sendmessage method has been + split and renamed "nosymessage" specifically for things like the nosy + reactor, and "send_message" which just sends the message. +- link() htmltemplate function now has a "showid" option for links and + multilinks. When true, it only displays the linked node id as the anchor + text. The link value is displayed as a tooltip using the title anchor + attribute. + To use in eg. the superseder field, have something like this: + + + + +
View: +
+ +- stripping of the email message body can now be controlled through the + config variables EMAIL_KEEP_QUOTED_TEXT and EMAIL_LEAVE_BODY_UNCHANGED. +- all database files created are now group readable and writable. +- added option to automatically add the authors and recipients of messages + to the nosy lists with the options ADD_AUTHOR_TO_NOSY (default 'new') and + ADD_RECIPIENTS_TO_NOSY (default 'new'). These settings emulate the current + behaviour. Setting them to 'yes' will add the author/recipients to the nosy + on messages that create issues and followup messages. +- reverting to dates for intervals > 2 months sucks +- changed the default message list in issues to display the message body +- applied patch #558876 ] cgi client customization +- split instance initialisation into two steps, allowing config changes + before the database is initialised. +- don't create an empty message on email issue creation if the email is empty +- may now display additional fields in Multilink form menus +- #541941 ] changing multilink properties by mail +- #526730 ] search for messages capability +- #505180 ] split MailGW.handle_Message + - also changed cgi client since it was duplicating the functionality + +Fixed: +- stop sending blank (whitespace-only) notes +- cleanup of serialisation for database storage +- node ids are now generated from a lockable store - no more race conditions +- sorting was applied to all nodes of the MultiLink class instead of + to the nodes that are actually linked to in the "field" template + function. This adds about 20+ seconds in the display of an issue if + your database has a 1000 or more issues in it. +- added missing documentation for a few of the config option values +- file upload broke if you didn't supply a change note +- fixed SCRIPT_NAME in ZRoundup for instances not at top level of Zope + (thanks dman) +- fixed some sorting issues that were breaking some unit tests under py2.2 +- mailgw test output dir was confusing the init test (but only on 2.2 *shrug*) +- node caching now works, and gives a small boost in performance +- #449374 ] re-enable bsddb3 backend + bsddb3 backend now works, reinstating +- #551483 ] assignedto in Client.make_index_link +- made backends.__init__ be more specific about which ImportErrors it really + wants to ignore +- fixed the example addresses in the templates to use correct example domains +- cleaned out the template stylesheets, removing a bunch of junk that really + wasn't necessary (font specs, styles never used) and added a style for + message content +- build htmlbase if tests are run using CVS checkout +- #565979 ] code error in hyperdb.Class.find +- #565996 ] The "Attach a File to this Issue" fails +- #564271 ] find() and new properties +- #562130 ] cookie path generated from ZRoundup was wrong in some situations +- remove CR characters embedded in messages (ZRoundup) +- properly quote the email address and "real name" in all situations using the + 'email' module if it is available and 'rfc822' otherwise +- #565992 ] if ISSUE_TRACKER_WEB doesn't have the trailing '/', add it +- use the rfc822 module to ensure that every (oddball) email address and + real-name is properly quoted +- #558867 ] ZRoundup redirect /instance requests to /instance/ +- #569415 ] {version} +- #569178 ] type error + was fixed as part of the general cleanup of reactors + + +2002-03-25 - 0.4.1 +Feature: +- use blobfiles in back_anydbm which is used in back_bsddb. + change test_db as dirlist does not work for subdirectories. + ATTENTION: blobfiles now creates subdirectories for files. +- add module blobfiles in backends with file access functions. +- roundup db catch only IOError in getfile. +- roundup db catches retrieving not existing files. +- #503204 ] mailgw needs a default class + - partially done - the setting of additional properties can wait for a + better configuration system. +- Alternate email addresses are now available for users. See the MIGRATION + file for info on how to activate the feature. +- #511168 ] Web interface: Adding new products + Classes that don't provide template html get a default edit interface now: + - access using the admin "class list" interface + - limited to admin-only + - requires the csv module from object-craft (url given if it's missing) +- Added popup help for classes using the classhelp html template function. + - add + to an item page, and it generates a link to a popup window which displays + the id, name and description for the priority class. The description + field won't exist in most installations, but it will be added to the + default templates. +- #517734 ] web header customisation is obscure +- All messages sent to the nosy list are now encoded as + quoted-printable before they are sent. +- Fixed display of mutlilink properties when using the template + functions, menu and plain. + +Fixed: +- Clean up mail handling, multipart handling. +- respect encodings in non multipart messages. +- makeHtmlBase: re.sub under python 2.2 did not replace '.', string.replace + does it. +- preamble in tepmlateBuilder mentioned htmldata +- mailgw checks encoding on first part too. +- #511586 ] unittest FAIL: testReldate_date +- Added a uniquely Roundup header to email, "X-Roundup-Name" +- All forms now have "double-submit" protection when Javascript is enabled + on the client-side. +- #516883 ] mail interface + ANONYMOUS_REGISTER +- #516854 ] "My Issues" and redisplay +- #517906 ] Attribute order in "View customisation" +- #514854 ] History: "User" is always ticket creator +- wasn't handling cvs parser feeding correctly +- fixed some problems in date calculations (calendar.py doesn't handle over- + and under-flow). Also, hour/minute/second intervals may now be more than + 99 each. +- #527416 ] roundup-admin uses undefined value +- #527503 ] unfriendly init blowup when parent dir + (also handles UsageError correctly now in init) +- #524129 ] roundup-admin gets python path wrong + + +2002-01-24 - 0.4.0 +Feature: +- much nicer history display (actualy real handling of property types etc) +- journal entries for link and mutlilink properties can be switched on or + off +- properties in change note are now sorted +- you can now use the roundup-admin tool pack the database + +Fixed: +- the mail gateway now responds with an error message when invalid values + for arguments are specified for link or mutlilink properties +- modified unit test to check nosy and assignedto when specified as arguments +- handle attachments with no name (eg tnef) +- fixed setting nosy as argument in subject line +- fixed back_bsddb so it passed the journal tests +- fixed status changes in mail gateway (eg. unread -> chatting) +- we'll actually distribute the frontends directory now, as advertised... +- handle stripping of "AW:" from subject line +- htmltemplate list() wasn't sorting... +- unit tests for html templating (and re-enabled the listbox field for + multilinks) +- allow abbreviation of "help" in admin tool too. +- run_tests testReldate_date failed if LANG is 'german' +- mailgw failures (unexpected ones) are forwarded to the roundup admin + + +2002-01-16 - 0.4.0b2 +Fixed: +- #495392 ] empty nosy -patch +- #500574 ] messageid must have format +- fixed some problems with web editing and change detection +- mail splitting wasn't detecting responses in the same "section" as quoted + text +- missed a "from i18n import _" in date.py +- #501690 ] MIGRATION.txt incomplete +- #502342 ] pipe interface +- #502437 ] rogue reactor and unittest +- re-enabled dumbdbm when using python >2.1.1 (ie 2.1.2, 2.2) +- changed all config accesses so they access either the instance or the + config attriubute on the db. This means that all config is obtained from + instance_config instead of the mish-mash of classes. This will make + switching to a ConfigParser setup easier too, I hope. +- #502951 ] adding new properties to old database +- #502953 ] nosy-like treatment of other multilinks +- #503164 ] create and passwords +- plain rendering of links in the htmltemplate now generate a hyperlink to + the linked node's page. +- #503330 ] ANONYMOUS_REGISTER now applies to mail +- #503353 ] setting properties in initial email +- #502956 ] filtering by multilink not supported +- #503340 ] creating issue with [asignedto=p.ohly] +- #502949 ] index view for non-issues and redisplay +- #503793 ] changing assignedto resets nosy list +- lots of date/interval related changes: + - more relaxed date format for input + - handle None for date/interval properties + + +2002-01-08 - 0.4.0b1 +Feature: +- Added INSTANCE_NAME to configuration - used in web and email to identify + the instance. +- Added EMAIL_SIGNATURE_POSITION to indicate where to place the roundup + signature info in e-mails. +- Some more flexibility in the mail gateway and more error handling. +- Login now takes you to the page you back to the were denied access to. +- Admin user now can has a user index link on their web interface. +- We now have basic transaction support. Information is only written to + the database when the commit() method is called. Only the anydbm and + bsddb3 backends are modified in this way - the bsddb3 backend needs a + lot more work anyway... + - the CGI and mailgw automatically commit() at the end of processing a + single transaction + - the admin tool requires an explicit "commit" - it will prompt at exit + if there are unsaved changes. A "rollback" removes all changes made + during the session (up to the last commit). +- Added the "display" command to the admin tool - displays a node's values +- Message author's name appears in From: instead of roundup instance name + (which still appears in the Reply-To:) +- Added a Zope frontend for roundup. +- Centralised the python version check code, bumped version to 2.1.1 (really + needs to be 2.1.2, but that isn't released yet :) +- much better attaching of erroneous messages in the mail gateway +- #496356 ] Use threading in messages + This adds the tracking of messages by message-id and allows threading + using in-reply-to. Most e-mail clients support threading using this + feature, and we hope to add support for it to the web gateway. + +Fixed: +- Lots of bugs, thanks Roch? and others on the devel mailing list! +- login_action and newuser_action return values were being ignored +- Woohoo! Found that bloody re-login bug that was killing the mail + gateway. +- Fixed login/registration forwarding the user to the right page (or not, + on a failure) +- We now use weakrefs in the Classes to keep the database reference, so + the close() method on the database is no longer needed. +- #487480 ] roundup-server +- #487476 ] INSTALL.txt +- #489760 ] [issue] only subject +- fixed doc/index.html to include the quoting in the mail alias. +- fixed the backends __init__ so we can pydoc the backend modules +- web i/f reports "note added" if there are no changes but a note is entered +- we were assuming database files created by anydbm had the same name, but + this is not the case for dbm. We now perform a much better check _and_ + cope with the anydbm implementation module changing too! +- envelope-from is now set to the roundup-admin and not roundup itself so + delivery reports aren't sent to roundup (thanks Patrick Ohly) +- #495400 ] entering blanks + Values with spaces are now accepted in roundup-admin - check the long help + for details. +- #496360 ] table width does not work +- detectors were being registered multiple times +- added tests for mailgw + + +2001-11-23 - 0.3.0 +Feature: +- #467129 ] Lossage when username=e-mail-address +- #473123 ] Change message generation for author +- MailGW now moves 'resolved' to 'chatting' on receiving e-mail for an issue. +- Added Structured Text rendering to htmltemplate, thanks Brad Clements. +- Added CGI configuration via env vars (see roundup.cgi for details) +- "roundup.cgi" is now installed to "/share/roundup/cgi-bin" +- roundup-admin now accepts abbreviated commands (eg. l = li = lis = list) +- roundup-mailgw now supports unix mailbox and POP as sources of mail. +- roundup-admin now handles all hyperdb exceptions +- users may attach files to issues (and support in ext) through the web now +- incorporated patch from Roch'e Compaan implementing attachments in nosy + e-mail +- added a target version field to the extended issue schema +- added dummy hooks for I18N and some preliminary (test) markup of + translatable messages + +Fixed: +- Fixed a bug in HTMLTemplate changes. +- 'unread' to 'chatting' automagic status change was b0rken. +- Anonymous user lockout wasn't working. +- roundup-server now works on Windows, thanks Juergen Hermann. +- Fixed install documentation, also thanks Juergen Hermann. +- Fixed some URL issues in roundup.cgi, again thanks Juergen Hermann. +- bug #475347 ] WindowsError still not caught (patch from Juergen Hermann) +- bug #474749 ] indentations lost +- bug #477104 ] HTML tag error in roundup-server +- bug #477107 ] HTTP header problem +- bug #477687 ] conforming html +- bug #474372 ] Netscape 4.77 do not render Support form +- bug #477685 ] base64.decodestring breaks +- bug #477837 ] lynx does not like the cookie +- bug #477892 ] Password edit doesn't fix login cookie +- newuser_action now presents error messages rather than tracebacks. +- bug #479511 ] mailgw to pop +- bug #479508 ] roundup-admin crash on wrong class +- bad error report in hyperdb +- roundup.mailgw now handles errors on the set() and create() at the end + of processing +- roundup.mailgw also handles messages that are passed to it that don't + contain a From: line - apparently some POP servers can do this. It punts + an error message to the roundup admin. +- fixed nosy reaction and author copy handling +- errors in nosy reaction will be propogated now (were effectively being + squashed) +- re-open the database as the author in mail handling +- missing "return" in filter_section (thanks Roch'e Compaan) + + +2001-10-23 - 0.3.0 pre 3 +Feature: +- MailGW now moves 'unread' to 'chatting' on receiving e-mail for an issue. +- feature #473127: Filenames. I modified the file.index and htmltemplate + source so that the filename is used in the link and the creation + information is displayed. + Admin Tool (roundup-admin): + - Interactive mode for running multiple (independant at present) commands. + - Tabular display of nodes. + - Import and export via colon-separated files. + +Changed: +- re-organised the html templating code. Fixed some bugs, probably + introduced some more. Hopefully not too many. + +Fixed: +- Stand-alone server now has a configurable setuid user. +- CGI interface wasn't handling checkboxes at all. +- Fixed quopri usage in mailgw from bug reports on mailing list. +- Remove the "freshen" command from the roundup-admin tool. +- Catch errors in login - no username or password supplied. +- Fixed editing of password (Password property type) thanks Roch'e Compaan. +- Fixed grouping of non-str properties thanks Roch'e Compaan. +- bug #473121: The customisation view and filters (CGI interface view + customisation section may now be hidden (patch from Roch'e Compaan.) +- bug #473122: Issue id sorting (hyperdb sorts strings-that-look-like-numbers + as numbers now. +- bug #473124: UI inconsistency with Link fields. + This also prompted me to fix a fairly long-standing usability issue - + that of being able to turn off certain filters. +- bug #473125: Paragraph in e-mails +- bug #473126: Sender unknown +- bug #473130: Nosy list not set correctly + + +2001-10-11 - 0.3.0 pre 2 +Fixed: +- Hyperdatabase was inserting empty strings instead of None for missing + property values. This broke a lot of things. + + +2001-10-10 - 0.3.0 pre 1 +Feature: +- roundup-admin create now prompts for property info if none is supplied + on the command-line. +- hyperdb Class getprops() method may now return only the mutable + properties. +- CGI interfaces now generate a top-level index of their known instances. + +Changed: +- Login now uses cookies, which makes it a whole lot more flexible. We can + now support anonymous user access (read-only, unless there's an + "anonymous" user, in which case write access is permitted). Login + handling has been moved into cgi_client.Client.main() +- The "extended" schema is now the default in roundup init. +- The schemas have had their page headings modified to cope with the new + login handling. Existing installations should copy the interfaces.py + file from the roundup lib directory to their instance home. +- Passwords are now encoded by default (except exising databases which + will only be encoded when the passwords are changed). The scheme used + at the moment is SHA - but the code is flexible enough to take any + number of encoding systems. +- The roundup-admin tool always operates as the "admin" user now. Database + protection should be achieved using file system protections (see the + documentation for details.) + +Fixed: +- Incorrectly had a Bizar Software copyright on the cgitb.py module from + Ping - has been removed. +- Pretty time interval wasn't handling > 1 month properly. +- Generation of links to Link/Multilink in indexes. (thanks Hubert Hoegl) +- AssignedTo wasn't in the "classic" schema's item page. +- Fixed a whole bunch of places in the CGI interface where we should have + been returning Not Found instead of throwing an exception. +- Fixed a deviation from the spec: trying to modify the 'id' property of + an item now throws an exception. +- The plain() template function now html-escapes the content. +- Change message was stuffing up for multilinks with no key property. + + + +-------------- + +2001-08-30 - 0.2.8 +Fixed: +- Wasn't handling unguessable mime types for file uploads. +- Missing import in mailgw. + + +2001-08-29 - 0.2.7 +Feature: +- Text searches are now case insensitive. All forms of text search use + regular expressions now. + +Fixed: +- Had another 2.1-ism in the unit tests +- Made the mail parser a little more robust w.r.t missing Subject: + (both thanks Mikhail Sobolev) +- Missed some isFooType usages (thanks Mikhail Sobolev for spotting them) +- Reverted back to sending change messages to the web editor of a node so + that the change note message is actually genrated. +- CGI interface wasn't generating correct change messages. +- Notes entered during a change are saved to the messages list even if + there's no nosy list. No message is generated if there's no nosy list and + there's no change note (since it would just duplicates the journal). +- Completely removed the bsddb3 module from the tests - will be reinstated + when the http://bsddb.sourceforge.net/'s bugs #439959 and #456408 are + dealt with. One is fixed in CVS, the other pending. + + +2001-08-08 - 0.2.6 +Note: +- Roundup is now released under the same terms as the Python License. + +Feature: +- Added tests for instance initialisation. No more releasing the software + with bugs in roundup.init! +- Now bundling unittest with the package so that python 2.0 users can use + the tests. +- Much better error handling and messages generated by the mail gateway. + +Fixed: +- Implemented correct mail splitting. Added unit tests. Also snips + signatures now too. +- Bug #447671 - typo in roundup/init.py +- Changed date.Date to use regular string formatting instead of strftime - + win32 seems to have problems with %T and no hour... or something... +- Bug #448484 - now catching correct exception from makedirs. +- Instances are now opened by a special function that generates a unique + module name for the instances on import time. + + +2001-08-03 - 0.2.5 +Note: +- The bsddb3 module has a bug that renders it non-functional. Users should + select the anydbm or bsddb backend instead. + +Fixed: +- Python 2.0 does not contain the unittest module. The setup.py module now + checks for unittest before attempting to run the unit tests. + + +2001-08-03 - 0.2.4 +Features: +- Added ability for cgi newblah forms to indicate that the new node + should be linked somewhere. +- Added time logging and file uploading to the templates. +- Added "My Issues" and "My Support" to extended template. Changed "Your + Details" to "My Details". Changed the "New Foo" links to "Add Foo". + Added links for unassigned support and issues. Generally reorganised and + cleanup the header up. +- Changed the order of the information in the message generated by web edits. +- Extended the range of intervals that are pretty-printed before actual dates + are displayed. +- Added more BUILD instructions including the "clean" command to force + rebuild. +- Web edit messages aren't sent to the person who did the edit any more. No + message is generated if they are the only person on the nosy list. +- Roundupdb now appends "mailing list" information to its messages which + include the e-mail address and web interface address. Templates may + override this in their db classes to include specific information (support + instructions, etc). + +Fixed: +- Argument handling for the roundup-admin find command. +- Handling of summary when no note supplied for newblah. Again. +- Detection of no form in htmltemplate Field display. +- Checklist html template command was setting wrong name. +- 2.1-specific gmtime() (no arg) call in roundup.date. (thanks Paul Wright) +- mailgw was making naughty assumptions about the schema of the classes it + was creating nodes for. +- remove the $Foo$ from the HTML files stored in the htmlbase modules. +- Instance import now imports the instance using imp.load_module so that + we can have instance homes of "roundup" or other existing python package + names. + + +2001-07-30 - 0.2.3 +Big change: +- I've split off the support class from the issue class in "extended". + Anyone who has any support entries, sorry. It should be possible to + write a scipt that moves the entries over pretty easily. If this causes + you pain, I'll do so. You'll want to update your instance with the new + code in "extended" either way. + +Features: +- Added the unit tests to the start of setup.py so they're run whenever + we do anything distutils'y. +- Added nicer prompting to the roundup-admin "init" command. +- Actually, the roundup-admin code is totally revamped, and has command + help and better command-line arg handling. +- The cgi_client.Client base class now reflects the structure of "classic" + rather than "extended" since "classic" is more of a "base" template. +- Added more DB to test. Skips tests where imports fail. + +Fixed: +- One of the tests in test_date had the wrong expected result. +- Fixed IssueClass so that superseders links to its classname rather than + hard-coded to "issue". +- templatebuilder was catching IOError instead of OSError. +- The cgi_client newblah method wasn't detecting the __note form field + properly. +- The History command in htmltemplate didn't handle a new node (None + nodeid) properly. + + +2001-07-29 - 0.2.2 +Features: +- Added implementation.txt to the doc directory. Contains implementation + notes specific to this implementations of Roundup. +- Cleaned up mailgw some (subclass Message for getPart) and added some + tests for multipart splitting. +- Better checking for html dir in templatebuilder. +- Base hyperdb.Class now fakes the "id" property. +- Made the classic roundup look more like the original prototype. +- Made cgi_client and templating slightly more generic. +- Moved some code around in cgi_client allowing for subclassing to change + behaviour. +- Added the fabricated property "id" to all hyperdb classes. +- Cleanup of the link label generation (new method on hyperdb.Class to do + it). + +Fixed: +- Everything uses errno module now to check errno values. +- New issue form handles lack of note better now. +- HTML templating uses section-bar style for index group headers now. +- Fixed problem in link display when Link value is None. +- Form handling in cgi client wasn't propogating through the previous + query elements. +- Fixed sort arguments generated for column headings so sorting can be + changed now. + + +2001-07-28 - 0.2.1 +Features: +- Added docstring to roundup package so pydoc reports useful information. +- Added the roundup 1 software carpentry submission HTML to the doc + directory as "overview.html". + +Fixes: +- Fixed bug in init command - templatebuilder was assuming existence of + "html" directory in instance home. +- Fixed INSTALL.txt to reflect some changes in the installation and test + procedure. Whatdya know, "setup.py install" does the script install. + There you go... +- Fixed some non-string node ids in cgi_client now that the hyperdb is + strict about such things. + +2001-07-26 - 0.2.0 +Features: +- Major reorganisation of code to allow multiple roundup instances and a + single, site-packages -based installation. Also allows multiple database + back-ends. +- Moved the bin/ proggies into the top dir, so that it all works + out-of-the-box +- Added the "classic" template - a direct implementation of the Roundup + spec. Well, as close as we're going to get, anyway. +- Added an issue priority of support to "extended" +- Added command-line arg handling to roundup-server so it's more useful + out-of-the-box. +- Added distutils-style installation of "lib" files. +- Added some unit tests. + +Fixes: +- Fixed bug in re generation in the filter +- Fixed handling of None String property in grouped list headings +- Fixed adding new issue with no change note +- Fixed values in text input fields which contained quotes (") are now + quoted. +- Fixed a bug in the hyperdb filter - wrong variable names in the error + message. + +2001-07-19 - 0.1.3 +- Reldate now takes an argument "pretty" - when true, it pretty-prints the + interval generated up to 5 days, then pretty-prints the date of last + activity. The issue index and item now use the pretty format. +- Classes list for admin user in CGI interface. +- Made the view configuration more accessible, neater and more realistic. +- Fixed list view grouping handling grouping by a Multilink or String or Link + value of None or Date, ... (mind you, sorting by Date???) +- Fixed bug in the plain formatter when a Link was None. +- Fixed ordering of list view column headings. +- Fixed list view column heading sort links - and limited the number of + columns to sort by to 2. +- Added searching by glob to StringType filtering - + ^text - search for text at start of fields + text$ - search for text at end of fields + ^text$ - exactly match text in fields + te*xt - search for text matching "te""xt" + te?xt - search for text matching "te""xt" +- Added more fields to the issue.filter and issue.index templates + + +2001-07-18 - 0.1.2 +- Set default index to ?:group=priority&:columns=activity,status,title so + the priority column isn't displayed. +- Thanks Anthony: + - added notes to the README about Python prerequisites + - added check to roundup.py, roundup.cgi, server.py and roundup-mailgw.py + for python 2+ - and made the file itself parseable by 1.5.2 ;) + - python 2.0 didn't have the default args for the time module functions. + - better handling of db directory in initDB +- Sorting on the extra properties defined by roundupdb classes was broken + due to the caching used. May now sort on activity and creation + properties, etc. +- Set the default index to sort on activity + + +2001-07-18 - 0.1.1 +- Initial version release with consent of Roundup spec author, Ka-Ping Yee: + "Amazing! Nice work. I'll watch for the source code on your website." + +2001-07-11 - 0.1.0 +- Needed a bug tracking system. Looked around. Tried to install many + Perl-based systems, to no avail. Got tired of waiting for Roundup to be + released. Had just finished major product project, so needed something + different for a while. Roundup here I come... + + Added: tracker/vendor/roundup/current/COPYING.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/COPYING.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,106 @@ +Roundup Licensing +----------------- + +Copyright (c) 2003 Richard Jones (richard at mechanicalcat.net) +Copyright (c) 2002 eKit.com Inc (http://www.ekit.com/) +Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +PageTemplates Licensing +----------------------- + +Portions of this code (roundup.cgi.PageTemplates, roundup.cgi.TAL and +roundup.cgi.ZTUtils) have been copied from Zope. They have been modified in +the following manner: + +- removal of unit tests, Zope-specific code and support files from + PageTemplates: PageTemplateFile.py, ZPythonExpr.py, ZRPythonExpr.py, + ZopePageTemplate.py, examples, help, tests, CHANGES.txt, HISTORY.txt, + version.txt and www. From TAL: DummyEngine.py, HISTORY.txt, CHANGES.txt, + benchmark, driver.py, markbench.py, ndiff.py, runtest.py, setpath.py, + tests and timer.py. From ZTUtils: SimpleTree.py, Zope.py, CHANGES.txt and + HISTORY.txt. +- editing to remove dependencies on Zope modules (see files for change notes) + +The license for this code is the `Zope Public License (ZPL) Version 2.0`_, +included below. + + +Zope Public License (ZPL) Version 2.0 +------------------------------------- + +This software is Copyright (c) Zope Corporation (tm) and +Contributors. All rights reserved. + +This license has been certified as open source. It has also +been designated as GPL compatible by the Free Software +Foundation (FSF). + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the +following conditions are met: + +1. Redistributions in source code must retain the above + copyright notice, this list of conditions, and the following + disclaimer. + +2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions, and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + +3. The name Zope Corporation (tm) must not be used to + endorse or promote products derived from this software + without prior written permission from Zope Corporation. + +4. The right to distribute this software or to use it for + any purpose does not give you the right to use Servicemarks + (sm) or Trademarks (tm) of Zope Corporation. Use of them is + covered in a separate agreement (see + http://www.zope.com/Marks). + +5. If any files are modified, you must cause the modified + files to carry prominent notices stating that you changed + the files and the date of any change. + +Disclaimer + + THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS'' + AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT + NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. + + +This software consists of contributions made by Zope +Corporation and many individuals on behalf of Zope +Corporation. Specific attributions are listed in the +accompanying credits file. + Added: tracker/vendor/roundup/current/ChangeLog ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/ChangeLog Sun Nov 5 21:30:25 2006 @@ -0,0 +1,918 @@ +2001-08-03 11:54 richard + + * BUILD.txt, CHANGES.txt, README.txt, setup.py, + roundup/templates/classic/htmlbase.py: Started stuff off for the + 0.2.5 release + +2001-08-03 11:28 richard + + * roundup-admin, roundup-mailgw, roundup-server, + cgi-bin/roundup.cgi, roundup/init.py: Used the much nicer + load_package, pointed out by Steve Majewski. + +2001-08-03 11:19 richard + + * roundup/templates/classic/: htmlbase.py, html/issue.item, + html/style.css: finished of colourising the classic template + +2001-08-03 10:59 richard + + * CHANGES.txt: chnages + +2001-08-03 10:59 richard + + * roundup-admin, roundup-mailgw, roundup-server, + cgi-bin/roundup.cgi, roundup/init.py: Instance import now imports + the instance using imp.load_module so that we can have instance + homes of "roundup" or other existing python package names. + +2001-08-02 20:26 richard + + * README.txt: changes + +2001-08-02 16:38 richard + + * roundup/: cgi_client.py, hyperdb.py, roundupdb.py, + templates/classic/dbinit.py, templates/classic/instance_config.py, + templates/extended/dbinit.py, + templates/extended/instance_config.py: Roundupdb now appends + "mailing list" information to its messages which include the e-mail + address and web interface address. Templates may override this in + their db classes to include specific information (support + instructions, etc). + +2001-08-02 16:00 richard + + * CHANGES.txt: anges + +2001-08-02 15:55 richard + + * roundup/cgi_client.py: Web edit messages aren't sent to the + person who did the edit any more. No message is generated if they + are the only person on the nosy list. + +2001-08-02 11:01 richard + + * CHANGES.txt: changes + +2001-08-02 11:00 richard + + * BUILD.txt: Added the 'clean' command to the instructions - + distutils doesn't seem to always detect when it needs to rebuild + when it should. + +2001-08-02 10:43 richard + + * roundup/templates/extended/interfaces.py: Even better (more + useful) headings + +2001-08-02 10:36 richard + + * roundup/templates/extended/interfaces.py: Made all the + user-specific link names the same (My Foo) + +2001-08-02 10:34 richard + + * roundup/cgi_client.py: bleah syntax error + +2001-08-02 10:27 richard + + * CHANGES.txt, roundup/templates/extended/htmlbase.py: changes + +2001-08-02 10:27 richard + + * roundup/date.py: Extended the range of intervals that are + pretty-printed before actual dates are displayed. + +2001-08-02 10:26 richard + + * roundup/cgi_client.py: Changed the order of the information in + the message generated by web edits. + +2001-08-01 15:15 richard + + * CHANGES.txt: changes + +2001-08-01 15:15 richard + + * README.txt, roundup/templates/extended/htmlbase.py, + roundup/templates/extended/interfaces.py, + roundup/templates/extended/html/issue.index, + roundup/templates/extended/html/support.index: Added "My Issues" + and "My Support" to extended template. + +2001-08-01 15:06 richard + + * CHANGES.txt: changes + +2001-08-01 15:06 richard + + * roundup/: templatebuilder.py, templates/classic/htmlbase.py, + templates/extended/htmlbase.py: htmlbase doesn't have extraneous + $Foo$ in it any more + +2001-08-01 14:24 richard + + * roundup/: hyperdb.py, mailgw.py: mailgw was assuming certain + properties existed on the issues being created. + +2001-08-01 13:52 richard + + * CHANGES.txt, roundup/htmltemplate.py: Checklist was using wrong + name. + +2001-08-01 13:48 richard + + * README.txt: Just a new idea... + +2001-07-31 19:58 richard + + * CHANGES.txt: changes + +2001-07-31 19:54 richard + + * roundup/date.py: Fixed the 2.1-specific gmtime() (no arg) call in + roundup.date. (Paul Wright) + +2001-07-30 18:12 richard + + * CHANGES.txt, roundup-admin, roundup/cgi_client.py, + roundup/htmltemplate.py, roundup/templatebuilder.py, + roundup/templates/classic/htmlbase.py, + roundup/templates/classic/html/file.newitem, + roundup/templates/classic/html/issue.item, + roundup/templates/extended/htmlbase.py, + roundup/templates/extended/interfaces.py: Added time logging and + file uploading to the templates. + +2001-07-30 18:04 richard + + * roundup/templates/extended/html/: file.newitem, timelog.index, + timelog.item: oops + +2001-07-30 18:03 richard + + * roundup/templates/extended/html/: issue.item, support.item: Fixes + to the uploading stuff (I forgot to put the code in the issue class + ;) + +2001-07-30 17:17 richard + + * setup.py: Just making sure we've got the right version in there + for development. + +2001-07-30 16:26 richard + + * roundup/cgi_client.py: Added some documentation on how the + newblah works. + +2001-07-30 16:17 richard + + * roundup/: cgi_client.py, htmltemplate.py: Features: . Added + ability for cgi newblah forms to indicate that the new node + should be linked somewhere. Fixed: . Fixed the agument handling + for the roundup-admin find command. . Fixed handling of summary + when no note supplied for newblah. Again. . Fixed detection of no + form in htmltemplate Field display. + +2001-07-30 13:53 richard + + * CHANGES.txt: chanegs + +2001-07-30 13:52 richard + + * roundup-admin: init help now lists templates and backends + +2001-07-30 13:52 richard + + * roundup/backends/__init__.py: Checks for ability to import the + specific back-end module. + +2001-07-30 13:45 richard + + * test/test_db.py: Added more DB to test_db. Can skip tests where + imports fail. + +2001-07-30 12:38 richard + + * roundup/templates/: classic/htmlbase.py, extended/htmlbase.py: + updated htmlbases + +2001-07-30 12:38 richard + + * roundup/: hyperdb.py, roundupdb.py: get() now has a default arg - + for migration only. + +2001-07-30 12:37 richard + + * roundup/htmltemplate.py: Temporary measure until we have decent + schema migration. + +2001-07-30 12:37 richard + + * roundup/cgi_client.py: Temporary measure until we have decent + schema migration... + +2001-07-30 12:37 richard + + * roundup-admin: Freshen is really broken. Commented out. + +2001-07-30 12:36 richard + + * roundup/backends/: back_bsddb.py, back_bsddb3.py: Handle + non-existence of db files in the other backends (code from anydbm). + +2001-07-30 12:35 richard + + * roundup/templates/extended/html/issue.item: Should've been + supportcall + +2001-07-30 11:47 richard + + * roundup/templates/extended/html/issue.item: Forgot to add the + support call property to the item page. + +2001-07-30 11:41 richard + + * roundup/backends/: back_anydbm.py, back_bsddb.py, back_bsddb3.py: + Makes schema changes mucho easier. + +2001-07-30 11:32 richard + + * CHANGES.txt, README.txt: noted changes + +2001-07-30 11:28 richard + + * roundup-admin: Bugfixes + +2001-07-30 11:28 richard + + * roundup/templates/__init__.py: Support for determining the + installed tempaltes + +2001-07-30 11:27 richard + + * roundup/templates/extended/html/: support.filter, support.index, + support.item: Oops - these are the HTML displays for the support + class. + +2001-07-30 11:26 richard + + * roundup/templates/extended/: dbinit.py, htmlbase.py, + interfaces.py, html/issue.filter, html/issue.index, + html/issue.item: Big changes: . split off the support priority + into its own class . added "new support, new user" to the page + head . fixed the display options for the heading links + +2001-07-30 11:25 richard + + * roundup/templates/classic/: htmlbase.py, interfaces.py: Changes + to reflect cgi_client now implementing this template by default, + and not "extended". + +2001-07-30 11:25 richard + + * roundup/cgi_client.py: Default implementation is now "classic" + rather than "extended" as one would expect. + +2001-07-30 11:24 richard + + * roundup/htmltemplate.py: Handles new node display now. + +2001-07-30 10:57 richard + + * roundup-admin: Now uses getopt, much improved command-line + parsing. Much fuller help. Much better internal structure. It's + just BETTER. :) + +2001-07-30 10:06 richard + + * roundup/templatebuilder.py: Hrm - had IOError instead of OSError. + Not sure why there's two. Ho hum. + +2001-07-30 10:05 richard + + * roundup/roundupdb.py: Fixed IssueClass so that superseders links + to its classname rather than hard-coded to "issue". + +2001-07-30 10:04 richard + + * roundup-admin: Made the "init" prompting more friendly. + +2001-07-30 09:34 richard + + * CHANGES.txt, roundup/templates/classic/htmlbase.py, + roundup/templates/extended/htmlbase.py: changes + +2001-07-30 09:34 richard + + * setup.py: Added unit tests so they're run whenever we + package/install/whatever. + +2001-07-30 09:32 richard + + * test/test_dates.py: Fixed bug in unit test ;) + +2001-07-29 19:43 richard + + * setup.py: Make sure that the htmlbase is up-to-date when we build + a source dist. + +2001-07-29 19:33 richard + + * CHANGES.txt: changes + +2001-07-29 19:31 richard + + * roundup/htmltemplate.py: oops + +2001-07-29 19:28 richard + + * roundup/: htmltemplate.py, hyperdb.py: Fixed sorting by clicking + on column headings. + +2001-07-29 18:37 richard + + * CHANGES.txt, README.txt, setup.py: changes + +2001-07-29 18:27 richard + + * roundup/: cgi_client.py, htmltemplate.py, hyperdb.py: Fixed + handling of passed-in values in form elements (ie. during a + drill-down) + +2001-07-29 17:01 richard + + * README.txt, roundup-admin, roundup-mailgw, roundup-server, + setup.py, cgi-bin/roundup.cgi, roundup/__init__.py, + roundup/cgi_client.py, roundup/cgitb.py, roundup/date.py, + roundup/htmltemplate.py, roundup/hyperdb.py, roundup/init.py, + roundup/mailgw.py, roundup/roundupdb.py, + roundup/templatebuilder.py, roundup/templates/classic/__init__.py, + roundup/templates/classic/dbinit.py, + roundup/templates/classic/instance_config.py, + roundup/templates/classic/interfaces.py, + roundup/templates/extended/__init__.py, + roundup/templates/extended/dbinit.py, + roundup/templates/extended/instance_config.py, + roundup/templates/extended/interfaces.py, test/README.txt, + test/__init__.py, test/test_dates.py, test/test_db.py, + test/test_multipart.py, test/test_schema.py: Added vim command to + all source so that we don't get no steenkin' tabs :) + +2001-07-29 16:42 richard + + * test/test_dates.py: Added Interval tests. + +2001-07-29 15:41 richard + + * CHANGES.txt: changes + +2001-07-29 15:36 richard + + * roundup/: htmltemplate.py, hyperdb.py: Cleanup of the link label + generation. + +2001-07-29 14:11 richard + + * CHANGES.txt: Reverse the entries so most recent is first. + +2001-07-29 14:09 richard + + * test/test_db.py: Added the fabricated property "id" to all + hyperdb classes. + +2001-07-29 14:07 richard + + * roundup/templates/classic/: interfaces.py, html/file.index, + html/issue.filter, html/issue.index, html/issue.item, + html/msg.index, html/msg.item, html/style.css, html/user.index, + html/user.item: Fixed the classic template so it's more like the + "advertised" Roundup template. + +2001-07-29 14:06 richard + + * roundup/htmltemplate.py: Fixed problem in link display when Link + value is None. + +2001-07-29 14:05 richard + + * roundup/: hyperdb.py, roundupdb.py: Added the fabricated property + "id". + +2001-07-29 14:04 richard + + * roundup/cgi_client.py: Moved some code around allowing for + subclassing to change behaviour. + +2001-07-28 18:17 richard + + * roundup/htmltemplate.py: fixed use of stylesheet + +2001-07-28 18:16 richard + + * roundup/cgi_client.py: New issue form handles lack of note better + now. + +2001-07-28 18:02 richard + + * roundup/templatebuilder.py: commented out print + +2001-07-28 17:59 richard + + * roundup/: htmltemplate.py, init.py, templatebuilder.py: Replaced + errno integers with their module values. De-tabbed + templatebuilder.py + +2001-07-28 17:35 richard + + * README.txt: todo refinement ;) + +2001-07-28 16:44 richard + + * CHANGES.txt, README.txt, doc/implementation.txt: Split off + implementation notes into separate file in doc directory. Added + some todo items to the README + +2001-07-28 16:43 richard + + * roundup/mailgw.py, test/__init__.py, test/test_multipart.py: + Multipart message class has the getPart method now. Added some + tests for it. + +2001-07-28 11:56 richard + + * CHANGES.txt, MANIFEST.in: changes + +2001-07-28 11:45 richard + + * doc/: overview.html, spec.html, images/edit.gif, images/edit.png, + images/hyperdb.gif, images/hyperdb.png, images/logo-acl-medium.gif, + images/logo-acl-medium.png, images/logo-codesourcery-medium.gif, + images/logo-codesourcery-medium.png, + images/logo-software-carpentry-standard.gif, + images/logo-software-carpentry-standard.png, images/roundup-1.gif, + images/roundup-1.png, images/roundup.gif, images/roundup.png: GIF + -> PNG, saving about 100k + +2001-07-28 11:40 richard + + * doc/: overview.html, images/edit.gif, images/hyperdb.gif, + images/roundup-1.gif, images/roundup.gif: added more documentation + +2001-07-28 11:39 richard + + * roundup/__init__.py: Added some documentation to the roundup + package. + +2001-07-28 10:39 richard + + * CHANGES.txt, setup.py: changes for the 0.2.1 distribution build. + +2001-07-28 10:34 richard + + * CHANGES.txt: changes + +2001-07-28 10:34 richard + + * roundup/: cgi_client.py, mailgw.py: Fixed some non-string node + ids. + +2001-07-28 10:31 richard + + * INSTALL.txt, roundup/templatebuilder.py: Fixed some problems with + installation. + +2001-07-27 17:33 richard + + * INSTALL.txt: more notes for installation + +2001-07-27 17:30 richard + + * BUILD.txt: minor notes + +2001-07-27 17:27 richard + + * BUILD.txt, README.txt: Added build instructions, changed my + e-mail address in the docs to the sourceforge address. + +2001-07-27 17:20 richard + + * Makefile, setup.cfg, setup.py: Makefile is now obsolete - setup + does what it used to do. + +2001-07-27 17:18 richard + + * MANIFEST.in: Added the distutils manifest template (for + "documentation", see distutils.filelist). Has no facility for + comments, so no ID or LOG for this baby. + +2001-07-27 17:16 richard + + * test/: README.TXT, README.txt: rename for consistency + +2001-07-27 17:04 richard + + * INSTALL.TXT, CHANGES.txt, INSTALL.txt, README.TXT, README.txt: + name changes to make distutils happy + +2001-07-27 16:56 richard + + * setup.cfg, setup.py: Added scripts to the setup and added the + config so the default script install dir is /usr/local/bin. + +2001-07-27 16:55 richard + + * test/: README.TXT, __init__.py, test_dates.py, test_db.py, + test_schema.py: moving tests -> test + +2001-07-27 16:25 richard + + * roundup/hyperdb.py: Fixed some of the exceptions so they're the + right type. Removed the str()-ification of node ids so we don't + mask oopsy errors any more. + +2001-07-27 15:17 richard + + * roundup/hyperdb.py: just some comments + +2001-07-26 17:14 richard + + * setup.py: Made setup.py executable, added id and log. + +2001-07-26 16:47 richard + + * INSTALL.TXT: Updated for new installation procedure + +2001-07-25 14:19 anthonybaxter + + * setup.py: first cut at setup.py - installs the package, but not + the bin/cgi-bin yet + +2001-07-25 14:09 richard + + * roundup/date.py: Fixed offset handling (shoulda read the spec a + little better) + +2001-07-25 13:40 richard + + * README.TXT: added note about the spec + +2001-07-25 13:39 richard + + * roundup/htmltemplate.py: Hrm - displaying links to classes that + don't specify a key property. I've got it defaulting to 'name', + then 'title' and then a "random" property (first one returned by + getprops().keys(). Needs to be moved onto the Class I think... + +2001-07-25 11:23 richard + + * doc/spec.html, doc/images/logo-acl-medium.gif, + doc/images/logo-codesourcery-medium.gif, + doc/images/logo-software-carpentry-standard.gif, + roundup/backends/back_anydbm.py, + roundup/templates/extended/dbinit.py: Added the Roundup spec to the + new documentation directory. + +2001-07-24 21:18 anthonybaxter + + * roundup/init.py: oops. left a print in + +2001-07-24 20:54 anthonybaxter + + * roundup/: init.py, templatebuilder.py: oops. Html. + +2001-07-24 20:46 anthonybaxter + + * roundup/: init.py, templatebuilder.py, templates/__init__.py, + templates/classic/__init__.py, templates/classic/dbinit.py, + templates/classic/htmlbase.py, templates/extended/__init__.py, + templates/extended/htmlbase.py: Added templatebuilder module. two + functions - one to pack up the html base, one to unpack it. Packed + up the two standard templates into htmlbases. Modified __init__ to + install them. + + __init__.py magic was needed for the rather high levels of wierd + import magic. Reducing level of import magic == (good, future) + +2001-07-24 14:26 anthonybaxter + + * roundup/backends/back_bsddb3.py: bsddb3 implementation. For now, + it's the bsddb implementation with a "3" added in crayon. + +2001-07-24 11:07 richard + + * roundup-server: Added command-line arg handling to roundup-server + so it's more useful out-of-the-box. + +2001-07-24 11:06 richard + + * roundup/templates/classic/dbinit.py: Oops - accidentally duped + the keywords class + +2001-07-24 09:32 richard + + * INSTALL.TXT: minor edit + +2001-07-24 09:28 richard + + * roundup/templates/: README.txt, classic/__init__.py, + classic/dbinit.py, classic/instance_config.py, + classic/interfaces.py, classic/detectors/__init__.py, + classic/detectors/nosyreaction.py, classic/html/file.index, + classic/html/issue.filter, classic/html/issue.index, + classic/html/issue.item, classic/html/msg.index, + classic/html/msg.item, classic/html/style.css, + classic/html/user.index, classic/html/user.item: Adding the classic + template + +2001-07-24 09:20 richard + + * roundup/templates/extended/dbinit.py: forgot to remove the + interfaces from the dbinit module ;) + +2001-07-24 09:16 richard + + * roundup/templates/extended/: __init__.py, interfaces.py: Split + off the interfaces (CGI, mailgw) into a separate file from the DB + stuff. + +2001-07-23 20:31 richard + + * roundup-server: disabled the reloading until it can be done + properly + +2001-07-23 18:55 richard + + * CHANGES, INSTALL.TXT, README, README.TXT: renamed the text files + so that they're recognised as text files on windows added + INSTALL.TXT + +2001-07-23 18:53 richard + + * README, roundup-server: Fixed the ROUNDUPS decl in roundup-server + Move the installation notes to INSTALL + +2001-07-23 18:45 richard + + * roundup-admin, roundup/init.py, + roundup/templates/extended/dbinit.py: ok, so now "./roundup-admin + init" will ask questions in an attempt to get a workable + instance_home set up :) _and_ anydbm has had its first test :) + +2001-07-23 18:25 richard + + * roundup/backends/back_bsddb.py: more handling of bad journals + +2001-07-23 18:20 richard + + * roundup-admin, roundup/backends/back_anydbm.py, + roundup/backends/back_bsddb.py: Moved over to using marshal in the + bsddb and anydbm backends. roundup-admin now has a "freshen" + command that'll load/save all nodes (not retired - mod + hyperdb.Class.list() so it lists retired nodes) + +2001-07-23 17:56 richard + + * roundup/: date.py, backends/back_bsddb.py: Storing only + marshallable data in the db - no nasty pickled class references. + +2001-07-23 17:22 richard + + * roundup/backends/: __init__.py, _anydbm.py, _bsddb.py, + back_anydbm.py, back_bsddb.py: *sigh* some databases have _foo.so + as their underlying implementation. This time for sure, Rocky. + +2001-07-23 17:15 richard + + * roundup/backends/: _anydbm.py, _bsddb.py, bsddb.py: Moved the + backends into the backends package. Anydbm hasn't been tested at + all. + +2001-07-23 17:14 richard + + * roundup/: roundupdb.py, backends/__init__.py, + templates/extended/dbinit.py: Moved the database backends off into + backends. + +2001-07-23 16:25 richard + + * roundup/templates/extended/dbinit.py: relfected the move to + roundup/backends + +2001-07-23 16:24 richard + + * roundup/backends/__init__.py: made backends a package + +2001-07-23 16:23 richard + + * roundup/: hyper_bsddb.py, backends/bsddb.py: moved hyper_bsddb.py + to the new backends package as bsddb.py + +2001-07-23 14:49 anthonybaxter + + * README: changed the 'snip' lines so they don't look like CVS + conflict markers. + +2001-07-23 14:47 anthonybaxter + + * cgi-bin/roundup.cgi: renamed ROUNDUPS to ROUNDUP_INSTANCE_HOMES + sys.exit(0) if python version wrong. + +2001-07-23 14:33 richard + + * cgi-bin/roundup.cgi: brought the CGI instance config dict in line + with roundup-server + +2001-07-23 14:33 anthonybaxter + + * roundup/templates/extended/: __init__.py, dbinit.py, + instance_config.py: split __init__.py into 2. dbinit and + instance_config. + +2001-07-23 14:31 richard + + * CHANGES, cgi-bin/roundup.cgi: Fixed the roundup CGI script for + updates to cgi_client.py + +2001-07-23 14:21 richard + + * roundup/templates/extended/: html/file.index, html/issue.filter, + html/issue.index, html/issue.item, html/msg.index, html/msg.item, + html/style.css, html/user.index, html/user.item, issue.filter, + issue.item, msg.item, style.css, user.item: moving HTML templates + to their own dir + +2001-07-23 14:19 richard + + * roundup/templates/extended/: file.index, issue.index, msg.index, + user.index: moving the HTML templates into their own dir + +2001-07-23 14:05 anthonybaxter + + * roundup-server: actually quit if python version wrong + +2001-07-23 13:56 richard + + * roundup/cgi_client.py: oops, missed a config removal + +2001-07-23 13:50 anthonybaxter + + * roundup/templates/extended/: __init__.py, file.index, + issue.filter, issue.index, issue.item, msg.index, msg.item, + style.css, user.index, user.item, detectors/__init__.py, + detectors/nosyreaction.py: moved templates to proper location + +2001-07-23 13:46 richard + + * roundup-admin, roundup-mailgw, roundup-server: moving the bin + files to facilitate out-of-the-boxness + +2001-07-22 22:09 richard + + * roundup/: __init__.py, cgi_client.py, cgitb.py, date.py, + htmltemplate.py, hyper_bsddb.py, hyperdb.py, init.py, mailgw.py, + roundupdb.py: Final commit of Grande Splite + +2001-07-22 21:58 richard + + * roundup/: __init__.py, cgi_client.py, cgitb.py, date.py, + htmltemplate.py, hyper_bsddb.py, hyperdb.py, init.py, mailgw.py, + roundupdb.py: More Grande Splite + +2001-07-22 21:47 richard + + * cgi-bin/roundup.cgi: More Grande Splite + +2001-07-22 21:11 richard + + * CHANGES, README, cgitb.py, config.py, date.py, hyperdb.py, + hyperdb_bsddb.py, roundup-mailgw.py, roundup.cgi, roundup.py, + roundup_cgi.py, roundupdb.py, server.py, style.css, template.py, + test.py: Initial commit of the Grande Splite + +2001-07-20 22:33 richard + + * server.py: oops ;) + +2001-07-20 18:20 richard + + * CHANGES: update for recent chagnes + +2001-07-20 18:20 richard + + * README, hyperdb.py: Fixed a bug in the filter - wrong variable + names in the error message. Recognised that the filter has an + outstanding bug. Hrm. we need a bug tracker for this project :) + +2001-07-20 17:35 richard + + * CHANGES, hyperdb.py, hyperdb_bsddb.py, roundup_cgi.py, + roundupdb.py, test.py: largish changes as a start of splitting off + bits and pieces to allow more flexible installation / database + back-ends + +2001-07-20 17:34 richard + + * template.py: Quote the value put in the text input value + attribute. + +2001-07-20 11:37 richard + + * README: Just registering a new TODO + +2001-07-20 10:53 richard + + * roundup_cgi.py: Default index now filters out the resolved issues + ;) + +2001-07-20 10:23 richard + + * CHANGES: update for latest changes + +2001-07-20 10:22 richard + + * roundupdb.py: Priority list changes - removed the redundant TODO + and added support. See roundup-devel for details. + +2001-07-20 10:17 richard + + * roundup_cgi.py: Fixed adding a new issue when there is no __note + +2001-07-19 20:43 anthonybaxter + + * config.py, server.py: HTTP_HOST and HTTP_PORT config options. + +2001-07-19 16:37 anthonybaxter + + * README: added more todo items + +2001-07-19 16:27 anthonybaxter + + * cgitb.py, config.py, date.py, hyperdb.py, roundup-mailgw.py, + roundup.py, roundup_cgi.py, roundupdb.py, server.py, template.py: + fixing (manually) the (dollarsign)Log(dollarsign) entries caused by + my using the magic (dollarsign)Id(dollarsign) and + (dollarsign)Log(dollarsign) strings in a commit message. I'm a + twonk. + + Also broke the help string in two. + +2001-07-19 16:14 richard + + * Makefile, README, dummy_config.py: minor changes to test the cvs + mailout system + +2001-07-19 16:08 anthonybaxter + + * roundup.py: fixed typo in usage string because it was bugging me + each time I saw it. + +2001-07-19 15:52 anthonybaxter + + * cgitb.py, config.py, date.py, hyperdb.py, roundup-mailgw.py, + roundup.py, roundup_cgi.py, roundupdb.py, server.py, template.py: + Added CVS keywords $Id: ChangeLog,v 1.7 2001/08/03 02:12:07 anthonybaxter Exp $ and $Log: ChangeLog,v $ + Added CVS keywords $Id$ and Revision 1.7 2001/08/03 02:12:07 anthonybaxter + Added CVS keywords $Id$ and regenerated on Fri Aug 3 12:12:00 EST 2001 + Added CVS keywords $Id$ and to all python files. + +2001-07-19 15:46 anthonybaxter + + * config.py: modified to use localconfig.py (if it exists) and to + make the various options (e.g. paths) based on ROUNDUP_HOME &c. + +2001-07-19 15:23 richard + + * CHANGES, Makefile, config.py, hyperdb.py, roundup_cgi.py, + roundupdb.py, template.py: . Fixed bug in re generation in the + filter (I hadn't finished the code ;) + . Added TODO as a priority (between bug and usability) + . Fixed handling of None String property in grouped list headings + +2001-07-19 13:12 richard + + * README: mention config.py in the install instructions, removed a + bug + +2001-07-19 13:11 richard + + * Makefile, dummy_config.py: Added stuff to help with release + generation. . Makefile has the release tgz builder in it . + dummy_config.py is an empty config file that replaces the config.py + in the release + +2001-07-19 12:16 richard + + * README, date.py, hyperdb.py, roundup.cgi, roundup_cgi.py, + roundupdb.py, CHANGES, cgitb.py, config.py, roundup-mailgw.py, + roundup.py, server.py, style.css, template.py: Initial revision + +2001-07-19 12:16 richard + + * README, date.py, hyperdb.py, roundup.cgi, roundup_cgi.py, + roundupdb.py, CHANGES, cgitb.py, config.py, roundup-mailgw.py, + roundup.py, server.py, style.css, template.py: Initial import of + code - currently version 1.0.2 but with the 1.0.3 changes as given + in the CHANGES file. Is about ready for a 1.0.3 release. + Added: tracker/vendor/roundup/current/MANIFEST.in ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/MANIFEST.in Sun Nov 5 21:30:25 2006 @@ -0,0 +1,14 @@ +recursive-include roundup *.* +recursive-include frontends *.* +recursive-include scripts *.* *-* +recursive-include tools *.* +recursive-include cgi-bin *.cgi +recursive-include test *.py *.txt +recursive-include doc *.html *.png *.txt *.css *.1 +recursive-include detectors *.py +recursive-include templates *.* home* page* +global-exclude .cvsignore *.pyc *.pyo .DS_Store +include run_tests.py *.txt demo.py MANIFEST.in MANIFEST +exclude BUILD.txt I18N_PROGRESS.txt TODO.txt +exclude doc/security.txt doc/templating.txt +include locale/*.po locale/*.mo locale/roundup.pot Added: tracker/vendor/roundup/current/README.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/README.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,39 @@ +======================================================= +Roundup: an Issue-Tracking System for Knowledge Workers +======================================================= + +Copyright (c) 2003 Richard Jones (richard at mechanicalcat.net) +Copyright (c) 2002 eKit.com Inc (http://www.ekit.com/) +Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) + + +INSTANT GRATIFICATION +===================== + +The impatient may try Roundup immediately by typing at the console:: + + python demo.py + +To start anew (a fresh demo instance):: + + python demo.py nuke + +Installation +============ +For installation instructions, please see installation.txt in the "doc" +directory. + + +Upgrading +========= +For upgrading instructions, please see upgrading.txt in the "doc" directory. + + +Usage and Other Information +=========================== +See the index.txt file in the "doc" directory. + + +License +======= +See COPYING.txt Added: tracker/vendor/roundup/current/cgi-bin/roundup.cgi ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/cgi-bin/roundup.cgi Sun Nov 5 21:30:25 2006 @@ -0,0 +1,229 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) +# This module is free software, and you may redistribute it and/or modify +# under the same terms as Python, so long as this copyright message and +# disclaimer are retained in their original form. +# +# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR +# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING +# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" +# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +# +# $Id: roundup.cgi,v 1.42 2005/05/18 05:39:21 richard Exp $ + +# python version check +from roundup import version_check +from roundup.i18n import _ +import sys, time + +# +## Configuration +# + +# Configuration can also be provided through the OS environment (or via +# the Apache "SetEnv" configuration directive). If the variables +# documented below are set, they _override_ any configuation defaults +# given in this file. + +# TRACKER_HOMES is a list of trackers, in the form +# "NAME=DIRNAME2=DIR2...", where is the directory path +# separator (";" on Windows, ":" on Unix). + +# Make sure the NAME part doesn't include any url-unsafe characters like +# spaces, as these confuse the cookie handling in browsers like IE. + +# ROUNDUP_LOG is the name of the logfile; if it's empty or does not exist, +# logging is turned off (unless you changed the default below). + +# DEBUG_TO_CLIENT specifies whether debugging goes to the HTTP server (via +# stderr) or to the web client (via cgitb). +DEBUG_TO_CLIENT = False + +# This indicates where the Roundup tracker lives +TRACKER_HOMES = { +# 'example': '/path/to/example', +} + +# Where to log debugging information to. Use an instance of DevNull if you +# don't want to log anywhere. +class DevNull: + def write(self, info): + pass + def close(self): + pass + def flush(self): + pass +#LOG = open('/var/log/roundup.cgi.log', 'a') +LOG = DevNull() + +# +## end configuration +# + + +# +# Set up the error handler +# +try: + import traceback, StringIO, cgi + from roundup.cgi import cgitb +except: + print "Content-Type: text/plain\n" + print _("Failed to import cgitb!\n\n") + s = StringIO.StringIO() + traceback.print_exc(None, s) + print s.getvalue() + + +# +# Check environment for config items +# +def checkconfig(): + import os, string + global TRACKER_HOMES, LOG + + # see if there's an environment var. ROUNDUP_INSTANCE_HOMES is the + # old name for it. + if os.environ.has_key('ROUNDUP_INSTANCE_HOMES'): + homes = os.environ.get('ROUNDUP_INSTANCE_HOMES') + else: + homes = os.environ.get('TRACKER_HOMES', '') + if homes: + TRACKER_HOMES = {} + for home in string.split(homes, os.pathsep): + try: + name, dir = string.split(home, '=', 1) + except ValueError: + # ignore invalid definitions + continue + if name and dir: + TRACKER_HOMES[name] = dir + + logname = os.environ.get('ROUNDUP_LOG', '') + if logname: + LOG = open(logname, 'a') + + # ROUNDUP_DEBUG is checked directly in "roundup.cgi.client" + + +# +# Provide interface to CGI HTTP response handling +# +class RequestWrapper: + '''Used to make the CGI server look like a BaseHTTPRequestHandler + ''' + def __init__(self, wfile): + self.wfile = wfile + def write(self, data): + self.wfile.write(data) + def send_response(self, code): + self.write('Status: %s\r\n'%code) + def send_header(self, keyword, value): + self.write("%s: %s\r\n" % (keyword, value)) + def end_headers(self): + self.write("\r\n") + +# +# Main CGI handler +# +def main(out, err): + import os, string + import roundup.instance + path = string.split(os.environ.get('PATH_INFO', '/'), '/') + request = RequestWrapper(out) + request.path = os.environ.get('PATH_INFO', '/') + tracker = path[1] + os.environ['TRACKER_NAME'] = tracker + os.environ['PATH_INFO'] = string.join(path[2:], '/') + if TRACKER_HOMES.has_key(tracker): + # redirect if we need a trailing '/' + if len(path) == 2: + request.send_response(301) + # redirect + if os.environ.get('HTTPS', '') == 'on': + protocol = 'https' + else: + protocol = 'http' + absolute_url = '%s://%s%s/'%(protocol, os.environ['HTTP_HOST'], + os.environ.get('REQUEST_URI', '')) + request.send_header('Location', absolute_url) + request.end_headers() + out.write('Moved Permanently') + else: + tracker_home = TRACKER_HOMES[tracker] + tracker = roundup.instance.open(tracker_home) + import roundup.cgi.client + if hasattr(tracker, 'Client'): + client = tracker.Client(tracker, request, os.environ) + else: + client = roundup.cgi.client.Client(tracker, request, os.environ) + try: + client.main() + except roundup.cgi.client.Unauthorised: + request.send_response(403) + request.send_header('Content-Type', 'text/html') + request.end_headers() + out.write('Unauthorised') + except roundup.cgi.client.NotFound: + request.send_response(404) + request.send_header('Content-Type', 'text/html') + request.end_headers() + out.write('Not found: %s'%client.path) + + else: + import urllib + request.send_response(200) + request.send_header('Content-Type', 'text/html') + request.end_headers() + w = request.write + w(_('Roundup trackers index\n')) + w(_('

Roundup trackers index

    \n')) + homes = TRACKER_HOMES.keys() + homes.sort() + for tracker in homes: + w(_('
  1. %(tracker_name)s\n')%{ + 'tracker_url': os.environ['SCRIPT_NAME']+'/'+ + urllib.quote(tracker), + 'tracker_name': cgi.escape(tracker)}) + w(_('
')) + +# +# Now do the actual CGI handling +# +out, err = sys.stdout, sys.stderr +try: + # force input/output to binary (important for file up/downloads) + if sys.platform == "win32": + import os, msvcrt + msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) + msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + checkconfig() + sys.stdout = sys.stderr = LOG + main(out, err) +except SystemExit: + pass +except: + sys.stdout, sys.stderr = out, err + out.write('Content-Type: text/html\n\n') + if DEBUG_TO_CLIENT: + cgitb.handler() + else: + out.write(cgitb.breaker()) + ts = time.ctime() + out.write('''

%s: An error occurred. Please check + the server log for more infomation.

'''%ts) + print >> sys.stderr, 'EXCEPTION AT', ts + traceback.print_exc(0, sys.stderr) + +sys.stdout.flush() +sys.stdout, sys.stderr = out, err +LOG.close() + +# vim: set filetype=python ts=4 sw=4 et si Added: tracker/vendor/roundup/current/demo.py ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/demo.py Sun Nov 5 21:30:25 2006 @@ -0,0 +1,129 @@ +#! /usr/bin/env python +# +# Copyright (c) 2003 Richard Jones (richard at mechanicalcat.net) +# +# $Id: demo.py,v 1.24 2006/02/08 04:03:54 richard Exp $ + +import errno +import os +import socket +import sys +import urlparse +from glob import glob + +from roundup import configuration +from roundup.scripts import roundup_server + +def install_demo(home, backend, template): + """Install a demo tracker + + Parameters: + home: + tracker home directory path + backend: + database backend name + template: + full path to the tracker template directory + + """ + from roundup import init, instance, password, backends + + # set up the config for this tracker + config = configuration.CoreConfig() + config['TRACKER_HOME'] = home + config['MAIL_DOMAIN'] = 'localhost' + config['DATABASE'] = 'db' + config['WEB_DEBUG'] = True + if backend in ('mysql', 'postgresql'): + config['RDBMS_HOST'] = 'localhost' + config['RDBMS_USER'] = 'rounduptest' + config['RDBMS_PASSWORD'] = 'rounduptest' + config['RDBMS_NAME'] = 'rounduptest' + + # see if we have further db nuking to perform + module = backends.get_backend(backend) + if module.db_exists(config): + module.db_nuke(config) + + init.install(home, template) + # don't have email flying around + os.remove(os.path.join(home, 'detectors', 'nosyreaction.py')) + try: + os.remove(os.path.join(home, 'detectors', 'nosyreaction.pyc')) + except os.error, error: + if error.errno != errno.ENOENT: + raise + init.write_select_db(home, backend) + + # figure basic params for server + hostname = 'localhost' + # pick a fairly odd, random port + port = 8917 + while 1: + print 'Trying to set up web server on port %d ...'%port, + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + try: + s.connect((hostname, port)) + except socket.error, e: + if not hasattr(e, 'args') or e.args[0] != errno.ECONNREFUSED: + raise + print 'should be ok.' + break + else: + s.close() + print 'already in use.' + port += 100 + config['TRACKER_WEB'] = 'http://%s:%s/demo/'%(hostname, port) + + # write the config + config['INSTANT_REGISTRATION'] = 1 + config.save(os.path.join(home, config.INI_FILE)) + + # open the tracker and initialise + tracker = instance.open(home) + tracker.init(password.Password('admin')) + + # add the "demo" user + db = tracker.open('admin') + db.user.create(username='demo', password=password.Password('demo'), + realname='Demo User', roles='User') + db.commit() + db.close() + +def run_demo(home): + """Run the demo tracker installed in ``home``""" + cfg = configuration.CoreConfig(home) + url = cfg["TRACKER_WEB"] + hostname, port = urlparse.urlparse(url)[1].split(':') + port = int(port) + success_message = '''Server running - connect to: + %s +1. Log in as "demo"/"demo" or "admin"/"admin". +2. Hit Control-C to stop the server. +3. Re-start the server by running "python demo.py" again. +4. Re-initialise the server by running "python demo.py nuke". +''' % url + + # disable command line processing in roundup_server + sys.argv = sys.argv[:1] + ['-p', str(port), 'demo=' + home] + roundup_server.run(success_message=success_message) + +def demo_main(): + """Run a demo server for users to play with for instant gratification. + + Sets up the web service on localhost. Disables nosy lists. + """ + home = os.path.abspath('demo') + if not os.path.exists(home) or (sys.argv[-1] == 'nuke'): + if len(sys.argv) > 2: + backend = sys.argv[-2] + else: + backend = 'anydbm' + install_demo(home, backend, os.path.join('templates', 'classic')) + run_demo(home) + +if __name__ == '__main__': + demo_main() + +# vim: set filetype=python sts=4 sw=4 et si : Added: tracker/vendor/roundup/current/detectors/creator_resolution.py ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/detectors/creator_resolution.py Sun Nov 5 21:30:25 2006 @@ -0,0 +1,43 @@ +# This detector was written by richard at mechanicalcat.net and it's been +# placed in the Public Domain. Copy and modify to your heart's content. + +#$Id: creator_resolution.py,v 1.2 2004/04/07 06:32:54 richard Exp $ + +from roundup.exceptions import Reject + +def creator_resolution(db, cl, nodeid, newvalues): + '''Catch attempts to set the status to "resolved" - if the assignedto + user isn't the creator, then set the status to "in-progress" (try + "confirm-done" first though, but "classic" Roundup doesn't have that + status) + ''' + if not newvalues.has_key('status'): + return + + # get the resolved state ID + resolved_id = db.status.lookup('resolved') + + if newvalues['status'] != resolved_id: + return + + # check the assignedto + assignedto = newvalues.get('assignedto', cl.get(nodeid, 'assignedto')) + creator = cl.get(nodeid, 'creator') + if assignedto == creator: + if db.getuid() != creator: + name = db.user.get(creator, 'username') + raise Reject, 'Only the creator (%s) may close this issue'%name + return + + # set the assignedto and status + newvalues['assignedto'] = creator + try: + status = db.status.lookup('confirm-done') + except KeyError: + status = db.status.lookup('in-progress') + newvalues['status'] = status + +def init(db): + db.issue.audit('set', creator_resolution) + +# vim: set filetype=python ts=4 sw=4 et si Added: tracker/vendor/roundup/current/detectors/emailauditor.py ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/detectors/emailauditor.py Sun Nov 5 21:30:25 2006 @@ -0,0 +1,42 @@ + +def eml_to_mht(db, cl, nodeid, newvalues): + '''This auditor fires whenever a new file entity is created. + + If the file is of type message/rfc822, we tack onthe extension .eml. + + The reason for this is that Microsoft Internet Explorer will not open + things with a .eml attachment, as they deem it 'unsafe'. Worse yet, + they'll just give you an incomprehensible error message. For more + information, please see: + + http://support.microsoft.com/default.aspx?scid=kb;EN-US;825803 + + Their suggested work around is (excerpt): + + WORKAROUND + + To work around this behavior, rename the .EML file that the URL + links to so that it has a .MHT file name extension, and then update + the URL to reflect the change to the file name. To do this: + + 1. In Windows Explorer, locate and then select the .EML file that + the URL links. + 2. Right-click the .EML file, and then click Rename. + 3. Change the file name so that the .EML file uses a .MHT file name + extension, and then press ENTER. + 4. Updated the URL that links to the file to reflect the new file + name extension. + + So... we do that. :)''' + if newvalues.get('type', '').lower() == "message/rfc822": + if not newvalues.has_key('name'): + newvalues['name'] = 'email.mht' + return + name = newvalues['name'] + if name.endswith('.eml'): + name = name[:-4] + newvalues['name'] = name + '.mht' + +def init(db): + db.file.audit('create', eml_to_mht) + Added: tracker/vendor/roundup/current/detectors/newissuecopy.py ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/detectors/newissuecopy.py Sun Nov 5 21:30:25 2006 @@ -0,0 +1,22 @@ +# copied from nosyreaction + +from roundup import roundupdb + +def newissuecopy(db, cl, nodeid, oldvalues): + ''' Copy a message about new issues to a team address. + ''' + # so use all the messages in the create + change_note = cl.generateCreateNote(nodeid) + + # send a copy to the nosy list + for msgid in cl.get(nodeid, 'messages'): + try: + # note: last arg must be a list + cl.send_message(nodeid, msgid, change_note, ['team at team.host']) + except roundupdb.MessageSendError, message: + raise roundupdb.DetectorError, message + +def init(db): + db.issue.react('create', newissuecopy) + +# vim: set filetype=python ts=4 sw=4 et si Added: tracker/vendor/roundup/current/doc/.cvsignore ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/.cvsignore Sun Nov 5 21:30:25 2006 @@ -0,0 +1,21 @@ +announcement.html +customizing.html +developers.html +implementation.html +index.html +installation.html +user_guide.html +FAQ.html +security.html +features.html +upgrading.html +glossary.html +design.html +admin_guide.html +overview.html +mysql.html +postgresql.html +tracker_templates.html +whatsnew-0.7.html +whatsnew-0.8.html +*.ht Added: tracker/vendor/roundup/current/doc/FAQ.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/FAQ.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,212 @@ +=========== +Roundup FAQ +=========== + +:Version: $Revision: 1.22 $ + +.. contents:: + + +Installation +------------ + +Living without a mailserver +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Remove the nosy reactor, means delete the tracker file +``detectors/nosyreactor.py`` from your tracker home. + + +The cgi-bin is very slow! +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Yep, it sure is. It has to start up Python and load all of the support +libraries for *every* request. + +The solution is to use the built in server. + +To make Roundup more seamless with your website, you may place the built +in server behind apache and link it into your web tree + + +How do I put Roundup behind Apache +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We have a project (foo) running on ``tracker.example:8080``. +We want ``http://tracker.example/issues`` to use the roundup server, so we +set that up on port 8080 on ``tracker.example`` with the ``config.ini`` line:: + + [tracker] + ... + web = 'http://tracker.example/issues/' + +We have a "foo_issues" tracker and we run the server with:: + + roundup-server -p 8080 issues=/home/roundup/trackers/issues + +Then, on the Apache machine (eg. redhat 7.3 with apache 1.3), in +``/etc/httpd/conf/httpd.conf`` uncomment:: + + LoadModule proxy_module modules/libproxy.so + +and:: + + AddModule mod_proxy.c + +Then add:: + + # roundup stuff (added manually) + + # proxy through one tracker + ProxyPass /issues/ http://tracker.example:8080/issues/ + # proxy through all tracker(*) + #ProxyPass /roundup/ http://tracker.example:8080/ + + +Then restart Apache. Now Apache will proxy the request on to the +roundup-server. + +Note that if you're proxying multiple trackers, you'll need to use the +second ProxyPass rule described above. It will mean that your TRACKER_WEB +will change to:: + + TRACKER_WEB = 'http://tracker.example/roundup/issues/' + +Once you're done, you can firewall off port 8080 from the rest of the world. + +Note that in some situations (eg. virtual hosting) you might need to use a +more complex rewrite rule instead of the simpler ProxyPass above. The +following should be useful as a starting template:: + + # roundup stuff (added manually) + + + RewriteEngine on + + # General Roundup + RewriteRule ^/roundup$ roundup/ [R] + RewriteRule ^/roundup/(.*)$ http://tracker.example:8080/$1 [P,L] + + # Handle Foo Issues + RewriteRule ^/issues$ issues/ [R] + RewriteRule ^/issues/(.*)$ http://tracker.example:8080/issues/$1 [P,L] + + + + +How do I run Roundup through SSL (HTTPS)? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You should proxy through apache and use its SSL service. See the previous +question on how to proxy through apache. + + +Roundup runs very slowly on my XP machine when accessed from the Internet +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The issue is probably related to host name resolution for the client +performing the request. You can turn off the resolution of the names +when it's so slow like this. To do so, edit the module +roundup/scripts/roundup_server.py around line 77 to add the following +to the RoundupRequestHandler class: + + def address_string(self): + return self.client_address[0] + + +Templates +--------- + +What is that stuff in the tracker html directory? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the template code that Roundup uses to display the various pages. +This is based upon the template markup language in Zope called, oddly +enough "Zope Page Templates". There's documentation in the Roundup +customisation_ documentation. For more information have a look at: + + http://www.zope.org/Documentation/Books/ZopeBook/2_6Edition/ + +specifically chapter 10 "Using Zope Page Templates" and chapter 14 "Advanced +Page Templates". + + +But I just want a select/option list for .... +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Really easy... edit ``html/issue.item``. For 'nosy', change line 53 from:: + + + +to:: + + + +For 'assigned to', change line 61 from:: + + assignedto menu + +to:: + + assignedto menu + + + +Great! But now the select/option list is too big +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Thats a little harder (but only a little ;^) + +Again, edit ``html/issue.item``. For nosy, change line 53 from: + + + +to:: + + + +for more information, go and read about Zope Page Templates. + + +Using Roundup +------------- + +I got an error and I cant reload it! +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you're using Netscape/Mozilla, try holding shift and pressing reload. +If you're using IE then install Mozilla and try again ;^) + + +I keep getting logged out +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Make sure that the ``tracker`` -> ``web`` setting in your tracker's +config.ini is set to the URL of the tracker. + + +How is sorting performed, and why does it seem to fail sometimes? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When we sort items in the hyperdb, we use one of a number of methods, +depending on the properties being sorted on: + +1. If it's a String, Number, Date or Interval property, we just sort the + scalar value of the property. Strings are sorted case-sensitively. +2. If it's a Link property, we sort by either the linked item's "order" + property (if it has one) or the linked item's "id". +3. Mulitlinks sort similar to #2, but we start with the first + Multilink list item, and if they're the same, we sort by the second item, + and so on. + +Note that if an "order" property is defined on a Class that is used for +sorting, all items of that Class *must* have a value against the "order" +property, or sorting will result in random ordering. + +----------------- + +Back to `Table of Contents`_ + +.. _`Table of Contents`: index.html +.. _`customisation`: customizing.html + Added: tracker/vendor/roundup/current/doc/Makefile ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/Makefile Sun Nov 5 21:30:25 2006 @@ -0,0 +1,25 @@ +STXTOHTML = rst2html.py +STXTOHT = rst2ht.py +WEBDIR = ../../htdocs/htdocs/doc-1.0 + +SOURCE = announcement.txt customizing.txt developers.txt FAQ.txt features.txt \ + glossary.txt implementation.txt index.txt design.txt mysql.txt \ + installation.txt upgrading.txt user_guide.txt admin_guide.txt \ + postgresql.txt tracker_templates.txt + +COMPILED := $(SOURCE:.txt=.html) +WEBHT := $(SOURCE:.txt=.ht) + +all: ${COMPILED} ${WEBHT} + +website: ${WEBHT} + cp *.ht ${WEBDIR} + +%.html: %.txt + ${STXTOHTML} --report=warning -d $< $@ + +%.ht: %.txt + ${STXTOHT} --report=warning -d $< $@ + +clean: + rm -f ${COMPILED} Added: tracker/vendor/roundup/current/doc/ZPL.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/ZPL.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,59 @@ +Zope Public License (ZPL) Version 2.0 +----------------------------------------------- + +This software is Copyright (c) Zope Corporation (tm) and +Contributors. All rights reserved. + +This license has been certified as open source. It has also +been designated as GPL compatible by the Free Software +Foundation (FSF). + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the +following conditions are met: + +1. Redistributions in source code must retain the above + copyright notice, this list of conditions, and the following + disclaimer. + +2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions, and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + +3. The name Zope Corporation (tm) must not be used to + endorse or promote products derived from this software + without prior written permission from Zope Corporation. + +4. The right to distribute this software or to use it for + any purpose does not give you the right to use Servicemarks + (sm) or Trademarks (tm) of Zope Corporation. Use of them is + covered in a separate agreement (see + http://www.zope.com/Marks). + +5. If any files are modified, you must cause the modified + files to carry prominent notices stating that you changed + the files and the date of any change. + +Disclaimer + + THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS'' + AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT + NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. + + +This software consists of contributions made by Zope +Corporation and many individuals on behalf of Zope +Corporation. Specific attributions are listed in the +accompanying credits file. Added: tracker/vendor/roundup/current/doc/admin_guide.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/admin_guide.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,357 @@ +==================== +Administration Guide +==================== + +:Version: $Revision: 1.20 $ + +.. contents:: + +What does Roundup install? +========================== + +There's two "installations" that we talk about when using Roundup: + +1. The installation of the software and its support files. This uses the + standard Python mechanism called "distutils" and thus Roundup's core code, + executable scripts and support data files are installed in Python's + directories. On Windows, this is typically: + + Scripts + ``\scripts\...`` + Core code + ``\lib\site-packages\roundup\...`` + Support files + ``\share\roundup\...`` + + and on Unix-like systems (eg. Linux): + + Scripts + ``/bin/...`` + Core code + ``/lib-/site-packages/roundup/...`` + Support files + ``/share/roundup/...`` + +2. The installation of a specific tracker. When invoking the roundup-admin + "inst" (and "init") commands, you're creating a new Roundup tracker. This + installs configuration files, HTML templates, detector code and a new + database. You have complete control over where this stuff goes through + both choosing your "tracker home" and the ``main`` -> ``database`` variable + in the tracker's config.ini. + + +Configuring Roundup's Logging of Messages For Sysadmins +======================================================= + +You may configure where Roundup logs messages in your tracker's config.ini +file. Roundup will use the standard Python (2.3+) logging implementation +when available. If not, then a very basic logging implementation will be used +(see BasicLogging in the roundup.rlog module for details). + +Configuration for standard "logging" module: + - tracker configuration file specifies the location of a logging + configration file as ``logging`` -> ``config`` + - ``roundup-server`` specifies the location of a logging configuration + file on the command line +Configuration for "BasicLogging" implementation: + - tracker configuration file specifies the location of a log file + ``logging`` -> ``filename`` + - tracker configuration file specifies the level to log to as + ``logging`` -> ``level`` + - ``roundup-server`` specifies the location of a log file on the command + line + - ``roundup-server`` specifies the level to log to on the command line + +(``roundup-mailgw`` always logs to the tracker's log file) + +In both cases, if no logfile is specified then logging will simply be sent +to sys.stderr with only logging of ERROR messages. + + +Configuring roundup-server +========================== + +The basic configuration file layout is as follows (take from the +``roundup-server.ini.example`` file in the "doc" directory):: + + [main] + port = 8080 + ;hostname = + ;user = + ;group = + ;log_ip = yes + ;pidfile = + ;logfile = + + [trackers] + ; Add one of these per tracker being served + name = /path/to/tracker/name + +Values ";commented out" are optional. The meaning of the various options +are as follows: + +**port** + Defines the local TCP port to listen for clients on. +**hostname** + Defines the local hostname to listen for clients on. Only required if + "localhost" is not sufficient. +**user** and **group** + Defines the Unix user and group to run the server as. Only work if the + server is started as root. +**log_ip** + If ``yes`` then we log IP addresses against accesses. If ``no`` then we + log the hostname of the client. The latter can be much slower. +**pidfile** + If specified, the server will fork at startup and write its new PID to + the file. +**logfile** + Any unhandled exception messages or other output from Roundup will be + written to this file. It must be specified if **pidfile** is specified. + If per-tracker logging is specified, then very little will be written to + this file. +**trackers** section + Each line denotes a mapping from a URL component to a tracker home. + Make sure the name part doesn't include any url-unsafe characters like + spaces. Stick to alphanumeric characters and you'll be ok. + + +Users and Security +================== + +Roundup holds its own user database which primarily contains a username, +password and email address for the user. Roundup *must* have its own user +listing, in order to maintain internal consistency of its data. It is a +relatively simple exercise to update this listing on a regular basis, or on +demand, so that it matches an external listing (eg. unix passwd file, LDAP, +etc.) + +Roundup identifies users in a number of ways: + +1. Through the web, users may be identified by either HTTP Basic + Authentication or cookie authentication. If you are running the web + server (roundup-server) through another HTTP server (eg. apache or IIS) + then that server may require HTTP Basic Authentication, and it will pass + the ``REMOTE_USER`` variable through to Roundup. If this variable is not + present, then Roundup defaults to using its own cookie-based login + mechanism. +2. In email messages handled by roundup-mailgw, users are identified by the + From address in the message. + +In both cases, Roundup's behaviour when dealing with unknown users is +controlled by Permissions defined in the "SECURITY SETTINGS" section of the +tracker's ``schema.py`` module: + +Web Registration + If granted to the Anonymous Role, then anonymous users will be able to + register through the web. +Email Registration + If granted to the Anonymous Role, then email messages from unknown users + will result in those users being registered with the tracker. + +More information about how to customise your tracker's security settings +may be found in the `customisation documentation`_. + + +Tasks +===== + +Maintenance of Roundup can involve one of the following: + +1. `tracker backup`_ +2. `software upgrade`_ +3. `migrating backends`_ +4. `moving a tracker`_ +5. `migrating from other software`_ +6. `adding a user from the command-line`_ + + +Tracker Backup +-------------- + +Stop the web and email frontends and to copy the contents of the tracker home +directory to some other place using standard backup tools. + + +Software Upgrade +---------------- + +Always make a backup of your tracker before upgrading software. Steps you may +take: + +1. Ensure that the unit tests run on your system:: + + python run_tests.py + +2. If you're using an RDBMS backend, make a backup of its contents now. +3. Make a backup of the tracker home itself. +4. Stop the tracker web and email frontends. +5. Follow the steps in the `upgrading documentation`_ for the new version of + the software in the copied. +6. You may test each of the admin tool, web interface and mail gateway using + the new version of the software. To do this, invoke the scripts directly + in the source directory with:: + + PYTHONPATH=. python roundup/scripts/roundup_server.py + PYTHONPATH=. python roundup/scripts/roundup_admin.py + PYTHONPATH=. python roundup/scripts/roundup_mailgw.py + + Note that on Windows, this would read:: + + C:\sources\roundup-0.7.4> SET PYTHONPATH=. + C:\sources\roundup-0.7.4> python roundup/scripts/roundup_server.py + +7. Once you're comfortable that the upgrade will work using that copy, you + should install the new version of the software:: + + python setup.py install + +8. Restart your tracker web and email frontends. + +If something bad happens, you may reinstate your backup of the tracker and +reinstall the older version of the sofware using the same install command:: + + python setup.py install + + +Migrating Backends +------------------ + +1. stop the existing tracker web and email frontends (preventing changes) +2. use the roundup-admin tool "export" command to export the contents of + your tracker to disk +3. copy the tracker home to a new directory +4. delete the "db" directory from the new directory +5. enter the new backend name in the tracker home ``db/backend_name`` file +6. use the roundup-admin "import" command to import the previous export with + the new tracker home +7. test each of the admin tool, web interface and mail gateway using the new + backend +8. move the old tracker home out of the way (rename to "tracker.old") and + move the new tracker home into its place +9. restart web and email frontends + + +Moving a Tracker +---------------- + +If you're moving the tracker to a similar machine, you should: + +1. install Roundup on the new machine and test that it works there, +2. stop the existing tracker web and email frontends (preventing changes), +3. copy the tracker home directory over to the new machine, and +4. start the tracker web and email frontends on the new machine. + +Most of the backends are actually portable across platforms (ie. from Unix to +Windows to Mac). If this isn't the case (ie. the tracker doesn't work when +moved using the above steps) then you'll need to: + +1. install Roundup on the new machine and test that it works there, +2. stop the existing tracker web and email frontends (preventing changes), +3. use the roundup-admin tool "export" command to export the contents of + the existing tracker, +4. copy the export to the new machine, +5. use the roundup-admin "import" command to import the tracker on the new + machine, and +6. start the tracker web and email frontends on the new machine. + + +Migrating From Other Software +----------------------------- + +You have a couple of choices. You can either use a CSV import into Roundup, +or you can write a simple Python script which uses the Roundup API +directly. The latter is almost always simpler -- see the "scripts" +directory in the Roundup source for some example uses of the API. + +"roundup-admin import" will import data into your tracker from a +directory containing files with the following format: + +- one colon-separated-values file per Class with columns for each property, + named .csv +- one colon-separated-values file per Class with journal information, + named -journals.csv (this is required, even if it's empty) +- if the Class is a FileClass, you may have the "content" property + stored in separate files from the csv files. This goes in a directory + structure:: + + -files// + + where ```` is the item's ```` combination. + The ```` value is ``int( / 1000)``. + + +Adding A User From The Command-Line +----------------------------------- + +The ``roundup-admin`` program can create any data you wish to in the +database. To create a new user, use:: + + roundup-admin create user + +To figure out what good values might be for some of the fields (eg. Roles) +you can just display another user:: + + roundup-admin list user + +(or if you know their username, and it happens to be "richard"):: + + roundup-admin find username=richard + +then using the user id you get from one of the above commands, you may +display the user's details:: + + roundup-admin display + + +Running the Servers +=================== + +Unix +---- + +On Unix systems, use the scripts/server-ctl script to control the +roundup-server server. Copy it somewhere and edit the variables at the top +to reflect your specific installation. + + +Windows +------- + +On Windows, the roundup-server program runs as a Windows Service, and +therefore may be controlled through the Services control panel. The +roundup-server program may also control the service directly: + +**install the service** + ``roundup-server -c install`` +**start the service** + ``roundup-server -c start`` +**stop the service** + ``roundup-server -c stop`` + +To bring up the services panel: + +Windows 2000 and later + Start/Control Panel/Administrative Tools/Services +Windows NT4 + Start/Control Panel/Services + +Running the Mail Gateway Script +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The mail gateway script should be scheduled to run regularly on your +Windows server. Normally this will result in a window popping up. The +solution to this is to: + +1. Create a new local account on the Roundup server +2. Set the scheduled task to run in the context of this user instead + of your normal login + + +------------------- + +Back to `Table of Contents`_ + +.. _`Table of Contents`: index.html +.. _`customisation documentation`: customizing.html +.. _`upgrading documentation`: upgrading.html + Added: tracker/vendor/roundup/current/doc/announcement.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/announcement.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,72 @@ +I'm proud to release version 1.1.2 of Roundup. + +Feature: + +- server-ctl script uses server configuration file (sf bug 1443805) + +Fixed: + +- indexing may be turned off for FileClass "content" now + ("content" and "type" properties are now automatically included in the + FileClass schema where previously the "content" property was faked and + "type" was optional) +- reduced frequency of session timestamp update +- progress display in roundup-admin reindex +- bug in menu() permission filter (sf bug 1444440) +- verbose output during import is optional now (sf bug 1475624) +- escape *all* uses of "schema" in mysql backend (sf bug 1472120) +- responses to user rego email (sf bug 1470254) +- dangling connections in session handling (sf bug 1463359) +- classhelp popup pagination forgot about "type" (sf bug 1465836) +- umask is now configurable (with the same 0002 default) +- sorting of entries in classhelp popup (sf bug 1449000) +- allow single digit seconds in date spec (sf bug 1447141) +- prevent generation of new single-digit seconds dates (sf bug 1429390) +- implement close() on all indexers (sf bug 1242477) + + +If you're upgrading from an older version of Roundup you *must* follow +the "Software Upgrade" guidelines given in the maintenance documentation. + +Roundup requires python 2.3 or later for correct operation. + +To give Roundup a try, just download (see below), unpack and run:: + + python demo.py + +Release info and download page: + http://cheeseshop.python.org/pypi/roundup +Source and documentation is available at the website: + http://roundup.sourceforge.net/ +Mailing lists - the place to ask questions: + http://sourceforge.net/mail/?group_id=31577 + + +About Roundup +============= + +Roundup is a simple-to-use and -install issue-tracking system with +command-line, web and e-mail interfaces. It is based on the winning design +from Ka-Ping Yee in the Software Carpentry "Track" design competition. + +Note: Ping is not responsible for this project. The contact for this +project is richard at users.sourceforge.net. + +Roundup manages a number of issues (with flexible properties such as +"description", "priority", and so on) and provides the ability to: + +(a) submit new issues, +(b) find and edit existing issues, and +(c) discuss issues with other participants. + +The system will facilitate communication among the participants by managing +discussions and notifying interested parties when issues are edited. One of +the major design goals for Roundup that it be simple to get going. Roundup +is therefore usable "out of the box" with any python 2.3+ installation. It +doesn't even need to be "installed" to be operational, though a +disutils-based install script is provided. + +It comes with two issue tracker templates (a classic bug/feature tracker and +a minimal skeleton) and five database back-ends (anydbm, sqlite, metakit, +mysql and postgresql). + Added: tracker/vendor/roundup/current/doc/customizing.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/customizing.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,4549 @@ +=================== +Customising Roundup +=================== + +:Version: $Revision: 1.197 $ + +.. This document borrows from the ZopeBook section on ZPT. The original is at: + http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx + +.. contents:: + :depth: 1 + +What You Can Do +=============== + +Before you get too far, it's probably worth having a quick read of the Roundup +`design documentation`_. + +Customisation of Roundup can take one of six forms: + +1. `tracker configuration`_ changes +2. database, or `tracker schema`_ changes +3. "definition" class `database content`_ changes +4. behavioural changes, through detectors_ +5. `security / access controls`_ +6. change the `web interface`_ + +The third case is special because it takes two distinctly different forms +depending upon whether the tracker has been initialised or not. The other two +may be done at any time, before or after tracker initialisation. Yes, this +includes adding or removing properties from classes. + + +Trackers in a Nutshell +====================== + +Trackers have the following structure: + +=================== ======================================================== +Tracker File Description +=================== ======================================================== +config.ini Holds the basic `tracker configuration`_ +schema.py Holds the `tracker schema`_ +initial_data.py Holds any data to be entered into the database when the + tracker is initialised. +db/ Holds the tracker's database +db/files/ Holds the tracker's upload files and messages +db/backend_name Names the database back-end for the tracker +detectors/ Auditors and reactors for this tracker +extensions/ Additional web actions and templating utilities. +html/ Web interface templates, images and style sheets +=================== ======================================================== + + +Tracker Configuration +===================== + +The ``config.ini`` located in your tracker home contains the basic +configuration for the web and e-mail components of roundup's interfaces. + +Changes to the data captured by your tracker is controlled by the `tracker +schema`_. Some configuration is also performed using permissions - see the +`security / access controls`_ section. For example, to allow users to +automatically register through the email interface, you must grant the +"Anonymous" Role the "Email Access" Permission. + +The following is taken from the `Python Library Reference`__ (May 20, 2004) +section "ConfigParser -- Configuration file parser": + + The configuration file consists of sections, led by a "[section]" header + and followed by "name = value" entries, with line continuations on a + newline with leading whitespace. Note that leading whitespace is removed + from values. The optional values can contain format strings which + refer to other values in the same section. Lines beginning with "#" or ";" + are ignored and may be used to provide comments. + + For example:: + + [My Section] + foodir = %(dir)s/whatever + dir = frob + + would resolve the "%(dir)s" to the value of "dir" ("frob" in this case) + resulting in "foodir" being "frob/whatever". + +__ http://docs.python.org/lib/module-ConfigParser.html + +Section **main** + database -- ``db`` + Database directory path. The path may be either absolute or relative + to the directory containig this config file. + + templates -- ``html`` + Path to the HTML templates directory. The path may be either absolute + or relative to the directory containig this config file. + + admin_email -- ``roundup-admin`` + Email address that roundup will complain to if it runs into trouble. If + the email address doesn't contain an ``@`` part, the MAIL_DOMAIN defined + below is used. + + dispatcher_email -- ``roundup-admin`` + The 'dispatcher' is a role that can get notified of new items to the + database. It is used by the ERROR_MESSAGES_TO config setting. If the + email address doesn't contain an ``@`` part, the MAIL_DOMAIN defined + below is used. + + email_from_tag -- default *blank* + Additional text to include in the "name" part of the From: address used + in nosy messages. If the sending user is "Foo Bar", the From: line + is usually: ``"Foo Bar" `` + the EMAIL_FROM_TAG goes inside the "Foo Bar" quotes like so: + ``"Foo Bar EMAIL_FROM_TAG" `` + + new_web_user_roles -- ``User`` + Roles that a user gets when they register with Web User Interface. + This is a comma-separated list of role names (e.g. ``Admin,User``). + + new_email_user_roles -- ``User`` + Roles that a user gets when they register with Email Gateway. + This is a comma-separated string of role names (e.g. ``Admin,User``). + + error_messages_to -- ``user`` + Send error message emails to the ``dispatcher``, ``user``, or ``both``? + The dispatcher is configured using the DISPATCHER_EMAIL setting. + Allowed values: ``dispatcher``, ``user``, or ``both`` + + html_version -- ``html4`` + HTML version to generate. The templates are ``html4`` by default. + If you wish to make them xhtml, then you'll need to change this + var to ``xhtml`` too so all auto-generated HTML is compliant. + Allowed values: ``html4``, ``xhtml`` + + timezone -- ``0`` + Numeric timezone offset used when users do not choose their own + in their settings. + + instant_registration -- ``yes`` + Register new users instantly, or require confirmation via + email? + Allowed values: ``yes``, ``no`` + + email_registration_confirmation -- ``yes`` + Offer registration confirmation by email or only through the web? + Allowed values: ``yes``, ``no`` + + indexer_stopwords -- default *blank* + Additional stop-words for the full-text indexer specific to + your tracker. See the indexer source for the default list of + stop-words (e.g. ``A,AND,ARE,AS,AT,BE,BUT,BY, ...``). + +Section **tracker** + name -- ``Roundup issue tracker`` + A descriptive name for your roundup instance. + + web -- ``http://host.example/demo/`` + The web address that the tracker is viewable at. + This will be included in information sent to users of the tracker. + The URL MUST include the cgi-bin part or anything else + that is required to get to the home page of the tracker. + You MUST include a trailing '/' in the URL. + + email -- ``issue_tracker`` + Email address that mail to roundup should go to. + +Section **web** + http_auth -- ``yes`` + Whether to use HTTP Basic Authentication, if present. + Roundup will use either the REMOTE_USER or HTTP_AUTHORIZATION + variables supplied by your web server (in that order). + Set this option to 'no' if you do not wish to use HTTP Basic + Authentication in your web interface. + + use_browser_language -- ``yes`` + Whether to use HTTP Accept-Language, if present. + Browsers send a language-region preference list. + It's usually set in the client's browser or in their + Operating System. + Set this option to 'no' if you want to ignore it. + + debug -- ``no`` + Setting this option makes Roundup display error tracebacks + in the user's browser rather than emailing them to the + tracker admin."), + +Section **rdbms** + Settings in this section are used by Postgresql and MySQL backends only + + name -- ``roundup`` + Name of the database to use. + + host -- ``localhost`` + Database server host. + + port -- default *blank* + TCP port number of the database server. Postgresql usually resides on + port 5432 (if any), for MySQL default port number is 3306. Leave this + option empty to use backend default. + + user -- ``roundup`` + Database user name that Roundup should use. + + password -- ``roundup`` + Database user password. + +Section **logging** + config -- default *blank* + Path to configuration file for standard Python logging module. If this + option is set, logging configuration is loaded from specified file; + options 'filename' and 'level' in this section are ignored. The path may + be either absolute or relative to the directory containig this config file. + + filename -- default *blank* + Log file name for minimal logging facility built into Roundup. If no file + name specified, log messages are written on stderr. If above 'config' + option is set, this option has no effect. The path may be either absolute + or relative to the directory containig this config file. + + level -- ``ERROR`` + Minimal severity level of messages written to log file. If above 'config' + option is set, this option has no effect. + Allowed values: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR`` + +Section **mail** + Outgoing email options. Used for nosy messages, password reset and + registration approval requests. + + domain -- ``localhost`` + Domain name used for email addresses. + + host -- default *blank* + SMTP mail host that roundup will use to send mail + + username -- default *blank* + SMTP login name. Set this if your mail host requires authenticated access. + If username is not empty, password (below) MUST be set! + + password -- default *blank* + SMTP login password. + Set this if your mail host requires authenticated access. + + tls -- ``no`` + If your SMTP mail host provides or requires TLS (Transport Layer Security) + then you may set this option to 'yes'. + Allowed values: ``yes``, ``no`` + + tls_keyfile -- default *blank* + If TLS is used, you may set this option to the name of a PEM formatted + file that contains your private key. The path may be either absolute or + relative to the directory containig this config file. + + tls_certfile -- default *blank* + If TLS is used, you may set this option to the name of a PEM formatted + certificate chain file. The path may be either absolute or relative + to the directory containig this config file. + + charset -- utf-8 + Character set to encode email headers with. We use utf-8 by default, as + it's the most flexible. Some mail readers (eg. Eudora) can't cope with + that, so you might need to specify a more limited character set + (eg. iso-8859-1). + + debug -- default *blank* + Setting this option makes Roundup to write all outgoing email messages + to this file *instead* of sending them. This option has the same effect + as environment variable SENDMAILDEBUG. Environment variable takes + precedence. The path may be either absolute or relative to the directory + containig this config file. + +Section **mailgw** + Roundup Mail Gateway options + + keep_quoted_text -- ``yes`` + Keep email citations when accepting messages. Setting this to ``no`` strips + out "quoted" text from the message. Signatures are also stripped. + Allowed values: ``yes``, ``no`` + + leave_body_unchanged -- ``no`` + Preserve the email body as is - that is, keep the citations *and* + signatures. + Allowed values: ``yes``, ``no`` + + default_class -- ``issue`` + Default class to use in the mailgw if one isn't supplied in email subjects. + To disable, leave the value blank. + + subject_prefix_parsing -- ``strict`` + Controls the parsing of the [prefix] on subject lines in incoming emails. + ``strict`` will return an error to the sender if the [prefix] is not + recognised. ``loose`` will attempt to parse the [prefix] but just + pass it through as part of the issue title if not recognised. ``none`` + will always pass any [prefix] through as part of the issue title. + + subject_suffix_parsing -- ``strict`` + Controls the parsing of the [suffix] on subject lines in incoming emails. + ``strict`` will return an error to the sender if the [suffix] is not + recognised. ``loose`` will attempt to parse the [suffix] but just + pass it through as part of the issue title if not recognised. ``none`` + will always pass any [suffix] through as part of the issue title. + + subject_suffix_delimiters -- ``[]`` + Defines the brackets used for delimiting the commands suffix in a subject + line. + + subject_content_match -- ``always`` + Controls matching of the incoming email subject line against issue titles + in the case where there is no designator [prefix]. ``never`` turns off + matching. ``creation + interval`` or ``activity + interval`` will match + an issue for the interval after the issue's creation or last activity. + The interval is a standard Roundup interval. + +Section **nosy** + Nosy messages sending + + messages_to_author -- ``no`` + Send nosy messages to the author of the message. + Allowed values: ``yes``, ``no``, ``new`` + + signature_position -- ``bottom`` + Where to place the email signature. + Allowed values: ``top``, ``bottom``, ``none`` + + add_author -- ``new`` + Does the author of a message get placed on the nosy list automatically? + If ``new`` is used, then the author will only be added when a message + creates a new issue. If ``yes``, then the author will be added on + followups too. If ``no``, they're never added to the nosy. + Allowed values: ``yes``, ``no``, ``new`` + + add_recipients -- ``new`` + Do the recipients (``To:``, ``Cc:``) of a message get placed on the nosy + list? If ``new`` is used, then the recipients will only be added when a + message creates a new issue. If ``yes``, then the recipients will be added + on followups too. If ``no``, they're never added to the nosy. + Allowed values: ``yes``, ``no``, ``new`` + + email_sending -- ``single`` + Controls the email sending from the nosy reactor. If ``multiple`` then + a separate email is sent to each recipient. If ``single`` then a single + email is sent with each recipient as a CC address. + +You may generate a new default config file using the ``roundup-admin +genconfig`` command. + +Configuration variables may be referred to in lower or upper case. In code, +variables not in the "main" section are referred to using their section and +name, so "domain" in the section "mail" becomes MAIL_DOMAIN. The +configuration variables available are: + + +Tracker Schema +============== + +.. note:: + if you modify the schema, you'll most likely need to edit the + `web interface`_ HTML template files and `detectors`_ to reflect + your changes. + +A tracker schema defines what data is stored in the tracker's database. +Schemas are defined using Python code in the ``schema.py`` module of your +tracker. + +The ``schema.py`` module +------------------------ + +The ``schema.py`` module contains two functions: + +**open** + This function defines what your tracker looks like on the inside, the + **schema** of the tracker. It defines the **Classes** and **properties** + on each class. It also defines the **security** for those Classes. The + next few sections describe how schemas work and what you can do with + them. +**init** + This function is responsible for setting up the initial state of your + tracker. It's called exactly once - but the ``roundup-admin initialise`` + command. See the start of the section on `database content`_ for more + info about how this works. + + +The "classic" schema +-------------------- + +The "classic" schema looks like this (see section `setkey(property)`_ +below for the meaning of ``'setkey'`` -- you may also want to look into +the sections `setlabelprop(property)`_ and `setorderprop(property)`_ for +specifying (default) labelling and ordering of classes.):: + + pri = Class(db, "priority", name=String(), order=String()) + pri.setkey("name") + + stat = Class(db, "status", name=String(), order=String()) + stat.setkey("name") + + keyword = Class(db, "keyword", name=String()) + keyword.setkey("name") + + user = Class(db, "user", username=String(), organisation=String(), + password=String(), address=String(), realname=String(), + phone=String()) + user.setkey("username") + + msg = FileClass(db, "msg", author=Link("user"), summary=String(), + date=Date(), recipients=Multilink("user"), + files=Multilink("file")) + + file = FileClass(db, "file", name=String(), type=String()) + + issue = IssueClass(db, "issue", topic=Multilink("keyword"), + status=Link("status"), assignedto=Link("user"), + priority=Link("priority")) + issue.setkey('title') + + +What you can't do to the schema +------------------------------- + +You must never: + +**Remove the users class** + This class is the only *required* class in Roundup. + +**Remove the "username", "address", "password" or "realname" user properties** + Various parts of Roundup require these properties. Don't remove them. + +**Change the type of a property** + Property types must *never* be changed - the database simply doesn't take + this kind of action into account. Note that you can't just remove a + property and re-add it as a new type either. If you wanted to make the + assignedto property a Multilink, you'd need to create a new property + assignedto_list and remove the old assignedto property. + + +What you can do to the schema +----------------------------- + +Your schema may be changed at any time before or after the tracker has been +initialised (or used). You may: + +**Add new properties to classes, or add whole new classes** + This is painless and easy to do - there are generally no repurcussions + from adding new information to a tracker's schema. + +**Remove properties** + Removing properties is a little more tricky - you need to make sure that + the property is no longer used in the `web interface`_ *or* by the + detectors_. + + + +Classes and Properties - creating a new information store +--------------------------------------------------------- + +In the tracker above, we've defined 7 classes of information: + + priority + Defines the possible levels of urgency for issues. + + status + Defines the possible states of processing the issue may be in. + + keyword + Initially empty, will hold keywords useful for searching issues. + + user + Initially holding the "admin" user, will eventually have an entry + for all users using roundup. + + msg + Initially empty, will hold all e-mail messages sent to or + generated by roundup. + + file + Initially empty, will hold all files attached to issues. + + issue + Initially empty, this is where the issue information is stored. + +We define the "priority" and "status" classes to allow two things: +reduction in the amount of information stored on the issue and more +powerful, accurate searching of issues by priority and status. By only +requiring a link on the issue (which is stored as a single number) we +reduce the chance that someone mis-types a priority or status - or +simply makes a new one up. + + +Class and Items +~~~~~~~~~~~~~~~ + +A Class defines a particular class (or type) of data that will be stored +in the database. A class comprises one or more properties, which gives +the information about the class items. + +The actual data entered into the database, using ``class.create()``, are +called items. They have a special immutable property called ``'id'``. We +sometimes refer to this as the *itemid*. + + +Properties +~~~~~~~~~~ + +A Class is comprised of one or more properties of the following types: + +* String properties are for storing arbitrary-length strings. +* Password properties are for storing encoded arbitrary-length strings. + The default encoding is defined on the ``roundup.password.Password`` + class. +* Date properties store date-and-time stamps. Their values are Timestamp + objects. +* Number properties store numeric values. +* Boolean properties store on/off, yes/no, true/false values. +* A Link property refers to a single other item selected from a + specified class. The class is part of the property; the value is an + integer, the id of the chosen item. +* A Multilink property refers to possibly many items in a specified + class. The value is a list of integers. + +All Classes automatically have a number of properties by default: + +*creator* + Link to the user that created the item. +*creation* + Date the item was created. +*actor* + Link to the user that last modified the item. +*activity* + Date the item was last modified. + + +FileClass +~~~~~~~~~ + +FileClasses save their "content" attribute off in a separate file from +the rest of the database. This reduces the number of large entries in +the database, which generally makes databases more efficient, and also +allows us to use command-line tools to operate on the files. They are +stored in the files sub-directory of the ``'db'`` directory in your +tracker. + + +IssueClass +~~~~~~~~~~ + +IssueClasses automatically include the "messages", "files", "nosy", and +"superseder" properties. + +The messages and files properties list the links to the messages and +files related to the issue. The nosy property is a list of links to +users who wish to be informed of changes to the issue - they get "CC'ed" +e-mails when messages are sent to or generated by the issue. The nosy +reactor (in the ``'detectors'`` directory) handles this action. The +superseder link indicates an issue which has superseded this one. + +They also have the dynamically generated "creation", "activity" and +"creator" properties. + +The value of the "creation" property is the date when an item was +created, and the value of the "activity" property is the date when any +property on the item was last edited (equivalently, these are the dates +on the first and last records in the item's journal). The "creator" +property holds a link to the user that created the issue. + + +setkey(property) +~~~~~~~~~~~~~~~~ + +Select a String property of the class to be the key property. The key +property must be unique, and allows references to the items in the class +by the content of the key property. That is, we can refer to users by +their username: for example, let's say that there's an issue in roundup, +issue 23. There's also a user, richard, who happens to be user 2. To +assign an issue to him, we could do either of:: + + roundup-admin set issue23 assignedto=2 + +or:: + + roundup-admin set issue23 assignedto=richard + +Note, the same thing can be done in the web and e-mail interfaces. + +setlabelprop(property) +~~~~~~~~~~~~~~~~~~~~~~ + +Select a property of the class to be the label property. The label +property is used whereever an item should be uniquely identified, e.g., +when displaying a link to an item. If setlabelprop is not specified for +a class, the following values are tried for the label: + + * the key of the class (see the `setkey(property)`_ section above) + * the "name" property + * the "title" property + * the first property from the sorted property name list + +So in most cases you can get away without specifying setlabelprop +explicitly. + +setorderprop(property) +~~~~~~~~~~~~~~~~~~~~~~ + +Select a property of the class to be the order property. The order +property is used whenever using a default sort order for the class, +e.g., when grouping or sorting class A by a link to class B in the user +interface, the order property of class B is used for sorting. If +setorderprop is not specified for a class, the following values are tried +for the order property: + + * the property named "order" + * the label property (see `setlabelprop(property)`_ above) + +So in most cases you can get away without specifying setorderprop +explicitly. + +create(information) +~~~~~~~~~~~~~~~~~~~ + +Create an item in the database. This is generally used to create items +in the "definitional" classes like "priority" and "status". + + +A note about ordering +~~~~~~~~~~~~~~~~~~~~~ + +When we sort items in the hyperdb, we use one of a number of methods, +depending on the properties being sorted on: + +1. If it's a String, Number, Date or Interval property, we just sort the + scalar value of the property. Strings are sorted case-sensitively. +2. If it's a Link property, we sort by either the linked item's "order" + property (if it has one) or the linked item's "id". +3. Mulitlinks sort similar to #2, but we start with the first Multilink + list item, and if they're the same, we sort by the second item, and + so on. + +Note that if an "order" property is defined on a Class that is used for +sorting, all items of that Class *must* have a value against the "order" +property, or sorting will result in random ordering. + + +Examples of adding to your schema +--------------------------------- + +TODO + + +Detectors - adding behaviour to your tracker +============================================ +.. _detectors: + +Detectors are initialised every time you open your tracker database, so +you're free to add and remove them any time, even after the database is +initialised via the ``roundup-admin initialise`` command. + +The detectors in your tracker fire *before* (**auditors**) and *after* +(**reactors**) changes to the contents of your database. They are Python +modules that sit in your tracker's ``detectors`` directory. You will +have some installed by default - have a look. You can write new +detectors or modify the existing ones. The existing detectors installed +for you are: + +**nosyreaction.py** + This provides the automatic nosy list maintenance and email sending. + The nosy reactor (``nosyreaction``) fires when new messages are added + to issues. The nosy auditor (``updatenosy``) fires when issues are + changed, and figures out what changes need to be made to the nosy list + (such as adding new authors, etc.) +**statusauditor.py** + This provides the ``chatty`` auditor which changes the issue status + from ``unread`` or ``closed`` to ``chatting`` if new messages appear. + It also provides the ``presetunread`` auditor which pre-sets the + status to ``unread`` on new items if the status isn't explicitly + defined. +**messagesummary.py** + Generates the ``summary`` property for new messages based on the message + content. +**userauditor.py** + Verifies the content of some of the user fields (email addresses and + roles lists). + +If you don't want this default behaviour, you're completely free to change +or remove these detectors. + +See the detectors section in the `design document`__ for details of the +interface for detectors. + +__ design.html + + +Detector API +------------ + +Auditors are called with the arguments:: + + audit(db, cl, itemid, newdata) + +where ``db`` is the database, ``cl`` is an instance of Class or +IssueClass within the database, and ``newdata`` is a dictionary mapping +property names to values. + +For a ``create()`` operation, the ``itemid`` argument is None and +newdata contains all of the initial property values with which the item +is about to be created. + +For a ``set()`` operation, newdata contains only the names and values of +properties that are about to be changed. + +For a ``retire()`` or ``restore()`` operation, newdata is None. + +Reactors are called with the arguments:: + + react(db, cl, itemid, olddata) + +where ``db`` is the database, ``cl`` is an instance of Class or +IssueClass within the database, and ``olddata`` is a dictionary mapping +property names to values. + +For a ``create()`` operation, the ``itemid`` argument is the id of the +newly-created item and ``olddata`` is None. + +For a ``set()`` operation, ``olddata`` contains the names and previous +values of properties that were changed. + +For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of +the retired or restored item and ``olddata`` is None. + + +Additional Detectors Ready For Use +---------------------------------- + +Sample additional detectors that have been found useful will appear in +the ``'detectors'`` directory of the Roundup distribution. If you want +to use one, copy it to the ``'detectors'`` of your tracker instance: + +**newissuecopy.py** + This detector sends an email to a team address whenever a new issue is + created. The address is hard-coded into the detector, so edit it + before you use it (look for the text 'team at team.host') or you'll get + email errors! +**creator_resolution.py** + Catch attempts to set the status to "resolved" - if the assignedto + user isn't the creator, then set the status to "confirm-done". Note that + "classic" Roundup doesn't have that status, so you'll have to add it. If + you don't want to though, it'll just use "in-progress" instead. +**email_auditor.py** + If a file added to an issue is of type message/rfc822, we tack on the + extension .eml. + The reason for this is that Microsoft Internet Explorer will not open + things with a .eml attachment, as they deem it 'unsafe'. Worse yet, + they'll just give you an incomprehensible error message. For more + information, see the detector code - it has a length explanation. + + +Auditor or Reactor? +------------------- + +Generally speaking, the following rules should be observed: + +**Auditors** + Are used for `vetoing creation of or changes to items`_. They might + also make automatic changes to item properties. +**Reactors** + Detect changes in the database and react accordingly. They should avoid + making changes to the database where possible, as this could create + detector loops. + + +Vetoing creation of or changes to items +--------------------------------------- + +Auditors may raise the ``Reject`` exception to prevent the creation of +or changes to items in the database. The mail gateway, for example, will +not attach files or messages to issues when the creation of those files or +messages are prevented through the ``Reject`` exception. It'll also not create +users if that creation is ``Reject``'ed too. + +To use, simply add at the top of your auditor:: + + from roundup.exceptions import Reject + +And then when your rejection criteria have been detected, simply:: + + raise Reject + + +Generating email from Roundup +----------------------------- + +The module ``roundup.mailer`` contains most of the nuts-n-bolts required +to generate email messages from Roundup. + +In addition, the ``IssueClass`` methods ``nosymessage()`` and +``send_message()`` are used to generate nosy messages, and may generate +messages which only consist of a change note (ie. the message id parameter +is not required - this is referred to as a "System Message" because it +comes from "the system" and not a user). + + +Database Content +================ + +.. note:: + if you modify the content of definitional classes, you'll most + likely need to edit the tracker `detectors`_ to reflect your changes. + +Customisation of the special "definitional" classes (eg. status, +priority, resolution, ...) may be done either before or after the +tracker is initialised. The actual method of doing so is completely +different in each case though, so be careful to use the right one. + +**Changing content before tracker initialisation** + Edit the initial_data.py module in your tracker to alter the items + created using the ``create( ... )`` methods. + +**Changing content after tracker initialisation** + As the "admin" user, click on the "class list" link in the web + interface to bring up a list of all database classes. Click on the + name of the class you wish to change the content of. + + You may also use the ``roundup-admin`` interface's create, set and + retire methods to add, alter or remove items from the classes in + question. + +See "`adding a new field to the classic schema`_" for an example that +requires database content changes. + + +Security / Access Controls +========================== + +A set of Permissions is built into the security module by default: + +- Create (everything) +- Edit (everything) +- View (everything) + +These are assigned to the "Admin" Role by default, and allow a user to do +anything. Every Class you define in your `tracker schema`_ also gets an +Create, Edit and View Permission of its own. The web and email interfaces +also define: + +*Email Access* + If defined, the user may use the email interface. Used by default to deny + Anonymous users access to the email interface. When granted to the + Anonymous user, they will be automatically registered by the email + interface (see also the ``new_email_user_roles`` configuration option). +*Web Access* + If defined, the user may use the web interface. All users are able to see + the login form, regardless of this setting (thus enabling logging in). +*Web Roles* + Controls user access to editing the "roles" property of the "user" class. + TODO: deprecate in favour of a property-based control. + +These are hooked into the default Roles: + +- Admin (Create, Edit, View and everything; Web Roles) +- User (Web Access; Email Access) +- Anonymous (Web Access) + +And finally, the "admin" user gets the "Admin" Role, and the "anonymous" +user gets "Anonymous" assigned when the tracker is installed. + +For the "User" Role, the "classic" tracker defines: + +- Create, Edit and View issue, file, msg, query, keyword +- View priority, status +- View user +- Edit their own user record + +And the "Anonymous" Role is defined as: + +- Web interface access +- Create user (for registration) +- View issue, file, msg, query, keyword, priority, status + +Put together, these settings appear in the tracker's ``schema.py`` file:: + + # + # TRACKER SECURITY SETTINGS + # + # See the configuration and customisation document for information + # about security setup. + + # + # REGULAR USERS + # + # Give the regular users access to the web and email interface + db.security.addPermissionToRole('User', 'Web Access') + db.security.addPermissionToRole('User', 'Email Access') + + # Assign the access and edit Permissions for issue, file and message + # to regular users now + for cl in 'issue', 'file', 'msg', 'query', 'keyword': + db.security.addPermissionToRole('User', 'View', cl) + db.security.addPermissionToRole('User', 'Edit', cl) + db.security.addPermissionToRole('User', 'Create', cl) + for cl in 'priority', 'status': + db.security.addPermissionToRole('User', 'View', cl) + + # May users view other user information? Comment these lines out + # if you don't want them to + db.security.addPermissionToRole('User', 'View', 'user') + + # Users should be able to edit their own details -- this permission + # is limited to only the situation where the Viewed or Edited item + # is their own. + def own_record(db, userid, itemid): + '''Determine whether the userid matches the item being accessed.''' + return userid == itemid + p = db.security.addPermission(name='View', klass='user', check=own_record, + description="User is allowed to view their own user details") + db.security.addPermissionToRole('User', p) + p = db.security.addPermission(name='Edit', klass='user', check=own_record, + description="User is allowed to edit their own user details") + db.security.addPermissionToRole('User', p) + + # + # ANONYMOUS USER PERMISSIONS + # + # Let anonymous users access the web interface. Note that almost all + # trackers will need this Permission. The only situation where it's not + # required is in a tracker that uses an HTTP Basic Authenticated front-end. + db.security.addPermissionToRole('Anonymous', 'Web Access') + + # Let anonymous users access the email interface (note that this implies + # that they will be registered automatically, hence they will need the + # "Create" user Permission below) + # This is disabled by default to stop spam from auto-registering users on + # public trackers. + #db.security.addPermissionToRole('Anonymous', 'Email Access') + + # Assign the appropriate permissions to the anonymous user's Anonymous + # Role. Choices here are: + # - Allow anonymous users to register + db.security.addPermissionToRole('Anonymous', 'Create', 'user') + + # Allow anonymous users access to view issues (and the related, linked + # information) + for cl in 'issue', 'file', 'msg', 'keyword', 'priority', 'status': + db.security.addPermissionToRole('Anonymous', 'View', cl) + + # [OPTIONAL] + # Allow anonymous users access to create or edit "issue" items (and the + # related file and message items) + #for cl in 'issue', 'file', 'msg': + # db.security.addPermissionToRole('Anonymous', 'Create', cl) + # db.security.addPermissionToRole('Anonymous', 'Edit', cl) + + +Automatic Permission Checks +--------------------------- + +Permissions are automatically checked when information is rendered +through the web. This includes: + +1. View checks for properties when being rendered via the ``plain()`` or + similar methods. If the check fails, the text "[hidden]" will be + displayed. +2. Edit checks for properties when the edit field is being rendered via + the ``field()`` or similar methods. If the check fails, the property + will be rendered via the ``plain()`` method (see point 1. for subsequent + checking performed) +3. View checks are performed in index pages for each item being displayed + such that if the user does not have permission, the row is not rendered. +4. View checks are performed at the top of item pages for the Item being + displayed. If the user does not have permission, the text "You are not + allowed to view this page." will be displayed. +5. View checks are performed at the top of index pages for the Class being + displayed. If the user does not have permission, the text "You are not + allowed to view this page." will be displayed. + + +New User Roles +-------------- + +New users are assigned the Roles defined in the config file as: + +- NEW_WEB_USER_ROLES +- NEW_EMAIL_USER_ROLES + +The `users may only edit their issues`_ example shows customisation of +these parameters. + + +Changing Access Controls +------------------------ + +You may alter the configuration variables to change the Role that new +web or email users get, for example to not give them access to the web +interface if they register through email. + +You may use the ``roundup-admin`` "``security``" command to display the +current Role and Permission configuration in your tracker. + + +Adding a new Permission +~~~~~~~~~~~~~~~~~~~~~~~ + +When adding a new Permission, you will need to: + +1. add it to your tracker's ``schema.py`` so it is created, using + ``security.addPermission``, for example:: + + self.security.addPermission(name="View", klass='frozzle', + description="User is allowed to access frozzles") + + will set up a new "View" permission on the Class "frozzle". +2. enable it for the Roles that should have it (verify with + "``roundup-admin security``") +3. add it to the relevant HTML interface templates +4. add it to the appropriate xxxPermission methods on in your tracker + interfaces module + +The ``addPermission`` method takes a couple of optional parameters: + +**properties** + A sequence of property names that are the only properties to apply the + new Permission to (eg. ``... klass='user', properties=('name', + 'email') ...``) +**check** + A function to be execute which returns boolean determining whether the + Permission is allowed. The function has the signature ``check(db, userid, + itemid)`` where ``db`` is a handle on the open database, ``userid`` is + the user attempting access and ``itemid`` is the specific item being + accessed. + +Example Scenarios +~~~~~~~~~~~~~~~~~ + +See the `examples`_ section for longer examples of customisation. + +**anonymous access through the e-mail gateway** + Give the "anonymous" user the "Email Access", ("Edit", "issue") and + ("Create", "msg") Permissions but do not not give them the ("Create", + "user") Permission. This means that when an unknown user sends email + into the tracker, they're automatically logged in as "anonymous". + Since they don't have the ("Create", "user") Permission, they won't + be automatically registered, but since "anonymous" has permission to + use the gateway, they'll still be able to submit issues. Note that + the Sender information - their email address - will not be available + - they're *anonymous*. + +**automatic registration of users in the e-mail gateway** + By giving the "anonymous" user the ("Create", "user" Permission, any + unidentified user will automatically be registered with the tracker + (with no password, so they won't be able to log in through + the web until an admin sets their password). This is the default + behaviour in the tracker templates that ship with Roundup. The new user + is given the Roles list defined in the "new_email_user_roles" config + variable. + +**only developers may be assigned issues** + Create a new Permission called "Fixer" for the "issue" class. Create a + new Role "Developer" which has that Permission, and assign that to the + appropriate users. Filter the list of users available in the assignedto + list to include only those users. Enforce the Permission with an + auditor. See the example + `restricting the list of users that are assignable to a task`_. + +**only managers may sign off issues as complete** + Create a new Permission called "Closer" for the "issue" class. Create a + new Role "Manager" which has that Permission, and assign that to the + appropriate users. In your web interface, only display the "resolved" + issue state option when the user has the "Closer" Permissions. Enforce + the Permission with an auditor. This is very similar to the previous + example, except that the web interface check would look like:: + + + +**don't give web access to users who register through email** + Create a new Role called "Email User" which has all the Permissions of + the normal "User" Role minus the "Web Access" Permission. This will + allow users to send in emails to the tracker, but not access the web + interface. + +**let some users edit the details of all users** + Create a new Role called "User Admin" which has the Permission for + editing users:: + + db.security.addRole(name='User Admin', description='Managing users') + p = db.security.getPermission('Edit', 'user') + db.security.addPermissionToRole('User Admin', p) + + and assign the Role to the users who need the permission. + + +Web Interface +============= + +.. contents:: + :local: + +The web interface is provided by the ``roundup.cgi.client`` module and +is used by ``roundup.cgi``, ``roundup-server`` and ``ZRoundup`` +(``ZRoundup`` is broken, until further notice). In all cases, we +determine which tracker is being accessed (the first part of the URL +path inside the scope of the CGI handler) and pass control on to the +``roundup.cgi.client.Client`` class - which handles the rest of the +access through its ``main()`` method. This means that you can do pretty +much anything you want as a web interface to your tracker. + + + +Repercussions of changing the tracker schema +--------------------------------------------- + +If you choose to change the `tracker schema`_ you will need to ensure +the web interface knows about it: + +1. Index, item and search pages for the relevant classes may need to + have properties added or removed, +2. The "page" template may require links to be changed, as might the + "home" page's content arguments. + + +How requests are processed +-------------------------- + +The basic processing of a web request proceeds as follows: + +1. figure out who we are, defaulting to the "anonymous" user +2. figure out what the request is for - we call this the "context" +3. handle any requested action (item edit, search, ...) +4. render the template requested by the context, resulting in HTML + output + +In some situations, exceptions occur: + +- HTTP Redirect (generally raised by an action) +- SendFile (generally raised by ``determine_context``) + here we serve up a FileClass "content" property +- SendStaticFile (generally raised by ``determine_context``) + here we serve up a file from the tracker "html" directory +- Unauthorised (generally raised by an action) + here the action is cancelled, the request is rendered and an error + message is displayed indicating that permission was not granted for + the action to take place +- NotFound (raised wherever it needs to be) + this exception percolates up to the CGI interface that called the + client + + +Determining web context +----------------------- + +To determine the "context" of a request, we look at the URL and the +special request variable ``@template``. The URL path after the tracker +identifier is examined. Typical URL paths look like: + +1. ``/tracker/issue`` +2. ``/tracker/issue1`` +3. ``/tracker/@@file/style.css`` +4. ``/cgi-bin/roundup.cgi/tracker/file1`` +5. ``/cgi-bin/roundup.cgi/tracker/file1/kitten.png`` + +where the "tracker identifier" is "tracker" in the above cases. That means +we're looking at "issue", "issue1", "@@file/style.css", "file1" and +"file1/kitten.png" in the cases above. The path is generally only one +entry long - longer paths are handled differently. + +a. if there is no path, then we are in the "home" context. See `the "home" + context`_ below for more information about how it may be used. +b. if the path starts with "@@file" (as in example 3, + "/tracker/@@file/style.css"), then the additional path entry, + "style.css" specifies the filename of a static file we're to serve up + from the tracker TEMPLATES (or STATIC_FILES, if configured) directory. + This is usually the tracker's "html" directory. Raises a SendStaticFile + exception. +c. if there is something in the path (as in example 1, "issue"), it + identifies the tracker class we're to display. +d. if the path is an item designator (as in examples 2 and 4, "issue1" + and "file1"), then we're to display a specific item. +e. if the path starts with an item designator and is longer than one + entry (as in example 5, "file1/kitten.png"), then we're assumed to be + handling an item of a ``FileClass``, and the extra path information + gives the filename that the client is going to label the download + with (i.e. "file1/kitten.png" is nicer to download than "file1"). + This raises a ``SendFile`` exception. + +Both b. and e. stop before we bother to determine the template we're +going to use. That's because they don't actually use templates. + +The template used is specified by the ``@template`` CGI variable, which +defaults to: + +- only classname suplied: "index" +- full item designator supplied: "item" + + +The "home" Context +------------------ + +The "home" context is special because it allows you to add templated +pages to your tracker that don't rely on a class or item (ie. an issues +list or specific issue). + +Let's say you wish to add frames to control the layout of your tracker's +interface. You'd probably have: + +- A top-level frameset page. This page probably wouldn't be templated, so + it could be served as a static file (see `serving static content`_) +- A sidebar frame that is templated. Let's call this page + "home.navigation.html" in your tracker's "html" directory. To load that + page up, you use the URL: + + /home?@template=navigation + + +Serving static content +---------------------- + +See the previous section `determining web context`_ where it describes +``@@file`` paths. + + +Performing actions in web requests +---------------------------------- + +When a user requests a web page, they may optionally also request for an +action to take place. As described in `how requests are processed`_, the +action is performed before the requested page is generated. Actions are +triggered by using a ``@action`` CGI variable, where the value is one +of: + +**login** + Attempt to log a user in. + +**logout** + Log the user out - make them "anonymous". + +**register** + Attempt to create a new user based on the contents of the form and then + log them in. + +**edit** + Perform an edit of an item in the database. There are some `special form + variables`_ you may use. + +**new** + Add a new item to the database. You may use the same `special form + variables`_ as in the "edit" action. + +**retire** + Retire the item in the database. + +**editCSV** + Performs an edit of all of a class' items in one go. See also the + *class*.csv templating method which generates the CSV data to be + edited, and the ``'_generic.index'`` template which uses both of these + features. + +**search** + Mangle some of the form variables: + + - Set the form ":filter" variable based on the values of the filter + variables - if they're set to anything other than "dontcare" then add + them to :filter. + + - Also handle the ":queryname" variable and save off the query to the + user's query list. + +Each of the actions is implemented by a corresponding ``*XxxAction*`` (where +"Xxx" is the name of the action) class in the ``roundup.cgi.actions`` module. +These classes are registered with ``roundup.cgi.client.Client``. If you need +to define new actions, you may add them there (see `defining new +web actions`_). + +Each action class also has a ``*permission*`` method which determines whether +the action is permissible given the current user. The base permission checks +for each action are: + +**login** + Determine whether the user has the "Web Access" Permission. +**logout** + No permission checks are made. +**register** + Determine whether the user has the ("Create", "user") Permission. +**edit** + Determine whether the user has permission to edit this item. If we're + editing the "user" class, users are allowed to edit their own details - + unless they try to edit the "roles" property, which requires the + special Permission "Web Roles". +**new** + Determine whether the user has permission to create this item. No + additional property checks are made. Additionally, new user items may + be created if the user has the ("Create", "user") Permission. +**editCSV** + Determine whether the user has permission to edit this class. +**search** + Determine whether the user has permission to view this class. + + +Special form variables +---------------------- + +Item properties and their values are edited with html FORM +variables and their values. You can: + +- Change the value of some property of the current item. +- Create a new item of any class, and edit the new item's + properties, +- Attach newly created items to a multilink property of the + current item. +- Remove items from a multilink property of the current item. +- Specify that some properties are required for the edit + operation to be successful. +- Set up user interface locale. + +These operations will only take place if the form action (the +``@action`` variable) is "edit" or "new". + +In the following, values are variable, "@" may be +either ":" or "@", and other text "required" is fixed. + +Two special form variables are used to specify user language preferences: + +``@language`` + value may be locale name or ``none``. If this variable is set to + locale name, web interface language is changed to given value + (provided that appropriate translation is available), the value + is stored in the browser cookie and will be used for all following + requests. If value is ``none`` the cookie is removed and the + language is changed to the tracker default, set up in the tracker + configuration or OS environment. + +``@charset`` + value may be character set name or ``none``. Character set name + is stored in the browser cookie and sets output encoding for all + HTML pages generated by Roundup. If value is ``none`` the cookie + is removed and HTML output is reset to Roundup internal encoding + (UTF-8). + +Most properties are specified as form variables: + +```` + property on the current context item + +``"@"`` + property on the indicated item (for editing related information) + +Designators name a specific item of a class. + +```` + Name an existing item of class . + +``"-"`` + Name the th new item of class . If the form + submission is successful, a new item of is + created. Within the submitted form, a particular + designator of this form always refers to the same new + item. + +Once we have determined the "propname", we look at it to see +if it's special: + +``@required`` + The associated form value is a comma-separated list of + property names that must be specified when the form is + submitted for the edit operation to succeed. + + When the is missing, the properties are + for the current context item. When is + present, they are for the item specified by + . + + The "@required" specifier must come before any of the + properties it refers to are assigned in the form. + +``@remove@=id(s)`` or ``@add@=id(s)`` + The "@add@" and "@remove@" edit actions apply only to + Multilink properties. The form value must be a + comma-separate list of keys for the class specified by + the simple form variable. The listed items are added + to (respectively, removed from) the specified + property. + +``@link@=`` + If the edit action is "@link@", the simple form + variable must specify a Link or Multilink property. + The form value is a comma-separated list of + designators. The item corresponding to each + designator is linked to the property given by simple + form variable. + +None of the above (ie. just a simple form value) + The value of the form variable is converted + appropriately, depending on the type of the property. + + For a Link('klass') property, the form value is a + single key for 'klass', where the key field is + specified in schema.py. + + For a Multilink('klass') property, the form value is a + comma-separated list of keys for 'klass', where the + key field is specified in schema.py. + + Note that for simple-form-variables specifiying Link + and Multilink properties, the linked-to class must + have a key field. + + For a String() property specifying a filename, the + file named by the form value is uploaded. This means we + try to set additional properties "filename" and "type" (if + they are valid for the class). Otherwise, the property + is set to the form value. + + For Date(), Interval(), Boolean(), and Number() + properties, the form value is converted to the + appropriate + +Any of the form variables may be prefixed with a classname or +designator. + +Two special form values are supported for backwards compatibility: + + at note + This is equivalent to:: + + @link at messages=msg-1 + msg-1 at content=value + + except that in addition, the "author" and "date" properties of + "msg-1" are set to the userid of the submitter, and the current + time, respectively. + + at file + This is equivalent to:: + + @link at files=file-1 + file-1 at content=value + + The String content value is handled as described above for file + uploads. + +If both the "@note" and "@file" form variables are +specified, the action:: + + @link at msg-1@files=file-1 + +is also performed. + +We also check that FileClass items have a "content" property with +actual content, otherwise we remove them from all_props before +returning. + + +Default templates +----------------- + +The default templates are html4 compliant. If you wish to change them to be +xhtml compliant, you'll need to change the ``html_version`` configuration +variable in ``config.ini`` to ``'xhtml'`` instead of ``'html4'``. + +Most customisation of the web view can be done by modifying the +templates in the tracker ``'html'`` directory. There are several types +of files in there. The *minimal* template includes: + +**page.html** + This template usually defines the overall look of your tracker. When + you view an issue, it appears inside this template. When you view an + index, it also appears inside this template. This template defines a + macro called "icing" which is used by almost all other templates as a + coating for their content, using its "content" slot. It also defines + the "head_title" and "body_title" slots to allow setting of the page + title. +**home.html** + the default page displayed when no other page is indicated by the user +**home.classlist.html** + a special version of the default page that lists the classes in the + tracker +**classname.item.html** + displays an item of the *classname* class +**classname.index.html** + displays a list of *classname* items +**classname.search.html** + displays a search page for *classname* items +**_generic.index.html** + used to display a list of items where there is no + ``*classname*.index`` available +**_generic.help.html** + used to display a "class help" page where there is no + ``*classname*.help`` +**user.register.html** + a special page just for the user class, that renders the registration + page +**style.css.html** + a static file that is served up as-is + +The *classic* template has a number of additional templates. + +Remember that you can create any template extension you want to, +so if you just want to play around with the templating for new issues, +you can copy the current "issue.item" template to "issue.test", and then +access the test template using the "@template" URL argument:: + + http://your.tracker.example/tracker/issue?@template=test + +and it won't affect your users using the "issue.item" template. + + +How the templates work +---------------------- + + +Basic Templating Actions +~~~~~~~~~~~~~~~~~~~~~~~~ + +Roundup's templates consist of special attributes on the HTML tags. +These attributes form the `Template Attribute Language`_, or TAL. +The basic TAL commands are: + +**tal:define="variable expression; variable expression; ..."** + Define a new variable that is local to this tag and its contents. For + example:: + + + + + + In this example, the variable "title" is defined as the result of the + expression "request/description". The "tal:content" command inside the + tag may then use the "title" variable. + +**tal:condition="expression"** + Only keep this tag and its contents if the expression is true. For + example:: + +

+ Display some issue information. +

+ + In the example, the

tag and its contents are only displayed if + the user has the "View" permission for issues. We consider the number + zero, a blank string, an empty list, and the built-in variable + nothing to be false values. Nearly every other value is true, + including non-zero numbers, and strings with anything in them (even + spaces!). + +**tal:repeat="variable expression"** + Repeat this tag and its contents for each element of the sequence + that the expression returns, defining a new local variable and a + special "repeat" variable for each element. For example:: + + + + + + + + The example would iterate over the sequence of users returned by + "user/list" and define the local variable "u" for each entry. Using + the repeat command creates a new variable called "repeat" which you + may access to gather information about the iteration. See the section + below on `the repeat variable`_. + +**tal:replace="expression"** + Replace this tag with the result of the expression. For example:: + + + + The example would replace the tag and its contents with the + user's realname. If the user's realname was "Bruce", then the + resultant output would be "Bruce". + +**tal:content="expression"** + Replace the contents of this tag with the result of the expression. + For example:: + + user's name appears here + + + The example would replace the contents of the tag with the + user's realname. If the user's realname was "Bruce" then the + resultant output would be "Bruce". + +**tal:attributes="attribute expression; attribute expression; ..."** + Set attributes on this tag to the results of expressions. For + example:: + + My Details + + In the example, the "href" attribute of the tag is set to the + value of the "string:user${request/user/id}" expression, which will + be something like "user123". + +**tal:omit-tag="expression"** + Remove this tag (but not its contents) if the expression is true. For + example:: + + Hello, world! + + would result in output of:: + + Hello, world! + +Note that the commands on a given tag are evaulated in the order above, +so *define* comes before *condition*, and so on. + +Additionally, you may include tags such as , which are +removed from output. Its content is kept, but the tag itself is not (so +don't go using any "tal:attributes" commands on it). This is useful for +making arbitrary blocks of HTML conditional or repeatable (very handy +for repeating multiple table rows, which would othewise require an +illegal tag placement to effect the repeat). + +.. _TAL: +.. _Template Attribute Language: + http://dev.zope.org/Wikis/DevSite/Projects/ZPT/TAL%20Specification%201.4 + + +Templating Expressions +~~~~~~~~~~~~~~~~~~~~~~ + +Templating Expressions are covered by `Template Attribute Language +Expression Syntax`_, or TALES. The expressions you may use in the +attribute values may be one of the following forms: + +**Path Expressions** - eg. ``item/status/checklist`` + These are object attribute / item accesses. Roughly speaking, the + path ``item/status/checklist`` is broken into parts ``item``, + ``status`` and ``checklist``. The ``item`` part is the root of the + expression. We then look for a ``status`` attribute on ``item``, or + failing that, a ``status`` item (as in ``item['status']``). If that + fails, the path expression fails. When we get to the end, the object + we're left with is evaluated to get a string - if it is a method, it + is called; if it is an object, it is stringified. Path expressions + may have an optional ``path:`` prefix, but they are the default + expression type, so it's not necessary. + + If an expression evaluates to ``default``, then the expression is + "cancelled" - whatever HTML already exists in the template will + remain (tag content in the case of ``tal:content``, attributes in the + case of ``tal:attributes``). + + If an expression evaluates to ``nothing`` then the target of the + expression is removed (tag content in the case of ``tal:content``, + attributes in the case of ``tal:attributes`` and the tag itself in + the case of ``tal:replace``). + + If an element in the path may not exist, then you can use the ``|`` + operator in the expression to provide an alternative. So, the + expression ``request/form/foo/value | default`` would simply leave + the current HTML in place if the "foo" form variable doesn't exist. + + You may use the python function ``path``, as in + ``path("item/status")``, to embed path expressions in Python + expressions. + +**String Expressions** - eg. ``string:hello ${user/name}`` + These expressions are simple string interpolations - though they can + be just plain strings with no interpolation if you want. The + expression in the ``${ ... }`` is just a path expression as above. + +**Python Expressions** - eg. ``python: 1+1`` + These expressions give the full power of Python. All the "root level" + variables are available, so ``python:item.status.checklist()`` would + be equivalent to ``item/status/checklist``, assuming that + ``checklist`` is a method. + +Modifiers: + +**structure** - eg. ``structure python:msg.content.plain(hyperlink=1)`` + The result of expressions are normally *escaped* to be safe for HTML + display (all "<", ">" and "&" are turned into special entities). The + ``structure`` expression modifier turns off this escaping - the + result of the expression is now assumed to be HTML, which is passed + to the web browser for rendering. + +**not:** - eg. ``not:python:1=1`` + This simply inverts the logical true/false value of another + expression. + +.. _TALES: +.. _Template Attribute Language Expression Syntax: + http://dev.zope.org/Wikis/DevSite/Projects/ZPT/TALES%20Specification%201.3 + + +Template Macros +~~~~~~~~~~~~~~~ + +Macros are used in Roundup to save us from repeating the same common +page stuctures over and over. The most common (and probably only) macro +you'll use is the "icing" macro defined in the "page" template. + +Macros are generated and used inside your templates using special +attributes similar to the `basic templating actions`_. In this case, +though, the attributes belong to the `Macro Expansion Template +Attribute Language`_, or METAL. The macro commands are: + +**metal:define-macro="macro name"** + Define that the tag and its contents are now a macro that may be + inserted into other templates using the *use-macro* command. For + example:: + + + ... + + + defines a macro called "page" using the ```` tag and its + contents. Once defined, macros are stored on the template they're + defined on in the ``macros`` attribute. You can access them later on + through the ``templates`` variable, eg. the most common + ``templates/page/macros/icing`` to access the "page" macro of the + "page" template. + +**metal:use-macro="path expression"** + Use a macro, which is identified by the path expression (see above). + This will replace the current tag with the identified macro contents. + For example:: + + + ... + + + will replace the tag and its contents with the "page" macro of the + "page" template. + +**metal:define-slot="slot name"** and **metal:fill-slot="slot name"** + To define *dynamic* parts of the macro, you define "slots" which may + be filled when the macro is used with a *use-macro* command. For + example, the ``templates/page/macros/icing`` macro defines a slot like + so:: + + title goes here + + In your *use-macro* command, you may now use a *fill-slot* command + like this:: + + My Title + + where the tag that fills the slot completely replaces the one defined + as the slot in the macro. + +Note that you may not mix `METAL`_ and `TAL`_ commands on the same tag, but +TAL commands may be used freely inside METAL-using tags (so your +*fill-slots* tags may have all manner of TAL inside them). + +.. _METAL: +.. _Macro Expansion Template Attribute Language: + http://dev.zope.org/Wikis/DevSite/Projects/ZPT/METAL%20Specification%201.0 + +Information available to templates +---------------------------------- + +This is implemented by ``roundup.cgi.templating.RoundupPageTemplate`` + +The following variables are available to templates. + +**context** + The current context. This is either None, a `hyperdb class wrapper`_ + or a `hyperdb item wrapper`_ +**request** + Includes information about the current request, including: + - the current index information (``filterspec``, ``filter`` args, + ``properties``, etc) parsed out of the form. + - methods for easy filterspec link generation + - *user*, the current user item as an HTMLItem instance + - *form* + The current CGI form information as a mapping of form argument name + to value +**config** + This variable holds all the values defined in the tracker config.ini + file (eg. TRACKER_NAME, etc.) +**db** + The current database, used to access arbitrary database items. +**templates** + Access to all the tracker templates by name. Used mainly in + *use-macro* commands. +**utils** + This variable makes available some utility functions like batching. +**nothing** + This is a special variable - if an expression evaluates to this, then + the tag (in the case of a ``tal:replace``), its contents (in the case + of ``tal:content``) or some attributes (in the case of + ``tal:attributes``) will not appear in the the output. So, for + example:: + + Hello, World! + + would result in:: + + Hello, World! + +**default** + Also a special variable - if an expression evaluates to this, then the + existing HTML in the template will not be replaced or removed, it will + remain. So:: + + Hello, World! + + would result in:: + + Hello, World! + +**true**, **false** + Boolean constants that may be used in `templating expressions`_ + instead of ``python:1`` and ``python:0``. +**i18n** + Internationalization service, providing two string translation methods: + + **gettext** (*message*) + Return the localized translation of message + **ngettext** (*singular*, *plural*, *number*) + Like ``gettext()``, but consider plural forms. If a translation + is found, apply the plural formula to *number*, and return the + resulting message (some languages have more than two plural forms). + If no translation is found, return singular if *number* is 1; + return plural otherwise. + + This function requires python2.3; in earlier python versions + may not work as expected. + +The context variable +~~~~~~~~~~~~~~~~~~~~ + +The *context* variable is one of three things based on the current +context (see `determining web context`_ for how we figure this out): + +1. if we're looking at a "home" page, then it's None +2. if we're looking at a specific hyperdb class, it's a + `hyperdb class wrapper`_. +3. if we're looking at a specific hyperdb item, it's a + `hyperdb item wrapper`_. + +If the context is not None, we can access the properties of the class or +item. The only real difference between cases 2 and 3 above are: + +1. the properties may have a real value behind them, and this will + appear if the property is displayed through ``context/property`` or + ``context/property/field``. +2. the context's "id" property will be a false value in the second case, + but a real, or true value in the third. Thus we can determine whether + we're looking at a real item from the hyperdb by testing + "context/id". + +Hyperdb class wrapper +::::::::::::::::::::: + +This is implemented by the ``roundup.cgi.templating.HTMLClass`` +class. + +This wrapper object provides access to a hyperb class. It is used +primarily in both index view and new item views, but it's also usable +anywhere else that you wish to access information about a class, or the +items of a class, when you don't have a specific item of that class in +mind. + +We allow access to properties. There will be no "id" property. The value +accessed through the property will be the current value of the same name +from the CGI form. + +There are several methods available on these wrapper objects: + +=========== ============================================================= +Method Description +=========== ============================================================= +properties return a `hyperdb property wrapper`_ for all of this class's + properties. +list lists all of the active (not retired) items in the class. +csv return the items of this class as a chunk of CSV text. +propnames lists the names of the properties of this class. +filter lists of items from this class, filtered and sorted. Two + options are avaible for sorting: + + 1. by the current *request* filterspec/filter/sort/group args + 2. by the "filterspec", "sort" and "group" keyword args. + "filterspec" is ``{propname: value(s)}``. "sort" and + "group" are ``(dir, prop)`` where dir is '+', '-' or None + and prop is a prop name or None. + + eg. ``issue.filter(filterspec={"priority": "1"}, + sort=('activity', '+'))`` + +classhelp display a link to a javascript popup containing this class' + "help" template. + + This generates a link to a popup window which displays the + properties indicated by "properties" of the class named by + "classname". The "properties" should be a comma-separated list + (eg. 'id,name,description'). Properties defaults to all the + properties of a class (excluding id, creator, created and + activity). + + You may optionally override the "label" displayed, the "width", + the "height", the number of items per page ("pagesize") and + the field on which the list is sorted ("sort"). + + With the "filter" arg it is possible to specify a filter for + which items are supposed to be displayed. It has to be of + the format "=;=;...". + + The popup window will be resizable and scrollable. + + If the "property" arg is given, it's passed through to the + javascript help_window function. This allows updating of a + property in the calling HTML page. + + If the "form" arg is given, it's passed through to the + javascript help_window function - it's the name of the form + the "property" belongs to. + +submit generate a submit button (and action hidden element) +renderWith render this class with the given template. +history returns 'New node - no history' :) +is_edit_ok is the user allowed to Edit the current class? +is_view_ok is the user allowed to View the current class? +=========== ============================================================= + +Note that if you have a property of the same name as one of the above +methods, you'll need to access it using a python "item access" +expression. For example:: + + python:context['list'] + +will access the "list" property, rather than the list method. + + +Hyperdb item wrapper +:::::::::::::::::::: + +This is implemented by the ``roundup.cgi.templating.HTMLItem`` +class. + +This wrapper object provides access to a hyperb item. + +We allow access to properties. There will be no "id" property. The value +accessed through the property will be the current value of the same name +from the CGI form. + +There are several methods available on these wrapper objects: + +=============== ======================================================== +Method Description +=============== ======================================================== +submit generate a submit button (and action hidden element) +journal return the journal of the current item (**not + implemented**) +history render the journal of the current item as HTML +renderQueryForm specific to the "query" class - render the search form + for the query +hasPermission specific to the "user" class - determine whether the + user has a Permission. The signature is:: + + hasPermission(self, permission, [classname=], + [property=], [itemid=]) + + where the classname defaults to the current context. +hasRole specific to the "user" class - determine whether the + user has a Role. The signature is:: + + hasRole(self, rolename) + +is_edit_ok is the user allowed to Edit the current item? +is_view_ok is the user allowed to View the current item? +is_retired is the item retired? +download_url generate a url-quoted link for download of FileClass + item contents (ie. file/) +copy_url generate a url-quoted link for creating a copy + of this item. By default, the copy will acquire + all properties of the current item except for + ``messages`` and ``files``. This can be overridden + by passing ``exclude`` argument which contains a list + (or any iterable) of property names that shall not be + copied. Database-driven properties like ``id`` or + ``activity`` cannot be copied. +=============== ======================================================== + +Note that if you have a property of the same name as one of the above +methods, you'll need to access it using a python "item access" +expression. For example:: + + python:context['journal'] + +will access the "journal" property, rather than the journal method. + + +Hyperdb property wrapper +:::::::::::::::::::::::: + +This is implemented by subclasses of the +``roundup.cgi.templating.HTMLProperty`` class (``HTMLStringProperty``, +``HTMLNumberProperty``, and so on). + +This wrapper object provides access to a single property of a class. Its +value may be either: + +1. if accessed through a `hyperdb item wrapper`_, then it's a value from + the hyperdb +2. if access through a `hyperdb class wrapper`_, then it's a value from + the CGI form + + +The property wrapper has some useful attributes: + +=============== ======================================================== +Attribute Description +=============== ======================================================== +_name the name of the property +_value the value of the property if any - this is the actual + value retrieved from the hyperdb for this property +=============== ======================================================== + +There are several methods available on these wrapper objects: + +=========== ================================================================ +Method Description +=========== ================================================================ +plain render a "plain" representation of the property. This method + may take two arguments: + + escape + If true, escape the text so it is HTML safe (default: no). The + reason this defaults to off is that text is usually escaped + at a later stage by the TAL commands, unless the "structure" + option is used in the template. The following ``tal:content`` + expressions are all equivalent:: + + "structure python:msg.content.plain(escape=1)" + "python:msg.content.plain()" + "msg/content/plain" + "msg/content" + + Usually you'll only want to use the escape option in a + complex expression. + + hyperlink + If true, turn URLs, email addresses and hyperdb item + designators in the text into hyperlinks (default: no). Note + that you'll need to use the "structure" TAL option if you + want to use this ``tal:content`` expression:: + + "structure python:msg.content.plain(hyperlink=1)" + + The text is automatically HTML-escaped before the hyperlinking + transformation done in the plain() method. + +hyperlinked The same as msg.content.plain(hyperlink=1), but nicer:: + + "structure msg/content/hyperlinked" + +field render an appropriate form edit field for the property - for + most types this is a text entry box, but for Booleans it's a + tri-state yes/no/neither selection. This method may take some + arguments: + + size + Sets the width in characters of the edit field + + format (Date properties only) + Sets the format of the date in the field - uses the same + format string argument as supplied to the ``pretty`` method + below. + +stext only on String properties - render the value of the property + as StructuredText (requires the StructureText module to be + installed separately) +multiline only on String properties - render a multiline form edit + field for the property +email only on String properties - render the value of the property + as an obscured email address +confirm only on Password properties - render a second form edit field + for the property, used for confirmation that the user typed + the password correctly. Generates a field with name + "name:confirm". +now only on Date properties - return the current date as a new + property +reldate only on Date properties - render the interval between the date + and now +local only on Date properties - return this date as a new property + with some timezone offset, for example:: + + python:context.creation.local(10) + + will render the date with a +10 hour offset. +pretty Date properties - render the date as "dd Mon YYYY" (eg. "19 + Mar 2004"). Takes an optional format argument, for example:: + + python:context.activity.pretty('%Y-%m-%d') + + Will format as "2004-03-19" instead. + + Interval properties - render the interval in a pretty + format (eg. "yesterday"). The format arguments are those used + in the standard ``strftime`` call (see the `Python Library + Reference: time module`__) +popcal Generate a link to a popup calendar which may be used to + edit the date field, for example:: + + + +menu only on Link and Multilink properties - render a form select + list for this property. Takes a number of optional arguments + + size + is used to limit the length of the list labels + height + is used to set the + + + + + File + + + + +   + + submit button will go here + + + + + +When a change is submitted, the system automatically generates a message +describing the changed properties. As shown in the example, the editor +template can use the ":note" and ":file" fields, which are added to the +standard changenote message generated by Roundup. + + +Form values +::::::::::: + +We have a number of ways to pull properties out of the form in order to +meet the various needs of: + +1. editing the current item (perhaps an issue item) +2. editing information related to the current item (eg. messages or + attached files) +3. creating new information to be linked to the current item (eg. time + spent on an issue) + +In the following, ```` values are variable, ":" may be one of +":" or "@", and other text ("required") is fixed. + +Properties are specified as form variables: + +```` + property on the current context item + +``:`` + property on the indicated item (for editing related information) + +``-:`` + property on the Nth new item of classname (generally for creating new + items to attach to the current item) + +Once we have determined the "propname", we check to see if it is one of +the special form values: + +``@required`` + The named property values must be supplied or a ValueError will be + raised. + +``@remove@=id(s)`` + The ids will be removed from the multilink property. + +``:add:=id(s)`` + The ids will be added to the multilink property. + +``:link:=`` + Used to add a link to new items created during edit. These are + collected and returned in ``all_links``. This will result in an + additional linking operation (either Link set or Multilink append) + after the edit/create is done using ``all_props`` in ``_editnodes``. + The on the current item will be set/appended the id of the + newly created item of class (where must be + -). + +Any of the form variables may be prefixed with a classname or +designator. + +Two special form values are supported for backwards compatibility: + +``:note`` + create a message (with content, author and date), linked to the + context item. This is ALWAYS designated "msg-1". +``:file`` + create a file, attached to the current item and any message created by + :note. This is ALWAYS designated "file-1". + + +Spool Section +~~~~~~~~~~~~~ + +The spool section lists related information like the messages and files +of an issue. + +TODO + + +History Section +~~~~~~~~~~~~~~~ + +The final section displayed is the history of the item - its database +journal. This is generally generated with the template:: + + + +*To be done:* + +*The actual history entries of the item may be accessed for manual +templating through the "journal" method of the item*:: + + + a journal entry + + +*where each journal entry is an HTMLJournalEntry.* + + +Defining new web actions +------------------------ + +You may define new actions to be triggered by the ``@action`` form variable. +These are added to the tracker ``extensions`` directory and registered +using ``instance.registerAction``. + +All the existing Actions are defined in ``roundup.cgi.actions``. + +Adding action classes takes three steps; first you `define the new +action class`_, then you `register the action class`_ with the cgi +interface so it may be triggered by the ``@action`` form variable. +Finally you `use the new action`_ in your HTML form. + +See "`setting up a "wizard" (or "druid") for controlled adding of +issues`_" for an example. + + +Define the new action class +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create a new action class in your tracker's ``extensions`` directory, for +example ``myaction.py``:: + + from roundup.cgi.actions import Action + + class MyAction(Action): + def handle(self): + ''' Perform some action. No return value is required. + ''' + +The *self.client* attribute is an instance of ``roundup.cgi.client.Client``. +See the docstring of that class for details of what it can do. + +The method will typically check the ``self.form`` variable's contents. +It may then: + +- add information to ``self.client.ok_message`` or ``self.client.error_message`` +- change the ``self.client.template`` variable to alter what the user will see + next +- raise Unauthorised, SendStaticFile, SendFile, NotFound or Redirect + exceptions (import them from roundup.cgi.exceptions) + + +Register the action class +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The class is now written, but isn't available to the user until you register +it with the following code appended to your ``myaction.py`` file:: + + def init(instance): + instance.registerAction('myaction', myActionClass) + +This maps the action name "myaction" to the action class we defined. + + +Use the new action +~~~~~~~~~~~~~~~~~~ + +In your HTML form, add a hidden form element like so:: + + + +where "myaction" is the name you registered in the previous step. + +Actions may return content to the user +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Actions generally perform some database manipulation and then pass control +on to the rendering of a template in the current context (see `Determining +web context`_ for how that works.) Some actions will want to generate the +actual content returned to the user. Action methods may return their own +content string to be displayed to the user, overriding the templating step. +In this situation, we assume that the content is HTML by default. You may +override the content type indicated to the user by calling ``setHeader``:: + + self.client.setHeader('Content-Type', 'text/csv') + +This example indicates that the value sent back to the user is actually +comma-separated value content (eg. something to be loaded into a +spreadsheet or database). + + +8-bit character set support in Web interface +-------------------------------------------- + +The web interface uses UTF-8 default. It may be overridden in both forms +and a browser cookie. + +- In forms, use the ``@charset`` variable. +- To use the cookie override, have the ``roundup_charset`` cookie set. + +In both cases, the value is a valid charset name (eg. ``utf-8`` or +``kio8-r``). + +Inside Roundup, all strings are stored and processed in utf-8. +Unfortunately, some older browsers do not work properly with +utf-8-encoded pages (e.g. Netscape Navigator 4 displays wrong +characters in form fields). This version allows one to change +the character set for http transfers. To do so, you may add +the following code to your ``page.html`` template:: + + + utf-8 + koi8-r + + +(substitute ``koi8-r`` with appropriate charset for your language). +Charset preference is kept in the browser cookie ``roundup_charset``. + +``meta http-equiv`` lines added to the tracker templates in version 0.6.0 +should be changed to include actual character set name:: + + + +The charset is also sent in the http header. + + +Examples +======== + +.. contents:: + :local: + :depth: 2 + + +Changing what's stored in the database +-------------------------------------- + +The following examples illustrate ways to change the information stored in +the database. + + +Adding a new field to the classic schema +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows how to add a simple field (a due date) to the default +classic schema. It does not add any additional behaviour, such as enforcing +the due date, or causing automatic actions to fire if the due date passes. + +You add new fields by editing the ``schema.py`` file in you tracker's home. +Schema changes are automatically applied to the database on the next +tracker access (note that roundup-server would need to be restarted as it +caches the schema). + +1. modify the schema:: + + issue = IssueClass(db, "issue", + assignedto=Link("user"), topic=Multilink("keyword"), + priority=Link("priority"), status=Link("status"), + due_date=Date()) + +2. add an edit field to the issue.item.html template:: + + + Due Date + + + +3. add the property to the issue.index.html page:: + + (in the heading row) + Due Date + (in the data row) + + +4. add the property to the issue.search.html page:: + + + Due Date: + + + + + + + +Adding a new constrained field to the classic schema +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows how to add a new constrained property (i.e. a +selection of distinct values) to your tracker. + + +Introduction +:::::::::::: + +To make the classic schema of Roundup useful as a TODO tracking system +for a group of systems administrators, it needs an extra data field per +issue: a category. + +This would let sysadmins quickly list all TODOs in their particular area +of interest without having to do complex queries, and without relying on +the spelling capabilities of other sysadmins (a losing proposition at +best). + + +Adding a field to the database +:::::::::::::::::::::::::::::: + +This is the easiest part of the change. The category would just be a +plain string, nothing fancy. To change what is in the database you need +to add some lines to the ``schema.py`` file of your tracker instance. +Under the comment:: + + # add any additional database schema configuration here + +add:: + + category = Class(db, "category", name=String()) + category.setkey("name") + +Here we are setting up a chunk of the database which we are calling +"category". It contains a string, which we are refering to as "name" for +lack of a more imaginative title. (Since "name" is one of the properties +that Roundup looks for on items if you do not set a key for them, it's +probably a good idea to stick with it for new classes if at all +appropriate.) Then we are setting the key of this chunk of the database +to be that "name". This is equivalent to an index for database types. +This also means that there can only be one category with a given name. + +Adding the above lines allows us to create categories, but they're not +tied to the issues that we are going to be creating. It's just a list of +categories off on its own, which isn't much use. We need to link it in +with the issues. To do that, find the lines +in ``schema.py`` which set up the "issue" class, and then add a link to +the category:: + + issue = IssueClass(db, "issue", ... , + category=Multilink("category"), ... ) + +The ``Multilink()`` means that each issue can have many categories. If +you were adding something with a one-to-one relationship to issues (such +as the "assignedto" property), use ``Link()`` instead. + +That is all you need to do to change the schema. The rest of the effort +is fiddling around so you can actually use the new category. + + +Populating the new category class +::::::::::::::::::::::::::::::::: + +If you haven't initialised the database with the ``roundup-admin`` +"initialise" command, then you can add the following to the tracker +``initial_data.py`` under the comment:: + + # add any additional database creation steps here - but only if you + # haven't initialised the database with the admin "initialise" command + +Add:: + + category = db.getclass('category') + category.create(name="scipy") + category.create(name="chaco") + category.create(name="weave") + +If the database has already been initalised, then you need to use the +``roundup-admin`` tool:: + + % roundup-admin -i + Roundup ready for input. + Type "help" for help. + roundup> create category name=scipy + 1 + roundup> create category name=chaco + 2 + roundup> create category name=weave + 3 + roundup> exit... + There are unsaved changes. Commit them (y/N)? y + + +Setting up security on the new objects +:::::::::::::::::::::::::::::::::::::: + +By default only the admin user can look at and change objects. This +doesn't suit us, as we want any user to be able to create new categories +as required, and obviously everyone needs to be able to view the +categories of issues for it to be useful. + +We therefore need to change the security of the category objects. This +is also done in ``schema.py``. + +There are currently two loops which set up permissions and then assign +them to various roles. Simply add the new "category" to both lists:: + + # Assign the access and edit permissions for issue, file and message + # to regular users now + for cl in 'issue', 'file', 'msg', 'category': + p = db.security.getPermission('View', cl) + db.security.addPermissionToRole('User', 'View', cl) + db.security.addPermissionToRole('User', 'Edit', cl) + db.security.addPermissionToRole('User', 'Create', cl) + +These lines assign the "View" and "Edit" Permissions to the "User" role, +so that normal users can view and edit "category" objects. + +This is all the work that needs to be done for the database. It will +store categories, and let users view and edit them. Now on to the +interface stuff. + + +Changing the web left hand frame +:::::::::::::::::::::::::::::::: + +We need to give the users the ability to create new categories, and the +place to put the link to this functionality is in the left hand function +bar, under the "Issues" area. The file that defines how this area looks +is ``html/page.html``, which is what we are going to be editing next. + +If you look at this file you can see that it contains a lot of +"classblock" sections which are chunks of HTML that will be included or +excluded in the output depending on whether the condition in the +classblock is met. We are going to add the category code at the end of +the classblock for the *issue* class:: + +

+ Categories
+ New Category
+

+ +The first two lines is the classblock definition, which sets up a +condition that only users who have "View" permission for the "category" +object will have this section included in their output. Next comes a +plain "Categories" header in bold. Everyone who can view categories will +get that. + +Next comes the link to the editing area of categories. This link will +only appear if the condition - that the user has "Edit" permissions for +the "category" objects - is matched. If they do have permission then +they will get a link to another page which will let the user add new +categories. + +Note that if you have permission to *view* but not to *edit* categories, +then all you will see is a "Categories" header with nothing underneath +it. This is obviously not very good interface design, but will do for +now. I just claim that it is so I can add more links in this section +later on. However, to fix the problem you could change the condition in +the classblock statement, so that only users with "Edit" permission +would see the "Categories" stuff. + + +Setting up a page to edit categories +:::::::::::::::::::::::::::::::::::: + +We defined code in the previous section which let users with the +appropriate permissions see a link to a page which would let them edit +conditions. Now we have to write that page. + +The link was for the *item* template of the *category* object. This +translates into Roundup looking for a file called ``category.item.html`` +in the ``html`` tracker directory. This is the file that we are going to +write now. + +First, we add an info tag in a comment which doesn't affect the outcome +of the code at all, but is useful for debugging. If you load a page in a +browser and look at the page source, you can see which sections come +from which files by looking for these comments:: + + + +Next we need to add in the METAL macro stuff so we get the normal page +trappings:: + + + Category editing + +

Category editing

+ + + +Next we need to setup up a standard HTML form, which is the whole +purpose of this file. We link to some handy javascript which sends the +form through only once. This is to stop users hitting the send button +multiple times when they are impatient and thus having the form sent +multiple times:: + +
+ +Next we define some code which sets up the minimum list of fields that +we require the user to enter. There will be only one field - "name" - so +they better put something in it, otherwise the whole form is pointless:: + + + +To get everything to line up properly we will put everything in a table, +and put a nice big header on it so the user has an idea what is +happening:: + + + + +Next, we need the field into which the user is going to enter the new +category. The ``context.name.field(size=60)`` bit tells Roundup to +generate a normal HTML field of size 60, and the contents of that field +will be the "name" variable of the current context (namely "category"). +The upshot of this is that when the user types something in +to the form, a new category will be created with that name:: + + + + + + +Then a submit button so that the user can submit the new category:: + + + + + + +Finally we finish off the tags we used at the start to do the METAL +stuff:: + + + + +So putting it all together, and closing the table and form we get:: + + + + Category editing + + + + +This is quite a lot to just ask the user one simple question, but there +is a lot of setup for basically one line (the form line) to do its work. +To add another field to "category" would involve one more line (well, +maybe a few extra to get the formatting correct). + + +Adding the category to the issue +:::::::::::::::::::::::::::::::: + +We now have the ability to create issues to our heart's content, but +that is pointless unless we can assign categories to issues. Just like +the ``html/category.item.html`` file was used to define how to add a new +category, the ``html/issue.item.html`` is used to define how a new issue +is created. + +Just like ``category.issue.html``, this file defines a form which has a +table to lay things out. It doesn't matter where in the table we add new +stuff, it is entirely up to your sense of aesthetics:: + + + + +First, we define a nice header so that the user knows what the next +section is, then the middle line does what we are most interested in. +This ``context/category/field`` gets replaced by a field which contains +the category in the current context (the current context being the new +issue). + +The classhelp lines generate a link (labelled "list") to a popup window +which contains the list of currently known categories. + + +Searching on categories +::::::::::::::::::::::: + +Now we can add categories, and create issues with categories. The next +obvious thing that we would like to be able to do, would be to search +for issues based on their category, so that, for example, anyone working +on the web server could look at all issues in the category "Web". + +If you look for "Search Issues" in the ``html/page.html`` file, you will +find that it looks something like +``Search Issues``. This shows us +that when you click on "Search Issues" it will be looking for a +``issue.search.html`` file to display. So that is the file that we will +change. + +If you look at this file it should begin to seem familiar, although it +does use some new macros. You can add the new category search code anywhere you +like within that form:: + + + + + + + + + +The definitions in the ```` opening tag are used by the macros: + +- ``search_select`` expands to a drop-down box with all categories using + ``db_klass`` and ``db_content``. +- ``column_input`` expands to a checkbox for selecting what columns + should be displayed. +- ``sort_input`` expands to a radio button for selecting what property + should be sorted on. +- ``group_input`` expands to a radio button for selecting what property + should be grouped on. + +The category search code above would expand to the following:: + + + + + + + + + +Adding category to the default view +::::::::::::::::::::::::::::::::::: + +We can now add categories, add issues with categories, and search for +issues based on categories. This is everything that we need to do; +however, there is some more icing that we would like. I think the +category of an issue is important enough that it should be displayed by +default when listing all the issues. + +Unfortunately, this is a bit less obvious than the previous steps. The +code defining how the issues look is in ``html/issue.index.html``. This +is a large table with a form down at the bottom for redisplaying and so +forth. + +Firstly we need to add an appropriate header to the start of the table:: + + + +The *condition* part of this statement is to avoid displaying the +Category column if the user has selected not to see it. + +The rest of the table is a loop which will go through every issue that +matches the display criteria. The loop variable is "i" - which means +that every issue gets assigned to "i" in turn. + +The new part of code to display the category will look like this:: + + + +The condition is the same as above: only display the condition when the +user hasn't asked for it to be hidden. The next part is to set the +content of the cell to be the category part of "i" - the current issue. + +Finally we have to edit ``html/page.html`` again. This time, we need to +tell it that when the user clicks on "Unassigned Issues" or "All Issues", +the category column should be included in the resulting list. If you +scroll down the page file, you can see the links with lots of options. +The option that we are interested in is the ``:columns=`` one which +tells roundup which fields of the issue to display. Simply add +"category" to that list and it all should work. + +Adding a time log to your issues +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We want to log the dates and amount of time spent working on issues, and +be able to give a summary of the total time spent on a particular issue. + +1. Add a new class to your tracker ``schema.py``:: + + # storage for time logging + timelog = Class(db, "timelog", period=Interval()) + + Note that we automatically get the date of the time log entry + creation through the standard property "creation". + +2. Link to the new class from your issue class (again, in + ``schema.py``):: + + issue = IssueClass(db, "issue", + assignedto=Link("user"), topic=Multilink("keyword"), + priority=Link("priority"), status=Link("status"), + times=Multilink("timelog")) + + the "times" property is the new link to the "timelog" class. + +3. We'll need to let people add in times to the issue, so in the web + interface we'll have a new entry field. This is a special field + because unlike the other fields in the ``issue.item`` template, it + affects a different item (a timelog item) and not the template's + item (an issue). We have a special syntax for form fields that affect + items other than the template default item (see the cgi + documentation on `special form variables`_). In particular, we add a + field to capture a new timelog item's period:: + + + + + + + and another hidden field that links that new timelog item (new + because it's marked as having id "-1") to the issue item. It looks + like this:: + + + + On submission, the "-1" timelog item will be created and assigned a + real item id. The "times" property of the issue will have the new id + added to it. + +4. We want to display a total of the timelog times that have been + accumulated for an issue. To do this, we'll need to actually write + some Python code, since it's beyond the scope of PageTemplates to + perform such calculations. We do this by adding a module ``timespent.py`` + to the ``extensions`` directory in our tracker. The contents of this + file is as follows:: + + def totalTimeSpent(times): + ''' Call me with a list of timelog items (which have an + Interval "period" property) + ''' + total = Interval('0d') + for time in times: + total += time.period._value + return total + + def init(instance): + instance.registerUtil('totalTimeSpent', totalTimeSpent) + + We will now be able to access the ``totalTimeSpent`` function via the + ``utils`` variable in our templates, as shown in the next step. + +5. Display the timelog for an issue:: + +
Category
Name + name
  + submit button will go here +
+

Category editing

+
+ + + + + + + + + + + + + + +
Category
Name + name
+   + + + submit button will go here +
+ +
Category + + +
Priority:
Category: + +
Category
Time Log +
(enter as '3y 1m 4d 2:40:02' or parts thereof) +
+ + + + + + + +
Time Log + +
DatePeriodLogged By
+ + I put this just above the Messages log in my issue display. Note our + use of the ``totalTimeSpent`` method which will total up the times + for the issue and return a new Interval. That will be automatically + displayed in the template as text like "+ 1y 2:40" (1 year, 2 hours + and 40 minutes). + +8. If you're using a persistent web server - ``roundup-server`` or + ``mod_python`` for example - then you'll need to restart that to pick up + the code changes. When that's done, you'll be able to use the new + time logging interface. + + +Tracking different types of issues +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you will want to track different types of issues - developer, +customer support, systems, sales leads, etc. A single Roundup tracker is +able to support multiple types of issues. This example demonstrates adding +a system support issue class to a tracker. + +1. Figure out what information you're going to want to capture. OK, so + this is obvious, but sometimes it's better to actually sit down for a + while and think about the schema you're going to implement. + +2. Add the new issue class to your tracker's ``schema.py``. Just after the + "issue" class definition, add:: + + # list our systems + system = Class(db, "system", name=String(), order=Number()) + system.setkey("name") + + # store issues related to those systems + support = IssueClass(db, "support", + assignedto=Link("user"), topic=Multilink("keyword"), + status=Link("status"), deadline=Date(), + affects=Multilink("system")) + +3. Copy the existing ``issue.*`` (item, search and index) templates in the + tracker's ``html`` to ``support.*``. Edit them so they use the properties + defined in the ``support`` class. Be sure to check for hidden form + variables like "required" to make sure they have the correct set of + required properties. + +4. Edit the modules in the ``detectors``, adding lines to their ``init`` + functions where appropriate. Look for ``audit`` and ``react`` registrations + on the ``issue`` class, and duplicate them for ``support``. + +5. Create a new sidebar box for the new support class. Duplicate the + existing issues one, changing the ``issue`` class name to ``support``. + +6. Re-start your tracker and start using the new ``support`` class. + + +Optionally, you might want to restrict the users able to access this new +class to just the users with a new "SysAdmin" Role. To do this, we add +some security declarations:: + + db.security.addPermissionToRole('SysAdmin', 'View', 'support') + db.security.addPermissionToRole('SysAdmin', 'Create', 'support') + db.security.addPermissionToRole('SysAdmin', 'Edit', 'support') + +You would then (as an "admin" user) edit the details of the appropriate +users, and add "SysAdmin" to their Roles list. + +Alternatively, you might want to change the Edit/View permissions granted +for the ``issue`` class so that it's only available to users with the "System" +or "Developer" Role, and then the new class you're adding is available to +all with the "User" Role. + + +Using External User Databases +----------------------------- + +Using an external password validation source +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: You will need to either have an "admin" user in your external + password source *or* have one of your regular users have + the Admin Role assigned. If you need to assign the Role *after* + making the changes below, you may use the ``roundup-admin`` + program to edit a user's details. + +We have a centrally-managed password changing system for our users. This +results in a UN*X passwd-style file that we use for verification of +users. Entries in the file consist of ``name:password`` where the +password is encrypted using the standard UN*X ``crypt()`` function (see +the ``crypt`` module in your Python distribution). An example entry +would be:: + + admin:aamrgyQfDFSHw + +Each user of Roundup must still have their information stored in the Roundup +database - we just use the passwd file to check their password. To do this, we +need to override the standard ``verifyPassword`` method defined in +``roundup.cgi.actions.LoginAction`` and register the new class. The +following is added as ``externalpassword.py`` in the tracker ``extensions`` +directory:: + + import os, crypt + from roundup.cgi.actions import LoginAction + + class ExternalPasswordLoginAction(LoginAction): + def verifyPassword(self, userid, password): + '''Look through the file, line by line, looking for a + name that matches. + ''' + # get the user's username + username = self.db.user.get(userid, 'username') + + # the passwords are stored in the "passwd.txt" file in the + # tracker home + file = os.path.join(self.db.config.TRACKER_HOME, 'passwd.txt') + + # see if we can find a match + for ent in [line.strip().split(':') for line in + open(file).readlines()]: + if ent[0] == username: + return crypt.crypt(password, ent[1][:2]) == ent[1] + + # user doesn't exist in the file + return 0 + + def init(instance): + instance.registerAction('login', ExternalPasswordLoginAction) + +You should also remove the redundant password fields from the ``user.item`` +template. + + +Using a UN*X passwd file as the user database +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On some systems the primary store of users is the UN*X passwd file. It +holds information on users such as their username, real name, password +and primary user group. + +Roundup can use this store as its primary source of user information, +but it needs additional information too - email address(es), roundup +Roles, vacation flags, roundup hyperdb item ids, etc. Also, "retired" +users must still exist in the user database, unlike some passwd files in +which the users are removed when they no longer have access to a system. + +To make use of the passwd file, we therefore synchronise between the two +user stores. We also use the passwd file to validate the user logins, as +described in the previous example, `using an external password +validation source`_. We keep the user lists in sync using a fairly +simple script that runs once a day, or several times an hour if more +immediate access is needed. In short, it: + +1. parses the passwd file, finding usernames, passwords and real names, +2. compares that list to the current roundup user list: + + a. entries no longer in the passwd file are *retired* + b. entries with mismatching real names are *updated* + c. entries only exist in the passwd file are *created* + +3. send an email to administrators to let them know what's been done. + +The retiring and updating are simple operations, requiring only a call +to ``retire()`` or ``set()``. The creation operation requires more +information though - the user's email address and their Roundup Roles. +We're going to assume that the user's email address is the same as their +login name, so we just append the domain name to that. The Roles are +determined using the passwd group identifier - mapping their UN*X group +to an appropriate set of Roles. + +The script to perform all this, broken up into its main components, is +as follows. Firstly, we import the necessary modules and open the +tracker we're to work on:: + + import sys, os, smtplib + from roundup import instance, date + + # open the tracker + tracker_home = sys.argv[1] + tracker = instance.open(tracker_home) + +Next we read in the *passwd* file from the tracker home:: + + # read in the users from the "passwd.txt" file + file = os.path.join(tracker_home, 'passwd.txt') + users = [x.strip().split(':') for x in open(file).readlines()] + +Handle special users (those to ignore in the file, and those who don't +appear in the file):: + + # users to not keep ever, pre-load with the users I know aren't + # "real" users + ignore = ['ekmmon', 'bfast', 'csrmail'] + + # users to keep - pre-load with the roundup-specific users + keep = ['comment_pool', 'network_pool', 'admin', 'dev-team', + 'cs_pool', 'anonymous', 'system_pool', 'automated'] + +Now we map the UN*X group numbers to the Roles that users should have:: + + roles = { + '501': 'User,Tech', # tech + '502': 'User', # finance + '503': 'User,CSR', # customer service reps + '504': 'User', # sales + '505': 'User', # marketing + } + +Now we do all the work. Note that the body of the script (where we have +the tracker database open) is wrapped in a ``try`` / ``finally`` clause, +so that we always close the database cleanly when we're finished. So, we +now do all the work:: + + # open the database + db = tracker.open('admin') + try: + # store away messages to send to the tracker admins + msg = [] + + # loop over the users list read in from the passwd file + for user,passw,uid,gid,real,home,shell in users: + if user in ignore: + # this user shouldn't appear in our tracker + continue + keep.append(user) + try: + # see if the user exists in the tracker + uid = db.user.lookup(user) + + # yes, they do - now check the real name for correctness + if real != db.user.get(uid, 'realname'): + db.user.set(uid, realname=real) + msg.append('FIX %s - %s'%(user, real)) + except KeyError: + # nope, the user doesn't exist + db.user.create(username=user, realname=real, + address='%s at ekit-inc.com'%user, roles=roles[gid]) + msg.append('ADD %s - %s (%s)'%(user, real, roles[gid])) + + # now check that all the users in the tracker are also in our + # "keep" list - retire those who aren't + for uid in db.user.list(): + user = db.user.get(uid, 'username') + if user not in keep: + db.user.retire(uid) + msg.append('RET %s'%user) + + # if we did work, then send email to the tracker admins + if msg: + # create the email + msg = '''Subject: %s user database maintenance + + %s + '''%(db.config.TRACKER_NAME, '\n'.join(msg)) + + # send the email + smtp = smtplib.SMTP(db.config.MAILHOST) + addr = db.config.ADMIN_EMAIL + smtp.sendmail(addr, addr, msg) + + # now we're done - commit the changes + db.commit() + finally: + # always close the database cleanly + db.close() + +And that's it! + + +Using an LDAP database for user information +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A script that reads users from an LDAP store using +http://python-ldap.sf.net/ and then compares the list to the users in the +roundup user database would be pretty easy to write. You'd then have it run +once an hour / day (or on demand if you can work that into your LDAP store +workflow). See the example `Using a UN*X passwd file as the user database`_ +for more information about doing this. + +To authenticate off the LDAP store (rather than using the passwords in the +Roundup user database) you'd use the same python-ldap module inside an +extension to the cgi interface. You'd do this by overriding the method called +``verifyPassword`` on the ``LoginAction`` class in your tracker's +``extensions`` directory (see `using an external password validation +source`_). The method is implemented by default as:: + + def verifyPassword(self, userid, password): + ''' Verify the password that the user has supplied + ''' + stored = self.db.user.get(self.userid, 'password') + if password == stored: + return 1 + if not password and not stored: + return 1 + return 0 + +So you could reimplement this as something like:: + + def verifyPassword(self, userid, password): + ''' Verify the password that the user has supplied + ''' + # look up some unique LDAP information about the user + username = self.db.user.get(self.userid, 'username') + # now verify the password supplied against the LDAP store + + +Changes to Tracker Behaviour +---------------------------- + +Stop "nosy" messages going to people on vacation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When users go on vacation and set up vacation email bouncing, you'll +start to see a lot of messages come back through Roundup "Fred is on +vacation". Not very useful, and relatively easy to stop. + +1. add a "vacation" flag to your users:: + + user = Class(db, "user", + username=String(), password=Password(), + address=String(), realname=String(), + phone=String(), organisation=String(), + alternate_addresses=String(), + roles=String(), queries=Multilink("query"), + vacation=Boolean()) + +2. So that users may edit the vacation flags, add something like the + following to your ``user.item`` template:: + + + On Vacation + vacation + + +3. edit your detector ``nosyreactor.py`` so that the ``nosyreaction()`` + consists of:: + + def nosyreaction(db, cl, nodeid, oldvalues): + users = db.user + messages = db.msg + # send a copy of all new messages to the nosy list + for msgid in determineNewMessages(cl, nodeid, oldvalues): + try: + # figure the recipient ids + sendto = [] + seen_message = {} + recipients = messages.get(msgid, 'recipients') + for recipid in messages.get(msgid, 'recipients'): + seen_message[recipid] = 1 + + # figure the author's id, and indicate they've received + # the message + authid = messages.get(msgid, 'author') + + # possibly send the message to the author, as long as + # they aren't anonymous + if (db.config.MESSAGES_TO_AUTHOR == 'yes' and + users.get(authid, 'username') != 'anonymous'): + sendto.append(authid) + seen_message[authid] = 1 + + # now figure the nosy people who weren't recipients + nosy = cl.get(nodeid, 'nosy') + for nosyid in nosy: + # Don't send nosy mail to the anonymous user (that + # user shouldn't appear in the nosy list, but just + # in case they do...) + if users.get(nosyid, 'username') == 'anonymous': + continue + # make sure they haven't seen the message already + if not seen_message.has_key(nosyid): + # send it to them + sendto.append(nosyid) + recipients.append(nosyid) + + # generate a change note + if oldvalues: + note = cl.generateChangeNote(nodeid, oldvalues) + else: + note = cl.generateCreateNote(nodeid) + + # we have new recipients + if sendto: + # filter out the people on vacation + sendto = [i for i in sendto + if not users.get(i, 'vacation', 0)] + + # map userids to addresses + sendto = [users.get(i, 'address') for i in sendto] + + # update the message's recipients list + messages.set(msgid, recipients=recipients) + + # send the message + cl.send_message(nodeid, msgid, note, sendto) + except roundupdb.MessageSendError, message: + raise roundupdb.DetectorError, message + + Note that this is the standard nosy reaction code, with the small + addition of:: + + # filter out the people on vacation + sendto = [i for i in sendto if not users.get(i, 'vacation', 0)] + + which filters out the users that have the vacation flag set to true. + +Adding in state transition control +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes tracker admins want to control the states to which users may +move issues. You can do this by following these steps: + +1. make "status" a required variable. This is achieved by adding the + following to the top of the form in the ``issue.item.html`` + template:: + + + + This will force users to select a status. + +2. add a Multilink property to the status class:: + + stat = Class(db, "status", ... , transitions=Multilink('status'), + ...) + + and then edit the statuses already created, either: + + a. through the web using the class list -> status class editor, or + b. using the ``roundup-admin`` "set" command. + +3. add an auditor module ``checktransition.py`` in your tracker's + ``detectors`` directory, for example:: + + def checktransition(db, cl, nodeid, newvalues): + ''' Check that the desired transition is valid for the "status" + property. + ''' + if not newvalues.has_key('status'): + return + current = cl.get(nodeid, 'status') + new = newvalues['status'] + if new == current: + return + ok = db.status.get(current, 'transitions') + if new not in ok: + raise ValueError, 'Status not allowed to move from "%s" to "%s"'%( + db.status.get(current, 'name'), db.status.get(new, 'name')) + + def init(db): + db.issue.audit('set', checktransition) + +4. in the ``issue.item.html`` template, change the status editing bit + from:: + + Status + status + + to:: + + Status + + + + + + which displays only the allowed status to transition to. + + +Blocking issues that depend on other issues +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We needed the ability to mark certain issues as "blockers" - that is, +they can't be resolved until another issue (the blocker) they rely on is +resolved. To achieve this: + +1. Create a new property on the ``issue`` class: + ``blockers=Multilink("issue")``. To do this, edit the definition of + this class in your tracker's ``schema.py`` file. Change this:: + + issue = IssueClass(db, "issue", + assignedto=Link("user"), topic=Multilink("keyword"), + priority=Link("priority"), status=Link("status")) + + to this, adding the blockers entry:: + + issue = IssueClass(db, "issue", + blockers=Multilink("issue"), + assignedto=Link("user"), topic=Multilink("keyword"), + priority=Link("priority"), status=Link("status")) + +2. Add the new ``blockers`` property to the ``issue.item.html`` edit + page, using something like:: + + Waiting On + + + + +
View: +
+ + You'll need to fiddle with your item page layout to find an + appropriate place to put it - I'll leave that fun part up to you. + Just make sure it appears in the first table, possibly somewhere near + the "superseders" field. + +3. Create a new detector module (see below) which enforces the rules: + + - issues may not be resolved if they have blockers + - when a blocker is resolved, it's removed from issues it blocks + + The contents of the detector should be something like this:: + + + def blockresolution(db, cl, nodeid, newvalues): + ''' If the issue has blockers, don't allow it to be resolved. + ''' + if nodeid is None: + blockers = [] + else: + blockers = cl.get(nodeid, 'blockers') + blockers = newvalues.get('blockers', blockers) + + # don't do anything if there's no blockers or the status hasn't + # changed + if not blockers or not newvalues.has_key('status'): + return + + # get the resolved state ID + resolved_id = db.status.lookup('resolved') + + # format the info + u = db.config.TRACKER_WEB + s = ', '.join(['%s'%( + u,id,id) for id in blockers]) + if len(blockers) == 1: + s = 'issue %s is'%s + else: + s = 'issues %s are'%s + + # ok, see if we're trying to resolve + if newvalues['status'] == resolved_id: + raise ValueError, "This issue can't be resolved until %s resolved."%s + + + def resolveblockers(db, cl, nodeid, oldvalues): + ''' When we resolve an issue that's a blocker, remove it from the + blockers list of the issue(s) it blocks. + ''' + newstatus = cl.get(nodeid,'status') + + # no change? + if oldvalues.get('status', None) == newstatus: + return + + resolved_id = db.status.lookup('resolved') + + # interesting? + if newstatus != resolved_id: + return + + # yes - find all the blocked issues, if any, and remove me from + # their blockers list + issues = cl.find(blockers=nodeid) + for issueid in issues: + blockers = cl.get(issueid, 'blockers') + if nodeid in blockers: + blockers.remove(nodeid) + cl.set(issueid, blockers=blockers) + + def init(db): + # might, in an obscure situation, happen in a create + db.issue.audit('create', blockresolution) + db.issue.audit('set', blockresolution) + + # can only happen on a set + db.issue.react('set', resolveblockers) + + Put the above code in a file called "blockers.py" in your tracker's + "detectors" directory. + +4. Finally, and this is an optional step, modify the tracker web page + URLs so they filter out issues with any blockers. You do this by + adding an additional filter on "blockers" for the value "-1". For + example, the existing "Show All" link in the "page" template (in the + tracker's "html" directory) looks like this:: + + Show All
+ + modify it to add the "blockers" info to the URL (note, both the + "@filter" *and* "blockers" values must be specified):: + + Show All
+ + The above examples are line-wrapped on the trailing & and should + be unwrapped. + +That's it. You should now be able to set blockers on your issues. Note +that if you want to know whether an issue has any other issues dependent +on it (i.e. it's in their blockers list) you can look at the journal +history at the bottom of the issue page - look for a "link" event to +another issue's "blockers" property. + +Add users to the nosy list based on the topic +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Let's say we need the ability to automatically add users to the nosy +list based +on the occurance of a topic. Every user should be allowed to edit their +own list of topics for which they want to be added to the nosy list. + +Below, we'll show that this change can be done with minimal +understanding of the Roundup system, using only copy and paste. + +This requires three changes to the tracker: a change in the database to +allow per-user recording of the lists of topics for which he wants to +be put on the nosy list, a change in the user view allowing them to edit +this list of topics, and addition of an auditor which updates the nosy +list when a topic is set. + +Adding the nosy topic list +:::::::::::::::::::::::::: + +The change to make in the database, is that for any user there should be +a list of topics for which he wants to be put on the nosy list. Adding +a ``Multilink`` of ``keyword`` seems to fullfill this (note that within +the code, topics are called ``keywords``.) As such, all that has to be +done is to add a new field to the definition of ``user`` within the +file ``schema.py``. We will call this new field ``nosy_keywords``, and +the updated definition of user will be:: + + user = Class(db, "user", + username=String(), password=Password(), + address=String(), realname=String(), + phone=String(), organisation=String(), + alternate_addresses=String(), + queries=Multilink('query'), roles=String(), + timezone=String(), + nosy_keywords=Multilink('keyword')) + +Changing the user view to allow changing the nosy topic list +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: + +We want any user to be able to change the list of topics for which +he will by default be added to the nosy list. We choose to add this +to the user view, as is generated by the file ``html/user.item.html``. +We can easily +see that the topic field in the issue view has very similar editing +requirements as our nosy topics, both being lists of topics. As +such, we look for Topics in ``issue.item.html``, and extract the +associated parts from there. We add this to ``user.item.html`` at the +bottom of the list of viewed items (i.e. just below the 'Alternate +E-mail addresses' in the classic template):: + + + Nosy Topics + + + + + + + +Addition of an auditor to update the nosy list +:::::::::::::::::::::::::::::::::::::::::::::: + +The more difficult part is the logic to add +the users to the nosy list when required. +We choose to perform this action whenever the topics on an +item are set (this includes the creation of items). +Here we choose to start out with a copy of the +``detectors/nosyreaction.py`` detector, which we copy to the file +``detectors/nosy_keyword_reaction.py``. +This looks like a good start as it also adds users +to the nosy list. A look through the code reveals that the +``nosyreaction`` function actually sends the e-mail. +We don't need this. Therefore, we can change the ``init`` function to:: + + def init(db): + db.issue.audit('create', update_kw_nosy) + db.issue.audit('set', update_kw_nosy) + +After that, we rename the ``updatenosy`` function to ``update_kw_nosy``. +The first two blocks of code in that function relate to setting +``current`` to a combination of the old and new nosy lists. This +functionality is left in the new auditor. The following block of +code, which handled adding the assignedto user(s) to the nosy list in +``updatenosy``, should be replaced by a block of code to add the +interested users to the nosy list. We choose here to loop over all +new topics, than looping over all users, +and assign the user to the nosy list when the topic occurs in the user's +``nosy_keywords``. The next part in ``updatenosy`` -- adding the author +and/or recipients of a message to the nosy list -- is obviously not +relevant here and is thus deleted from the new auditor. The last +part, copying the new nosy list to ``newvalues``, can stay as is. +This results in the following function:: + + def update_kw_nosy(db, cl, nodeid, newvalues): + '''Update the nosy list for changes to the topics + ''' + # nodeid will be None if this is a new node + current = {} + if nodeid is None: + ok = ('new', 'yes') + else: + ok = ('yes',) + # old node, get the current values from the node if they haven't + # changed + if not newvalues.has_key('nosy'): + nosy = cl.get(nodeid, 'nosy') + for value in nosy: + if not current.has_key(value): + current[value] = 1 + + # if the nosy list changed in this transaction, init from the new value + if newvalues.has_key('nosy'): + nosy = newvalues.get('nosy', []) + for value in nosy: + if not db.hasnode('user', value): + continue + if not current.has_key(value): + current[value] = 1 + + # add users with topic in nosy_keywords to the nosy list + if newvalues.has_key('topic') and newvalues['topic'] is not None: + topic_ids = newvalues['topic'] + for topic in topic_ids: + # loop over all users, + # and assign user to nosy when topic in nosy_keywords + for user_id in db.user.list(): + nosy_kw = db.user.get(user_id, "nosy_keywords") + found = 0 + for kw in nosy_kw: + if kw == topic: + found = 1 + if found: + current[user_id] = 1 + + # that's it, save off the new nosy list + newvalues['nosy'] = current.keys() + +These two function are the only ones needed in the file. + +TODO: update this example to use the ``find()`` Class method. + +Caveats +::::::: + +A few problems with the design here can be noted: + +Multiple additions + When a user, after automatic selection, is manually removed + from the nosy list, he is added to the nosy list again when the + topic list of the issue is updated. A better design might be + to only check which topics are new compared to the old list + of topics, and only add users when they have indicated + interest on a new topic. + + The code could also be changed to only trigger on the ``create()`` + event, rather than also on the ``set()`` event, thus only setting + the nosy list when the issue is created. + +Scalability + In the auditor, there is a loop over all users. For a site with + only few users this will pose no serious problem; however, with + many users this will be a serious performance bottleneck. + A way out would be to link from the topics to the users who + selected these topics as nosy topics. This will eliminate the + loop over all users. + +Changes to Security and Permissions +----------------------------------- + +Restricting the list of users that are assignable to a task +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. In your tracker's ``schema.py``, create a new Role, say "Developer":: + + db.security.addRole(name='Developer', description='A developer') + +2. Just after that, create a new Permission, say "Fixer", specific to + "issue":: + + p = db.security.addPermission(name='Fixer', klass='issue', + description='User is allowed to be assigned to fix issues') + +3. Then assign the new Permission to your "Developer" Role:: + + db.security.addPermissionToRole('Developer', p) + +4. In the issue item edit page (``html/issue.item.html`` in your tracker + directory), use the new Permission in restricting the "assignedto" + list:: + + + +For extra security, you may wish to setup an auditor to enforce the +Permission requirement (install this as ``assignedtoFixer.py`` in your +tracker ``detectors`` directory):: + + def assignedtoMustBeFixer(db, cl, nodeid, newvalues): + ''' Ensure the assignedto value in newvalues is used with the + Fixer Permission + ''' + if not newvalues.has_key('assignedto'): + # don't care + return + + # get the userid + userid = newvalues['assignedto'] + if not db.security.hasPermission('Fixer', userid, cl.classname): + raise ValueError, 'You do not have permission to edit %s'%cl.classname + + def init(db): + db.issue.audit('set', assignedtoMustBeFixer) + db.issue.audit('create', assignedtoMustBeFixer) + +So now, if an edit action attempts to set "assignedto" to a user that +doesn't have the "Fixer" Permission, the error will be raised. + + +Users may only edit their issues +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this case, users registering themselves are granted Provisional +access, meaning they +have access to edit the issues they submit, but not others. We create a new +Role called "Provisional User" which is granted to newly-registered users, +and has limited access. One of the Permissions they have is the new "Edit +Own" on issues (regular users have "Edit".) + +First up, we create the new Role and Permission structure in +``schema.py``:: + + # + # New users not approved by the admin + # + db.security.addRole(name='Provisional User', + description='New user registered via web or email') + + # These users need to be able to view and create issues but only edit + # and view their own + db.security.addPermissionToRole('Provisional User', 'Create', 'issue') + def own_issue(db, userid, itemid): + '''Determine whether the userid matches the creator of the issue.''' + return userid == db.issue.get(itemid, 'creator') + p = db.security.addPermission(name='Edit', klass='issue', + check=own_issue, description='Can only edit own issues') + db.security.addPermissionToRole('Provisional User', p) + p = db.security.addPermission(name='View', klass='issue', + check=own_issue, description='Can only view own issues') + db.security.addPermissionToRole('Provisional User', p) + + # Assign the Permissions for issue-related classes + for cl in 'file', 'msg', 'query', 'keyword': + db.security.addPermissionToRole('Provisional User', 'View', cl) + db.security.addPermissionToRole('Provisional User', 'Edit', cl) + db.security.addPermissionToRole('Provisional User', 'Create', cl) + for cl in 'priority', 'status': + db.security.addPermissionToRole('Provisional User', 'View', cl) + + # and give the new users access to the web and email interface + db.security.addPermissionToRole('Provisional User', 'Web Access') + db.security.addPermissionToRole('Provisional User', 'Email Access') + + # make sure they can view & edit their own user record + def own_record(db, userid, itemid): + '''Determine whether the userid matches the item being accessed.''' + return userid == itemid + p = db.security.addPermission(name='View', klass='user', check=own_record, + description="User is allowed to view their own user details") + db.security.addPermissionToRole('Provisional User', p) + p = db.security.addPermission(name='Edit', klass='user', check=own_record, + description="User is allowed to edit their own user details") + db.security.addPermissionToRole('Provisional User', p) + +Then, in ``config.ini``, we change the Role assigned to newly-registered +users, replacing the existing ``'User'`` values:: + + [main] + ... + new_web_user_roles = 'Provisional User' + new_email_user_roles = 'Provisional User' + + +All users may only view and edit issues, files and messages they create +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Replace the standard "classic" tracker View and Edit Permission assignments +for the "issue", "file" and "msg" classes with the following:: + + def checker(klass): + def check(db, userid, itemid, klass=klass): + return db.getclass(klass).get(itemid, 'creator') == userid + return check + for cl in 'issue', 'file', 'msg': + p = db.security.addPermission(name='View', klass=cl, + check=checker(cl)) + db.security.addPermissionToRole('User', p) + p = db.security.addPermission(name='Edit', klass=cl, + check=checker(cl)) + db.security.addPermissionToRole('User', p) + db.security.addPermissionToRole('User', 'Create', cl) + + + +Changes to the Web User Interface +--------------------------------- + +Adding action links to the index page +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Add a column to the ``item.index.html`` template. + +Resolving the issue:: + + resolve + +"Take" the issue:: + + take + +... and so on. + +Colouring the rows in the issue index according to priority +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A simple ``tal:attributes`` statement will do the bulk of the work here. In +the ``issue.index.html`` template, add this to the ```` that +displays the rows of data:: + + + +and then in your stylesheet (``style.css``) specify the colouring for the +different priorities, as follows:: + + tr.priority-critical td { + background-color: red; + } + + tr.priority-urgent td { + background-color: orange; + } + +and so on, with far less offensive colours :) + +Editing multiple items in an index view +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To edit the status of all items in the item index view, edit the +``issue.item.html``: + +1. add a form around the listing table (separate from the existing + index-page form), so at the top it reads:: + +
+ + + and at the bottom of that table:: + +
+
`` from the list table, not the + navigation table or the subsequent form table. + +2. in the display for the issue property, change:: + +   + + to:: + +   + + this will result in an edit field for the status property. + +3. after the ``tal:block`` which lists the index items (marked by + ``tal:repeat="i batch"``) add a new table row:: + + + + + + + + + + which gives us a submit button, indicates that we are performing an edit + on any changed statuses. The final ``tal:block`` will make sure that the + current index view parameters (filtering, columns, etc) will be used in + rendering the next page (the results of the editing). + + +Displaying only message summaries in the issue display +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Alter the ``issue.item`` template section for messages to:: + + + + + + + + + + +
Messages
authordatesummary + + remove +
+ + +Enabling display of either message summaries or the entire messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is pretty simple - all we need to do is copy the code from the +example `displaying only message summaries in the issue display`_ into +our template alongside the summary display, and then introduce a switch +that shows either the one or the other. We'll use a new form variable, +``@whole_messages`` to achieve this:: + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Messages + show entire messages +
authordatesummary + remove +
Messages + show only summaries +
authordate + (remove) +
+ + +Setting up a "wizard" (or "druid") for controlled adding of issues +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Set up the page templates you wish to use for data input. My wizard + is going to be a two-step process: first figuring out what category + of issue the user is submitting, and then getting details specific to + that category. The first page includes a table of help, explaining + what the category names mean, and then the core of the form:: + +
+ + + + Category: + + + + + The next page has the usual issue entry information, with the + addition of the following form fragments:: + +
+ + + + + . + . + . +
+ + Note that later in the form, I use the value of "cat" to decide which + form elements should be displayed. For example:: + + + + Operating System + + + + Web Browser + + + + + ... the above section will only be displayed if the category is one + of 6, 10, 13, 14, 15, 16 or 17. + +3. Determine what actions need to be taken between the pages - these are + usually to validate user choices and determine what page is next. Now encode + those actions in a new ``Action`` class (see `defining new web actions`_):: + + from roundup.cgi.actions import Action + + class Page1SubmitAction(Action): + def handle(self): + ''' Verify that the user has selected a category, and then move + on to page 2. + ''' + category = self.form['category'].value + if category == '-1': + self.error_message.append('You must select a category of report') + return + # everything's ok, move on to the next page + self.template = 'add_page2' + + def init(instance): + instance.registerAction('page1_submit', Page1SubmitAction) + +4. Use the usual "new" action as the ``@action`` on the final page, and + you're done (the standard context/submit method can do this for you). + + +Debugging Trackers +================== + +There are three switches in tracker configs that turn on debugging in +Roundup: + +1. web :: debug +2. mail :: debug +3. logging :: level + +See the config.ini file or the `tracker configuration`_ section above for +more information. + +Additionally, the ``roundup-server.py`` script has its own debugging mode +in which it reloads edited templates immediately when they are changed, +rather than requiring a web server restart. + + +------------------- + +Back to `Table of Contents`_ + +.. _`Table of Contents`: index.html +.. _`design documentation`: design.html +.. _`admin guide`: admin_guide.html + Added: tracker/vendor/roundup/current/doc/debugging.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/debugging.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,31 @@ +Debugging Flags +--------------- + +Roundup uses a number of debugging environment variables to help you +figure out what the heck it's doing. + +HYPERDBDEBUG +============ + +This environment variable should be set to a filename - the hyperdb will +write debugging information for various events (including, for instance, +the SQL used). + +This is only obeyed when python is _not_ running in -O mode. + +HYPERDBTRACE +============ + +This environment variable should be set to a filename - the hyperdb will +write a timestamp entry for various events. This appears to be suffering +rather extreme bit-rot and may go away soon. + +This is only obeyed when python is _not_ running in -O mode. + +SENDMAILDEBUG +============= + +Set to a filename and roundup will write a copy of each email message +that it sends to that file. This environment variable is independent of +the python -O flag. + Added: tracker/vendor/roundup/current/doc/default.css ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/default.css Sun Nov 5 21:30:25 2006 @@ -0,0 +1,239 @@ +/* +:Author: David Goodger +:Contact: goodger at users.sourceforge.net +:date: $Date: 2004/06/09 00:25:32 $ +:version: $Revision: 1.13 $ +:copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. +*/ + +a.target { + color: blue } + +a.toc-backref { + text-decoration: none ; + color: black } + +dd { + margin-bottom: 0.5em } + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.attention, div.caution, div.danger, div.error, +div.important, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.hint, div.note { + font-size: 80%; + float: right; + width: 15em; + margin: 0.5em; + margin-left: 1em ; + border: solid #aaa; + background: #eee; + padding: 1em; +} + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +div.hint p.admonition-title, div.important p.admonition-title, +div.note p.admonition-title, div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em } + +div.footer, div.header { + font-size: smaller } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1 { + margin-top: 2em; + text-decoration: underline; +} + +h1.title { + text-align: center; + margin-top: .5em; +} + +h2.subtitle { + text-align: center } + +hr { + width: 75% } + +ol.simple, ul.simple { + margin-top: 0; + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.first { + margin-top: 0 } + +p.label { + white-space: nowrap } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font-family: serif ; + font-size: 100% } + +pre.line-block { + font-family: serif ; + font-size: 100% } + +pre.literal-block, pre.doctest-block { + margin-left: 2em ; + margin-right: 2em ; + background-color: #eeeeee } + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.field-argument { + font-style: italic } + +span.interpreted { + font-family: sans-serif } + +span.option-argument { + font-style: italic } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +table { + margin-top: 0.5em ; + margin-bottom: 0.5em ; + } + +table.citation { + border-top: 0; + border-bottom: 0; + border-right: 0; + border-left: solid thin gray ; + padding-left: 0.5ex } + +table.docinfo { + margin: 2em 4em } + +table.footnote { + border-left: solid thin black ; + padding-left: 0.5ex } + +td, th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: baseline; +} + +table.table { + border-spacing: 0px; + border-collapse: separate; +} +table.table td { + text-align: left; + border: solid thin gray; +} + +table.table th { + text-align: left; + border: solid thin gray; +} + +td > p:first-child, th > p:first-child { + margin-top: 0em } + +th.docinfo-name { + font-weight: bold ; + text-align: right } + +th.field-name { + font-weight: bold ; + text-align: right } + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + font-size: 100% } + +tt { + background-color: #eeeeee } + +tt.literal span.pre { + background-color: #eeeeee +} + +ul.auto-toc { + list-style-type: none } Added: tracker/vendor/roundup/current/doc/design.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/design.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,1639 @@ +======================================================== +Roundup - An Issue-Tracking System for Knowledge Workers +======================================================== + +:Authors: Ka-Ping Yee (original), Richard Jones (implementation) + +.. contents:: + +Introduction +--------------- + +This document presents a description of the components of the Roundup +system and specifies their interfaces and behaviour in sufficient detail +to guide an implementation. For the philosophy and rationale behind the +Roundup design, see the first-round Software Carpentry `submission for +Roundup`__. This document fleshes out that design as well as specifying +interfaces so that the components can be developed separately. + +__ spec.html + + +The Layer Cake +----------------- + +Lots of software design documents come with a picture of a cake. +Everybody seems to like them. I also like cakes (i think they are +tasty). So I, too, shall include a picture of a cake here:: + + ________________________________________________________________ + | E-mail Client | Web Browser | Detector Scripts | Shell | + |---------------+---------------+--------------------+-----------| + | E-mail User | Web User | Detector | Command | + |----------------------------------------------------------------| + | Roundup Database Layer | + |----------------------------------------------------------------| + | Hyperdatabase Layer | + |----------------------------------------------------------------| + | Storage Layer | + ---------------------------------------------------------------- + +The colourful parts of the cake are part of our system; the faint grey +parts of the cake are external components. + +I will now proceed to forgo all table manners and eat from the bottom of +the cake to the top. You may want to stand back a bit so you don't get +covered in crumbs. + + +Hyperdatabase +------------- + +The lowest-level component to be implemented is the hyperdatabase. The +hyperdatabase is a flexible data store that can hold configurable data +in records which we call items. + +The hyperdatabase is implemented on top of the storage layer, an +external module for storing its data. The "batteries-includes" distribution +implements the hyperdatabase on the standard anydbm module. The storage +layer could be a third-party RDBMS; for a low-maintenance solution, +implementing the hyperdatabase on the SQLite RDBMS is suggested. + + +Dates and Date Arithmetic +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before we get into the hyperdatabase itself, we need a way of handling +dates. The hyperdatabase module provides Timestamp objects for +representing date-and-time stamps and Interval objects for representing +date-and-time intervals. + +As strings, date-and-time stamps are specified with the date in +international standard format (``yyyy-mm-dd``) joined to the time +(``hh:mm:ss``) by a period "``.``". Dates in this form can be easily +compared and are fairly readable when printed. An example of a valid +stamp is "``2000-06-24.13:03:59``". We'll call this the "full date +format". When Timestamp objects are printed as strings, they appear in +the full date format with the time always given in GMT. The full date +format is always exactly 19 characters long. + +For user input, some partial forms are also permitted: the whole time or +just the seconds may be omitted; and the whole date may be omitted or +just the year may be omitted. If the time is given, the time is +interpreted in the user's local time zone. The Date constructor takes +care of these conversions. In the following examples, suppose that +``yyyy`` is the current year, ``mm`` is the current month, and ``dd`` is +the current day of the month; and suppose that the user is on Eastern +Standard Time. + +- "2000-04-17" means +- "01-25" means +- "2000-04-17.03:45" means +- "08-13.22:13" means +- "11-07.09:32:43" means +- "14:25" means +- +- "8:47:11" means +- +- the special date "." means "right now" + + +Date intervals are specified using the suffixes "y", "m", and "d". The +suffix "w" (for "week") means 7 days. Time intervals are specified in +hh:mm:ss format (the seconds may be omitted, but the hours and minutes +may not). + +- "3y" means three years +- "2y 1m" means two years and one month +- "1m 25d" means one month and 25 days +- "2w 3d" means two weeks and three days +- "1d 2:50" means one day, two hours, and 50 minutes +- "14:00" means 14 hours +- "0:04:33" means four minutes and 33 seconds + + +The Date class should understand simple date expressions of the form +*stamp* ``+`` *interval* and *stamp* ``-`` *interval*. When adding or +subtracting intervals involving months or years, the components are +handled separately. For example, when evaluating "``2000-06-25 + 1m +10d``", we first add one month to get 2000-07-25, then add 10 days to +get 2000-08-04 (rather than trying to decide whether 1m 10d means 38 or +40 or 41 days). + +Here is an outline of the Date and Interval classes:: + + class Date: + def __init__(self, spec, offset): + """Construct a date given a specification and a time zone + offset. + + 'spec' is a full date or a partial form, with an optional + added or subtracted interval. 'offset' is the local time + zone offset from GMT in hours. + """ + + def __add__(self, interval): + """Add an interval to this date to produce another date.""" + + def __sub__(self, interval): + """Subtract an interval from this date to produce another + date. + """ + + def __cmp__(self, other): + """Compare this date to another date.""" + + def __str__(self): + """Return this date as a string in the yyyy-mm-dd.hh:mm:ss + format. + """ + + def local(self, offset): + """Return this date as yyyy-mm-dd.hh:mm:ss in a local time + zone. + """ + + class Interval: + def __init__(self, spec): + """Construct an interval given a specification.""" + + def __cmp__(self, other): + """Compare this interval to another interval.""" + + def __str__(self): + """Return this interval as a string.""" + + + +Here are some examples of how these classes would behave in practice. +For the following examples, assume that we are on Eastern Standard Time +and the current local time is 19:34:02 on 25 June 2000:: + + >>> Date(".") + + >>> _.local(-5) + "2000-06-25.19:34:02" + >>> Date(". + 2d") + + >>> Date("1997-04-17", -5) + + >>> Date("01-25", -5) + + >>> Date("08-13.22:13", -5) + + >>> Date("14:25", -5) + + >>> Interval(" 3w 1 d 2:00") + + >>> Date(". + 2d") - Interval("3w") + + + +Items and Classes +~~~~~~~~~~~~~~~~~ + +Items contain data in properties. To Python, these properties are +presented as the key-value pairs of a dictionary. Each item belongs to a +class which defines the names and types of its properties. The database +permits the creation and modification of classes as well as items. + + +Identifiers and Designators +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Each item has a numeric identifier which is unique among items in its +class. The items are numbered sequentially within each class in order +of creation, starting from 1. The designator for an item is a way to +identify an item in the database, and consists of the name of the item's +class concatenated with the item's numeric identifier. + +For example, if "spam" and "eggs" are classes, the first item created in +class "spam" has id 1 and designator "spam1". The first item created in +class "eggs" also has id 1 but has the distinct designator "eggs1". Item +designators are conventionally enclosed in square brackets when +mentioned in plain text. This permits a casual mention of, say, +"[patch37]" in an e-mail message to be turned into an active hyperlink. + + +Property Names and Types +~~~~~~~~~~~~~~~~~~~~~~~~ + +Property names must begin with a letter. + +A property may be one of five basic types: + +- String properties are for storing arbitrary-length strings. + +- Boolean properties are for storing true/false, or yes/no values. + +- Number properties are for storing numeric values. + +- Date properties store date-and-time stamps. Their values are Timestamp + objects. + +- A Link property refers to a single other item selected from a + specified class. The class is part of the property; the value is an + integer, the id of the chosen item. + +- A Multilink property refers to possibly many items in a specified + class. The value is a list of integers. + +*None* is also a permitted value for any of these property types. An +attempt to store None into a Multilink property stores an empty list. + +A property that is not specified will return as None from a *get* +operation. + + +Hyperdb Interface Specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +TODO: replace the Interface Specifications with links to the pydoc + +The hyperdb module provides property objects to designate the different +kinds of properties. These objects are used when specifying what +properties belong in classes:: + + class String: + def __init__(self, indexme='no'): + """An object designating a String property.""" + + class Boolean: + def __init__(self): + """An object designating a Boolean property.""" + + class Number: + def __init__(self): + """An object designating a Number property.""" + + class Date: + def __init__(self): + """An object designating a Date property.""" + + class Link: + def __init__(self, classname, do_journal='yes'): + """An object designating a Link property that links to + items in a specified class. + + If the do_journal argument is not 'yes' then changes to + the property are not journalled in the linked item. + """ + + class Multilink: + def __init__(self, classname, do_journal='yes'): + """An object designating a Multilink property that links + to items in a specified class. + + If the do_journal argument is not 'yes' then changes to + the property are not journalled in the linked item(s). + """ + + +Here is the interface provided by the hyperdatabase:: + + class Database: + """A database for storing records containing flexible data + types. + """ + + def __init__(self, config, journaltag=None): + """Open a hyperdatabase given a specifier to some storage. + + The 'storagelocator' is obtained from config.DATABASE. The + meaning of 'storagelocator' depends on the particular + implementation of the hyperdatabase. It could be a file + name, a directory path, a socket descriptor for a connection + to a database over the network, etc. + + The 'journaltag' is a token that will be attached to the + journal entries for any edits done on the database. If + 'journaltag' is None, the database is opened in read-only + mode: the Class.create(), Class.set(), Class.retire(), and + Class.restore() methods are disabled. + """ + + def __getattr__(self, classname): + """A convenient way of calling self.getclass(classname).""" + + def getclasses(self): + """Return a list of the names of all existing classes.""" + + def getclass(self, classname): + """Get the Class object representing a particular class. + + If 'classname' is not a valid class name, a KeyError is + raised. + """ + + class Class: + """The handle to a particular class of items in a hyperdatabase. + """ + + def __init__(self, db, classname, **properties): + """Create a new class with a given name and property + specification. + + 'classname' must not collide with the name of an existing + class, or a ValueError is raised. The keyword arguments in + 'properties' must map names to property objects, or a + TypeError is raised. + + A proxied reference to the database is available as the + 'db' attribute on instances. For example, in + 'IssueClass.send_message', the following is used to lookup + users, messages and files:: + + users = self.db.user + messages = self.db.msg + files = self.db.file + """ + + # Editing items: + + def create(self, **propvalues): + """Create a new item of this class and return its id. + + The keyword arguments in 'propvalues' map property names to + values. The values of arguments must be acceptable for the + types of their corresponding properties or a TypeError is + raised. If this class has a key property, it must be + present and its value must not collide with other key + strings or a ValueError is raised. Any other properties on + this class that are missing from the 'propvalues' dictionary + are set to None. If an id in a link or multilink property + does not refer to a valid item, an IndexError is raised. + """ + + def get(self, itemid, propname): + """Get the value of a property on an existing item of this + class. + + 'itemid' must be the id of an existing item of this class or + an IndexError is raised. 'propname' must be the name of a + property of this class or a KeyError is raised. + """ + + def set(self, itemid, **propvalues): + """Modify a property on an existing item of this class. + + 'itemid' must be the id of an existing item of this class or + an IndexError is raised. Each key in 'propvalues' must be + the name of a property of this class or a KeyError is + raised. All values in 'propvalues' must be acceptable types + for their corresponding properties or a TypeError is raised. + If the value of the key property is set, it must not collide + with other key strings or a ValueError is raised. If the + value of a Link or Multilink property contains an invalid + item id, a ValueError is raised. + """ + + def retire(self, itemid): + """Retire an item. + + The properties on the item remain available from the get() + method, and the item's id is never reused. Retired items + are not returned by the find(), list(), or lookup() methods, + and other items may reuse the values of their key + properties. + """ + + def restore(self, nodeid): + '''Restore a retired node. + + Make node available for all operations like it was before + retirement. + ''' + + def history(self, itemid): + """Retrieve the journal of edits on a particular item. + + 'itemid' must be the id of an existing item of this class or + an IndexError is raised. + + The returned list contains tuples of the form + + (date, tag, action, params) + + 'date' is a Timestamp object specifying the time of the + change and 'tag' is the journaltag specified when the + database was opened. 'action' may be: + + 'create' or 'set' -- 'params' is a dictionary of + property values + 'link' or 'unlink' -- 'params' is (classname, itemid, + propname) + 'retire' -- 'params' is None + """ + + # Locating items: + + def setkey(self, propname): + """Select a String property of this class to be the key + property. + + 'propname' must be the name of a String property of this + class or None, or a TypeError is raised. The values of the + key property on all existing items must be unique or a + ValueError is raised. + """ + + def getkey(self): + """Return the name of the key property for this class or + None. + """ + + def lookup(self, keyvalue): + """Locate a particular item by its key property and return + its id. + + If this class has no key property, a TypeError is raised. + If the 'keyvalue' matches one of the values for the key + property among the items in this class, the matching item's + id is returned; otherwise a KeyError is raised. + """ + + def find(self, **propspec): + """Get the ids of items in this class which link to the + given items. + + 'propspec' consists of keyword args propname=itemid or + propname={:1, : 1, ...} + 'propname' must be the name of a property in this class, + or a KeyError is raised. That property must + be a Link or Multilink property, or a TypeError + is raised. + + Any item in this class whose 'propname' property links to + any of the itemids will be returned. Examples:: + + db.issue.find(messages='1') + db.issue.find(messages={'1':1,'3':1}, files={'7':1}) + """ + + def filter(self, search_matches, filterspec, sort, group): + """ Return a list of the ids of the active items in this + class that match the 'filter' spec, sorted by the group spec + and then the sort spec. + """ + + def list(self): + """Return a list of the ids of the active items in this + class. + """ + + def count(self): + """Get the number of items in this class. + + If the returned integer is 'numitems', the ids of all the + items in this class run from 1 to numitems, and numitems+1 + will be the id of the next item to be created in this class. + """ + + # Manipulating properties: + + def getprops(self): + """Return a dictionary mapping property names to property + objects. + """ + + def addprop(self, **properties): + """Add properties to this class. + + The keyword arguments in 'properties' must map names to + property objects, or a TypeError is raised. None of the + keys in 'properties' may collide with the names of existing + properties, or a ValueError is raised before any properties + have been added. + """ + + def getitem(self, itemid, cache=1): + """ Return a Item convenience wrapper for the item. + + 'itemid' must be the id of an existing item of this class or + an IndexError is raised. + + 'cache' indicates whether the transaction cache should be + queried for the item. If the item has been modified and you + need to determine what its values prior to modification are, + you need to set cache=0. + """ + + class Item: + """ A convenience wrapper for the given item. It provides a + mapping interface to a single item's properties + """ + +Hyperdatabase Implementations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Hyperdatabase implementations exist to create the interface described in +the `hyperdb interface specification`_ over an existing storage +mechanism. Examples are relational databases, \*dbm key-value databases, +and so on. + +Several implementations are provided - they belong in the +``roundup.backends`` package. + + +Application Example +~~~~~~~~~~~~~~~~~~~ + +Here is an example of how the hyperdatabase module would work in +practice:: + + >>> import hyperdb + >>> db = hyperdb.Database("foo.db", "ping") + >>> db + + >>> hyperdb.Class(db, "status", name=hyperdb.String()) + + >>> _.setkey("name") + >>> db.status.create(name="unread") + 1 + >>> db.status.create(name="in-progress") + 2 + >>> db.status.create(name="testing") + 3 + >>> db.status.create(name="resolved") + 4 + >>> db.status.count() + 4 + >>> db.status.list() + [1, 2, 3, 4] + >>> db.status.lookup("in-progress") + 2 + >>> db.status.retire(3) + >>> db.status.list() + [1, 2, 4] + >>> hyperdb.Class(db, "issue", title=hyperdb.String(), status=hyperdb.Link("status")) + + >>> db.issue.create(title="spam", status=1) + 1 + >>> db.issue.create(title="eggs", status=2) + 2 + >>> db.issue.create(title="ham", status=4) + 3 + >>> db.issue.create(title="arguments", status=2) + 4 + >>> db.issue.create(title="abuse", status=1) + 5 + >>> hyperdb.Class(db, "user", username=hyperdb.Key(), + ... password=hyperdb.String()) + + >>> db.issue.addprop(fixer=hyperdb.Link("user")) + >>> db.issue.getprops() + {"title": , "status": , + "user": } + >>> db.issue.set(5, status=2) + >>> db.issue.get(5, "status") + 2 + >>> db.status.get(2, "name") + "in-progress" + >>> db.issue.get(5, "title") + "abuse" + >>> db.issue.find("status", db.status.lookup("in-progress")) + [2, 4, 5] + >>> db.issue.history(5) + [(, "ping", "create", {"title": "abuse", + "status": 1}), + (, "ping", "set", {"status": 2})] + >>> db.status.history(1) + [(, "ping", "link", ("issue", 5, "status")), + (, "ping", "unlink", ("issue", 5, "status"))] + >>> db.status.history(2) + [(, "ping", "link", ("issue", 5, "status"))] + + +For the purposes of journalling, when a Multilink property is set to a +new list of items, the hyperdatabase compares the old list to the new +list. The journal records "unlink" events for all the items that appear +in the old list but not the new list, and "link" events for all the +items that appear in the new list but not in the old list. + + +Roundup Database +---------------- + +The Roundup database layer is implemented on top of the hyperdatabase +and mediates calls to the database. Some of the classes in the Roundup +database are considered issue classes. The Roundup database layer adds +detectors and user items, and on issues it provides mail spools, nosy +lists, and superseders. + + +Reserved Classes +~~~~~~~~~~~~~~~~ + +Internal to this layer we reserve three special classes of items that +are not issues. + +Users +""""" + +Users are stored in the hyperdatabase as items of class "user". The +"user" class has the definition:: + + hyperdb.Class(db, "user", username=hyperdb.String(), + password=hyperdb.String(), + address=hyperdb.String()) + db.user.setkey("username") + +Messages +"""""""" + +E-mail messages are represented by hyperdatabase items of class "msg". +The actual text content of the messages is stored in separate files. +(There's no advantage to be gained by stuffing them into the +hyperdatabase, and if messages are stored in ordinary text files, they +can be grepped from the command line.) The text of a message is saved +in a file named after the message item designator (e.g. "msg23") for the +sake of the command interface (see below). Attachments are stored +separately and associated with "file" items. The "msg" class has the +definition:: + + hyperdb.Class(db, "msg", author=hyperdb.Link("user"), + recipients=hyperdb.Multilink("user"), + date=hyperdb.Date(), + summary=hyperdb.String(), + files=hyperdb.Multilink("file")) + +The "author" property indicates the author of the message (a "user" item +must exist in the hyperdatabase for any messages that are stored in the +system). The "summary" property contains a summary of the message for +display in a message index. + + +Files +""""" + +Submitted files are represented by hyperdatabase items of class "file". +Like e-mail messages, the file content is stored in files outside the +database, named after the file item designator (e.g. "file17"). The +"file" class has the definition:: + + hyperdb.Class(db, "file", user=hyperdb.Link("user"), + name=hyperdb.String(), + type=hyperdb.String()) + +The "user" property indicates the user who submitted the file, the +"name" property holds the original name of the file, and the "type" +property holds the MIME type of the file as received. + + +Issue Classes +~~~~~~~~~~~~~ + +All issues have the following standard properties: + +=========== ========================== +Property Definition +=========== ========================== +title hyperdb.String() +messages hyperdb.Multilink("msg") +files hyperdb.Multilink("file") +nosy hyperdb.Multilink("user") +superseder hyperdb.Multilink("issue") +=========== ========================== + +Also, two Date properties named "creation" and "activity" are fabricated +by the Roundup database layer. Two user Link properties, "creator" and +"actor" are also fabricated. By "fabricated" we mean that no such +properties are actually stored in the hyperdatabase, but when properties +on issues are requested, the "creation"/"creator" and "activity"/"actor" +properties are made available. The value of the "creation"/"creator" +properties relate to issue creation, and the value of the "activity"/ +"actor" properties relate to the last editing of any property on the issue +(equivalently, these are the dates on the first and last records in the +issue's journal). + + +Roundupdb Interface Specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The interface to a Roundup database delegates most method calls to the +hyperdatabase, except for the following changes and additional methods:: + + class Database: + def getuid(self): + """Return the id of the "user" item associated with the user + that owns this connection to the hyperdatabase.""" + + class Class: + # Overridden methods: + + def create(self, **propvalues): + def set(self, **propvalues): + def retire(self, itemid): + """These operations trigger detectors and can be vetoed. + Attempts to modify the "creation", "creator", "activity" + properties or "actor" cause a KeyError. + """ + + class IssueClass(Class): + # Overridden methods: + + def __init__(self, db, classname, **properties): + """The newly-created class automatically includes the + "messages", "files", "nosy", and "superseder" properties. + If the 'properties' dictionary attempts to specify any of + these properties or a "creation", "creator", "activity" or + "actor" property, a ValueError is raised.""" + + def get(self, itemid, propname): + def getprops(self): + """In addition to the actual properties on the item, these + methods provide the "creation", "creator", "activity" and + "actor" properties.""" + + # New methods: + + def addmessage(self, itemid, summary, text): + """Add a message to an issue's mail spool. + + A new "msg" item is constructed using the current date, the + user that owns the database connection as the author, and + the specified summary text. The "files" and "recipients" + fields are left empty. The given text is saved as the body + of the message and the item is appended to the "messages" + field of the specified issue. + """ + + def nosymessage(self, itemid, msgid): + """Send a message to the members of an issue's nosy list. + + The message is sent only to users on the nosy list who are + not already on the "recipients" list for the message. These + users are then added to the message's "recipients" list. + """ + + +Default Schema +~~~~~~~~~~~~~~ + +The default schema included with Roundup turns it into a typical +software bug tracker. The database is set up like this:: + + pri = Class(db, "priority", name=hyperdb.String(), + order=hyperdb.String()) + pri.setkey("name") + pri.create(name="critical", order="1") + pri.create(name="urgent", order="2") + pri.create(name="bug", order="3") + pri.create(name="feature", order="4") + pri.create(name="wish", order="5") + + stat = Class(db, "status", name=hyperdb.String(), + order=hyperdb.String()) + stat.setkey("name") + stat.create(name="unread", order="1") + stat.create(name="deferred", order="2") + stat.create(name="chatting", order="3") + stat.create(name="need-eg", order="4") + stat.create(name="in-progress", order="5") + stat.create(name="testing", order="6") + stat.create(name="done-cbb", order="7") + stat.create(name="resolved", order="8") + + Class(db, "keyword", name=hyperdb.String()) + + Class(db, "issue", fixer=hyperdb.Multilink("user"), + topic=hyperdb.Multilink("keyword"), + priority=hyperdb.Link("priority"), + status=hyperdb.Link("status")) + +(The "order" property hasn't been explained yet. It gets used by the +Web user interface for sorting.) + +The above isn't as pretty-looking as the schema specification in the +first-stage submission, but it could be made just as easy with the +addition of a convenience function like Choice for setting up the +"priority" and "status" classes:: + + def Choice(name, *options): + cl = Class(db, name, name=hyperdb.String(), + order=hyperdb.String()) + for i in range(len(options)): + cl.create(name=option[i], order=i) + return hyperdb.Link(name) + + +Detector Interface +------------------ + +Detectors are Python functions that are triggered on certain kinds of +events. The definitions of the functions live in Python modules placed +in a directory set aside for this purpose. Importing the Roundup +database module also imports all the modules in this directory, and the +``init()`` function of each module is called when a database is opened +to provide it a chance to register its detectors. + +There are two kinds of detectors: + +1. an auditor is triggered just before modifying an item +2. a reactor is triggered just after an item has been modified + +When the Roundup database is about to perform a ``create()``, ``set()``, +``retire()``, or ``restore`` operation, it first calls any *auditors* +that have been registered for that operation on that class. Any auditor +may raise a *Reject* exception to abort the operation. + +If none of the auditors raises an exception, the database proceeds to +carry out the operation. After it's done, it then calls all of the +*reactors* that have been registered for the operation. + + +Detector Interface Specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``audit()`` and ``react()`` methods register detectors on a given +class of items:: + + class Class: + def audit(self, event, detector, priority=100): + """Register an auditor on this class. + + 'event' should be one of "create", "set", "retire", or + "restore". 'detector' should be a function accepting four + arguments. Detectors are called in priority order, execution + order is undefined for detectors with the same priority. + """ + + def react(self, event, detector, priority=100): + """Register a reactor on this class. + + 'event' should be one of "create", "set", "retire", or + "restore". 'detector' should be a function accepting four + arguments. Detectors are called in priority order, execution + order is undefined for detectors with the same priority. + """ + +Auditors are called with the arguments:: + + audit(db, cl, itemid, newdata) + +where ``db`` is the database, ``cl`` is an instance of Class or +IssueClass within the database, and ``newdata`` is a dictionary mapping +property names to values. + +For a ``create()`` operation, the ``itemid`` argument is None and +newdata contains all of the initial property values with which the item +is about to be created. + +For a ``set()`` operation, newdata contains only the names and values of +properties that are about to be changed. + +For a ``retire()`` or ``restore()`` operation, newdata is None. + +Reactors are called with the arguments:: + + react(db, cl, itemid, olddata) + +where ``db`` is the database, ``cl`` is an instance of Class or +IssueClass within the database, and ``olddata`` is a dictionary mapping +property names to values. + +For a ``create()`` operation, the ``itemid`` argument is the id of the +newly-created item and ``olddata`` is None. + +For a ``set()`` operation, ``olddata`` contains the names and previous +values of properties that were changed. + +For a ``retire()`` or ``restore()`` operation, ``itemid`` is the id of +the retired or restored item and ``olddata`` is None. + + +Detector Example +~~~~~~~~~~~~~~~~ + +Here is an example of detectors written for a hypothetical +project-management application, where users can signal approval of a +project by adding themselves to an "approvals" list, and a project +proceeds when it has three approvals:: + + # Permit users only to add themselves to the "approvals" list. + + def check_approvals(db, cl, id, newdata): + if newdata.has_key("approvals"): + if cl.get(id, "status") == db.status.lookup("approved"): + raise Reject, "You can't modify the approvals list " \ + "for a project that has already been approved." + old = cl.get(id, "approvals") + new = newdata["approvals"] + for uid in old: + if uid not in new and uid != db.getuid(): + raise Reject, "You can't remove other users from " \ + "the approvals list; you can only remove " \ + "yourself." + for uid in new: + if uid not in old and uid != db.getuid(): + raise Reject, "You can't add other users to the " \ + "approvals list; you can only add yourself." + + # When three people have approved a project, change its status from + # "pending" to "approved". + + def approve_project(db, cl, id, olddata): + if (olddata.has_key("approvals") and + len(cl.get(id, "approvals")) == 3): + if cl.get(id, "status") == db.status.lookup("pending"): + cl.set(id, status=db.status.lookup("approved")) + + def init(db): + db.project.audit("set", check_approval) + db.project.react("set", approve_project) + +Here is another example of a detector that can allow or prevent the +creation of new items. In this scenario, patches for a software project +are submitted by sending in e-mail with an attached file, and we want to +ensure that there are text/plain attachments on the message. The +maintainer of the package can then apply the patch by setting its status +to "applied":: + + # Only accept attempts to create new patches that come with patch + # files. + + def check_new_patch(db, cl, id, newdata): + if not newdata["files"]: + raise Reject, "You can't submit a new patch without " \ + "attaching a patch file." + for fileid in newdata["files"]: + if db.file.get(fileid, "type") != "text/plain": + raise Reject, "Submitted patch files must be " \ + "text/plain." + + # When the status is changed from "approved" to "applied", apply the + # patch. + + def apply_patch(db, cl, id, olddata): + if (cl.get(id, "status") == db.status.lookup("applied") and + olddata["status"] == db.status.lookup("approved")): + # ...apply the patch... + + def init(db): + db.patch.audit("create", check_new_patch) + db.patch.react("set", apply_patch) + + +Command Interface +----------------- + +The command interface is a very simple and minimal interface, intended +only for quick searches and checks from the shell prompt. (Anything more +interesting can simply be written in Python using the Roundup database +module.) + + +Command Interface Specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A single command, roundup, provides basic access to the hyperdatabase +from the command line:: + + roundup-admin help + roundup-admin get [-list] designator[, designator,...] propname + roundup-admin set designator[, designator,...] propname=value ... + roundup-admin find [-list] classname propname=value ... + +See ``roundup-admin help commands`` for a complete list of commands. + +Property values are represented as strings in command arguments and in +the printed results: + +- Strings are, well, strings. + +- Numbers are displayed the same as strings. + +- Booleans are displayed as 'Yes' or 'No'. + +- Date values are printed in the full date format in the local time + zone, and accepted in the full format or any of the partial formats + explained above. + +- Link values are printed as item designators. When given as an + argument, item designators and key strings are both accepted. + +- Multilink values are printed as lists of item designators joined by + commas. When given as an argument, item designators and key strings + are both accepted; an empty string, a single item, or a list of items + joined by commas is accepted. + +When multiple items are specified to the roundup get or roundup set +commands, the specified properties are retrieved or set on all the +listed items. + +When multiple results are returned by the roundup get or roundup find +commands, they are printed one per line (default) or joined by commas +(with the -list) option. + + +Usage Example +~~~~~~~~~~~~~ + +To find all messages regarding in-progress issues that contain the word +"spam", for example, you could execute the following command from the +directory where the database dumps its files:: + + shell% for issue in `roundup find issue status=in-progress`; do + > grep -l spam `roundup get $issue messages` + > done + msg23 + msg49 + msg50 + msg61 + shell% + +Or, using the -list option, this can be written as a single command:: + + shell% grep -l spam `roundup get \ + \`roundup find -list issue status=in-progress\` messages` + msg23 + msg49 + msg50 + msg61 + shell% + + +E-mail User Interface +--------------------- + +The Roundup system must be assigned an e-mail address at which to +receive mail. Messages should be piped to the Roundup mail-handling +script by the mail delivery system (e.g. using an alias beginning with +"|" for sendmail). + + +Message Processing +~~~~~~~~~~~~~~~~~~ + +Incoming messages are examined for multiple parts. In a multipart/mixed +message or part, each subpart is extracted and examined. In a +multipart/alternative message or part, we look for a text/plain subpart +and ignore the other parts. The text/plain subparts are assembled to +form the textual body of the message, to be stored in the file +associated with a "msg" class item. Any parts of other types are each +stored in separate files and given "file" class items that are linked to +the "msg" item. + +The "summary" property on message items is taken from the first +non-quoting section in the message body. The message body is divided +into sections by blank lines. Sections where the second and all +subsequent lines begin with a ">" or "|" character are considered +"quoting sections". The first line of the first non-quoting section +becomes the summary of the message. + +All of the addresses in the To: and Cc: headers of the incoming message +are looked up among the user items, and the corresponding users are +placed in the "recipients" property on the new "msg" item. The address +in the From: header similarly determines the "author" property of the +new "msg" item. The default handling for addresses that don't have +corresponding users is to create new users with no passwords and a +username equal to the address. (The web interface does not permit +logins for users with no passwords.) If we prefer to reject mail from +outside sources, we can simply register an auditor on the "user" class +that prevents the creation of user items with no passwords. + +The subject line of the incoming message is examined to determine +whether the message is an attempt to create a new issue or to discuss an +existing issue. A designator enclosed in square brackets is sought as +the first thing on the subject line (after skipping any "Fwd:" or "Re:" +prefixes). + +If an issue designator (class name and id number) is found there, the +newly created "msg" item is added to the "messages" property for that +issue, and any new "file" items are added to the "files" property for +the issue. + +If just an issue class name is found there, we attempt to create a new +issue of that class with its "messages" property initialized to contain +the new "msg" item and its "files" property initialized to contain any +new "file" items. + +Both cases may trigger detectors (in the first case we are calling the +set() method to add the message to the issue's spool; in the second case +we are calling the create() method to create a new item). If an auditor +raises an exception, the original message is bounced back to the sender +with the explanatory message given in the exception. + + +Nosy Lists +~~~~~~~~~~ + +A standard detector is provided that watches for additions to the +"messages" property. When a new message is added, the detector sends it +to all the users on the "nosy" list for the issue that are not already +on the "recipients" list of the message. Those users are then appended +to the "recipients" property on the message, so multiple copies of a +message are never sent to the same user. The journal recorded by the +hyperdatabase on the "recipients" property then provides a log of when +the message was sent to whom. + + +Setting Properties +~~~~~~~~~~~~~~~~~~ + +The e-mail interface also provides a simple way to set properties on +issues. At the end of the subject line, ``propname=value`` pairs can be +specified in square brackets, using the same conventions as for the +roundup ``set`` shell command. + + +Web User Interface +------------------ + +The web interface is provided by a CGI script that can be run under any +web server. A simple web server can easily be built on the standard +CGIHTTPServer module, and should also be included in the distribution +for quick out-of-the-box deployment. + +The user interface is constructed from a number of template files +containing mostly HTML. Among the HTML tags in templates are +interspersed some nonstandard tags, which we use as placeholders to be +replaced by properties and their values. + + +Views and View Specifiers +~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are two main kinds of views: *index* views and *issue* views. An +index view displays a list of issues of a particular class, optionally +sorted and filtered as requested. An issue view presents the properties +of a particular issue for editing and displays the message spool for the +issue. + +A view specifier is a string that specifies all the options needed to +construct a particular view. It goes after the URL to the Roundup CGI +script or the web server to form the complete URL to a view. When the +result of selecting a link or submitting a form takes the user to a new +view, the Web browser should be redirected to a canonical location +containing a complete view specifier so that the view can be bookmarked. + + +Displaying Properties +~~~~~~~~~~~~~~~~~~~~~ + +Properties appear in the user interface in three contexts: in indices, +in editors, and as search filters. For each type of property, there are +several display possibilities. For example, in an index view, a string +property may just be printed as a plain string, but in an editor view, +that property should be displayed in an editable field. + +The display of a property is handled by functions in the +``cgi.templating`` module. + +Displayer functions are triggered by ``tal:content`` or ``tal:replace`` +tag attributes in templates. The value of the attribute provides an +expression for calling the displayer function. For example, the +occurrence of:: + + tal:content="context/status/plain" + +in a template triggers a call to:: + + context['status'].plain() + +where the context would be an item of the "issue" class. The displayer +functions can accept extra arguments to further specify details about +the widgets that should be generated. + +Some of the standard displayer functions include: + +========= ============================================================== +Function Description +========= ============================================================== +plain display a String property directly; + display a Date property in a specified time zone with an + option to omit the time from the date stamp; for a Link or + Multilink property, display the key strings of the linked + items (or the ids if the linked class has no key property) +field display a property like the plain displayer above, but in a + text field to be edited +menu for a Link property, display a menu of the available choices +========= ============================================================== + +See the `customisation`_ documentation for the complete list. + + +Index Views +~~~~~~~~~~~ + +An index view contains two sections: a filter section and an index +section. The filter section provides some widgets for selecting which +issues appear in the index. The index section is a table of issues. + + +Index View Specifiers +""""""""""""""""""""" + +An index view specifier looks like this (whitespace has been added for +clarity):: + + /issue?status=unread,in-progress,resolved& + topic=security,ui& + :group=priority& + :sort=-activity& + :filters=status,topic& + :columns=title,status,fixer + + +The index view is determined by two parts of the specifier: the layout +part and the filter part. The layout part consists of the query +parameters that begin with colons, and it determines the way that the +properties of selected items are displayed. The filter part consists of +all the other query parameters, and it determines the criteria by which +items are selected for display. + +The filter part is interactively manipulated with the form widgets +displayed in the filter section. The layout part is interactively +manipulated by clicking on the column headings in the table. + +The filter part selects the union of the sets of issues with values +matching any specified Link properties and the intersection of the sets +of issues with values matching any specified Multilink properties. + +The example specifies an index of "issue" items. Only issues with a +"status" of either "unread" or "in-progres" or "resolved" are displayed, +and only issues with "topic" values including both "security" and "ui" +are displayed. The issues are grouped by priority, arranged in +ascending order; and within groups, sorted by activity, arranged in +descending order. The filter section shows filters for the "status" and +"topic" properties, and the table includes columns for the "title", +"status", and "fixer" properties. + +Associated with each issue class is a default layout specifier. The +layout specifier in the above example is the default layout to be +provided with the default bug-tracker schema described above in section +4.4. + +Index Section +""""""""""""" + +The template for an index section describes one row of the index table. +Fragments protected by a ``tal:condition="request/show/"`` are +included or omitted depending on whether the view specifier requests a +column for a particular property. The table cells are filled by the +``tal:content="context/"`` directive, which displays the value +of the property. + +Here's a simple example of an index template:: + + + + + + + +Sorting +""""""" + +String and Date values are sorted in the natural way. Link properties +are sorted according to the value of the "order" property on the linked +items if it is present; or otherwise on the key string of the linked +items; or finally on the item ids. Multilink properties are sorted +according to how many links are present. + +Issue Views +~~~~~~~~~~~ + +An issue view contains an editor section and a spool section. At the top +of an issue view, links to superseding and superseded issues are always +displayed. + +Issue View Specifiers +""""""""""""""""""""" + +An issue view specifier is simply the issue's designator:: + + /patch23 + + +Editor Section +"""""""""""""" + +The editor section is generated from a template containing +``tal:content="context//"`` directives to insert the +appropriate widgets for editing properties. + +Here's an example of a basic editor template:: + + + + + + + + + + + + + + + + +
+ +
+ +As shown in the example, the editor template can also include a ":note" +field, which is a text area for entering a note to go along with a +change. + +When a change is submitted, the system automatically generates a message +describing the changed properties. The message displays all of the +property values on the issue and indicates which ones have changed. An +example of such a message might be this:: + + title: Polly Parrot is dead + priority: critical + status: unread -> in-progress + fixer: (none) + keywords: parrot,plumage,perch,nailed,dead + +If a note is given in the ":note" field, the note is appended to the +description. The message is then added to the issue's message spool +(thus triggering the standard detector to react by sending out this +message to the nosy list). + + +Spool Section +""""""""""""" + +The spool section lists messages in the issue's "messages" property. +The index of messages displays the "date", "author", and "summary" +properties on the message items, and selecting a message takes you to +its content. + +Access Control +-------------- + +At each point that requires an action to be performed, the security +mechanisms are asked if the current user has permission. This permission +is defined as a Permission. + +Individual assignment of Permission to user is unwieldy. The concept of +a Role, which encompasses several Permissions and may be assigned to +many Users, is quite well developed in many projects. Roundup will take +this path, and allow the multiple assignment of Roles to Users, and +multiple Permissions to Roles. These definitions are not persistent - +they're defined when the application initialises. + +There will be three levels of Permission. The Class level permissions +define logical permissions associated with all items of a particular +class (or all classes). The Item level permissions define logical +permissions associated with specific items by way of their user-linked +properties. The Property level permissions define logical permissions +associated with a specific property of an item. + + +Access Control Interface Specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The security module defines:: + + class Permission: + ''' Defines a Permission with the attributes + - name + - description + - klass (optional) + - properties (optional) + - check function (optional) + + The klass may be unset, indicating that this permission is + not locked to a particular hyperdb class. There may be + multiple Permissions for the same name for different + classes. + + If property names are set, permission is restricted to those + properties only. + + If check function is set, permission is granted only when + the function returns value interpreted as boolean true. + The function is called with arguments db, userid, itemid. + ''' + + class Role: + ''' Defines a Role with the attributes + - name + - description + - permissions + ''' + + class Security: + def __init__(self, db): + ''' Initialise the permission and role stores, and add in + the base roles (for admin user). + ''' + + def getPermission(self, permission, classname=None, properties=None, + check=None): + ''' Find the Permission exactly matching the name, class, + properties list and check function. + + Raise ValueError if there is no exact match. + ''' + + def hasPermission(self, permission, userid, classname=None, + property=None, itemid=None): + ''' Look through all the Roles, and hence Permissions, and + see if "permission" exists given the constraints of + classname, property and itemid. + + If classname is specified (and only classname) then the + search will match if there is *any* Permission for that + classname, even if the Permission has additional + constraints. + + If property is specified, the Permission matched must have + either no properties listed or the property must appear in + the list. + + If itemid is specified, the Permission matched must have + either no check function defined or the check function, + when invoked, must return a True value. + + Note that this functionality is actually implemented by the + Permission.test() method. + ''' + + def addPermission(self, **propspec): + ''' Create a new Permission with the properties defined in + 'propspec'. See the Permission class for the possible + keyword args. + ''' + + def addRole(self, **propspec): + ''' Create a new Role with the properties defined in + 'propspec' + ''' + + def addPermissionToRole(self, rolename, permission): + ''' Add the permission to the role's permission list. + + 'rolename' is the name of the role to add permission to. + ''' + +Modules such as ``cgi/client.py`` and ``mailgw.py`` define their own +permissions like so (this example is ``cgi/client.py``):: + + def initialiseSecurity(security): + ''' Create some Permissions and Roles on the security object + + This function is directly invoked by + security.Security.__init__() as a part of the Security + object instantiation. + ''' + p = security.addPermission(name="Web Registration", + description="Anonymous users may register through the web") + security.addToRole('Anonymous', p) + +Detectors may also define roles in their init() function:: + + def init(db): + # register an auditor that checks that a user has the "May + # Resolve" Permission before allowing them to set an issue + # status to "resolved" + db.issue.audit('set', checkresolvedok) + p = db.security.addPermission(name="May Resolve", klass="issue") + security.addToRole('Manager', p) + +The tracker dbinit module then has in ``open()``:: + + # open the database - it must be modified to init the Security class + # from security.py as db.security + db = Database(config, name) + + # add some extra permissions and associate them with roles + ei = db.security.addPermission(name="Edit", klass="issue", + description="User is allowed to edit issues") + db.security.addPermissionToRole('User', ei) + ai = db.security.addPermission(name="View", klass="issue", + description="User is allowed to access issues") + db.security.addPermissionToRole('User', ai) + +In the dbinit ``init()``:: + + # create the two default users + user.create(username="admin", password=Password(adminpw), + address=config.ADMIN_EMAIL, roles='Admin') + user.create(username="anonymous", roles='Anonymous') + +Then in the code that matters, calls to ``hasPermission`` and +``hasItemPermission`` are made to determine if the user has permission +to perform some action:: + + if db.security.hasPermission('issue', 'Edit', userid): + # all ok + + if db.security.hasItemPermission('issue', itemid, + assignedto=userid): + # all ok + +Code in the core will make use of these methods, as should code in +auditors in custom templates. The HTML templating may access the access +controls through the *user* attribute of the *request* variable. It +exposes a ``hasPermission()`` method:: + + tal:condition="python:request.user.hasPermission('Edit', 'issue')" + +or, if the *context* is *issue*, then the following is the same:: + + tal:condition="python:request.user.hasPermission('Edit')" + + +Authentication of Users +~~~~~~~~~~~~~~~~~~~~~~~ + +Users must be authenticated correctly for the above controls to work. +This is not done in the current mail gateway at all. Use of digital +signing of messages could alleviate this problem. + +The exact mechanism of registering the digital signature should be +flexible, with perhaps a level of trust. Users who supply their +signature through their first message into the tracker should be at a +lower level of trust to those who supply their signature to an admin for +submission to their user details. + + +Anonymous Users +~~~~~~~~~~~~~~~ + +The "anonymous" user must always exist, and defines the access +permissions for anonymous users. Unknown users accessing Roundup through +the web or email interfaces will be logged in as the "anonymous" user. + + +Use Cases +~~~~~~~~~ + +public - end users can submit bugs, request new features, request + support + The Users would be given the default "User" Role which gives "View" + and "Edit" Permission to the "issue" class. +developer - developers can fix bugs, implement new features, provide + support + A new Role "Developer" is created with the Permission "Fixer" which + is checked for in custom auditors that see whether the issue is + being resolved with a particular resolution ("fixed", "implemented", + "supported") and allows that resolution only if the permission is + available. +manager - approvers/managers can approve new features and signoff bug + fixes + A new Role "Manager" is created with the Permission "Signoff" which + is checked for in custom auditors that see whether the issue status + is being changed similar to the developer example. admin - + administrators can add users and set user's roles The existing Role + "Admin" has the Permissions "Edit" for all classes (including + "user") and "Web Roles" which allow the desired actions. +system - automated request handlers running various report/escalation + scripts + A combination of existing and new Roles, Permissions and auditors + could be used here. +privacy - issues that are only visible to some users + A new property is added to the issue which marks the user or group + of users who are allowed to view and edit the issue. An auditor will + check for edit access, and the template user object can check for + view access. + + +Deployment Scenarios +-------------------- + +The design described above should be general enough to permit the use of +Roundup for bug tracking, managing projects, managing patches, or +holding discussions. By using items of multiple types, one could deploy +a system that maintains requirement specifications, catalogs bugs, and +manages submitted patches, where patches could be linked to the bugs and +requirements they address. + + +Acknowledgements +---------------- + +My thanks are due to Christy Heyl for reviewing and contributing +suggestions to this paper and motivating me to get it done, and to Jesse +Vincent, Mark Miller, Christopher Simons, Jeff Dunmall, Wayne Gramlich, +and Dean Tribble for their assistance with the first-round submission. + + +Changes to this document +------------------------ + +- Added Boolean and Number types +- Added section Hyperdatabase Implementations +- "Item" has been renamed to "Issue" to account for the more specific + nature of the Class. +- New Templating +- Access Controls +- Added "actor" property + +------------------ + +Back to `Table of Contents`_ + +.. _`Table of Contents`: index.html +.. _customisation: customizing.html + Added: tracker/vendor/roundup/current/doc/developers.txt ============================================================================== --- (empty file) +++ tracker/vendor/roundup/current/doc/developers.txt Sun Nov 5 21:30:25 2006 @@ -0,0 +1,477 @@ +================== +Developing Roundup +================== + +:Version: $Revision: 1.14 $ + +.. note:: + The intended audience of this document is the developers of the core + Roundup code. If you just wish to alter some behaviour of your Roundup + installation, see `customising roundup`_. + +.. contents:: + +Getting Started +--------------- + +Anyone wishing to help in the development of Roundup must read `Roundup's +Design Document`_ and the `implementation notes`_. + +All development is coordinated through two resources: + +- roundup-dev mailing list at + http://lists.sourceforge.net/mailman/listinfo/roundup-devel +- Sourceforge's issue trackers at + https://sourceforge.net/tracker/?group_id=31577 + +Small Changes +------------- + +Most small changes can be submitted through the Feature tracker, with patches +attached that give context diffs of the affected source. + + +CVS Access +---------- + +To get CVS access, contact richard at users.sourceforge.net. + +CVS stuff: + +1. to tag a release (eg. the pre-release of 0.5.0):: + + cvs tag release-0-5-0-pr1 + +1. to make a branch (eg. branching for code freeze/release):: + + cvs co -d maint-0-5 -r release-0-5-0-pr1 roundup + cd maint-0-5 + cvs tag -b maint-0-5 + +2. to check out a branch (eg. the maintenance branch for 0.5.x):: + + cvs co -d maint-0-5 -r maint-0-5 + +3. to merge changes from the maintenance branch to the trunk, in the + directory containing the HEAD checkout:: + + cvs up -j maint-0-5 + + though this is highly discouraged, as it generally creates a whole swag + of conflicts :( + +Standard tag names: + +*release-maj-min-patch[-sub]* + Release of the major.minor.patch release, possibly a beta or pre-release, + in which case *sub* will be one of "b*N*" or "pr*N*". +*maint-maj-min* + Maintenance branch for the major.minor release. Patch releases are tagged in + this branch. + +Typically, release happen like this: + +1. work progresses in the HEAD branch until milestones are met, +2. a series of beta releases are tagged in the HEAD until the code is + stable enough to freeze, +3. the pre-release is tagged in the HEAD, with the resultant code branched + to the maintenance branch for that release, +4. bugs in the release are patched in the maintenance branch, and the final + and patch releases are tagged there, and +5. further major work happens in the HEAD. + +Project Rules +------------- + +Mostly the project follows Guido's Style (though naming tends to be a little +relaxed sometimes). In short: + +- 80 column width code +- 4-space indentations +- All modules must have a CVS Id line near the top + +Other project rules: + +- New functionality must be documented, even briefly (so at least we know + where there's missing documentation) and changes to tracker configuration + must be logged in the upgrading document. +- subscribe to roundup-checkins to receive checkin notifications from the + other developers with CVS access +- discuss any changes with the other developers on roundup-dev. If nothing + else, this makes sure there's no rude shocks +- write unit tests for changes you make (where possible), and ensure that + all unit tests run before committing changes +- run pychecker over changed code + +The administrators of the project reserve the right to boot developers who +consistently check in code which is either broken or takes the codebase in +directions that have not been agreed to. + + +Debugging Aids +-------------- + +Try turning on logging of DEBUG level messages. This may be done a number +of ways, depending on what it is you're testing: + +1. If you're testing the database unit tests, then set the environment + variable ``LOGGING_LEVEL=DEBUG``. This may be done like so: + + LOGGING_LEVEL=DEBUG python run_tests.py + + This variable replaces the older HYPERDBDEBUG environment var. + +2. If you're testing a particular tracker, then set the logging level in + your tracker's ``config.ini``. + + +Internationalization Notes +-------------------------- + +How stuff works: + +1. Strings that may require translation (messages in human language) + are marked in the source code. This step is discussed in + `Marking Strings for Translation`_ section. + +2. These strings are all extracted into Message Template File + ``locale/roundup.pot`` (_`POT` file). See `Extracting Translatable + Messages`_ below. + +3. Language teams use POT file to make Message Files for national + languages (_`PO` files). All PO files for Roundup are kept in + the ``locale`` directory. Names of these files are target + locale names, usually just 2-letter language codes. `Translating + Messages`_ section of this chapter gives useful hints for + message translators. + +4. Translated Message Files are compiled into binary form (_`MO` files) + and stored in ``locale`` directory (but not kept in the `Roundup + CVS`_ repository, as they may be easily made from PO files). + See `Compiling Message Catalogs`_ section. + +5. Roundup installer creates runtime locale structure on the file + system, putting MO files in their appropriate places. + +6. Runtime internationalization (_`I18N`) services use these MO files + to translate program messages into language selected by current + Roundup user. Roundup command line interface uses locale name + set in OS environment variable ``LANGUAGE``, ``LC_ALL``, + ``LC_MESSAGES``, or ``LANG`` (in that order). Roundup Web User + Interface uses language selected by currently authenticated user. + +Additional details may be found in `GNU gettext`_ and Python `gettext +module`_ documentation. + +`Roundup source distribution`_ includes POT and PO files for message +translators, and also pre-built MO files to facilitate installations +from source. Roundup binary distribution includes MO files only. + +.. _GNU gettext: + +GNU gettext package +^^^^^^^^^^^^^^^^^^^ + +This chapter is full of references to GNU `gettext package`_. +GNU gettext is a "must have" for nearly all steps of internationalizing +any program, and it's manual is definetely a recommended reading +for people involved in `I18N`_. + +There are GNU gettext ports to all major OS platforms. +Windows binaries are available from `GNU mirror sites`_. + +Roundup does not use GNU gettext at runtime, but it's tools +are used for `extracting translatable messages`_, `compiling +message catalogs`_ and, optionally, for `translating messages`_. + +Note that ``gettext`` package in some OS distributions means just +runtime tools and libraries. In such cases gettext development tools +are usually distributed in separate package named ``gettext-devel``. + +Marking Strings for Translation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Strings that need translation must be marked in the source code. +Following subsections explain how this is done in different cases. + +If translatable string is used as a format string, it is recommended +to always use *named* format specifiers:: + + _('Index of %(classname)s') % locals() + +This helps translators to better understand the context of the +message and, with Python formatting, remove format specifier altogether +(which is sometimes useful, especially in singular cases of `Plural Forms`_). + +When there is more than one format specifier in the translatable +format string, named format specifiers **must** be used almost always, +because translation may require different order of items. + +It is better to *not* mark for translation strings that are not +locale-dependent, as this makes it more difficult to keep track +of translation completeness. For example, string ```` +(in ``index()`` method of the request handler in ``roundup_server`` +script) has no human readable parts at all, and needs no translations. +Such strings are left untranslated in PO files, and are reported +as such by PO status checkers (e.g. ``msgfmt --statistics``). + +Command Line Interfaces +~~~~~~~~~~~~~~~~~~~~~~~ + +Scripts and routines run from the command line use "static" language +defined by environment variables recognized by ``gettext`` module +from Python library (``LANGUAGE``, ``LC_ALL``, ``LC_MESSAGES``, and +``LANG``). Primarilly, these are ``roundup-admin`` script and +``admin.py`` module, but also help texts and startup error messages +in other scripts and their supporting modules. + +For these interfaces, Python ``gettext`` engine must be initialized +to use Roundup message catalogs. This is normally done by including +the following line in the module imports:: + + from i18n import _, ngettext + +Simple translations are automatically marked by calls to builtin +message translation function ``_()``:: + + print _("This message is translated") + +Translations for messages whose grammatical depends on a number +must be done by ``ngettext()`` function:: + + print ngettext("Nuked %i file", "Nuked %i files", number_of_files_nuked) + +Deferred Translations +~~~~~~~~~~~~~~~~~~~~~ + +Sometimes translatable strings appear in the source code in untranslated +form [#note_admin.py]_ and must be translated elsewhere. +Example:: + + for meal in ("spam", "egg", "beacon"): + print _(meal) + +In such cases, strings must be marked for translation without actual +call to the translating function. To mark these strings, we use Python +feature of automatic concatenation of adjacent strings and different +types of string quotes:: + + strings_to_translate = ( + ''"This string will be translated", + ""'me too', + ''r"\raw string", + ''""" + multiline string""" + ) + +.. [#note_admin.py] In current Roundup sources, this feature is + extensively used in the ``admin`` module using method docstrings + as help messages. + +Web User Interface +~~~~~~~~~~~~~~~~~~ + +For Web User Interface, translation services are provided by Client +object. Action classes have methods ``_()`` and ``gettext()``, +delegating translation to the Client instance. In HTML templates, +translator object is available as context variable ``i18n``. + +HTML templates have special markup for translatable strings. +The syntax for this markup is defined on `ZPTInternationalizationSupport`_ +page. Roundup translation service currently ignores values for +``i18n:domain``, ``i18n:source`` and ``i18n:target``. + +Template markup examples: + +* simplest case:: + +
+ Say + no + more! +
+ + this will result in msgid ``"Say no more!"``, with all leading and + trailing whitespace stripped, and inner blanks replaced with single + space character. + +* using variable slots:: + +
+ And now...
+ No.
+ THE LARCH +
+ + Msgid will be: ``"And now...
No.${slideNo}
THE LARCH"``. + Template rendering will use context variable ``number`` (you may use + any expression) to put instead of ``${slideNo}`` in translation. + +* attribute translation:: + +