Merge branch 'master' of github.com:Pylons/pyramid
2 files added
34 files modified
| | |
| | | Features |
| | | -------- |
| | | |
| | | - Add support for language fallbacks: when trying to translate for a |
| | | specific territory (such as ``en_GB``) fall back to translations |
| | | for the language (ie ``en``). This brings the translation behaviour in line |
| | | with GNU gettext and fixes partially translated texts when using C |
| | | extensions. |
| | | |
| | | - New authentication policy: |
| | | ``pyramid.authentication.SessionAuthenticationPolicy``, which uses a session |
| | | to store credentials. |
| | |
| | | - Philip Jenvey, 2011/04/24 |
| | | |
| | | - Michael Merickel, 2011/5/25 |
| | | |
| | | - Christoph Zwerschke, 2011/06/07 |
| | |
| | | for action in resolveConflicts(self.actions): |
| | | File "zope/configuration/config.py", line 1507, in resolveConflicts |
| | | raise ConfigurationConflictError(conflicts) |
| | | zope.configuration.config.ConfigurationConflictError: |
| | | zope.configuration.config.ConfigurationConflictError: |
| | | Conflicting configuration actions |
| | | For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>, |
| | | For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>, |
| | | None, None, None, None, None, False, None, None, None) |
| | | ('app.py', 14, '<module>', 'config.add_view(hello_world)') |
| | | ('app.py', 17, '<module>', 'config.add_view(hello_world)') |
| | |
| | | :meth:`~pyramid.config.Configurator.add_route`, |
| | | :meth:`~pyramid.config.Configurator.add_renderer`, |
| | | :meth:`~pyramid.config.Configurator.set_request_factory`, |
| | | :meth:`~pyramid.config.Configurator.set_renderer_globals_factory` |
| | | :meth:`~pyramid.config.Configurator.set_renderer_globals_factory`, |
| | | :meth:`~pyramid.config.Configurator.set_locale_negotiator` and |
| | | :meth:`~pyramid.config.Configurator.set_default_permission`. |
| | | |
| | |
| | | |
| | | if __name__ == '__main__': |
| | | config = Configurator() |
| | | config.add_directive('add_newrequest_subscriber', |
| | | config.add_directive('add_newrequest_subscriber', |
| | | add_newrequest_subscriber) |
| | | |
| | | Once :meth:`~pyramid.config.Configurator.add_directive` is called, a user can |
| | |
| | | :linenos: |
| | | |
| | | def includeme(config) |
| | | config.add_directive('add_newrequest_subscriber', |
| | | config.add_directive('add_newrequest_subscriber', |
| | | add_newrequest_subscriber) |
| | | |
| | | The user of the add-on package ``pyramid_subscriberhelpers`` would then be |
| | |
| | | single: environment variables |
| | | single: ini file settings |
| | | single: PasteDeploy settings |
| | | |
| | | |
| | | .. _environment_chapter: |
| | | |
| | | Environment Variables and ``.ini`` File Settings |
| | |
| | | | ``PYRAMID_DEBUG_AUTHORIZATION`` | ``debug_authorization`` | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | +---------------------------------+-----------------------------+ |
| | | |
| | | Debugging Not Found Errors |
| | |
| | | Mako Import |
| | | +++++++++++ |
| | | |
| | | String list of Python statements, typically individual “import” lines, which |
| | | String list of Python statements, typically individual "import" lines, which |
| | | will be placed into the module level preamble of all generated Python modules. |
| | | |
| | | |
| | |
| | | ``reload_templates``. |
| | | |
| | | If you want to turn all ``reload`` settings (every setting that starts |
| | | with ``reload_``). on in one fell swoop, you can use |
| | | with ``reload_``) on in one fell swoop, you can use |
| | | ``PYRAMID_RELOAD_ALL=1`` as an environment variable setting or you may use |
| | | ``reload_all=true`` in the config file. Note that this does not |
| | | affect settings that do not start with ``reload_*`` such as |
| | |
| | | most useful during development, where you may wish to augment or |
| | | override the more permanent settings in the configuration file. |
| | | This is useful because many of the reload and debug settings may |
| | | have performance or security (i.e., disclosure) implications |
| | | have performance or security (i.e., disclosure) implications |
| | | that make them undesirable in a production environment. |
| | | |
| | | .. index:: |
| | | .. index:: |
| | | single: reload_templates |
| | | single: reload_assets |
| | | |
| | |
| | | registry = pyramid.threadlocal.get_current_registry() |
| | | settings = registry.settings |
| | | debug_frobnosticator = settings['debug_frobnosticator'] |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | |
| | | method. Assets are files that are |
| | | accessed by :app:`Pyramid` using the :term:`pkg_resources` API such as static |
| | | files and templates via a :term:`asset specification`. Other directives and |
| | | configurator methods also deal in routes, views, and assets. For example, |
| | | configurator methods also deal in routes, views, and assets. For example, the |
| | | ``add_handler`` directive of the ``pyramid_handlers`` package adds a single |
| | | route, and some number of views. |
| | | |
| | |
| | | if __name__ == '__main__': |
| | | config.scan('someotherpackage') |
| | | config.commit() |
| | | config.add_view('mypackage.views.myview', name='myview' |
| | | config.add_view('mypackage.views.myview', name='myview') |
| | | |
| | | Once this is done, you should be able to extend or override the application |
| | | like any other (see :ref:`extending_the_application`). |
| | |
| | | application (e.g. ``python setup.py develop`` or ``python setup.py |
| | | install``). |
| | | |
| | | - Change the ``main`` function in the new package's ``__init__py`` to include |
| | | - Change the ``main`` function in the new package's ``__init__.py`` to include |
| | | the original :app:`Pyramid` application's configuration functions via |
| | | :meth:`pyramid.config.Configurator.include` statements or a :term:`scan`. |
| | | |
| | |
| | | the view which generates it can be overridden as necessary. |
| | | |
| | | The :term:`forbidden view` callable is a view callable like any other. The |
| | | :term:`view configuration` which causes it to be a "not found" view consists |
| | | :term:`view configuration` which causes it to be a "forbidden" view consists |
| | | only of naming the :exc:`pyramid.exceptions.Forbidden` class as the |
| | | ``context`` of the view configuration. |
| | | |
| | |
| | | ----------------------- |
| | | |
| | | Whenever :app:`Pyramid` handles a request to perform a rendering (after a |
| | | view with a ``renderer=`` configuration attribute is invoked, or when the any |
| | | view with a ``renderer=`` configuration attribute is invoked, or when any |
| | | of the methods beginning with ``render`` within the :mod:`pyramid.renderers` |
| | | module are called), *renderer globals* can be injected into the *system* |
| | | values sent to the renderer. By default, no renderer globals are injected, |
| | |
| | | :linenos: |
| | | |
| | | def renderer_globals_factory(system): |
| | | return {'a':1} |
| | | return {'a': 1} |
| | | |
| | | config = Configurator( |
| | | renderer_globals_factory=renderer_globals_factory) |
| | |
| | | from pyramid.config import Configurator |
| | | |
| | | def renderer_globals_factory(system): |
| | | return {'a':1} |
| | | return {'a': 1} |
| | | |
| | | config = Configurator() |
| | | config.set_renderer_globals_factory(renderer_globals_factory) |
| | |
| | | ----------------------------- |
| | | |
| | | Subscribers to the :class:`pyramid.events.BeforeRender` event may introspect |
| | | the and modify the set of :term:`renderer globals` before they are passed to |
| | | a :term:`renderer`. This event object iself has a dictionary-like interface |
| | | and modify the set of :term:`renderer globals` before they are passed to a |
| | | :term:`renderer`. This event object iself has a dictionary-like interface |
| | | that can be used for this purpose. For example: |
| | | |
| | | .. code-block:: python |
| | |
| | | from myapp.traversal import URLGenerator |
| | | from myapp.resources import MyRoot |
| | | |
| | | config.registry.registerAdapter(URLGenerator, (MyRoot, Interface), |
| | | config.registry.registerAdapter(URLGenerator, (MyRoot, Interface), |
| | | IContextURL) |
| | | |
| | | In the above example, the ``myapp.traversal.URLGenerator`` class will be used |
| | |
| | | |
| | | The default calling conventions for view callables are documented in the |
| | | :ref:`views_chapter` chapter. You can change the way users define view |
| | | callbles by employing a :term:`view mapper`. |
| | | callables by employing a :term:`view mapper`. |
| | | |
| | | A view mapper is an object that accepts a set of keyword arguments and which |
| | | returns a callable. The returned callable is called with the :term:`view |
| | |
| | | :linenos: |
| | | |
| | | import venusian |
| | | from pyramid.threadlocal import get_current_registry |
| | | from mypackage.interfaces import IMyUtility |
| | | |
| | | |
| | | class registerFunction(object): |
| | | |
| | | |
| | | def __init__(self, path): |
| | | self.path = path |
| | | |
| | | def register(self, scanner, name, wrapped): |
| | | registry = scanner.config.registry |
| | | registry.getUtility(IMyUtility).register( |
| | | self.path, wrapped |
| | | ) |
| | | |
| | | self.path, wrapped) |
| | | |
| | | def __call__(self, wrapped): |
| | | venusian.attach(wrapped, self.register) |
| | | return wrapped |
| | | |
| | | |
| | | This decorator could then be used to register functions throughout |
| | | your code: |
| | | |
| | |
| | | |
| | | from paste.httpserver import serve |
| | | from pyramid.config import Configurator |
| | | from mypackage.interfaces import IMyUtility |
| | | |
| | | class UtilityImplementation: |
| | | |
| | | implements(ISomething) |
| | | implements(IMyUtility) |
| | | |
| | | def __init__(self): |
| | | self.registrations = {} |
| | | |
| | | def register(self,path,callable_): |
| | | self.registrations[path]=callable_ |
| | | def register(self, path, callable_): |
| | | self.registrations[path] = callable_ |
| | | |
| | | if __name__ == '__main__': |
| | | config = Configurator() |
| | |
| | | :linenos: |
| | | |
| | | from pyramid.i18n import TranslationString |
| | | ts = TranslationString('Add ${number}', mapping={'number':1}, |
| | | ts = TranslationString('Add ${number}', mapping={'number':1}, |
| | | domain='form') |
| | | |
| | | The above translation string named a domain of ``form``. A |
| | |
| | | :linenos: |
| | | |
| | | from pyramid.i18n import TranslationString as _ |
| | | ts = _('Add ${number}', msgid='add-number', mapping={'number':1}, |
| | | ts = _('Add ${number}', msgid='add-number', mapping={'number':1}, |
| | | domain='pyramid') |
| | | |
| | | You can set up your own translation string factory much like the one |
| | |
| | | |
| | | A ``.pot`` file is created by a program which searches through your |
| | | project's source code and which picks out every :term:`message |
| | | identifier` passed to one of the '``_()`` functions |
| | | identifier` passed to one of the ``_()`` functions |
| | | (eg. :term:`translation string` constructions). The list of all |
| | | message identifiers is placed into a ``.pot`` file, which serves as |
| | | a template for creating ``.po`` files. |
| | |
| | | .. code-block:: text |
| | | |
| | | C> cd \my\virtualenv |
| | | C> bin\easy_install Babel lingua |
| | | C> Scripts\easy_install Babel lingua |
| | | |
| | | .. index:: |
| | | single: Babel; message extractors |
| | |
| | | from pyramid.i18n import get_localizer |
| | | from pyramid.i18n import TranslationString |
| | | |
| | | ts = TranslationString('Add ${number}', mapping={'number':1}, |
| | | ts = TranslationString('Add ${number}', mapping={'number':1}, |
| | | domain='pyramid') |
| | | |
| | | def aview(request): |
| | |
| | | |
| | | .. code-block:: python |
| | | :linenos: |
| | | |
| | | |
| | | from pyramid.threadlocal import get_current_registry |
| | | settings = get_current_registry().settings |
| | | languages = settings['available_languages'].split() |
| | |
| | | :linenos: |
| | | |
| | | from pyramid.config import Configurator |
| | | config.add_translation_dirs('my.application:locale/', |
| | | config.add_translation_dirs('my.application:locale/', |
| | | 'another.application:locale/') |
| | | |
| | | A message catalog in a translation directory added via |
| | |
| | | While the ZCA is an excellent tool with which to build a *framework* |
| | | such as :app:`Pyramid`, it is not always the best tool with which |
| | | to build an *application* due to the opacity of the ``zope.component`` |
| | | APIs. Accordingly, :app:`Pyramid` tends to hide the the presence |
| | | of the ZCA from application developers. You needn't understand the |
| | | ZCA to create a :app:`Pyramid` application; its use is effectively |
| | | only a framework implementation detail. |
| | | APIs. Accordingly, :app:`Pyramid` tends to hide the presence of the |
| | | ZCA from application developers. You needn't understand the ZCA to |
| | | create a :app:`Pyramid` application; its use is effectively only a |
| | | framework implementation detail. |
| | | |
| | | However, developers who are already used to writing :term:`Zope` |
| | | applications often still wish to use the ZCA while building a |
| | |
| | | |
| | | <Directory /Users/chrism/modwsgi/env> |
| | | WSGIProcessGroup pyramid |
| | | Order allow, deny |
| | | Order allow,deny |
| | | Allow from all |
| | | </Directory> |
| | | |
| | |
| | | ~~~~~~~~~~~~~~~~~~~~~~ |
| | | |
| | | Add a ``security.py`` module within your package (in the same |
| | | directory as ``__init__.py``, ``views.py``, etc) with the following |
| | | directory as ``__init__.py``, ``views.py``, etc.) with the following |
| | | content: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/security.py |
| | |
| | | logged_in = authenticated_userid(request) |
| | | |
| | | We'll then change the return value of each view that has an associated |
| | | ``renderer`` to pass the `resulting `logged_in`` value to the |
| | | ``renderer`` to pass the resulting ``logged_in`` value to the |
| | | template. For example: |
| | | |
| | | .. ignore-next-block |
| | |
| | | credentials with the username ``editor``, password ``editor`` will |
| | | show the edit page form being displayed. |
| | | |
| | | - After logging in (as a result of hitting an edit or add page and |
| | | - After logging in (as a result of hitting an edit or add page and |
| | | submitting the login form with the ``editor`` credentials), we'll see |
| | | a Logout link in the upper right hand corner. When we click it, |
| | | we're logged out, and redirected back to the front page. |
| | |
| | | the request as a single argument, you can obtain it via |
| | | ``request.context``. |
| | | |
| | | We're going to define several :term:`view callable` functions then wire them |
| | | We're going to define several :term:`view callable` functions, then wire them |
| | | into :app:`Pyramid` using some :term:`view configuration`. |
| | | |
| | | The source code for this tutorial stage can be browsed via |
| | |
| | | |
| | | If the view execution is *not* a result of a form submission (if the |
| | | expression ``'form.submitted' in request.params`` is ``False``), the view |
| | | simply renders the edit form, passing the request, the page resource, and a |
| | | save_url which will be used as the action of the generated form. |
| | | simply renders the edit form, passing the page resource, and a ``save_url`` |
| | | which will be used as the action of the generated form. |
| | | |
| | | If the view execution *is* a result of a form submission (if the expression |
| | | ``'form.submitted' in request.params`` is ``True``), the view grabs the |
| | |
| | | |
| | | For cut and paste purposes, the source code for all stages of this |
| | | tutorial can be browsed at |
| | | `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki |
| | | <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki>`_. |
| | | `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/ |
| | | <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki/src/>`_. |
| | | |
| | | .. toctree:: |
| | | :maxdepth: 2 |
| | |
| | | |
| | | .. code-block:: text |
| | | |
| | | c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 \ |
| | | c:\pyramidtut> Scripts\easy_install docutils repoze.tm2 ^ |
| | | repoze.zodbconn nose coverage |
| | | |
| | | .. _making_a_project: |
| | |
| | | |
| | | .. code-block:: text |
| | | |
| | | c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \ |
| | | c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^ |
| | | --cover-erase --with-coverage |
| | | |
| | | Looks like the code in the ``pyramid_zodb`` scaffold for ZODB projects is |
| | |
| | | authn_policy = AuthTktAuthenticationPolicy(secret='sosecret', |
| | | callback=groupfinder) |
| | | authz_policy = ACLAuthorizationPolicy() |
| | | zodb_uri = settings.get('zodb_uri') |
| | | if zodb_uri is None: |
| | | zodb_uri = settings.get('zodb_uri', False) |
| | | if zodb_uri is False: |
| | | raise ValueError("No 'zodb_uri' in application configuration.") |
| | | |
| | | finder = PersistentApplicationFinder(zodb_uri, appmaker) |
| | |
| | | def main(global_config, **settings): |
| | | """ This function returns a Pyramid WSGI application. |
| | | """ |
| | | zodb_uri = settings.get('zodb_uri') |
| | | if zodb_uri is None: |
| | | zodb_uri = settings.get('zodb_uri', False) |
| | | if zodb_uri is False: |
| | | raise ValueError("No 'zodb_uri' in application configuration.") |
| | | |
| | | finder = PersistentApplicationFinder(zodb_uri, appmaker) |
| | |
| | | def main(global_config, **settings): |
| | | """ This function returns a WSGI application. |
| | | """ |
| | | zodb_uri = settings.get('zodb_uri') |
| | | if zodb_uri is None: |
| | | zodb_uri = settings.get('zodb_uri', False) |
| | | if zodb_uri is False: |
| | | raise ValueError("No 'zodb_uri' in application configuration.") |
| | | |
| | | finder = PersistentApplicationFinder(zodb_uri, appmaker) |
| | |
| | | self.tmpdir = tempfile.mkdtemp() |
| | | |
| | | dbpath = os.path.join( self.tmpdir, 'test.db') |
| | | settings = { 'zodb_uri' : 'file://' + dbpath } |
| | | from repoze.zodbconn.uri import db_from_uri |
| | | db = db_from_uri('file://' + dbpath) |
| | | settings = { 'zodb_uri' : None } |
| | | |
| | | app = main({}, **settings) |
| | | from repoze.zodbconn.middleware import EnvironmentDeleterMiddleware |
| | | app = EnvironmentDeleterMiddleware(app) |
| | | from repoze.zodbconn.connector import Connector |
| | | app = Connector(app, db) |
| | | self.db = db |
| | | from webtest import TestApp |
| | | self.testapp = TestApp(app) |
| | | |
| | | def tearDown(self): |
| | | import shutil |
| | | self.db.close() |
| | | shutil.rmtree( self.tmpdir ) |
| | | |
| | | def test_root(self): |
| | |
| | | def main(global_config, **settings): |
| | | """ This function returns a WSGI application. |
| | | """ |
| | | zodb_uri = settings.get('zodb_uri') |
| | | if zodb_uri is None: |
| | | zodb_uri = settings.get('zodb_uri', False) |
| | | if zodb_uri is False: |
| | | raise ValueError("No 'zodb_uri' in application configuration.") |
| | | |
| | | finder = PersistentApplicationFinder(zodb_uri, appmaker) |
| | |
| | | |
| | | ......... |
| | | ---------------------------------------------------------------------- |
| | | Ran 9 tests in 0.203s |
| | | Ran 23 tests in 1.653s |
| | | |
| | | OK |
| | |
| | | :meth:`pyramid.config.Configurator.add_route` for more info. |
| | | |
| | | We'll pass the ``RootFactory`` we created in the step above in as the |
| | | ``root_factory`` argument to a :term:`Configurator`. |
| | | ``root_factory`` argument to a :term:`Configurator`. |
| | | |
| | | Configuring an Authorization Policy |
| | | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| | |
| | | :term:`view callable`. This is also known as a :term:`forbidden view`: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/__init__.py |
| | | :lines: 41-43 |
| | | :lines: 25,41-43 |
| | | :linenos: |
| | | :language: python |
| | | |
| | |
| | | ---------------------- |
| | | |
| | | Add a ``security.py`` module within your package (in the same directory as |
| | | :file:`__init__.py`, :file:`views.py`, etc) with the following content: |
| | | :file:`__init__.py`, :file:`views.py`, etc.) with the following content: |
| | | |
| | | .. literalinclude:: src/authorization/tutorial/security.py |
| | | :linenos: |
| | |
| | | register views for the routes, mapping your patterns to code: |
| | | |
| | | .. literalinclude:: src/basiclayout/tutorial/__init__.py |
| | | :lines: 14 |
| | | :lines: 14-15 |
| | | :language: py |
| | | |
| | | The first positional ``add_view`` argument ``tutorial.views.my_view`` is the |
| | |
| | | ``renderer``, which is a template which lives in the ``templates`` |
| | | subdirectory of the package. When the ``tutorial.views.my_view`` view |
| | | returns a dictionary, a :term:`renderer` will use this template to create a |
| | | response. This |
| | | response. |
| | | |
| | | Finally, we use the :meth:`pyramid.config.Configurator.make_wsgi_app` |
| | | method to return a :term:`WSGI` application: |
| | |
| | | :linenos: |
| | | :language: py |
| | | |
| | | Next we set up a SQLAlchemy "DBSession" object: |
| | | Next we set up a SQLAlchemy "DBSession" object: |
| | | |
| | | .. literalinclude:: src/basiclayout/tutorial/models.py |
| | | :lines: 15-16 |
| | |
| | | :language: python |
| | | |
| | | As you can see, our ``Page`` class has a class level attribute |
| | | ``__tablename__`` which equals the string ``pages``. This means that |
| | | ``__tablename__`` which equals the string ``'pages'``. This means that |
| | | SQLAlchemy will store our wiki data in a SQL table named ``pages``. Our Page |
| | | class will also have class-level attributes named ``id``, ``name`` and |
| | | ``data`` (all instances of :class:`sqlalchemy.Column`). These will map to |
| | |
| | | largely the same as the ``initialize_sql`` in the paster-generated |
| | | ``models.py``. |
| | | |
| | | Our DBSession assignment stays the same as the original generated |
| | | Our ``DBSession`` assignment stays the same as the original generated |
| | | ``models.py``. |
| | | |
| | | Looking at the Result of all Our Edits to ``models.py`` |
| | |
| | | into the URL by the ``pattern`` of a ``route`` statement. For instance, if a |
| | | call to :meth:`pyramid.config.Configurator.add_route` in ``__init__.py`` had |
| | | the pattern ``{one}/{two}``, and the URL at ``http://example.com/foo/bar`` |
| | | was invoked, matching this pattern, the matchdict dictionary attached to the |
| | | request passed to the view would have a ``one`` key with the value ``foo`` |
| | | and a ``two`` key with the value ``bar``. |
| | | was invoked, matching this pattern, the ``matchdict`` dictionary attached to |
| | | the request passed to the view would have a ``'one'`` key with the value |
| | | ``'foo'`` and a ``'two'`` key with the value ``'bar'``. |
| | | |
| | | The source code for this tutorial stage can be browsed at |
| | | `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/views/ |
| | |
| | | The ``view_wiki`` view function |
| | | ------------------------------- |
| | | |
| | | The ``view_wiki`` function will respond as the :term:`default view` of a |
| | | ``Wiki`` model object. It always redirects to a URL which represents the |
| | | path to our "FrontPage". |
| | | The ``view_wiki`` function is the :term:`default view` that will be called |
| | | when a request is made to the root URL of our wiki. It always redirects to |
| | | a URL which represents the path to our "FrontPage". |
| | | |
| | | .. literalinclude:: src/views/tutorial/views.py |
| | | :pyobject: view_wiki |
| | |
| | | The ``view_page`` view function |
| | | ------------------------------- |
| | | |
| | | The ``view_page`` function will respond as the :term:`default view` of |
| | | a ``Page`` object. The ``view_page`` function renders the |
| | | :term:`ReStructuredText` body of a page (stored as the ``data`` |
| | | attribute of a Page object) as HTML. Then it substitutes an HTML |
| | | anchor for each *WikiWord* reference in the rendered HTML using a |
| | | The ``view_page`` function will be used to show a single page of our |
| | | wiki. It renders the :term:`ReStructuredText` body of a page (stored as |
| | | the ``data`` attribute of a Page object) as HTML. Then it substitutes an |
| | | HTML anchor for each *WikiWord* reference in the rendered HTML using a |
| | | compiled regular expression. |
| | | |
| | | .. literalinclude:: src/views/tutorial/views.py |
| | |
| | | :linenos: |
| | | :language: python |
| | | |
| | | The matchdict will have a ``pagename`` key that matches the name of the page |
| | | we'd like to add. If our add view is invoked via, |
| | | e.g. ``http://localhost:6543/add_page/SomeName``, the ``pagename`` value in |
| | | the matchdict will be ``SomeName``. |
| | | The ``matchdict`` will have a ``'pagename'`` key that matches the name of |
| | | the page we'd like to add. If our add view is invoked via, |
| | | e.g. ``http://localhost:6543/add_page/SomeName``, the value for |
| | | ``'pagename'`` in the ``matchdict`` will be ``'SomeName'``. |
| | | |
| | | If the view execution is *not* a result of a form submission (if the |
| | | expression ``'form.submitted' in request.params`` is ``False``), the view |
| | | callable renders a template. To do so, it generates a "save url" which the |
| | | template use as the form post URL during rendering. We're lazy here, so |
| | | template uses as the form post URL during rendering. We're lazy here, so |
| | | we're trying to use the same template (``templates/edit.pt``) for the add |
| | | view as well as the page edit view, so we create a dummy Page object in order |
| | | to satisfy the edit form's desire to have *some* page object exposed as |
| | |
| | | |
| | | If the view execution *is* a result of a form submission (if the expression |
| | | ``'form.submitted' in request.params`` is ``True``), we scrape the page body |
| | | from the form data, create a Page object using the name in the matchdict |
| | | ``pagename``, and obtain the page body from the request, and save it into the |
| | | database using ``session.add``. We then redirect back to the ``view_page`` |
| | | view (the :term:`default view` for a Page) for the newly created page. |
| | | from the form data, create a Page object with this page body and the name |
| | | taken from ``matchdict['pagename']``, and save it into the database using |
| | | ``session.add``. We then redirect back to the ``view_page`` view for the |
| | | newly created page. |
| | | |
| | | The ``edit_page`` view function |
| | | ------------------------------- |
| | |
| | | The ``edit_page`` function will be invoked when a user clicks the "Edit this |
| | | Page" button on the view form. It renders an edit form but it also acts as |
| | | the handler for the form it renders. The ``matchdict`` attribute of the |
| | | request passed to the ``edit_page`` view will have a ``pagename`` key |
| | | request passed to the ``edit_page`` view will have a ``'pagename'`` key |
| | | matching the name of the page the user wants to edit. |
| | | |
| | | .. literalinclude:: src/views/tutorial/views.py |
| | |
| | | |
| | | If the view execution is *not* a result of a form submission (if the |
| | | expression ``'form.submitted' in request.params`` is ``False``), the view |
| | | simply renders the edit form, passing the request, the page object, and a |
| | | save_url which will be used as the action of the generated form. |
| | | simply renders the edit form, passing the page object and a ``save_url`` |
| | | which will be used as the action of the generated form. |
| | | |
| | | If the view execution *is* a result of a form submission (if the expression |
| | | ``'form.submitted' in request.params`` is ``True``), the view grabs the |
| | | ``body`` element of the request parameter and sets it as the ``data`` |
| | | attribute of the page object. It then redirects to the default view of the |
| | | wiki page, which will always be the ``view_page`` view. |
| | | ``body`` element of the request parameters and sets it as the ``data`` |
| | | attribute of the page object. It then redirects to the ``view_page`` view |
| | | of the wiki page. |
| | | |
| | | Viewing the Result of all Our Edits to ``views.py`` |
| | | =================================================== |
| | |
| | | The ``__init__.py`` file contains |
| | | :meth:`pyramid.config.Configurator.add_view` calls which serve to map |
| | | routes via :term:`url dispatch` to views. First, we’ll get rid of the |
| | | existing route created by the template using the name ``home``. It’s only an |
| | | existing route created by the template using the name ``'home'``. It’s only an |
| | | example and isn’t relevant to our application. |
| | | |
| | | We then need to add four calls to ``add_route``. Note that the *ordering* of |
| | |
| | | the order they're found in the ``__init__.py`` file. |
| | | |
| | | #. Add a declaration which maps the pattern ``/`` (signifying the root URL) |
| | | to the route named ``view_wiki``. |
| | | to the route named ``view_wiki``. |
| | | |
| | | #. Add a declaration which maps the pattern ``/{pagename}`` to the route named |
| | | ``view_page``. This is the regular view for a page. |
| | |
| | | in a browser. You should see an interactive exception handler in the |
| | | browser which allows you to examine values in a post-mortem mode. |
| | | |
| | | Adding Tests |
| | | ============ |
| | | |
| | | Since we've added a good bit of imperative code here, it's useful to |
| | | define tests for the views we've created. We'll change our tests.py |
| | | module to look like this: |
| | | |
| | | .. literalinclude:: src/views/tutorial/tests.py |
| | | :linenos: |
| | | :language: python |
| | | |
| | | We can then run the tests using something like: |
| | | |
| | | .. code-block:: text |
| | | :linenos: |
| | | |
| | | $ python setup.py test -q |
| | | |
| | | The expected output is something like: |
| | | |
| | | .. code-block:: text |
| | | :linenos: |
| | | |
| | | running test |
| | | running egg_info |
| | | writing requirements to tutorial.egg-info/requires.txt |
| | | writing tutorial.egg-info/PKG-INFO |
| | | writing top-level names to tutorial.egg-info/top_level.txt |
| | | writing dependency_links to tutorial.egg-info/dependency_links.txt |
| | | writing entry points to tutorial.egg-info/entry_points.txt |
| | | unrecognized .svn/entries format in |
| | | reading manifest file 'tutorial.egg-info/SOURCES.txt' |
| | | writing manifest file 'tutorial.egg-info/SOURCES.txt' |
| | | running build_ext |
| | | ...... |
| | | ---------------------------------------------------------------------- |
| | | Ran 6 tests in 0.181s |
| | | |
| | | OK |
| | | |
| | | |
| | | |
| | | |
| | |
| | | |
| | | For cut and paste purposes, the source code for all stages of this |
| | | tutorial can be browsed at |
| | | `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/ |
| | | <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/>`_. |
| | | `http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/ |
| | | <http://github.com/Pylons/pyramid/tree/master/docs/tutorials/wiki2/src/>`_. |
| | | |
| | | .. toctree:: |
| | | :maxdepth: 2 |
| | |
| | | definingmodels |
| | | definingviews |
| | | authorization |
| | | tests |
| | | distributing |
| | | |
| | | |
| | |
| | | |
| | | .. code-block:: text |
| | | |
| | | c:\pyramidtut> Scripts\easy_install docutils \ |
| | | c:\pyramidtut> Scripts\easy_install docutils ^ |
| | | nose coverage zope.sqlalchemy SQLAlchemy repoze.tm2 |
| | | |
| | | |
| | |
| | | |
| | | .. code-block:: text |
| | | |
| | | c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial \ |
| | | c:\pyramidtut\tutorial> ..\Scripts\nosetests --cover-package=tutorial ^ |
| | | --cover-erase --with-coverage |
| | | |
| | | Looks like our package's ``models`` module doesn't quite have 100% |
| | |
| | | data = Column(Text) |
| | | |
| | | def __init__(self, name, data): |
| | | self.name = name |
| | | self.data = data |
| | | self.name = name |
| | | self.data = data |
| | | |
| | | def initialize_sql(engine): |
| | | DBSession.configure(bind=engine) |
| | | Base.metadata.bind = engine |
| | | Base.metadata.create_all(engine) |
| | | try: |
| | | transaction.begin() |
| | | session = DBSession() |
| | | page = Page('FrontPage', 'initial data') |
| | | page = Page('FrontPage', 'This is the front page') |
| | | session.add(page) |
| | | transaction.commit() |
| | | except IntegrityError: |
| | |
| | | |
| | | from docutils.core import publish_parts |
| | | |
| | | from pyramid.httpexceptions import HTTPFound |
| | | from pyramid.httpexceptions import HTTPFound, HTTPNotFound |
| | | from pyramid.security import authenticated_userid |
| | | from pyramid.url import route_url |
| | | |
| | |
| | | def view_page(request): |
| | | pagename = request.matchdict['pagename'] |
| | | session = DBSession() |
| | | page = session.query(Page).filter_by(name=pagename).one() |
| | | page = session.query(Page).filter_by(name=pagename).first() |
| | | if page is None: |
| | | return HTTPNotFound('No such page') |
| | | |
| | | def check(match): |
| | | word = match.group(1) |
| | |
| | | page = Page('', '') |
| | | logged_in = authenticated_userid(request) |
| | | return dict(page=page, save_url=save_url, logged_in=logged_in) |
| | | |
| | | |
| | | def edit_page(request): |
| | | name = request.matchdict['pagename'] |
| | | session = DBSession() |
| | |
| | | data = Column(Text) |
| | | |
| | | def __init__(self, name, data): |
| | | self.name = name |
| | | self.data = data |
| | | self.name = name |
| | | self.data = data |
| | | |
| | | def initialize_sql(engine): |
| | | DBSession.configure(bind=engine) |
| | | Base.metadata.bind = engine |
| | | Base.metadata.create_all(engine) |
| | | try: |
| | | transaction.begin() |
| | | session = DBSession() |
| | | page = Page('FrontPage', 'initial data') |
| | | page = Page('FrontPage', 'This is the front page') |
| | | session.add(page) |
| | | transaction.commit() |
| | | except IntegrityError: |
New file |
| | |
| | | import unittest |
| | | |
| | | from pyramid import testing |
| | | |
| | | |
| | | def _initTestingDB(): |
| | | from tutorial.models import DBSession |
| | | from tutorial.models import Base |
| | | from sqlalchemy import create_engine |
| | | engine = create_engine('sqlite:///:memory:') |
| | | DBSession.configure(bind=engine) |
| | | Base.metadata.bind = engine |
| | | Base.metadata.create_all(engine) |
| | | return DBSession |
| | | |
| | | def _registerRoutes(config): |
| | | config.add_route('view_page', '{pagename}') |
| | | config.add_route('edit_page', '{pagename}/edit_page') |
| | | config.add_route('add_page', 'add_page/{pagename}') |
| | | |
| | | |
| | | class PageModelTests(unittest.TestCase): |
| | | |
| | | def setUp(self): |
| | | self.session = _initTestingDB() |
| | | |
| | | def tearDown(self): |
| | | self.session.remove() |
| | | |
| | | def _getTargetClass(self): |
| | | from tutorial.models import Page |
| | | return Page |
| | | |
| | | def _makeOne(self, name='SomeName', data='some data'): |
| | | return self._getTargetClass()(name, data) |
| | | |
| | | def test_constructor(self): |
| | | instance = self._makeOne() |
| | | self.assertEqual(instance.name, 'SomeName') |
| | | self.assertEqual(instance.data, 'some data') |
| | | |
| | | class InitializeSqlTests(unittest.TestCase): |
| | | |
| | | def setUp(self): |
| | | from tutorial.models import DBSession |
| | | DBSession.remove() |
| | | |
| | | def tearDown(self): |
| | | from tutorial.models import DBSession |
| | | DBSession.remove() |
| | | |
| | | def _callFUT(self, engine): |
| | | from tutorial.models import initialize_sql |
| | | return initialize_sql(engine) |
| | | |
| | | def test_it(self): |
| | | from sqlalchemy import create_engine |
| | | engine = create_engine('sqlite:///:memory:') |
| | | self._callFUT(engine) |
| | | from tutorial.models import DBSession, Page |
| | | self.assertEqual(DBSession.query(Page).one().data, |
| | | 'This is the front page') |
| | | |
| | | class ViewWikiTests(unittest.TestCase): |
| | | def setUp(self): |
| | | self.config = testing.setUp() |
| | | |
| | | def tearDown(self): |
| | | testing.tearDown() |
| | | |
| | | def _callFUT(self, request): |
| | | from tutorial.views import view_wiki |
| | | return view_wiki(request) |
| | | |
| | | def test_it(self): |
| | | _registerRoutes(self.config) |
| | | request = testing.DummyRequest() |
| | | response = self._callFUT(request) |
| | | self.assertEqual(response.location, 'http://example.com/FrontPage') |
| | | |
| | | class ViewPageTests(unittest.TestCase): |
| | | def setUp(self): |
| | | self.session = _initTestingDB() |
| | | self.config = testing.setUp() |
| | | |
| | | def tearDown(self): |
| | | self.session.remove() |
| | | testing.tearDown() |
| | | |
| | | def _callFUT(self, request): |
| | | from tutorial.views import view_page |
| | | return view_page(request) |
| | | |
| | | def test_it(self): |
| | | from tutorial.models import Page |
| | | request = testing.DummyRequest() |
| | | request.matchdict['pagename'] = 'IDoExist' |
| | | page = Page('IDoExist', 'Hello CruelWorld IDoExist') |
| | | self.session.add(page) |
| | | _registerRoutes(self.config) |
| | | info = self._callFUT(request) |
| | | self.assertEqual(info['page'], page) |
| | | self.assertEqual( |
| | | info['content'], |
| | | '<div class="document">\n' |
| | | '<p>Hello <a href="http://example.com/add_page/CruelWorld">' |
| | | 'CruelWorld</a> ' |
| | | '<a href="http://example.com/IDoExist">' |
| | | 'IDoExist</a>' |
| | | '</p>\n</div>\n') |
| | | self.assertEqual(info['edit_url'], |
| | | 'http://example.com/IDoExist/edit_page') |
| | | |
| | | class AddPageTests(unittest.TestCase): |
| | | def setUp(self): |
| | | self.session = _initTestingDB() |
| | | self.config = testing.setUp() |
| | | self.config.begin() |
| | | |
| | | def tearDown(self): |
| | | self.session.remove() |
| | | testing.tearDown() |
| | | |
| | | def _callFUT(self, request): |
| | | from tutorial.views import add_page |
| | | return add_page(request) |
| | | |
| | | def test_it_notsubmitted(self): |
| | | _registerRoutes(self.config) |
| | | request = testing.DummyRequest() |
| | | request.matchdict = {'pagename':'AnotherPage'} |
| | | info = self._callFUT(request) |
| | | self.assertEqual(info['page'].data,'') |
| | | self.assertEqual(info['save_url'], |
| | | 'http://example.com/add_page/AnotherPage') |
| | | |
| | | def test_it_submitted(self): |
| | | from tutorial.models import Page |
| | | _registerRoutes(self.config) |
| | | request = testing.DummyRequest({'form.submitted':True, |
| | | 'body':'Hello yo!'}) |
| | | request.matchdict = {'pagename':'AnotherPage'} |
| | | self._callFUT(request) |
| | | page = self.session.query(Page).filter_by(name='AnotherPage').one() |
| | | self.assertEqual(page.data, 'Hello yo!') |
| | | |
| | | class EditPageTests(unittest.TestCase): |
| | | def setUp(self): |
| | | self.session = _initTestingDB() |
| | | self.config = testing.setUp() |
| | | |
| | | def tearDown(self): |
| | | self.session.remove() |
| | | testing.tearDown() |
| | | |
| | | def _callFUT(self, request): |
| | | from tutorial.views import edit_page |
| | | return edit_page(request) |
| | | |
| | | def test_it_notsubmitted(self): |
| | | from tutorial.models import Page |
| | | _registerRoutes(self.config) |
| | | request = testing.DummyRequest() |
| | | request.matchdict = {'pagename':'abc'} |
| | | page = Page('abc', 'hello') |
| | | self.session.add(page) |
| | | info = self._callFUT(request) |
| | | self.assertEqual(info['page'], page) |
| | | self.assertEqual(info['save_url'], |
| | | 'http://example.com/abc/edit_page') |
| | | |
| | | def test_it_submitted(self): |
| | | from tutorial.models import Page |
| | | _registerRoutes(self.config) |
| | | request = testing.DummyRequest({'form.submitted':True, |
| | | 'body':'Hello yo!'}) |
| | | request.matchdict = {'pagename':'abc'} |
| | | page = Page('abc', 'hello') |
| | | self.session.add(page) |
| | | response = self._callFUT(request) |
| | | self.assertEqual(response.location, 'http://example.com/abc') |
| | | self.assertEqual(page.data, 'Hello yo!') |
| | | |
| | | class FunctionalTests(unittest.TestCase): |
| | | |
| | | viewer_login = '/login?login=viewer&password=viewer' \ |
| | | '&came_from=FrontPage&form.submitted=Login' |
| | | viewer_wrong_login = '/login?login=viewer&password=incorrect' \ |
| | | '&came_from=FrontPage&form.submitted=Login' |
| | | editor_login = '/login?login=editor&password=editor' \ |
| | | '&came_from=FrontPage&form.submitted=Login' |
| | | |
| | | def setUp(self): |
| | | from tutorial import main |
| | | settings = { 'sqlalchemy.url': 'sqlite:///:memory:'} |
| | | app = main({}, **settings) |
| | | from webtest import TestApp |
| | | self.testapp = TestApp(app) |
| | | |
| | | def tearDown(self): |
| | | del self.testapp |
| | | from tutorial.models import DBSession |
| | | DBSession.remove() |
| | | |
| | | def test_root(self): |
| | | res = self.testapp.get('/', status=302) |
| | | self.assertTrue(not res.body) |
| | | |
| | | def test_FrontPage(self): |
| | | res = self.testapp.get('/FrontPage', status=200) |
| | | self.assertTrue('FrontPage' in res.body) |
| | | |
| | | def test_unexisting_page(self): |
| | | res = self.testapp.get('/SomePage', status=404) |
| | | |
| | | def test_successful_log_in(self): |
| | | res = self.testapp.get(self.viewer_login, status=302) |
| | | self.assertTrue(res.location == 'FrontPage') |
| | | |
| | | def test_failed_log_in(self): |
| | | res = self.testapp.get(self.viewer_wrong_login, status=200) |
| | | self.assertTrue('login' in res.body) |
| | | |
| | | def test_logout_link_present_when_logged_in(self): |
| | | self.testapp.get(self.viewer_login, status=302) |
| | | res = self.testapp.get('/FrontPage', status=200) |
| | | self.assertTrue('Logout' in res.body) |
| | | |
| | | def test_logout_link_not_present_after_logged_out(self): |
| | | self.testapp.get(self.viewer_login, status=302) |
| | | self.testapp.get('/FrontPage', status=200) |
| | | res = self.testapp.get('/logout', status=302) |
| | | self.assertTrue('Logout' not in res.body) |
| | | |
| | | def test_anonymous_user_cannot_edit(self): |
| | | res = self.testapp.get('/FrontPage/edit_page', status=200) |
| | | self.assertTrue('Login' in res.body) |
| | | |
| | | def test_anonymous_user_cannot_add(self): |
| | | res = self.testapp.get('/add_page/NewPage', status=200) |
| | | self.assertTrue('Login' in res.body) |
| | | |
| | | def test_viewer_user_cannot_edit(self): |
| | | self.testapp.get(self.viewer_login, status=302) |
| | | res = self.testapp.get('/FrontPage/edit_page', status=200) |
| | | self.assertTrue('Login' in res.body) |
| | | |
| | | def test_viewer_user_cannot_add(self): |
| | | self.testapp.get(self.viewer_login, status=302) |
| | | res = self.testapp.get('/add_page/NewPage', status=200) |
| | | self.assertTrue('Login' in res.body) |
| | | |
| | | def test_editors_member_user_can_edit(self): |
| | | self.testapp.get(self.editor_login, status=302) |
| | | res = self.testapp.get('/FrontPage/edit_page', status=200) |
| | | self.assertTrue('Editing' in res.body) |
| | | |
| | | def test_editors_member_user_can_add(self): |
| | | self.testapp.get(self.editor_login, status=302) |
| | | res = self.testapp.get('/add_page/NewPage', status=200) |
| | | self.assertTrue('Editing' in res.body) |
| | | |
| | | def test_editors_member_user_can_view(self): |
| | | self.testapp.get(self.editor_login, status=302) |
| | | res = self.testapp.get('/FrontPage', status=200) |
| | | self.assertTrue('FrontPage' in res.body) |
| | |
| | | data = Column(Text) |
| | | |
| | | def __init__(self, name, data): |
| | | self.name = name |
| | | self.data = data |
| | | self.name = name |
| | | self.data = data |
| | | |
| | | def initialize_sql(engine): |
| | | DBSession.configure(bind=engine) |
| | | Base.metadata.bind = engine |
| | | Base.metadata.create_all(engine) |
| | | try: |
| | | transaction.begin() |
| | | session = DBSession() |
| | | page = Page('FrontPage', 'initial data') |
| | | session.add(page) |
| | |
| | | |
| | | from docutils.core import publish_parts |
| | | |
| | | from pyramid.httpexceptions import HTTPFound |
| | | from pyramid.httpexceptions import HTTPFound, HTTPNotFound |
| | | from pyramid.url import route_url |
| | | |
| | | from tutorial.models import DBSession |
| | |
| | | pagename='FrontPage')) |
| | | |
| | | def view_page(request): |
| | | matchdict = request.matchdict |
| | | pagename = request.matchdict['pagename'] |
| | | session = DBSession() |
| | | page = session.query(Page).filter_by(name=matchdict['pagename']).one() |
| | | page = session.query(Page).filter_by(name=pagename).first() |
| | | if page is None: |
| | | return HTTPNotFound('No such page') |
| | | |
| | | def check(match): |
| | | word = match.group(1) |
| | |
| | | |
| | | content = publish_parts(page.data, writer_name='html')['html_body'] |
| | | content = wikiwords.sub(check, content) |
| | | edit_url = route_url('edit_page', request, |
| | | pagename=matchdict['pagename']) |
| | | edit_url = route_url('edit_page', request, pagename=pagename) |
| | | return dict(page=page, content=content, edit_url=edit_url) |
| | | |
| | | def add_page(request): |
| | |
| | | save_url = route_url('add_page', request, pagename=name) |
| | | page = Page('', '') |
| | | return dict(page=page, save_url=save_url) |
| | | |
| | | |
| | | def edit_page(request): |
| | | name = request.matchdict['pagename'] |
| | | session = DBSession() |
New file |
| | |
| | | ============ |
| | | Adding Tests |
| | | ============ |
| | | |
| | | We will now add tests for the models and the views and a few functional |
| | | tests in the ``tests.py``. Tests ensure that an application works, and |
| | | that it continues to work after some changes are made in the future. |
| | | |
| | | Testing the Models |
| | | ================== |
| | | |
| | | We write a test class for the model class ``Page`` and another test class |
| | | for the ``initialize_sql`` function. |
| | | |
| | | To do so, we'll retain the ``tutorial.tests.ViewTests`` class provided as a |
| | | result of the ``pyramid_routesalchemy`` project generator. We'll add two |
| | | test classes: one for the ``Page`` model named ``PageModelTests``, one for the |
| | | ``initialize_sql`` function named ``InitializeSqlTests``. |
| | | |
| | | Testing the Views |
| | | ================= |
| | | |
| | | We'll modify our ``tests.py`` file, adding tests for each view function we |
| | | added above. As a result, we'll *delete* the ``ViewTests`` test in the file, |
| | | and add four other test classes: ``ViewWikiTests``, ``ViewPageTests``, |
| | | ``AddPageTests``, and ``EditPageTests``. These test the ``view_wiki``, |
| | | ``view_page``, ``add_page``, and ``edit_page`` views respectively. |
| | | |
| | | Functional tests |
| | | ================ |
| | | |
| | | We test the whole application, covering security aspects that are not |
| | | tested in the unit tests, like logging in, logging out, checking that |
| | | the ``viewer`` user cannot add or edit pages, but the ``editor`` user |
| | | can, and so on. |
| | | |
| | | Viewing the results of all our edits to ``tests.py`` |
| | | ==================================================== |
| | | |
| | | Once we're done with the ``tests.py`` module, it will look a lot like the |
| | | below: |
| | | |
| | | .. literalinclude:: src/tests/tutorial/tests.py |
| | | :linenos: |
| | | :language: python |
| | | |
| | | Running the Tests |
| | | ================= |
| | | |
| | | We can run these tests by using ``setup.py test`` in the same way we did in |
| | | :ref:`running_tests`. Assuming our shell's current working directory is the |
| | | "tutorial" distribution directory: |
| | | |
| | | On UNIX: |
| | | |
| | | .. code-block:: text |
| | | |
| | | $ ../bin/python setup.py test -q |
| | | |
| | | On Windows: |
| | | |
| | | .. code-block:: text |
| | | |
| | | c:\pyramidtut\tutorial> ..\Scripts\python setup.py test -q |
| | | |
| | | The expected result looks something like: |
| | | |
| | | .. code-block:: text |
| | | |
| | | ...................... |
| | | ---------------------------------------------------------------------- |
| | | Ran 22 tests in 2.700s |
| | | |
| | | OK |
| | |
| | | translations found in the list of translation directories.""" |
| | | translations = Translations() |
| | | translations._catalog = {} |
| | | |
| | | locales_to_try = [current_locale_name] |
| | | if '_' in current_locale_name: |
| | | locales_to_try.append(current_locale_name.split('_')[0]) |
| | | |
| | | for tdir in translation_directories: |
| | | locale_dirs = [ (lname, os.path.join(tdir, lname)) for lname in |
| | | os.listdir(tdir) ] |
| | | for locale_name, locale_dir in locale_dirs: |
| | | if locale_name != current_locale_name: |
| | | if locale_name not in locales_to_try: |
| | | continue |
| | | messages_dir = os.path.join(locale_dir, 'LC_MESSAGES') |
| | | if not os.path.isdir(os.path.realpath(messages_dir)): |
| | |
| | | def main(global_config, **settings): |
| | | """ This function returns a Pyramid WSGI application. |
| | | """ |
| | | zodb_uri = settings.get('zodb_uri') |
| | | if zodb_uri is None: |
| | | zodb_uri = settings.get('zodb_uri', False) |
| | | if zodb_uri is False: |
| | | raise ValueError("No 'zodb_uri' in application configuration.") |
| | | |
| | | finder = PersistentApplicationFinder(zodb_uri, appmaker) |
| | |
| | | self.assertEqual(result.translate('Approve', 'deformsite'), |
| | | 'Approve') |
| | | |
| | | def test_territory_fallback(self): |
| | | import os |
| | | from pyramid.i18n import Localizer |
| | | here = os.path.dirname(__file__) |
| | | localedir = os.path.join(here, 'localeapp', 'locale') |
| | | localedirs = [localedir] |
| | | locale_name = 'de_DE' |
| | | result = self._callFUT(locale_name, localedirs) |
| | | self.assertEqual(result.__class__, Localizer) |
| | | self.assertEqual(result.translate('Approve', 'deformsite'), |
| | | 'Genehmigen') |
| | | |
| | | |
| | | class Test_get_localizer(unittest.TestCase): |
| | | def setUp(self): |
| | | cleanUp() |
| | |
| | | """Generates a fully qualified URL for a named :app:`Pyramid` |
| | | :term:`route configuration`. |
| | | |
| | | .. note:: Calling :meth:`pyramid.request.Request.route_url` can be used to |
| | | .. note:: Calling :meth:`pyramid.Request.route_url` can be used to |
| | | achieve the same result as :func:`pyramid.url.route_url`. |
| | | |
| | | Use the route's ``name`` as the first positional argument. Use a |
| | |
| | | """Generates a path (aka a 'relative URL', a URL minus the host, scheme, |
| | | and port) for a named :app:`Pyramid` :term:`route configuration`. |
| | | |
| | | .. note:: Calling :meth:`pyramid.request.Request.route_path` can be used to |
| | | .. note:: Calling :meth:`pyramid.Request.route_path` can be used to |
| | | achieve the same result as :func:`pyramid.url.route_path`. |
| | | |
| | | This function accepts the same argument as :func:`pyramid.url.route_url` |
| | |
| | | overall result of this function is always a UTF-8 encoded string |
| | | (never Unicode). |
| | | |
| | | .. note:: Calling :meth:`pyramid.request.Request.resource_url` can be used to |
| | | .. note:: Calling :meth:`pyramid.Request.resource_url` can be used to |
| | | achieve the same result as :func:`pyramid.url.resource_url`. |
| | | |
| | | Examples:: |
| | |
| | | :meth:`pyramid.config.Configurator.add_static_view` |
| | | :term:`configuration declaration` (see :ref:`static_assets_section`). |
| | | |
| | | .. note:: Calling :meth:`pyramid.request.Request.static_url` can be used to |
| | | .. note:: Calling :meth:`pyramid.Request.static_url` can be used to |
| | | achieve the same result as :func:`pyramid.url.static_url`. |
| | | |
| | | Example:: |