
-
6941
at 2009-11-06 21:38:07
by sorry, but unknown...
Index: /branches/1.5-quickstart-refactor/CHANGELOG.txt
===================================================================
--- /branches/1.5-quickstart-refactor/CHANGELOG.txt (revision 6557)
+++ /branches/1.5-quickstart-refactor/CHANGELOG.txt (revision 6941)
@@ -60,4 +60,177 @@
Mark Ramm, Sean O'Donnell, Bill Zingler, Ken Kuhlman
+1.1.1 (?)
+---------
+
+Fixes
+~~~~~
+
+* When mapping classes with ``__init__` method using `database.session_mapper`,
+ you could get an `AttributeError` (#2386).
+* It was not possible to enter Null values for optional fields using Catwalk.
+ Some fixes were made to solve this, particularly for numeric fields (#760).
+
+Features
+~~~~~~~~
+
+* The `database.session_mapper` now takes an optional `set_kwargs_on_init`
+ parameter allowing you to suppress the automatic setting of keyword args
+ as attributes for instances of the mapped class (#2386).
+
+
+1.1 (October 4, 2009)
+----------------------
+
+Fixes
+~~~~~
+
+* Name error in `identity.SecureResource` when `require` attribute not set
+ (r6686).
+* Unit test for model bootstrap functions in standard quickstart template
+ should not run when identity model can not be imported (r6692).
+* When a visitor requested a page shortly before the `visit.timeout` and
+ then again shortly afterwards, he could sometimes be logged of at the
+ second request because the visit manager had not yet updated the database.
+ This has now been fixed by first looking up visits cached in memory (#2346).
+* Added 'save_on_init' as alias for 'autoadd' kwarg of database.session_mapper
+ for backward-compatibility and fixed a bug occuring when mapping a class a
+ second time with session_mapper (r6747).
+* Genshi now adds correct doctype declarations to HTML and XHTML. By default,
+ the 'strict' variant is used (#1963).
+* Genshi engine now correctly changes the encoding when configured to do
+ so (r6787).
+* Added tests for Genshi template engine integration and separated test for
+ Kid engine in `test_view.py` from engine-independant tests (r6788).
+
+Features
+~~~~~~~~
+
+* There is a new config setting `visit.interval` allowing you to change
+ the visit manager time interval for updating the visit table in the database.
+ The setting must be specified in seconds; the default value is 30 seconds
+ (r6751).
+* The `genshi.default_doctype` configuration setting can now be a dictionary
+ mapping format names to doctype names. For details see the examples in
+ `app.cfg` in the default quickstart templates (r6787).
+
+Changes
+~~~~~~~
+
+* Updated PyPI trove classifiers to give stable status and supported Python
+ versions (r6691).
+* PasteScript version requirement is now >= 1.7 for Python 2.6 compatibility
+ (r6691).
+* TurboCheetah is not installed by default anymore (but Cheetah dependency is
+ pulled by PasteScipt). To install TurboGears with TurboCheetah support, use
+ `easy_install TurboGears[compat]` (r6691).
+* The `turbogears.feed` package is deprecated and using `FeedControlller` will
+ issue a deprecation warning (r6695).
+* The config setting `"tg.defaultview"` now has the default value `"genshi"`.
+ This means that to use kid templates, one has to set `tg.defaultview = "kid"`
+ in the application's configuration or prefix template names with `"kid:"`
+ in the expose decorator (r6696).
+* A missing or faulty dburi raises a `DatabaseConfigurationError` exception
+ in most cases when trying to establish a database connection (r6714).
+* Default identity provider code cleanup and aligning. Changes to
+ `identity.*.encryption_algorithm` are now effective immediately (r6715).
+* Identity tests now use `SqlAlchemyVisitManager` (r6716).
+* Instantiating `SqlObjectVisitManager` fails correctly if visit data model
+ tables can not be created (r6717).
+* Loading the template engine options from the configuration was changed so
+ that all options whose first part of the option name (i.e. everything before
+ the first dot) matches the start of the tempalte plugin name are passed to
+ the template engine when it is instantiated in `view.load_engines`. This
+ means, for example, that the plugin named `"genshi"` will receive all
+ `genshi.*` settings and the `"genshi-markup"` plugin will recieve those
+ as well and all `genshi-markup.*` settings too (r6756).
+* `expose` and `view.render` now take arbitrary keywords arguments, which are
+ passed as keyword args to the `render` method of template plugins, which
+ may use them as options for this rendering process. This replaces the
+ `mapping` keyword arg previously supported by `expose` and `view.render` but
+ which was never passed to the template renderer (r6783).
+* The config setting ``genshi.encoding`` is now ``genshi.default_encoding``.
+ If you used this setting and it was set to something other than `"utf-8"`,
+ you need to change your configuration (r6787).
+* New provisional favicon in quickstart templates. This may be replaced again
+ in a 1.1 bugfix release (r6789).
+
+Contributors (in alphabetical order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Christopher Arndt, Christoph Zwerschke
+
+See also the list of contributors for version 1.1rc1.
+
+
+1.1rc1 (September 20, 2009):
+----------------------------
+
+Fixes
+~~~~~
+
+* Elixir quickstart model now supports transparent passwort encryption as well
+ (#2047).
+* Hardcode kid as template engine for feed templates (#2348).
+* Rename 'commands' module in quickstarted projects to 'command' to avoid
+ conflict with 'commands' module from the standard library (#2141).
+* Adaption of the autocompletefield CSS file for fix #1914.
+* .po header lost when merging a .pot including genshi templates (#2256).
+* Lots of test fixes & improvements (r6318, r6623, r6647, r6648, r6652, r6658).
+* SQLAlchemy forward compatibility fixes (r6576).
+* Projects with no identity support do not include superfluous code (e.g. in
+ model.py) and files (``login.css`` and ``login.html``) any more (#2049).
+* Identity failures during a request to a controller exposed with
+ `tg_format=json` yielded a 500 error instead of 403. Additionally, when
+ no applicable controller method is found, Turbogears now returns HTTP
+ status 404 (#2036).
+* `tg-admin update` did not detect when a project uses Elixir and would
+ overwrite the model with the standard SQLAlchemy based one (#2046).
+* A "i18n merge" took the last message of the same id, while "i18n compile"
+ took the last nonempty message. To make this consistent, "merge" now also
+ takes the last nonempty message. (As suggested by rejoc in ticket #2258).
+
+Features
+~~~~~~~~
+
+* Backported the TG2 templates to 1.1 (r6611).
+* turbogears.database now supports its own version of a session-aware mapper,
+ since the SQLAlchemy version is deprecated and will be removed in the future
+ (r6638).
+* Added NotAny identity predicate (#1343) and unit tests for predicates (r6311).
+* Improved Number validator by adding a decimals parameter (r6316).
+* Minor standard quickstart model improvements (r6617).
+* Added requirements for tests to quickstart setup file (r6622).
+* Instead of a plugin name you can now specify a class in dotted-path notation
+ for the ``visit.manager`` config setting to use a custom visit manager
+ (r6651, thanks to Nic Bellamy fro provding the idea in #2260).
+* Support for JSONification of SQLAlchemy identity model objects in json.py
+ of quickstarted projects plus tests (r6657).
+* Many small improvements to the standard quickstart template files to enhance
+ clarity and conformance to Python coding standards (r6655, r6657).
+* Identity can now initiate HTTP basic authentication. Two new config settings
+ `identity.http_basic_auth` and `identity.http_auth_realm` have been added for
+ this purpose and are explained in the standard quickstart `app.cfg` (r6665).
+* Quickstarted projects now have a unit test case for the bootstrap functions
+ in model.py working for SQLObject and SQLAlchemy based model (r6673).
+
+Changes
+~~~~~~~
+
+* SQLAlchemy version requirement is now >= 0.4.3 (r6638).
+* Elixir version requirement is now >= 0.6.1 (r6640).
+* Made TG 1.1 completely independent of Session.mapper, but still export it as
+ database.mapper if available (r6633).
+* TGTest now automatically resets turbogears.config after every test (r6568).
+* Default visit manager plugin is now "sqlalchemy" instead of "sqlobject"
+ (r6650).
+* Identity failures again will cause a HTTP status code 403 to be returned.
+ Status 401 is only returned when HTTP basic authentication is used (r6665).
+
+Contributors (in alphabetical order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Florent Aide, Christopher Arndt, Ken Kuhlman, Jonathan Schemoul,
+Christoph Zwerschke
+
1.1b3 (December 3, 2008):
@@ -70,4 +243,5 @@
* Added the .mak file extension to list of supported files for translation
in admi18n. (#1795)
+
1.1b2 (December 2, 2008):
@@ -164,5 +338,5 @@
(r5666).
* 'testutil' does not choke when trying to load test configuration from a
- bogus 'config' package (r5708).
+ bogus 'config' package (r5708).
* Fix 'tg-admin update' command, which was broken due to not passing
'elixirversion' to PasteScript (r5732).
@@ -212,4 +386,168 @@
Florent Aide, Christopher Arndt, Roger Demetrescu, Jorge Godoy, Paul Johnston,
Ken Kuhlman, Luke Macken, Diez R. Roggisch, Christoph Zwerschke
+
+
+1.0.9 (October 16, 2009):
+-------------------------
+
+Changes
+~~~~~~~
+
+* ``turbogears.validators`` supports the new ``formencode.national`` module.
+* Lists as return values of controllers are handled like generators again.
+* ``tg-admin i18n collect`` supports a new option ``-e|--js-encoding`` to
+ set the file encoding of the JavaScript source files (r6645).
+
+Fixes
+~~~~~
+
+* Fixed some problems with non-ascci user names and URLs (#1130 and #2118).
+* Static files were not found when project package was part of a larger
+ namespace package (#12).
+* Better error message if validator for option list couldn't be guessed (#978).
+* Fixed Modeldesigner exception (#1109).
+* Multi-level inheritance did not work in model designer (#1092).
+* The PackageHub for SQLObject now only establishes a connection/transaction
+ when really necessary (fixed tickets #763, #817 and #2160).
+* The tgsetup script failed when ``find_link`` was set in ``.pydistutils.cfg``
+ as a single string (#2098).
+* A form widget that is displayed without action parameter now produces an
+ empty action attribute instead of leaving it out which is invalid (#2292).
+* Wrapping the ``CatWalk`` controller with ``identity.SecureObject`` did not
+ protect catwalk's sub-controller ``Browser`` (#2207).
+* ``tg-admin sql help`` produced an error (#2361, thanks to Izhar Firdaus).
+* ``tg-admin i18n collect`` DecodingError when collecting strings from
+ JavaScript files with non-ascii characters (#2183).
+* Removed some obsolete code to handle deleting of related joins from CatWalk
+ (#2162, thanks , Daniel Fetchinson).
+* When a visitor requested a page shortly before the ``visit.timeout`` and
+ then again shortly afterwards, he could sometimes be logged of at the
+ second request because the visit manager had not yet updated the database.
+ This has now been fixed by first looking up visits cached in memory (#2346).
+* The Genshi Buffet interface is now correctly initialized with the
+ ``default_encoding`` setting, not ``encoding`` (see end of #1963).
+* It was not possible to enter Null values for optional fields using Catwalk.
+ Some fixes were made to solve this, particularly for numeric fields (#760).
+* Better error message in Catwalk if foreign key cannot be found (#1412).
+* Primary key strings did not work properly with Catwalk when updating (#1029).
+* Pagination did not work with SQLAlchemy >= 0.6.
+* We now only pass string values as hidden fields in the login form, skipping
+ possible file fields which cannot be passed anyway (#1761).
+* Fixed scheduler shutdown problem with method.sequential (#1702).
+
+Features
+~~~~~~~~
+
+* Added links to the home pages of the individual packages on the toolbox
+ info page (#1155).
+* TurboGears now makes internal use of the ``is_active`` property provided
+ by SQLAlchemy 0.4.9 and newer.
+* Compiled Kid templates are now added when building eggs (#1463).
+* Added default MANIFEST.in to quickstart template to ensure clean builds.
+* Allowed nested validation schemas to be defined by nesting schema class
+ definitions and allowed schema classes, not only instances, to be attached
+ to form widgets (#2393).
+
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Peter Russell, Christoph Zwerschke, Christopher Arndt
+
+
+1.0.8 (December 17, 2008):
+--------------------------
+
+Changes
+~~~~~~~
+
+* Charset parameters are now only added to content type headers when the
+ respective mime type supports this (e.g. all "text" types, but not "pdf").
+* Use "text/html" again instead of "application/xhtml+xml" for XHTML format,
+ as was done in version 1.0.7. Though it is more standards-compliant, IE
+ unfortunately does not cope with this content type, and we want to avoid
+ the problems of content negotiation (see ticket #1998).
+* Updated and fixed the 'tgsetup.py' script (#2053).
+
+Fixes
+~~~~~
+
+* Passing a list of arguments to the redirect method did not work anymore since
+ version 1.0.5 and is now working again (#2006).
+* Solved a small problem in the login controller of quickstarted projects since
+ version 1.0.5 that occurred when directly accessing a non-authorized resource.
+* ``tg-admin toolbox`` ignored the ``--config`` option and adopted problematic
+ config settings from the current project.
+* Fixed an issue with i18n string collection when using the lang option (#2012).
+* Made ``not_anonymous`` internally consistent with others predicates (#2029).
+* ``tg-admin sql`` did not work with SQLAlchemy versions before 0.4.3 (#2057).
+* Hidden fields are now always put in an invisible 'div' section since they
+ must be contained in a block-level element to be valid (X)HTML (#2052).
+* The id of widgets like datagrid could not be changed at render time (#2023).
+* Improved some error messages and warnings.
+
+Features
+~~~~~~~~
+
+* Made content type delivered for a template format configurable with the
+ ``tg.format_mime_types`` setting (see problem with XHTML content above).
+* TurboGears 1.0.8 now additionally includes MochiKit version 1.4.2, though
+ version 1.3.1 is still used by default. You can use the newer version by
+ setting ``tg.mochikit_version`` or ``tg_mochikit.version`` to '1.4' (#2018).
+* Forms have got a new ``use_name`` parameter that allows setting the id
+ attribute instead of the name attribute of the form, since the latter is
+ deprecated in HTML and invalid in XHTML (#2052).
+
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Florent Aide, Christopher Arndt, Christopher Gabriel, Eloi Notario, Peter Russel,
+George Sakkis, Christophe de Vienne, Matt Wilson, Christoph Zwerschke
+
+
+1.0.7 (September 15, 2008):
+---------------------------
+
+Changes
+~~~~~~~
+
+* The standard controller method handling identity.failure_url ("/login") now
+ returns the proper HTTP status code "401 Unauthorized" (instead of
+ "403 Forbidden") (#1787).
+* The required version of SQLObject was bumped up to 0.10.1 (#1765).
+* 'tgsetup.py' installation script does not set script-dir to "/usr/local/bin"
+ anymore on Unix-like systems (#1317).
+
+Fixes
+~~~~~
+
+* The Label widget now renders 'attrs' which had been missing from its template
+ (Andy Grover) (#1964).
+* The 'testutil.call_with_request' did not work with controller methods which
+ raise redirect (Felix Schwarz) (#1203).
+* "tg-admin i18n" and the adm18n toolbox tool now correctly strip whitespace
+ around strings collected from Kid templates so that they get translated
+ properly (#1695).
+* 'identity.get_failure_url' was not backported from 1.1 branch but used in
+ the identity providers (r5338).
+* XHTML templates were delivered with a content type of "text/html" while it
+ should be "application/xhtml+xml" (r5323, related to #1963).
+* CalendarPicker was positioned wrong when scrolled in IE7 (Nick Murdoch)
+ (#1875).
+* Make paginate work with tg.strict_parameters=False (#1889).
+* 'i18n.parse_decimal' broke when locale is unknown (#1104).
+* 'feed.FeedController.index' now passes keyword args when redirecting to the
+ feed handler methods (#1732).
+* 'config.config_defaults()' should now set 'package_dir' correctly on all
+ platforms (r5328).
+
+Features
+~~~~~~~~
+
+None
+
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Christopher Arndt, Christoph Zwerschke
@@ -254,6 +592,6 @@
* Fixed several problems with toolbox admi18n (#1399, #1941, #1943).
-Contributors (in alphabetical order)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Florent Aide, Christopher Arndt, Ken Kuhlman, Tore Lundqvist, Christoph Zwerschke.
@@ -472,6 +810,6 @@
-1.0.4b3: (December, 2, 2007):
------------------------------
+1.0.4b3: (December 2, 2007):
+----------------------------
Deprecations
@@ -488,13 +826,23 @@
the shipped MochiKit 1.3.1. That allows to include custom mochikit versions.
* PaginateDataGrid template now makes use of paginate attributes to render
- the links for first/previous/next/last page.
+ the links for first/previous/next/last page (#1617).
* ``paginate.href_last`` returns a special URL that allows paginate decorator
- to compute the correct last page number at server-side.
-
-Features
-~~~~~~~~
-
-* Introduction of tg.mochikit_suppress to prevent the inclusion of
- the shipped MochiKit 1.3.1. That allows to include custom mochikit versions.
+ to compute the correct last page number at server-side (#1617).
+* The ``start-<project>.py`` script in a quickstarted project is now only a
+ wrapper for the ``start()`` function in a new ``commands`` module in the
+ project's package. The ``setup.py`` in new project also creates a console
+ script entry point for this, so easy_install can create a start script
+ when the project's egg is installed. It also allows to package a default
+ configuration file in the egg. For details see ticket (#1386).
+* Installation of TurboGears now does not require installation of an ORM.
+ Instead, a project that relies on SQLObject or SQLAlchemy will have a
+ ``setup.py`` file written with the proper requirements (#1501, #1620).
+* Removed the hard SQLAlchemy/SQLObject dependecies and modified quickstart
+ to render the required ORM as requirement into a projects setup.py.
+
+Features
+~~~~~~~~
+
+* Introduction of tg.mochikit_suppress to (see "Changes").
* Workaround in paginate for databases without ``OFFSET`` (#1601).
* The database module exports a mapper which is either session.mapper
@@ -509,5 +857,5 @@
last page is requested, respectively.
* Paginate ``default_order`` can now be a string or a list of strings.
- The list of string is used to specify the ordering of multiple columns.
+ The list of strings is used to specify the ordering of multiple columns.
Every string starting with a dash (``-``) indicates that the column will
have its default ordering reversed (#1618).
@@ -530,4 +878,11 @@
* Fix pagination of out of bound pages (#1617).
* ``tg-admin i18n`` now supports Unicode strings in Kid templates (#1397).
+* Fixed testutil to properly use the soClasses attribute in the model
+ in order to pick up only the classes defined in this list and not
+ the rest. Thanks to Gregor Horvath for this suggestion. (#1586).
+* Fixed the command line interface to i18n collection. Command line now
+ processes also the templates (#1436).
+* Identity bug when using non-ASCII characters in the URL (#1598, #1407,
+ #1022).
Project Updates
@@ -538,12 +893,13 @@
* TurboKid 1.0.4
-Contributors
-~~~~~~~~~~~~
-
-Joel Pearson, Christoph Zwerschke, Roger Demetrescu, Juan Germano
-
-
-1.0.4b2 (October, 27, 2007):
-----------------------------
+Contributors (in alphabetic order)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Florent Aide, Christopher Arndt, Roger Demetrescu, Juan Germano, Joel Pearson,
+Diez B. Roggisch, Christoph Zwerschke.
+
+
+1.0.4b2 (October 27, 2007):
+---------------------------
Changes
@@ -713,5 +1069,5 @@
Simon Wittber, Christopher Arndt, Christoph Zwerschke, Paul Johnston, FredLin,
[[BINARY DATA]]
Florent Aide.
@@ -1197,5 +1553,5 @@
Max Ischenko, Claudio Martinez, Matt Good, Rune Hansen, Michele Cella,
[[BINARY DATA]]
Mark Ramm-Christensen, Ronald Jaramillo,
Richard Standbrook, Roger Demetrescu, Patrick Lewis, Hal Wine,
@@ -1332,5 +1688,5 @@
~~~~~~~~~~~~
[[BINARY DATA]]
Simon Belak, Jorge Godoy, Patrick Lewis, Jorge Vargas, Joost Moesker,
Joseph Tate, Philip Walls, Bob Ippolito, Steve Bergman, Andrey Lebedev,
@@ -1548,5 +1904,5 @@
This release comes to you thanks to the work of Michele Cella,
Elvelind Grandin, Ronald Jaramillo, Simon Belak, Jeff Watkins,
[[BINARY DATA]]
Dan Weeks, Dennis Brakhane, Heikichi Umahara, Patrick Lewis,
Joost Moesker, Roger Demetrescu, Liza Daly.
@@ -1710,8 +2066,8 @@
Joey Smith, David Bernard, Simon Davy, Gary Godfrey, Jason Chu,
Liza Daly, Ryan Forsythe, Jeremy Jones, Egor Cheshkov, Claudio Martinez,
[[BINARY DATA]]
Brian Bockelman, Stephen Thorne, Robin Bryce, Ksenia Marasanova,
Ori Avtalion, Martina Oefelein, Ian Bicking, Rick Richardson,
[[BINARY DATA]]
A special thanks to Cliff Wells of Develix for sponsoring a bug bounty!
Index: /branches/1.5-quickstart-refactor/test.cfg
===================================================================
--- /branches/1.5-quickstart-refactor/test.cfg (revision 6084)
+++ /branches/1.5-quickstart-refactor/test.cfg (revision 6941)
@@ -3,4 +3,6 @@
sqlobject.dburi = 'sqlite:///:memory:'
sqlalchemy.dburi = 'sqlite:///:memory:'
+
+log.screen = False
[logging]
@@ -22,2 +24,9 @@
handlers=['test_out']
+[[[cp_error]]]
+level="DEBUG"
+qualname="cherrypy.error"
+handlers=['test_out']
+propgate=0
+
+
Index: /branches/1.5-quickstart-refactor/turbogears/identity/conditions.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/conditions.py (revision 6737)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/conditions.py (revision 6941)
@@ -196,9 +196,7 @@
if not request_available():
raise RequestRequiredException()
- ip = request.headers.get('X-Forwarded-For',
- request.headers.get('Remote-Addr'))
- if ip:
- return ip.rsplit(',', 1)[-1].strip()
- return None
+ else:
+ return request.headers.get('X-Forwarded-For', request.headers.get(
+ 'Remote-Addr', '')).rsplit(',', 1)[-1].strip() or None
class from_host(Predicate, IdentityPredicateHelper):
Index: /branches/1.5-quickstart-refactor/turbogears/identity/base.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/base.py (revision 6710)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/base.py (revision 6941)
@@ -27,6 +27,9 @@
import logging
-import md5
-import sha
+try:
+ from hashlib import md5, sha1
+except ImportError:
+ from sha import new as sha1
+ from md5 import new as md5
import cherrypy
@@ -111,7 +114,7 @@
password_8bit = password
if algorithm == 'md5':
- hashed_password = md5.new(password_8bit).hexdigest()
+ hashed_password = md5(password_8bit).hexdigest()
elif algorithm == 'sha1':
- hashed_password = sha.new(password_8bit).hexdigest()
+ hashed_password = sha1(password_8bit).hexdigest()
elif algorithm == 'custom':
custom_encryption_path = turbogears.config.get(
@@ -211,7 +214,14 @@
"""
- if (cherrypy.response.status < '400'):
- new_status = cherrypy.request.wsgi_environ.get('identity_status', None)
+ if (str(cherrypy.response.status) < '400'):
+ new_status = cherrypy.request.wsgi_environ.get('identity_status')
if new_status:
cherrypy.response.status = new_status
-
+ auth_realm = cherrypy.request.wsgi_environ.get('identity_auth_realm')
+ if auth_realm:
+ cherrypy.response.headers['WWW-Authenticate'] = auth_realm
+ # During the finalize stage, CP stops looking at response.headers,
+ # so populate header_list as well.
+ if not cherrypy.response.header_list:
+ cherrypy.response.header_list = []
+ cherrypy.response.header_list.append(('WWW-Authenticate', auth_realm))
Index: /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_identity.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_identity.py (revision 6843)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_identity.py (revision 6941)
@@ -14,5 +14,7 @@
from turbogears.identity.conditions import _remoteHost
from turbogears.identity.soprovider import TG_User, TG_Group, TG_Permission
-from turbogears.identity.exceptions import RequestRequiredException
+from turbogears.identity.exceptions import (IdentityConfigurationException,
+ RequestRequiredException)
+from turbogears.identity.visitor import IdentityVisitPlugin
#hub = database.AutoConnectHub("sqlite:///:memory:")
@@ -139,4 +141,8 @@
return 'in_other_group'
+ @expose(format='json')
+ def json(self):
+ return dict(json="restricted_json")
+
class IdentityRoot(RootController):
@@ -146,6 +152,7 @@
return dict()
- @expose()
+ @expose(format='html')
def identity_failed(self, **kw):
+ """Identity failure - this usually returns a login form."""
return 'identity_failed_answer'
@@ -167,5 +174,4 @@
assert identity.current.group_ids == group_ids
assert "samIam" == cherrypy.serving.request.identity.user_name
-
return 'in_peon_group'
@@ -256,7 +262,13 @@
return 'is_anonymous'
+ @expose(format='json')
+ @identity.require(in_group('peon'))
+ def json(self):
+ return dict(json="restricted_json")
class TestIdentity(testutil.TGTest):
+
+ stop_tg_only = True
def setUp(self):
@@ -266,5 +278,9 @@
'visit.manager': 'sqlalchemy',
'identity.failure_url': '/identity_failed',
- 'identity.soprovider.encryption_algorithm': None}}
+ 'identity.soprovider.encryption_algorithm': None,
+ 'identity.http_basic_auth': False,
+ 'identity.http_auth_realm': None,
+ 'tg.strict_parameters': True,
+ 'tg.allow_json': False}}
original_config = dict()
for key in test_config['global']:
@@ -302,9 +318,10 @@
def test_user_password_parameters(self):
- """Controller can receive user_name and password parameters."""
+ """Test that controller can receive user_name and password parameters."""
response = self.app.get('/new_user_setup?user_name=test&password=pw')
assert 'test pw' in response, response
def test_user_exists(self):
+ """Test that test user is present in data base."""
u = TG_User.by_user_name('samIam')
assert u.email_address == 'samiam@example.com'
@@ -409,4 +426,5 @@
def test_user_password_raw_unicode(self):
+ """Test that unicode passwords are encrypted correctly"""
config.update({'identity.soprovider.encryption_algorithm':'sha1'})
self.app.get('/')
@@ -440,5 +458,5 @@
def test_deny_anonymous(self):
"""Test that we have secured an url from anonymous users."""
- response = self.app.get('/logged_in_only', status=401)
+ response = self.app.get('/logged_in_only', status=403)
assert 'identity_failed_answer' in response, response
@@ -450,16 +468,25 @@
assert 'logged_in_only' in response, response
+ def test_authenticate_header(self):
+ """Test that identity returns correct WWW-Authenticate header."""
+ response = self.app.get('/logged_in_only', status=403)
+ assert 'WWW-Authenticate' not in response.headers
+ config.update({'identity.http_basic_auth': True,
+ 'identity.http_auth_realm': 'test realm'})
+ response = self.app.get('/logged_in_only', status=401)
+ assert response.headers['WWW-Authenticate'] == 'Basic realm="test realm"'
+
def test_basic_authentication(self):
"""Test HTTP basic authentication mechanism."""
credentials = base64.encodestring('samIam')[:-1]
response = self.app.get('/logged_in_only', headers={
- 'Authorization': 'Basic %s' % credentials}, status=401)
+ 'Authorization': 'Basic %s' % credentials}, status=403)
assert 'identity_failed_answer' in response, response
credentials = base64.encodestring('samIam:secret:appendix')[:-1]
response = self.app.get('/logged_in_only', headers={
- 'Authorization': 'Basic %s' % credentials}, status=401)
+ 'Authorization': 'Basic %s' % credentials}, status=403)
assert 'identity_failed_answer' in response, response
response = self.app.get('/logged_in_only', headers={
- 'Authorization': 'Basic samIam:secret'}, status=401)
+ 'Authorization': 'Basic samIam:secret'}, status=403)
assert 'identity_failed_answer' in response, response
credentials = base64.encodestring('samIam:secret')[:-1]
@@ -480,5 +507,5 @@
"""Test that a anonymous user can not access resource protected by
require(in_group(...))"""
- response = self.app.get('/in_peon_group', status=401)
+ response = self.app.get('/in_peon_group', status=403)
assert 'identity_failed_answer' in response, response
@@ -508,5 +535,5 @@
"""
response = self.app.get('/in_admin_group?'
- 'user_name=samIam&password=secret&login=Login', status=401)
+ 'user_name=samIam&password=secret&login=Login', status=403)
assert 'identity_failed_answer' in response, response
@@ -515,5 +542,5 @@
restricted url.
"""
- response = self.app.get('/has_chopper_permission', status=401)
+ response = self.app.get('/has_chopper_permission', status=403)
assert 'identity_failed_answer' in response, response
@@ -529,5 +556,5 @@
"""
response = self.app.get('/has_boss_permission?'
- 'user_name=samIam&password=secret&login=Login', status=401)
+ 'user_name=samIam&password=secret&login=Login', status=403)
assert 'identity_failed_answer' in response, response
@@ -541,10 +568,10 @@
"""Test that we are denied access if we provide a bad login."""
response = self.app.get('/logged_in_only?'
- 'user_name=samIam&password=wrong&login=Login', status=401)
+ 'user_name=samIam&password=wrong&login=Login', status=403)
assert 'identity_failed_answer' in response, response
def test_restricted_subdirectory(self):
"""Test that we can restrict access to a whole subdirectory."""
- response = self.app.get('/peon_area/index', status=401)
+ response = self.app.get('/peon_area/index', status=403)
assert 'identity_failed_answer' in response, response
@@ -567,5 +594,5 @@
in a restricted subdirectory"""
response = self.app.get('/peon_area/in_admin_group?'
- 'user_name=samIam&password=secret&login=Login', status=401)
+ 'user_name=samIam&password=secret&login=Login', status=403)
assert 'identity_failed_answer' in response, response
@@ -581,5 +608,5 @@
directory is handled as expected"""
response = self.app.get('/peon_area/in_admin_group_explicit_check?'
- 'user_name=samIam&password=secret&login=Login', status=401)
+ 'user_name=samIam&password=secret&login=Login', status=403)
assert 'identity_failed' in response, response
@@ -589,5 +616,5 @@
params = urllib.quote(IdentityRoot._test_encoded_params.decode(
'utf-8').encode('latin-1'), '=&')
- response = self.app.get('/test_params?' + params, status=401)
+ response = self.app.get('/test_params?' + params, status=403)
assert 'identity_failed_answer' in response, response
@@ -614,8 +641,33 @@
assert 'logged_in_only' in response, response
+ def test_json(self):
+ """Test that JSON controllers return the right status codes."""
+ # We check that we get an authorization error, not the server error
+ # caused by the identity_failure controller not accepting JSON.
+ response = self.app.get('/json', status=403)
+ # we get the output of the identity_failure controller in this case
+ assert 'identity_failed_answer' in response, response
+ response = self.app.get('/json?tg_format=json', status=403)
+ # we get the right status code, but not the output of identity_failure
+ assert 'identity_failed_answer' not in response, response
+ assert 'Forbidden' in response, response
+ response = self.app.get('/peon_area/json', status=403)
+ assert 'identity_failed_answer' in response, response
+ response = self.app.get('/peon_area/json?tg_format=json', status=403)
+ assert 'identity_failed_answer' not in response, response
+ response = self.app.get('/in_peon_group?'
+ 'user_name=samIam&password=secret&login=Login')
+ assert 'in_peon_group' in response, response
+ response = self.app.get('/json', status=200)
+ assert 'restricted_json' in response, response
+ response = self.app.get('/json?tg_format=json', status=200)
+ assert 'restricted_json' in response, response
+ response = self.app.get('/peon_area/json', status=200)
+ assert 'restricted_json' in response, response
+ response = self.app.get('/peon_area/json?tg_format=json', status=200)
+ assert 'restricted_json' in response, response
def test_remote_ip(self):
"""Test that our client IP is detected correctly."""
- r = self.app.get('/remote_ip', status=200)
r = self.app.get('/remote_ip', headers={'Remote-Addr': '127.0.0.1'},
status=200)
@@ -629,5 +681,5 @@
"""Test we can connect from 127.0.0.1."""
r = self.app.get('/from_localhost', headers={'Remote-Addr': '192.168.4.100'},
- status=401)
+ status=403)
assert 'identity_failed_answer' in r
r = self.app.get('/from_localhost', headers={'Remote-Addr': '127.0.0.1'},
@@ -638,5 +690,5 @@
"""Test we can connect from any host in an IP list."""
r = self.app.get('/from_any_host', headers={'Remote-Addr': '192.168.4.100'},
- status=401)
+ status=403)
assert 'identity_failed_answer' in r
r = self.app.get('/from_any_host', headers={'Remote-Addr': '127.0.0.1'},
@@ -650,4 +702,5 @@
class TestTGUser(testutil.DBTest):
model = TG_User
+ stop_tg_only = True
def setUp(self):
@@ -655,5 +708,4 @@
config.update({'identity.on': False})
super(TestTGUser, self).setUp()
- testutil.start_server()
def tearDown(self):
@@ -663,7 +715,41 @@
def test_create_user(self):
- """Check that User can be created outside of a running identity provider."""
+ """Test that User can be created outside of a running identity provider."""
u = TG_User(user_name='testcase',
email_address='testcase@example.com',
display_name='Test Me', password='test')
assert u.password=='test', u.password
+
+
+class TestIdentityVisitPlugin(unittest.TestCase):
+ def setUp(self):
+ test_config = {'global': {
+ 'tools.visit.on': True,
+ 'identity.on': True,
+ 'identity.source': 'form, http_auth, visit, '}}
+ original_config = dict()
+ for key in test_config['global']:
+ original_config[key] = config.get(key)
+ self._original_config = original_config
+ config.update(test_config['global'])
+
+ def tearDown(self):
+ config.update(self._original_config)
+
+ def test_identity_source(self):
+ """Test that identity.source setting is parsed correctly."""
+ plug = IdentityVisitPlugin()
+ assert plug.identity_from_form in plug.identity_sources
+ assert plug.identity_from_http_auth in plug.identity_sources
+ assert plug.identity_from_visit in plug.identity_sources
+
+ def test_unknown_identity_source(self):
+ """Test that unknown identity source raises identity configuration error."""
+ config.update({'identity.source': 'bogus'})
+ self.assertRaises(IdentityConfigurationException, IdentityVisitPlugin)
+
+ def test_decode_credentials(self):
+ """Test that HTTP basic auth credentials are decoded correctly."""
+ plug = IdentityVisitPlugin()
+ credentials = base64.encodestring('samIam:secret')[:-1]
+ assert plug.decode_basic_credentials(credentials) == ['samIam', 'secret']
Index: /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_visit.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_visit.py (revision 6745)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/tests/test_visit.py (revision 6941)
@@ -13,5 +13,4 @@
"""Returns a dict containing cookie information to pass to a server."""
return dict(Cookie=response.headers['Set-Cookie'])
-
def setup_module():
Index: /branches/1.5-quickstart-refactor/turbogears/identity/visitor.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/visitor.py (revision 6704)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/visitor.py (revision 6941)
@@ -70,7 +70,9 @@
# checked. These terms are mapped to methods by prepending
# "identity_from_".
- sources = get('identity.source', 'form,http_auth,visit').split(',')
+ sources = [s.strip() for s in
+ get('identity.source', 'form,http_auth,visit').split(',')]
self.identity_sources = list()
for s in sources:
+ if not s: continue
try:
source_method = getattr(self, 'identity_from_' + s)
@@ -189,3 +191,2 @@
set_current_identity(identity)
set_current_provider(self.provider)
-
Index: /branches/1.5-quickstart-refactor/turbogears/identity/exceptions.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/identity/exceptions.py (revision 5539)
+++ /branches/1.5-quickstart-refactor/turbogears/identity/exceptions.py (revision 6941)
@@ -16,4 +16,5 @@
import turbogears
+from turbogears import config
def set_identity_errors(errors):
@@ -71,9 +72,7 @@
args = ()
- def __init__(self, message):
- self.message = message
-
def __str__(self):
- return self.message
+ return (self.args and self.args[0] or
+ 'Unknown Identity configuration error')
@@ -92,7 +91,13 @@
# that a simple GET to /login receives a 401 http code.
- # This needs to be saved into the wsgi environment because
- # request & reposne are reset when we redirect.
- cherrypy.request.wsgi_environ['identity_status'] = '401 Unauthorized'
+ if config.get('identity.http_basic_auth', False):
+ # This needs to be saved into the wsgi environment because
+ # request & reposne are reset when we redirect.
+ cherrypy.request.wsgi_environ['identity_status'] = '401 Unauthorized'
+ cherrypy.request.wsgi_environ['identity_auth_realm'] = \
+ 'Basic realm="%s"' % config.get('identity.http_auth_realm',
+ 'TurboGears')
+ else:
+ cherrypy.request.wsgi_environ['identity_status'] = '403 Forbidden'
if turbogears.config.get('identity.force_external_redirect', False):
@@ -106,3 +111,5 @@
else:
# use internal redirect which is quicker
+ if cherrypy.request.query_string:
+ url += '?' + cherrypy.request.query_string
cherrypy.InternalRedirect.__init__(self, url)
Index: /branches/1.5-quickstart-refactor/turbogears/config.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/config.py (revision 6880)
+++ /branches/1.5-quickstart-refactor/turbogears/config.py (revision 6941)
@@ -247,5 +247,6 @@
server[key] = value
- if key in ['visit.on', 'toscawidgets.on']:
+ if key in ['visit.on', 'toscawidgets.on'] and \
+ 'tools.%s' % key not in server:
warnings.warn("Config key %s is deprecated. "
"Use tools.%s instead." % (key, key),
Index: /branches/1.5-quickstart-refactor/turbogears/controllers.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/controllers.py (revision 6903)
+++ /branches/1.5-quickstart-refactor/turbogears/controllers.py (revision 6941)
@@ -226,6 +226,6 @@
ruleparts = ['kw.get("tg_format", "default") == "%s"' % as_format]
if accept_format:
- ruleparts.append('(accept == "%s" and kw.get("tg_format"'
- ', "default") == "default")' % accept_format)
+ ruleparts.append('(accept == "%s" and '
+ 'kw.get("tg_format", "default") == "default")' % accept_format)
rule = " or ".join(ruleparts)
log.debug("Generated rule %s", rule)
@@ -240,9 +240,8 @@
if func._allow_json:
- log.debug("Adding allow_json rule: "
- 'allow_json and (kw.get("tg_format", None) == "json"'
+ rule = ('allow_json and (kw.get("tg_format", None) == "json"'
' or accept in ("application/json", "text/javascript"))')
- first(_expose, 'allow_json and (kw.get("tg_format", None) == "json"'
- ' or accept in ("application/json", "text/javascript"))')(
+ log.debug("Adding allow_json rule for %s: %s", func, rule)
+ first(_expose, rule)(
lambda _func, accept, allow_json, *args, **kw:
_execute_func(_func, "json", "json", "application/json",
@@ -363,7 +362,7 @@
# error unless a specific error status was already set
# (e.g. "unauthorized" was set by the identity provider):
- status = cherrypy.response.status
- if status and status // 100 == 4:
- raise cherrypy.HTTPError(status)
+ status = cherrypy.request.wsgi_environ.get('identity_status')
+ if status and str(status) >= '400':
+ raise cherrypy.HTTPError(*str(status).split(None, 1))
raise cherrypy.NotFound
# If the error was raised elsewhere inside the controller,
@@ -440,5 +439,5 @@
if template and template.startswith("."):
- template = func.__module__[:func.__module__.rfind('.')]+template
+ template = func.__module__[:func.__module__.rfind('.')] + template
return _process_output(output, template, format, content_type,
@@ -522,5 +521,4 @@
except AttributeError:
username = "-"
-
request_date = time.strftime('%d/%b/%Y:%H:%M:%S +0000',time.gmtime())
request_info = {
Index: /branches/1.5-quickstart-refactor/turbogears/visit/api.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/visit/api.py (revision 6866)
+++ /branches/1.5-quickstart-refactor/turbogears/visit/api.py (revision 6941)
@@ -14,6 +14,10 @@
]
+
import logging
-import sha
+try:
+ from hashlib import sha1
+except ImportError:
+ from sha import new as sha1
import threading
import time
@@ -83,4 +87,5 @@
return
+ log.info("Visit Tracking starting.")
# How long may the visit be idle before a new visit ID is assigned?
# The default is 20 minutes.
@@ -121,8 +126,9 @@
class VisitTool(object):
- """A filter that automatically tracks visitors."""
+ """A tool that automatically tracks visitors."""
def __init__(self):
- log.info("Visit filter initialised.")
+ log.info("Visit tool initialised.")
+ get = config.get
def __call__(self, **kw):
@@ -153,4 +159,5 @@
self.cookie_max_age = get("cookie.permanent",
False) and int(get("timeout", "20")) * 60 or None
+ log.info("Visit filter initialised")
cpreq = cherrypy.request
@@ -200,5 +207,5 @@
key_string = '%s%s%s%s' % (random(), datetime.now(),
cherrypy.request.remote.ip, cherrypy.request.remote.port)
- return sha.new(key_string).hexdigest()
+ return sha1(key_string).hexdigest()
_generate_key = staticmethod(_generate_key)
Index: /branches/1.5-quickstart-refactor/turbogears/visit/sovisit.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/visit/sovisit.py (revision 6856)
+++ /branches/1.5-quickstart-refactor/turbogears/visit/sovisit.py (revision 6941)
@@ -27,5 +27,5 @@
visit_class = load_class(visit_class_path)
if visit_class:
- log.info("Succesfully loaded \"%s\"" % visit_class_path)
+ log.info('Succesfully loaded "%s"', visit_class_path)
# base-class' __init__ triggers self.create_model, so mappers need to
# be initialized before.
@@ -33,19 +33,14 @@
def create_model(self):
- try:
- # Create the Visit table if it doesn't already exist
- hub.begin()
- visit_class.createTable(ifNotExists=True)
- hub.commit()
- hub.end()
- except KeyError:
- # No database configured...
- log.info("No database is configured: Visit Tracking is disabled.")
- return
+ hub.begin()
+ visit_class.createTable(ifNotExists=True)
+ hub.commit()
+ hub.end()
+ log.debug("Visit model database table(s) created.")
def new_visit_with_key(self, visit_key):
hub.begin()
visit = visit_class(visit_key=visit_key,
- expiry=datetime.now()+self.timeout)
+ expiry = datetime.now() + self.timeout)
hub.commit()
hub.end()
@@ -69,5 +64,5 @@
return None
# Visit hasn't expired, extend it
- self.update_visit(visit_key, now+self.timeout)
+ self.update_visit(visit_key, now + self.timeout)
return Visit(visit_key, False)
Index: /branches/1.5-quickstart-refactor/turbogears/visit/savisit.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/visit/savisit.py (revision 6856)
+++ /branches/1.5-quickstart-refactor/turbogears/visit/savisit.py (revision 6941)
@@ -9,5 +9,5 @@
from turbogears import config
-from turbogears.database import get_engine, bind_metadata, metadata, session
+from turbogears.database import bind_metadata, metadata, session
from turbogears.util import load_class
from turbogears.visit.api import BaseVisitManager, Visit
@@ -30,8 +30,9 @@
msg += ', did you run setup.py develop?'
log.error(msg)
+ else:
+ log.info('Succesfully loaded "%s"', visit_class_path)
- bind_metadata()
if visit_class is TG_Visit:
- # Do not try to map TG_Visit if already done
+ # Handle it gracefully when TG_Visit is already mapped.
# May happen, when the visit manager is shutdown and started again
try:
@@ -47,4 +48,5 @@
bind_metadata()
class_mapper(visit_class).local_table.create(checkfirst=True)
+ log.debug("Visit model database table(s) created.")
def new_visit_with_key(self, visit_key):
Index: /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/model.py_tmpl
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/model.py_tmpl (revision 6882)
+++ /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/model.py_tmpl (revision 6941)
@@ -477,5 +477,5 @@
@property
def permissions(self):
- """Return all permissions os all groups the user belongs to."""
+ """Return all permissions of all groups the user belongs to."""
p = set()
for g in self.groups:
Index: /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/config/app.cfg_tmpl
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/config/app.cfg_tmpl (revision 6914)
+++ /branches/1.5-quickstart-refactor/turbogears/qstemplates/tgapp/+package+/config/app.cfg_tmpl (revision 6941)
@@ -185,7 +185,19 @@
identity.on = True
+# Should the server request the client to provide credentials via HTTP basic
+# authentication? This is off by default, since identity identity uses
+# form/cookie-based authentication.
+# identity.http_basic_auth = False
+
+# The authentication realm to use for HTTP authentication
+identity.http_realm = "${project}"
+
# [REQUIRED] URL to which CherryPy will internally redirect when an access
# control check fails. If Identity management is turned on, a value for this
# option must be specified.
+# If you use HTTP basic auth and you don't want to allow form-based login
+# you should set this to the URL of a controller which just returns an
+# "Authorization required" error message. You probably also want to change
+# the 'identity.source' setting below.
identity.failure_url = "/login"
@@ -212,5 +224,9 @@
# What sources should the identity provider consider when determining the
# identity associated with a request? Comma separated list of identity sources.
-# Valid sources: form, visit, http_auth
+# Valid sources:
+# form = User and password supplied as GET or POST request parameters
+# visit = Session cookie
+# http_auth = User and password supplied via HTTP Authorization header.
+# Only HTTP Basic Authentication is currently supported.
# identity.source = "form,http_auth,visit"
Index: /branches/1.5-quickstart-refactor/turbogears/tests/test_controllers.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/tests/test_controllers.py (revision 6842)
+++ /branches/1.5-quickstart-refactor/turbogears/tests/test_controllers.py (revision 6941)
@@ -7,4 +7,11 @@
error_handler, exception_handler, expose, flash, redirect, startup,
testutil, url, util, validate, validators)
+
+
+def setup_module():
+ testutil.start_server()
+
+def teardown_module():
+ testutil.stop_server()
@@ -282,4 +289,5 @@
class TestRoot(testutil.TGTest):
+ stop_tg_only = True
def setUp(self):
@@ -634,4 +642,5 @@
class TestURLs(testutil.TGTest):
+ stop_tg_only = True
def setUp(self):
@@ -757,11 +766,5 @@
class TestAbsoluteURLs(testutil.TGTest):
root = MyRoot
-
- #def setUp(self):
- # super(TestAbsoluteURLs, self).setUp()
- #
- #def tearDown(self):
- # super(TestAbsoluteURLs, self).tearDown()
- # config.update({"server.webpath": ""})
+ stop_tg_only = True
def _config_update(self, data=None, **kw):
@@ -869,5 +872,4 @@
url = self.app.get('/absolute_url/?url=%2Ffoo',
headers={'Host': ''}).body
- print "CONFIG:", self.config_backup
self._config_reset()
print url
Index: /branches/1.5-quickstart-refactor/turbogears/tests/test_util.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/tests/test_util.py (revision 6848)
+++ /branches/1.5-quickstart-refactor/turbogears/tests/test_util.py (revision 6941)
@@ -214,35 +214,9 @@
-def test_get_template_encoding_default():
- assert util.get_template_encoding_default() == 'utf-8'
-
-
-def test_get_mime_type_for_format():
- assert util.get_mime_type_for_format('plain') == 'text/plain'
- assert util.get_mime_type_for_format('text') == 'text/plain'
- assert util.get_mime_type_for_format('html') == 'text/html'
- assert util.get_mime_type_for_format('xhtml') == 'text/html'
- assert util.get_mime_type_for_format('xml') == 'text/xml'
- assert util.get_mime_type_for_format('json') == 'application/json'
- assert util.get_mime_type_for_format('foo') == 'text/html'
- config.update({'global': {'tg.format_mime_types':
- {'xhtml': 'application/xhtml+xml', 'foo': 'text/bar'}}})
- assert util.get_mime_type_for_format('xhtml') == 'application/xhtml+xml'
- assert util.get_mime_type_for_format('json') == 'application/json'
- assert util.get_mime_type_for_format('foo') == 'text/bar'
- config.update({'global': {'tg.format_mime_types': {}}})
- assert util.get_mime_type_for_format('xhtml') == 'text/html'
- assert util.get_mime_type_for_format('foo') == 'text/html'
-
-
-def test_mime_type_has_charset():
- assert not util.mime_type_has_charset(None)
- assert not util.mime_type_has_charset('foo/bar')
- assert util.mime_type_has_charset('text/foobar')
- assert util.mime_type_has_charset('application/xml')
- assert util.mime_type_has_charset('application/foo+xml')
- assert util.mime_type_has_charset('application/javascript')
- assert not util.mime_type_has_charset('application/foobar')
- assert not util.mime_type_has_charset('application/json')
+def test_unquote_cookie():
+ assert util.unquote_cookie ('Hello%2C%20W\xf6rld!') \
+ == 'Hello, W\xf6rld!'
+ assert util.unquote_cookie ('%241%3B%09insert%20coin!') \
+ == '$1;\tinsert coin!'
Index: /branches/1.5-quickstart-refactor/turbogears/tests/test_session_mapper.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/tests/test_session_mapper.py (revision 6804)
+++ /branches/1.5-quickstart-refactor/turbogears/tests/test_session_mapper.py (revision 6941)
@@ -1,2 +1,4 @@
+"""Unit tests for turbogears.database.session_mapper."""
+
import unittest
@@ -7,4 +9,5 @@
except ImportError: # SQLAlchemy < 0.5
from sqlalchemy.exceptions import ArgumentError
+
from turbogears import config
Index: /branches/1.5-quickstart-refactor/turbogears/tests/test_paginate.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/tests/test_paginate.py (revision 6849)
+++ /branches/1.5-quickstart-refactor/turbogears/tests/test_paginate.py (revision 6941)
@@ -1055,4 +1055,5 @@
_so_dburi = config.get("sqlobject.dburi", "sqlite:///:memory:")
_sa_dburi = config.get("sqlalchemy.dburi", "sqlite:///:memory:")
+ # sqlalchemy setup
database.set_db_uri("sqlite:///:memory:", 'sqlalchemy')
sqlalchemy_cleanup()
Index: /branches/1.5-quickstart-refactor/turbogears/testutil.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/testutil.py (revision 6555)
+++ /branches/1.5-quickstart-refactor/turbogears/testutil.py (revision 6941)
@@ -109,5 +109,5 @@
def stop_server(tg_only = False):
"""Stop the server and unmount the application.
-
+
Use tg_only = True to leave CherryPy running (for faster tests).
Index: /branches/1.5-quickstart-refactor/turbogears/widgets/tests/test_nested_widgets.py
===================================================================
--- /branches/1.5-quickstart-refactor/turbogears/widgets/tests/test_nested_widgets.py (revision 6887)
+++ /branches/1.5-quickstart-refactor/turbogears/widgets/tests/test_nested_widgets.py (revision 6941)
@@ -330,4 +330,5 @@
+
class InnerSchema(validators.Schema):
ignore_key_missing = True