diff options
Diffstat (limited to 'docs')
45 files changed, 940 insertions, 713 deletions
diff --git a/docs/.gitignore b/docs/.gitignore index da7abd0c0..30d731d4a 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,4 +1,3 @@ -_themes _build diff --git a/docs/Makefile b/docs/Makefile index bb381fc53..e4a325022 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -25,7 +25,7 @@ help: clean: -rm -rf _build/* -html: +html: _themes mkdir -p _build/html _build/doctrees $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @@ -47,7 +47,7 @@ pickle: web: pickle -htmlhelp: +htmlhelp: _themes mkdir -p _build/htmlhelp _build/doctrees $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @@ -84,3 +84,5 @@ epub: @echo @echo "Build finished. The epub file is in _build/epub." +_themes: + git submodule update --init diff --git a/docs/_themes b/docs/_themes new file mode 160000 +Subproject f59f7bfce5259f50fbb67b9040c03ecb080130b diff --git a/docs/api/renderers.rst b/docs/api/renderers.rst index ab182365e..ea000ad02 100644 --- a/docs/api/renderers.rst +++ b/docs/api/renderers.rst @@ -15,8 +15,6 @@ .. autoclass:: JSONP -.. autoclass:: ObjectJSONEncoder - .. attribute:: null_renderer An object that can be used in advanced integration cases as input to the diff --git a/docs/conf.py b/docs/conf.py index ffbf15808..80ee0d2e5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,6 +48,7 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'repoze.sphinx.autointerface', + 'sphinx.ext.viewcode', # 'sphinx.ext.intersphinx' ] @@ -132,18 +133,15 @@ if 'sphinx-build' in ' '.join(sys.argv): # protect against dumb importers from subprocess import call, Popen, PIPE p = Popen('which git', shell=True, stdout=PIPE) - git = p.stdout.read().strip() + cwd = os.getcwd() _themes = os.path.join(cwd, '_themes') - - if not os.path.isdir(_themes): - call([git, 'clone', 'git://github.com/Pylons/pylons_sphinx_theme.git', - '_themes']) + p = Popen('which git', shell=True, stdout=PIPE) + git = p.stdout.read().strip() + if not os.listdir(_themes): + call([git, 'submodule', '--init']) else: - os.chdir(_themes) - call([git, 'checkout', 'master']) - call([git, 'pull']) - os.chdir(cwd) + call([git, 'submodule', 'update']) sys.path.append(os.path.abspath('_themes')) @@ -162,7 +160,7 @@ html_theme_path = ['_themes'] html_theme = 'pyramid' html_theme_options = dict( github_url='https://github.com/Pylons/pyramid', -# in_progress='true' + in_progress='true', ) # The style sheet to use for HTML and HTML Help pages. A file of that name # must exist either in Sphinx' static/ path, or in one of the custom paths @@ -476,7 +474,7 @@ def resig(app, what, name, obj, options, signature, return_annotation): # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. -epub_title = 'The Pyramid Web Application Development Framework, Version 1.3' +epub_title = 'The Pyramid Web Application Development Framework, Version 1.4' epub_author = 'Chris McDonough' epub_publisher = 'Agendaless Consulting' epub_copyright = '2008-2011' @@ -493,7 +491,7 @@ epub_scheme = 'ISBN' epub_identifier = '0615445675' # A unique identification for the text. -epub_uid = 'The Pyramid Web Application Development Framework, Version 1.3' +epub_uid = 'The Pyramid Web Application Development Framework, Version 1.4' # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. diff --git a/docs/designdefense.rst b/docs/designdefense.rst index bbcf9c2ec..d896022e6 100644 --- a/docs/designdefense.rst +++ b/docs/designdefense.rst @@ -375,73 +375,6 @@ at least some ZCA concepts. In some places it's used unabashedly, and will be forever. We know it's quirky, but it's also useful and fundamentally understandable if you take the time to do some reading about it. -Pyramid Uses Interfaces Too Liberally -------------------------------------- - -In this `TOPP Engineering blog entry -<http://www.coactivate.org/projects/topp-engineering/blog/2008/10/20/what-bothers-me-about-the-component-architecture/>`_, -Ian Bicking asserts that the way :mod:`repoze.bfg` used a Zope interface to -represent an HTTP request method added too much indirection for not enough -gain. We agreed in general, and for this reason, :mod:`repoze.bfg` version -1.1 (and subsequent versions including :app:`Pyramid` 1.0+) added :term:`view -predicate` and :term:`route predicate` modifiers to view configuration. -Predicates are request-specific (or :term:`context` -specific) matching -narrowers which don't use interfaces. Instead, each predicate uses a -domain-specific string as a match value. - -For example, to write a view configuration which matches only requests with -the ``POST`` HTTP request method, you might write a ``@view_config`` -decorator which mentioned the ``request_method`` predicate: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - @view_config(name='post_view', request_method='POST', renderer='json') - def post_view(request): - return 'POSTed' - -You might further narrow the matching scenario by adding an ``accept`` -predicate that narrows matching to something that accepts a JSON response: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - @view_config(name='post_view', request_method='POST', - accept='application/json', renderer='json') - def post_view(request): - return 'POSTed' - -Such a view would only match when the request indicated that HTTP request -method was ``POST`` and that the remote user agent passed -``application/json`` (or, for that matter, ``application/*``) in its -``Accept`` request header. - -Under the hood, these features make no use of interfaces. - -Many prebaked predicates exist. However, use of only prebaked predicates, -however, doesn't entirely meet Ian's criterion. He would like to be able to -match a request using a lambda or another function which interrogates the -request imperatively. In :mod:`repoze.bfg` version 1.2, we acommodate this -by allowing people to define custom view predicates: - -.. code-block:: python - :linenos: - - from pyramid.view import view_config - from pyramid.response import Response - - def subpath(context, request): - return request.subpath and request.subpath[0] == 'abc' - - @view_config(custom_predicates=(subpath,)) - def aview(request): - return Response('OK') - -The above view will only match when the first element of the request's -:term:`subpath` is ``abc``. - .. _zcml_encouragement: Pyramid "Encourages Use of ZCML" @@ -711,33 +644,22 @@ over 2K lines of Python code, excluding tests. Pyramid Has Too Many Dependencies --------------------------------- -This is true. At the time of this writing, the total number of Python -package distributions that :app:`Pyramid` depends upon transitively is 15 if -you use Python 2.7, or 17 if you use Python 2.5 or 2.6. This is a lot more -than zero package distribution dependencies: a metric which various Python -microframeworks and Django boast. - -The :mod:`zope.component`, package on which :app:`Pyramid` depends has -transitive dependencies on several other packages (:mod:`zope.event`, and -:mod:`zope.interface`). :app:`Pyramid` also has its own direct dependencies, -such as :term:`PasteDeploy`, :term:`Chameleon`, :term:`Mako`, :term:`WebOb`, -:mod:`zope.deprecation` and some of these in turn have their own transitive -dependencies. - -We try not to reinvent too many wheels (at least the ones that don't need -reinventing), and this comes at the cost of some number of dependencies. -However, "number of package distributions" is just not a terribly great -metric to measure complexity. For example, the :mod:`zope.event` -distribution on which :app:`Pyramid` depends has a grand total of four lines -of runtime code. - -In the meantime, :app:`Pyramid` has a number of package distribution -dependencies comparable to similarly-targeted frameworks such as Pylons 1.X. -It may be in the future that we shed more dependencies as the result of a -port to Python 3 (the less code we need to port, the better). In the future, -we may also move templating system dependencies out of the core and place -them in add-on packages, to be included by developers instead of by the -framework. This would reduce the number of core dependencies by about five. +This is true. At the time of this writing (Pyramid 1.3), the total number of +Python package distributions that :app:`Pyramid` depends upon transitively is +if you use Python 3.2 or Python 2.7 is 10. If you use Python 2.6, Pyramid +will pull in 12 package distributions. This is a lot more than zero package +distribution dependencies: a metric which various Python microframeworks and +Django boast. + +However, Pyramid 1.2 relied on 15 packages under Python 2.7 and 17 packages +under Python 2.6, so we've made progress here. A port to Python 3 completed +in Pyramid 1.3 helped us shed a good number of dependencies by forcing us to +make better packaging decisions. + +In the future, we may also move templating system dependencies out of the +core and place them in add-on packages, to be included by developers instead +of by the framework. This would reduce the number of core dependencies by +about five, leaving us with only five remaining core dependencies. Pyramid "Cheats" To Obtain Speed -------------------------------- @@ -1647,7 +1569,7 @@ Pyramid Doesn't Offer Pluggable Apps ------------------------------------ It is "Pyramidic" to compose multiple external sources into the same -configuration using :meth:`~pyramid.config.Configuration.include`. Any +configuration using :meth:`~pyramid.config.Configurator.include`. Any number of includes can be done to compose an application; includes can even be done from within other includes. Any directive can be used within an include that can be used outside of one (such as diff --git a/docs/glossary.rst b/docs/glossary.rst index 60920a73a..45a79326f 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -290,7 +290,7 @@ Glossary :term:`principal` (or principals) associated with a request. WSGI - `Web Server Gateway Interface <http://wsgi.org/>`_. This is a + `Web Server Gateway Interface <http://www.wsgi.org/>`_. This is a Python standard for connecting web applications to web servers, similar to the concept of Java Servlets. :app:`Pyramid` requires that your application be served as a WSGI application. @@ -299,7 +299,7 @@ Glossary *Middleware* is a :term:`WSGI` concept. It is a WSGI component that acts both as a server and an application. Interesting uses for middleware exist, such as caching, content-transport - encoding, and other functions. See `WSGI.org <http://wsgi.org>`_ + encoding, and other functions. See `WSGI.org <http://www.wsgi.org>`_ or `PyPI <http://python.org/pypi>`_ to find middleware for your application. @@ -922,9 +922,9 @@ Glossary http://docs.pylonsproject.org/projects/pyramid_debugtoolbar/dev/ . scaffold - A project template that helps users get started writing a Pyramid - application quickly. Scaffolds are usually used via the ``pcreate`` - command. + A project template that generates some of the major parts of a Pyramid + application and helps users to quickly get started writing larger + applications. Scaffolds are usually used via the ``pcreate`` command. pyramid_exclog A package which logs Pyramid application exception (error) information diff --git a/docs/narr/advconfig.rst b/docs/narr/advconfig.rst index 9cb4db325..2949dc808 100644 --- a/docs/narr/advconfig.rst +++ b/docs/narr/advconfig.rst @@ -282,7 +282,7 @@ Pyramid application, and they want to customize the configuration of this application without hacking its code "from outside", they can "include" a configuration function from the package and override only some of its configuration statements within the code that does the include. No conflicts -will be generated by configuration statements within the code which does the +will be generated by configuration statements within the code that does the including, even if configuration statements in the included code would conflict if it was moved "up" to the calling code. diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 886e075e3..af53c1f78 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -460,7 +460,7 @@ to the console. You can add request header values by using the ``--header`` option:: - $ bin/prequest --header=Host=example.com development.ini / + $ bin/prequest --header=Host:example.com development.ini / Headers are added to the WSGI environment by converting them to their CGI/WSGI equivalents (e.g. ``Host=example.com`` will insert the ``HTTP_HOST`` @@ -654,8 +654,11 @@ use the following command: .. code-block:: python - import logging.config - logging.config.fileConfig('/path/to/my/development.ini') + import pyramid.paster + pyramid.paster.setup_logging('/path/to/my/development.ini') + +See :ref:`logging_chapter` for more information on logging within +:app:`Pyramid`. .. index:: single: console script @@ -718,7 +721,7 @@ we'll pretend you have a distribution with a package in it named def settings_show(): description = """\ Print the deployment settings for a Pyramid application. Example: - 'psettings deployment.ini' + 'show_settings deployment.ini' """ usage = "usage: %prog config_uri" parser = optparse.OptionParser( diff --git a/docs/narr/hooks.rst b/docs/narr/hooks.rst index b6e3dd163..332805152 100644 --- a/docs/narr/hooks.rst +++ b/docs/narr/hooks.rst @@ -145,7 +145,7 @@ 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 "forbidden" view consists -of using the meth:`pyramid.config.Configurator.add_forbidden_view` API or the +of using the :meth:`pyramid.config.Configurator.add_forbidden_view` API or the :class:`pyramid.view.forbidden_view_config` decorator. For example, you can add a forbidden view by using the @@ -171,7 +171,7 @@ as a forbidden view: from pyramid.view import forbidden_view_config - forbidden_view_config() + @forbidden_view_config() def forbidden(request): return Response('forbidden') @@ -289,6 +289,36 @@ keys added to the renderer globals dictionary by all :class:`pyramid.events.BeforeRender` subscribers and renderer globals factories must be unique. +The dictionary returned from the view is accessible through the +:attr:`rendering_val` attribute of a :class:`~pyramid.events.BeforeRender` +event. + +Suppose you return ``{'mykey': 'somevalue', 'mykey2': 'somevalue2'}`` from +your view callable, like so: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + + @view_config(renderer='some_renderer') + def myview(request): + return {'mykey': 'somevalue', 'mykey2': 'somevalue2'} + +:attr:`rendering_val` can be used to access these values from the +:class:`~pyramid.events.BeforeRender` object: + +.. code-block:: python + :linenos: + + from pyramid.events import subscriber + from pyramid.events import BeforeRender + + @subscriber(BeforeRender) + def read_return(event): + # {'mykey': 'somevalue'} is returned from the view + print(event.rendering_val['mykey']) + See the API documentation for the :class:`~pyramid.events.BeforeRender` event interface at :class:`pyramid.interfaces.IBeforeRender`. @@ -625,7 +655,7 @@ converts the arbitrary return value into something that implements :class:`~pyramid.interfaces.IResponse`. For example, if you'd like to allow view callables to return bare string -objects (without requiring a a :term:`renderer` to convert a string to a +objects (without requiring a :term:`renderer` to convert a string to a response object), you can register an adapter which converts the string to a Response: diff --git a/docs/narr/introduction.rst b/docs/narr/introduction.rst index 8f7b17dc3..b5fa6a9f7 100644 --- a/docs/narr/introduction.rst +++ b/docs/narr/introduction.rst @@ -534,14 +534,14 @@ Configuration extensibility ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Unlike other systems, Pyramid provides a structured "include" mechanism (see -:meth:`~pyramid.config.Configurator.include`) that allows you to compose +:meth:`~pyramid.config.Configurator.include`) that allows you to combine applications from multiple Python packages. All the configuration statements that can be performed in your "main" Pyramid application can also be performed by included packages including the addition of views, routes, subscribers, and even authentication and authorization policies. You can even extend or override an existing application by including another application's configuration in your own, overriding or adding new views and routes to -it. This has the potential to allow you to compose a big application out of +it. This has the potential to allow you to create a big application out of many other smaller ones. For example, if you want to reuse an existing application that already has a bunch of routes, you can just use the ``include`` statement with a ``route_prefix``; the new application will live @@ -593,11 +593,12 @@ it is to shoehorn a route into an ordered list of other routes, or to create another entire instance of an application to service a department and glue code to allow disparate apps to share data. It's a great fit for sites that naturally lend themselves to changing departmental hierarchies, such as -content management systems and document management systems. Traversal also lends itself well to -systems that require very granular security ("Bob can edit *this* document" -as opposed to "Bob can edit documents"). +content management systems and document management systems. Traversal also +lends itself well to systems that require very granular security ("Bob can +edit *this* document" as opposed to "Bob can edit documents"). -Example: :ref:`hello_traversal_chapter` and :ref:`much_ado_about_traversal_chapter`. +Examples: :ref:`hello_traversal_chapter` and +:ref:`much_ado_about_traversal_chapter`. Tweens ~~~~~~ @@ -801,6 +802,42 @@ within a function called when another user uses the See also :ref:`add_directive`. +Programmatic Introspection +-------------------------- + +If you're building a large system that other users may plug code into, it's +useful to be able to get an enumeration of what code they plugged in *at +application runtime*. For example, you might want to show them a set of tabs +at the top of the screen based on an enumeration of views they registered. + +This is possible using Pyramid's :term:`introspector`. + +Here's an example of using Pyramid's introspector from within a view +callable: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + from pyramid.response import Response + + @view_config(route_name='bar') + def show_current_route_pattern(request): + introspector = request.registry.introspector + route_name = request.matched_route.name + route_intr = introspector.get('routes', route_name) + return Response(str(route_intr['pattern'])) + +See also :ref:`using_introspection`. + +Python 3 Compatibility +---------------------- + +Pyramid and most of its add-ons are Python 3 compatible. If you develop a +Pyramid application today, you won't need to worry that five years from now +you'll be backwatered because there are language features you'd like to use +but your framework doesn't support newer Python versions. + Testing ~~~~~~~ diff --git a/docs/narr/introspector.rst b/docs/narr/introspector.rst index 74595cac8..6bfaf11c0 100644 --- a/docs/narr/introspector.rst +++ b/docs/narr/introspector.rst @@ -32,7 +32,7 @@ callable: from pyramid.response import Response @view_config(route_name='bar') - def route_accepts(request): + def show_current_route_pattern(request): introspector = request.registry.introspector route_name = request.matched_route.name route_intr = introspector.get('routes', route_name) diff --git a/docs/narr/logging.rst b/docs/narr/logging.rst index 044655c1f..f4c38abb6 100644 --- a/docs/narr/logging.rst +++ b/docs/narr/logging.rst @@ -14,7 +14,7 @@ how to send log messages to loggers that you've configured. which help configure logging. All of the scaffolds which ship along with :app:`Pyramid` do this. If you're not using a scaffold, or if you've used a third-party scaffold which does not create these files, the - configuration information in this chapter will not be applicable. + configuration information in this chapter may not be applicable. .. _logging_config: @@ -36,10 +36,11 @@ application-related and logging-related sections in the configuration file can coexist peacefully, and the logging-related sections in the file are used from when you run ``pserve``. -The ``pserve`` command calls the `logging.fileConfig function +The ``pserve`` command calls the :func:`pyramid.paster.setup_logging` +function, a thin wrapper around the `logging.fileConfig <http://docs.python.org/lib/logging-config-api.html>`_ using the specified ini file if it contains a ``[loggers]`` section (all of the -scaffold-generated ``.ini`` files do). ``logging.fileConfig`` reads the +scaffold-generated ``.ini`` files do). ``setup_logging`` reads the logging configuration from the ini file upon which ``pserve`` was invoked. diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 57073900f..1e2c225d2 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -144,13 +144,13 @@ directories which he creates within his ``~/projects`` directory. On Windows, it's a good idea to put project directories within a directory that contains no space characters, so it's wise to *avoid* a path that contains i.e. ``My Documents``. As a result, the author, when he uses Windows, just -puts his projects in ``C:\\projects``. +puts his projects in ``C:\projects``. .. warning:: You’ll need to avoid using ``pcreate`` to create a project with the same - as a Python standard library component. In particular, this means you - should avoid using names the names ``site`` or ``test``, both of which + name as a Python standard library component. In particular, this means you + should avoid using the names ``site`` or ``test``, both of which conflict with Python standard library packages. You should also avoid using the name ``pyramid``, which will conflict with Pyramid itself. @@ -447,7 +447,7 @@ first column instead, for example like this: pyramid.includes = #pyramid_debugtoolbar -When you attempt to restart the application with a section like the abvoe +When you attempt to restart the application with a section like the above you'll receive an error that ends something like this, and the application will not start: @@ -684,7 +684,7 @@ testing your application, packaging, and distributing your application. .. note:: - ``setup.py`` is the defacto standard which Python developers use to + ``setup.py`` is the de facto standard which Python developers use to distribute their reusable code. You can read more about ``setup.py`` files and their usage in the `Setuptools documentation <http://peak.telecommunity.com/DevCenter/setuptools>`_ and `The @@ -966,7 +966,7 @@ named ``views`` instead of within a single ``views.py`` file, you might: You can then continue to add view callable functions to the ``blog.py`` module, but you can also add other ``.py`` files which contain view callable functions to the ``views`` directory. As long as you use the -``@view_config`` directive to register views in conjuction with +``@view_config`` directive to register views in conjunction with ``config.scan()`` they will be picked up automatically when the application is restarted. @@ -994,7 +994,7 @@ run a :app:`Pyramid` application is purely conventional based on the output of its scaffolding. But we strongly recommend using while developing your application, because many other convenience introspection commands (such as ``pviews``, ``prequest``, ``proutes`` and others) are also implemented in -terms of configuration availaibility of this ``.ini`` file format. It also +terms of configuration availability of this ``.ini`` file format. It also configures Pyramid logging and provides the ``--reload`` switch for convenient restarting of the server when code changes. diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 34bee3c7f..57b5bc65b 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -177,13 +177,15 @@ using the API of the ``request.response`` attribute. See .. index:: pair: renderer; JSON +.. _json_renderer: + JSON Renderer ~~~~~~~~~~~~~ -The ``json`` renderer renders view callable results to :term:`JSON`. It -passes the return value through the ``json.dumps`` standard library function, -and wraps the result in a response object. It also sets the response -content-type to ``application/json``. +The ``json`` renderer renders view callable results to :term:`JSON`. By +default, it passes the return value through the ``json.dumps`` standard +library function, and wraps the result in a response object. It also sets +the response content-type to ``application/json``. Here's an example of a view that returns a dictionary. Since the ``json`` renderer is specified in the configuration for this view, the view will @@ -207,11 +209,11 @@ representing the JSON serialization of the return value: '{"content": "Hello!"}' The return value needn't be a dictionary, but the return value must contain -values serializable by ``json.dumps``. +values serializable by the configured serializer (by default ``json.dumps``). .. note:: - Extra arguments can be passed to ``json.dumps`` by overriding the default + Extra arguments can be passed to the serializer by overriding the default ``json`` renderer. See :class:`pyramid.renderers.JSON` and :ref:`adding_and_overriding_renderers` for more information. @@ -223,9 +225,9 @@ You can configure a view to use the JSON renderer by naming ``json`` as the :linenos: config.add_view('myproject.views.hello_world', - name='hello', - context='myproject.resources.Hello', - renderer='json') + name='hello', + context='myproject.resources.Hello', + renderer='json') Views which use the JSON renderer can vary non-body response attributes by using the api of the ``request.response`` attribute. See @@ -238,8 +240,9 @@ Serializing Custom Objects Custom objects can be made easily JSON-serializable in Pyramid by defining a ``__json__`` method on the object's class. This method should return values -natively serializable by ``json.dumps`` (such as ints, lists, dictionaries, -strings, and so forth). +natively JSON-serializable (such as ints, lists, dictionaries, strings, and +so forth). It should accept a single additional argument, ``request``, which +will be the active request object at render time. .. code-block:: python :linenos: @@ -250,7 +253,7 @@ strings, and so forth). def __init__(self, x): self.x = x - def __json__(self): + def __json__(self, request): return {'x':self.x} @view_config(renderer='json') @@ -260,20 +263,40 @@ strings, and so forth). # the JSON value returned by ``objects`` will be: # [{"x": 1}, {"x": 2}] -.. note:: +If you aren't the author of the objects being serialized, it won't be +possible (or at least not reasonable) to add a custom ``__json__`` method to +to their classes in order to influence serialization. If the object passed +to the renderer is not a serializable type, and has no ``__json__`` method, +usually a :exc:`TypeError` will be raised during serialization. You can +change this behavior by creating a custom JSON renderer and adding adapters +to handle custom types. The renderer will attempt to adapt non-serializable +objects using the registered adapters. A short example follows: + +.. code-block:: python + :linenos: - Honoring the ``__json__`` method of custom objects is a feature new in - Pyramid 1.4. + from pyramid.renderers import JSON -.. warning:: + json_renderer = JSON() + def datetime_adapter(obj, request): + return obj.isoformat() + json_renderer.add_adapter(datetime.datetime, datetime_adapter) + + # then during configuration .... + config = Configurator() + config.add_renderer('json', json_renderer) + +The adapter should accept two arguments: the object needing to be serialized +and ``request``, which will be the current request object at render time. +The adapter should raise a :exc:`TypeError` if it can't determine what to do +with the object. + +See :class:`pyramid.renderers.JSON` and +:ref:`adding_and_overriding_renderers` for more information. + +.. note:: - The machinery which performs the ``__json__`` method-calling magic is in - the :class:`pyramid.renderers.ObjectJSONEncoder` class. This class will - be used for encoding any non-basic Python object when you use the default - ```json`` or ``jsonp`` renderers. But if you later define your own custom - JSON renderer and pass it a "cls" argument signifying a different encoder, - the encoder you pass will override Pyramid's use of - :class:`pyramid.renderers.ObjectJSONEncoder`. + Serializing custom objects is a feature new in Pyramid 1.4. .. index:: pair: renderer; JSONP diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index 6ff9e3dea..1aa1b6341 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -151,13 +151,12 @@ Using Alternate Session Factories --------------------------------- At the time of this writing, exactly one alternate session factory -implementation exists, named ``pyramid_beaker``. This is a session -factory that uses the `Beaker <http://beaker.groovie.org/>`_ library -as a backend. Beaker has support for file-based sessions, database -based sessions, and encrypted cookie-based sessions. See -`http://github.com/Pylons/pyramid_beaker -<http://github.com/Pylons/pyramid_beaker>`_ for more information about -``pyramid_beaker``. +implementation exists, named ``pyramid_beaker``. This is a session factory +that uses the `Beaker <http://beaker.groovie.org/>`_ library as a backend. +Beaker has support for file-based sessions, database based sessions, and +encrypted cookie-based sessions. See `the pyramid_beaker documentation +<http://docs.pylonsproject.org/projects/pyramid_beaker/en/latest/>`_ for more +information about ``pyramid_beaker``. .. index:: single: session factory (custom) diff --git a/docs/narr/startup.rst b/docs/narr/startup.rst index 8e28835af..f5c741f52 100644 --- a/docs/narr/startup.rst +++ b/docs/narr/startup.rst @@ -42,8 +42,8 @@ Here's a high-level time-ordered overview of what happens when you press ``[pipeline:main]``, or ``[composite:main]`` in the ``.ini`` file. This section represents the configuration of a :term:`WSGI` application that will be served. If you're using a simple application (e.g. - ``[app:main]``), the application :term:`entry point` or :term:`dotted - Python name` will be named on the ``use=`` line within the section's + ``[app:main]``), the application's ``paste.app_factory`` :term:`entry + point` will be named on the ``use=`` line within the section's configuration. If, instead of a simple application, you're using a WSGI :term:`pipeline` (e.g. a ``[pipeline:main]`` section), the application named on the "last" element will refer to your :app:`Pyramid` application. @@ -59,11 +59,11 @@ Here's a high-level time-ordered overview of what happens when you press system for this application. See :ref:`logging_config` for more information. -#. The application's *constructor* named by the entry point reference or - dotted Python name on the ``use=`` line of the section representing your - :app:`Pyramid` application is passed the key/value parameters mentioned - within the section in which it's defined. The constructor is meant to - return a :term:`router` instance, which is a :term:`WSGI` application. +#. The application's *constructor* named by the entry point reference on the + ``use=`` line of the section representing your :app:`Pyramid` application + is passed the key/value parameters mentioned within the section in which + it's defined. The constructor is meant to return a :term:`router` + instance, which is a :term:`WSGI` application. For :app:`Pyramid` applications, the constructor will be a function named ``main`` in the ``__init__.py`` file within the :term:`package` in which diff --git a/docs/narr/templates.rst b/docs/narr/templates.rst index 9db0b1c4d..860010a1a 100644 --- a/docs/narr/templates.rst +++ b/docs/narr/templates.rst @@ -714,6 +714,22 @@ This template doesn't use any advanced features of Mako, only the :term:`renderer globals`. See the `the Mako documentation <http://www.makotemplates.org/>`_ to use more advanced features. +Using def inside Mako Templates +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use a def inside a Mako template, given a :term:`Mako` template file named +``foo.mak`` and a def named ``bar``, you can configure the template as a +:term:`renderer` like so: + +.. code-block:: python + :linenos: + + from pyramid.view import view_config + + @view_config(renderer='foo#bar.mak') + def my_view(request): + return {'project':'my project'} + .. index:: single: automatic reloading of templates single: template automatic reload diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index f036ce94e..ecf3d026a 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -547,7 +547,7 @@ add to your application: config.add_route('idea', 'ideas/{idea}') config.add_route('user', 'users/{user}') - config.add_route('tag', 'tags/{tags}') + config.add_route('tag', 'tags/{tag}') config.add_view('mypackage.views.idea_view', route_name='idea') config.add_view('mypackage.views.user_view', route_name='user') @@ -954,7 +954,7 @@ will be prepended with the first: from pyramid.config import Configurator def timing_include(config): - config.add_route('show_times', /times') + config.add_route('show_times', '/times') def users_include(config): config.add_route('show_users', '/show') @@ -966,7 +966,7 @@ will be prepended with the first: In the above configuration, the ``show_users`` route will still have an effective route pattern of ``/users/show``. The ``show_times`` route -however, will have an effective pattern of ``/users/timing/show_times``. +however, will have an effective pattern of ``/users/timing/times``. Route prefixes have no impact on the requirement that the set of route *names* in any given Pyramid configuration must be entirely unique. If you @@ -981,7 +981,7 @@ that may be added in the future. For example: from pyramid.config import Configurator def timing_include(config): - config.add_route('timing.show_times', /times') + config.add_route('timing.show_times', '/times') def users_include(config): config.add_route('users.show_users', '/show') diff --git a/docs/narr/views.rst b/docs/narr/views.rst index c3bbbd50e..f6ee9a8d5 100644 --- a/docs/narr/views.rst +++ b/docs/narr/views.rst @@ -572,6 +572,17 @@ No matter which view calling convention is used, the view code always has access to the context via ``request.context``. .. index:: + single: Passing in configuration variables + +.. _passing_in_config_variables: + +Passing Configuration Variables to a View +----------------------------------------- + +For information on passing a variable from the configuration .ini files to a +view, see :ref:`deployment_settings`. + +.. index:: single: Pylons-style controller dispatch Pylons-1.0-Style "Controller" Dispatch diff --git a/docs/tutorials/modwsgi/index.rst b/docs/tutorials/modwsgi/index.rst index c2baa5bd8..d11167344 100644 --- a/docs/tutorials/modwsgi/index.rst +++ b/docs/tutorials/modwsgi/index.rst @@ -73,9 +73,10 @@ commands and files. .. code-block:: python - from pyramid.paster import get_app - application = get_app( - '/Users/chrism/modwsgi/env/myapp/production.ini', 'main') + from pyramid.paster import get_app, setup_logging + ini_path = '/Users/chrism/modwsgi/env/myapp/production.ini' + setup_logging(ini_path) + application = get_app(ini_path, 'main') The first argument to ``get_app`` is the project configuration file name. It's best to use the ``production.ini`` file provided by your @@ -85,6 +86,10 @@ commands and files. ``application`` is important: mod_wsgi requires finding such an assignment when it opens the file. + The call to ``setup_logging`` initializes the standard library's + `logging` module to allow logging within your application. + See :ref:`logging_config`. + #. Make the ``pyramid.wsgi`` script executable. .. code-block:: text @@ -99,7 +104,8 @@ commands and files. .. code-block:: apache # Use only 1 Python sub-interpreter. Multiple sub-interpreters - # play badly with C extensions. + # play badly with C extensions. See + # http://stackoverflow.com/a/10558360/209039 WSGIApplicationGroup %{GLOBAL} WSGIPassAuthorization On WSGIDaemonProcess pyramid user=chrism group=staff threads=4 \ diff --git a/docs/tutorials/wiki/authorization.rst b/docs/tutorials/wiki/authorization.rst index e599e7086..9e0bf0f09 100644 --- a/docs/tutorials/wiki/authorization.rst +++ b/docs/tutorials/wiki/authorization.rst @@ -2,121 +2,186 @@ Adding Authorization ==================== -Our application currently allows anyone with access to the server to view, -edit, and add pages to our wiki. For purposes of demonstration we'll change -our application to allow people who are members of a *group* named -``group:editors`` to add and edit wiki pages but we'll continue allowing -anyone with access to the server to view pages. :app:`Pyramid` provides -facilities for :term:`authorization` and :term:`authentication`. We'll make -use of both features to provide security to our application. - -We will add an :term:`authentication policy` and an -:term:`authorization policy` to our :term:`application -registry`, add a ``security.py`` module and give our :term:`root` -resource an :term:`ACL`. - -Then we will add ``login`` and ``logout`` views, and modify the -existing views to make them return a ``logged_in`` flag to the -renderer and add :term:`permission` declarations to their ``view_config`` -decorators. - -Finally, we will add a ``login.pt`` template and change the existing -``view.pt`` and ``edit.pt`` to show a "Logout" link when not logged in. - -The source code for this tutorial stage can be browsed via +:app:`Pyramid` provides facilities for :term:`authentication` and +:term:`authorization`. We'll make use of both features to provide security +to our application. Our application currently allows anyone with access to +the server to view, edit, and add pages to our wiki. We'll change that +to allow only people who are members of a *group* named ``group:editors`` +to add and edit wiki pages but we'll continue allowing +anyone with access to the server to view pages. + +We will also add a login page and a logout link on all the +pages. The login page will be shown when a user is denied +access to any of the views that require a permission, instead of +a default "403 Forbidden" page. + +We will implement the access control with the following steps: + +* Add users and groups (``security.py``, a new module). +* Add an :term:`ACL` (``models.py``). +* Add an :term:`authentication policy` and an :term:`authorization policy` + (``__init__.py``). +* Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` + views (``views.py``). + +Then we will add the login and logout feature: + +* Add ``login`` and ``logout`` views (``views.py``). +* Add a login template (``login.pt``). +* Make the existing views return a ``logged_in`` flag to the renderer (``views.py``). +* Add a "Logout" link to be shown when logged in and viewing or editing a page + (``view.pt``, ``edit.pt``). + +The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki/src/authorization/ <http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki/src/authorization/>`_. -Add Authentication and Authorization Policies -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Access Control +-------------- -We'll change our package's ``__init__.py`` file to enable an -``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` to enable -declarative security checking. We need to import the new policies: +Add users and groups +~~~~~~~~~~~~~~~~~~~~ -.. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 4-5,8 +Create a new ``tutorial/tutorial/security.py`` module with the +following content: + +.. literalinclude:: src/authorization/tutorial/security.py :linenos: :language: python -Then, we'll add those policies to the configuration: +The ``groupfinder`` function accepts a userid and a request and +returns one of these values: -.. literalinclude:: src/authorization/tutorial/__init__.py - :lines: 17-22 +- If the userid exists in the system, it will return a + sequence of group identifiers (or an empty sequence if the user + isn't a member of any groups). +- If the userid *does not* exist in the system, it will + return ``None``. + +For example, ``groupfinder('editor', request )`` returns ['group:editor'], +``groupfinder('viewer', request)`` returns [], and ``groupfinder('admin', request)`` +returns ``None``. We will use ``groupfinder()`` as an :term:`authentication policy` +"callback" that will provide the :term:`principal` or principals +for a user. + +In a production system, user and group +data will most often come from a database, but here we use "dummy" +data to represent user and groups sources. + +Add an ACL +~~~~~~~~~~ + +Open ``tutorial/tutorial/models.py`` and add the following import +statement at the head: + +.. literalinclude:: src/authorization/tutorial/models.py + :lines: 4-7 :linenos: :language: python -Note that the creation of an ``AuthTktAuthenticationPolicy`` requires two -arguments: ``secret`` and ``callback``. ``secret`` is a string representing -an encryption key used by the "authentication ticket" machinery represented -by this policy: it is required. The ``callback`` is a reference to a -``groupfinder`` function in the ``tutorial`` package's ``security.py`` file. -We haven't added that module yet, but we're about to. +Add the following lines to the ``Wiki`` class: + +.. literalinclude:: src/authorization/tutorial/models.py + :lines: 9-13 + :linenos: + :emphasize-lines: 4-5 + :language: python -When you're done, your ``__init__.py`` will -look like so: +We import :data:`~pyramid.security.Allow`, an action that +means that permission is allowed:, and +:data:`~pyramid.security.Everyone`, a special :term:`principal` +that is associated to all requests. Both are used in the +:term:`ACE` entries that make up the ACL. + +The ACL is a list that needs to be named `__acl__` and be an +attribute of a class. We define an :term:`ACL` with two +:term:`ACE` entries: the first entry allows any user the `view` +permission. The second entry allows the ``group:editors`` +principal the `edit` permission. + +The ``Wiki`` class that contains the ACL is the :term:`resource` +constructor for the :term:`root` resource, which is +a ``Wiki`` instance. The ACL is +provided to each view in the :term:`context` of the request, as +the ``context`` attribute. + +It's only happenstance that we're assigning this ACL at class scope. An ACL +can be attached to an object *instance* too; this is how "row level security" +can be achieved in :app:`Pyramid` applications. We actually only need *one* +ACL for the entire system, however, because our security requirements are +simple, so this feature is not demonstrated. See +:ref:`assigning_acls` for more information about what an +:term:`ACL` represents. + +Add Authentication and Authorization Policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Open ``tutorial/__init__.py`` and +add these import statements: .. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 4-5,8 :linenos: :language: python -Add ``security.py`` -~~~~~~~~~~~~~~~~~~~ - -Add a ``security.py`` module within your package (in the same -directory as ``__init__.py``, ``views.py``, etc.) with the following -content: +Now add those policies to the configuration: -.. literalinclude:: src/authorization/tutorial/security.py +.. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 17-22 :linenos: + :emphasize-lines: 1-3,5-6 :language: python -The ``groupfinder`` function defined here is an :term:`authentication policy` -"callback"; it is a callable that accepts a userid and a request. If the -userid exists in the system, the callback will return a sequence of group -identifiers (or an empty sequence if the user isn't a member of any groups). -If the userid *does not* exist in the system, the callback will return -``None``. In a production system, user and group data will most often come -from a database, but here we use "dummy" data to represent user and groups -sources. Note that the ``editor`` user is a member of the ``group:editors`` -group in our dummy group data (the ``GROUPS`` data structure). - -Give Our Root Resource an ACL -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +(Only the highlighted lines need to be added.) + +We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an auth +ticket that may be included in the request, and an ``ACLAuthorizationPolicy`` +that uses an ACL to determine the allow or deny outcome for a view. -We need to give our root resource object an :term:`ACL`. This ACL will be -sufficient to provide enough information to the :app:`Pyramid` security -machinery to challenge a user who doesn't have appropriate credentials when -he attempts to invoke the ``add_page`` or ``edit_page`` views. +Note that the +:class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor +accepts two arguments: ``secret`` and ``callback``. ``secret`` is a string +representing an encryption key used by the "authentication ticket" machinery +represented by this policy: it is required. The ``callback`` is the +``groupfinder()`` function that we created before. -We need to perform some imports at module scope in our ``models.py`` file: +Add permission declarations +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Add a ``permission='edit'`` parameter to the ``@view_config`` +decorator for ``add_page()`` and ``edit_page()``, for example: .. code-block:: python :linenos: + :emphasize-lines: 2 + + @view_config(route_name='add_page', renderer='templates/edit.pt', + permission='edit') + +(Only the highlighted line needs to be added.) - from pyramid.security import Allow - from pyramid.security import Everyone +The result is that only users who possess the ``edit`` +permission at the time of the request may invoke those two views. -Our root resource object is a ``Wiki`` instance. We'll add the following -line at class scope to our ``Wiki`` class: +Add a ``permission='view'`` parameter to the ``@view_config`` +decorator for ``view_wiki()`` and ``view_page()``, like this: .. code-block:: python :linenos: + :emphasize-lines: 2 - __acl__ = [ (Allow, Everyone, 'view'), - (Allow, 'group:editors', 'edit') ] + @view_config(route_name='view_page', renderer='templates/view.pt', + permission='view') -It's only happenstance that we're assigning this ACL at class scope. An ACL -can be attached to an object *instance* too; this is how "row level security" -can be achieved in :app:`Pyramid` applications. We actually only need *one* -ACL for the entire system, however, because our security requirements are -simple, so this feature is not demonstrated. +(Only the highlighted line needs to be added.) -Our resulting ``models.py`` file will now look like so: +This allows anyone to invoke these two views. -.. literalinclude:: src/authorization/tutorial/models.py - :linenos: - :language: python +We are done with the changes needed to control access. The +changes that follow will add the login and logout feature. + +Login, Logout +------------- Add Login and Logout Views ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -124,124 +189,103 @@ Add Login and Logout Views We'll add a ``login`` view which renders a login form and processes the post from the login form, checking credentials. -We'll also add a ``logout`` view to our application and provide a link -to it. This view will clear the credentials of the logged in user and -redirect back to the front page. +We'll also add a ``logout`` view callable to our application and +provide a link to it. This view will clear the credentials of the +logged in user and redirect back to the front page. -We'll add these views to the existing ``views.py`` file we have in our -project. Here's what the ``login`` view callable will look like: +Add the following import statements to the +head of ``tutorial/tutorial/views.py``: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 86-113 + :lines: 6-13,15-17 :linenos: + :emphasize-lines: 3,6-9,11 :language: python -Here's what the ``logout`` view callable will look like: +(Only the highlighted lines need to be added.) + +:meth:`~pyramid.view.forbidden_view_config` will be used +to customize the default 403 Forbidden page. +:meth:`~pyramid.security.remember` and +:meth:`~pyramid.security.forget` help to create and +expire an auth ticket cookie. + +Now add the ``login`` and ``logout`` views: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 115-119 + :lines: 87-120 :linenos: :language: python -Note that the ``login`` view callable has *two* view configuration -decorators. The order of these decorators is unimportant. Each just adds a -different :term:`view configuration` for the ``login`` view callable. - -The first view configuration decorator configures the ``login`` view callable -so it will be invoked when someone visits ``/login`` (when the context is a -Wiki and the view name is ``login``). The second decorator, named -``forbidden_view_config`` specifies a :term:`forbidden view`. This -configures our login view to be presented to the user when :app:`Pyramid` -detects that a view invocation can not be authorized. Because we've -configured a forbidden view, the ``login`` view callable will be invoked -whenever one of our users tries to execute a view callable that they are not -allowed to invoke as determined by the :term:`authorization policy` in use. -In our application, for example, this means that if a user has not logged in, -and he tries to add or edit a Wiki page, he will be shown the login form. -Before being allowed to continue on to the add or edit form, he will have to -provide credentials that give him permission to add or edit via this login -form. - -Note that we're relying on some additional imports within the bodies of these -views (e.g. ``remember`` and ``forget``). We'll see a rendering of the -entire views.py file a little later here to show you where those come from. - -Change Existing Views -~~~~~~~~~~~~~~~~~~~~~ - -In order to indicate whether the current user is logged in, we need to change -each of our ``view_page``, ``edit_page`` and ``add_page`` views in -``views.py`` to pass a "logged in" parameter into its template. We'll add -something like this to each view body: +``login()`` is decorated with two decorators: -.. code-block:: python +- a ``@view_config`` decorator which associates it with the + ``login`` route and makes it visible when we visit ``/login``, +- a ``@forbidden_view_config`` decorator which turns it into + an :term:`forbidden view`. ``login()`` will be invoked + when a users tries to execute a view callable that + they are not allowed to. For example, if a user has not logged in + and tries to add or edit a Wiki page, he will be shown the + login form before being allowed to continue on. + +The order of these two :term:`view configuration` decorators +is unimportant. + +``logout()`` is decorated with a ``@view_config`` decorator +which associates it with the ``logout`` route. It will be +invoked when we visit ``/logout``. + +Add the ``login.pt`` Template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create ``tutorial/tutorial/templates/login.pt`` with the following +content: + +.. literalinclude:: src/authorization/tutorial/templates/login.pt + :language: xml + +The above template is referred to within the login view we just +added to ``views.py``. + +Return a logged_in flag to the renderer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Add the following line to the import at the head of +``tutorial/tutorial/views.py``: + +.. literalinclude:: src/authorization/tutorial/views.py + :lines: 11-15 :linenos: + :emphasize-lines: 4 + :language: python - from pyramid.security import authenticated_userid - logged_in = authenticated_userid(request) +(Only the highlighted line needs to be added.) -We'll then change the return value of each view that has an associated -``renderer`` to pass the resulting ``logged_in`` value to the -template. For example: +Add a ``logged_in`` parameter to the return value of +``view_page()``, ``edit_page()`` and ``add_page()``, +like this: .. code-block:: python :linenos: + :emphasize-lines: 4 - return dict(page = context, + return dict(page = page, content = content, - logged_in = logged_in, - edit_url = edit_url) - -Add ``permission`` Declarations to our ``view_config`` Decorators -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To protect each of our views with a particular permission, we need to pass a -``permission`` argument to each of our :class:`pyramid.view.view_config` -decorators. To do so, within ``views.py``: - -- We add ``permission='view'`` to the decorator attached to the - ``view_wiki`` and ``view_page`` view functions. This makes the - assertion that only users who possess the ``view`` permission - against the context resource at the time of the request may - invoke these views. We've granted - :data:`pyramid.security.Everyone` the view permission at the - root model via its ACL, so everyone will be able to invoke the - ``view_wiki`` and ``view_page`` views. - -- We add ``permission='edit'`` to the decorator attached to the - ``add_page`` and ``edit_page`` view functions. This makes the - assertion that only users who possess the effective ``edit`` - permission against the context resource at the time of the - request may invoke these views. We've granted the - ``group:editors`` principal the ``edit`` permission at the - root model via its ACL, so only a user whom is a member of - the group named ``group:editors`` will able to invoke the - ``add_page`` or ``edit_page`` views. We've likewise given - the ``editor`` user membership to this group via the - ``security.py`` file by mapping him to the ``group:editors`` - group in the ``GROUPS`` data structure (``GROUPS - = {'editor':['group:editors']}``); the ``groupfinder`` - function consults the ``GROUPS`` data structure. This means - that the ``editor`` user can add and edit pages. + edit_url = edit_url, + logged_in = authenticated_userid(request)) -Add the ``login.pt`` Template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Add a ``login.pt`` template to your templates directory. It's -referred to within the login view we just added to ``views.py``. +(Only the highlighted line needs to be added.) -.. literalinclude:: src/authorization/tutorial/templates/login.pt - :language: xml +:meth:`~pyramid.security.authenticated_userid()` will return None +if the user is not authenticated, or some user id it the user +is authenticated. -Change ``view.pt`` and ``edit.pt`` +Add a "Logout" link when logged in ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We'll also need to change our ``edit.pt`` and ``view.pt`` templates to -display a "Logout" link if someone is logged in. This link will -invoke the logout view. - -To do so we'll add this to both templates within the ``<div id="right" -class="app-welcome align-right">`` div: +Open ``tutorial/tutorial/templates/edit.pt`` and +``tutorial/tutorial/templates/view.pt`` and add this within the +``<div id="right" class="app-welcome align-right">`` div: .. code-block:: xml @@ -249,57 +293,96 @@ class="app-welcome align-right">`` div: <a href="${request.application_url}/logout">Logout</a> </span> -See Our Changes To ``views.py`` and our Templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The attribute ``tal:condition="logged_in"`` will make the element be +included when ``logged_in`` is any user id. The link will invoke +the logout view. The above element will not be included if ``logged_in`` +is ``None``, such as when a user is not authenticated. + +Seeing Our Changes +------------------ + +Our ``tutorial/tutorial/__init__.py`` will look something like this +when we're done: + +.. literalinclude:: src/authorization/tutorial/__init__.py + :linenos: + :emphasize-lines: 4-5,8,17-19,21-22 + :language: python + +(Only the highlighted lines need to be added.) + +Our ``tutorial/tutorial/models.py`` will look something like this +when we're done: + +.. literalinclude:: src/authorization/tutorial/models.py + :linenos: + :emphasize-lines: 4-7,12-13 + :language: python -Our ``views.py`` module will look something like this when we're done: +(Only the highlighted lines need to be added.) + +Our ``tutorial/tutorial/views.py`` will look something like this +when we're done: .. literalinclude:: src/authorization/tutorial/views.py :linenos: + :emphasize-lines: 8,11-15,17,24,29,48,52,68,72,80,82-120 :language: python -Our ``edit.pt`` template will look something like this when we're done: +(Only the highlighted lines need to be added.) + +Our ``tutorial/tutorial/templates/edit.pt`` template will look +something like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt :linenos: + :emphasize-lines: 41-43 :language: xml -Our ``view.pt`` template will look something like this when we're done: +(Only the highlighted lines need to be added.) + +Our ``tutorial/tutorial/templates/view.pt`` template will look +something like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/view.pt :linenos: + :emphasize-lines: 41-43 :language: xml -View the Application in a Browser -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +(Only the highlighted lines need to be added.) -We can finally examine our application in a browser. The views we'll try are -as follows: +Viewing the Application in a Browser +------------------------------------ -- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` - view. This always redirects to the ``view_page`` view of the ``FrontPage`` - page resource. It is executable by any user. +We can finally examine our application in a browser (See +:ref:`wiki-start-the-application`). Launch a browser and visit +each of the following URLs, check that the result is as expected: -- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes the - ``view_page`` view of the ``FrontPage`` Page resource. This is because +- ``http://localhost:6543/`` invokes the + ``view_wiki`` view. This always redirects to the ``view_page`` view + of the ``FrontPage`` Page resource. It is executable by any user. + +- ``http://localhost:6543/FrontPage`` invokes + the ``view_page`` view of the ``FrontPage`` Page resource. This is because it's the :term:`default view` (a view without a ``name``) for ``Page`` resources. It is executable by any user. -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser invokes - the edit view for the ``FrontPage`` Page resource. It is executable by - only the ``editor`` user. If a different user (or the anonymous user) - invokes it, a login form will be displayed. Supplying the credentials with - the username ``editor``, password ``editor`` will show the edit page form - being displayed. +- ``http://localhost:6543/FrontPage/edit_page`` + invokes the edit view for the FrontPage object. It is executable by + only the ``editor`` user. If a different user (or the anonymous + user) invokes it, a login form will be displayed. Supplying the + credentials with the username ``editor``, password ``editor`` will + display the edit page form. -- Visiting ``http://localhost:6543/add_page/SomePageName`` in a - browser invokes the add view for a page. It is executable by only +- ``http://localhost:6543/add_page/SomePageName`` + invokes the add view for a page. It is executable by only the ``editor`` user. If a different user (or the anonymous user) invokes it, a login form will be displayed. Supplying the credentials with the username ``editor``, password ``editor`` will - show the edit page form being displayed. + display the edit page form. -- 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. +- 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. diff --git a/docs/tutorials/wiki/definingviews.rst b/docs/tutorials/wiki/definingviews.rst index 12bfa8b84..28cecb787 100644 --- a/docs/tutorials/wiki/definingviews.rst +++ b/docs/tutorials/wiki/definingviews.rst @@ -229,60 +229,63 @@ this: Adding Templates ================ -Most view callables we've added expected to be rendered via a -:term:`template`. The default templating systems in :app:`Pyramid` are -:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of :term:`ZPT`, -which is an XML-based templating language. Mako is a non-XML-based -templating language. Because we had to pick one, we chose Chameleon for this -tutorial. - -The templates we create will live in the ``templates`` directory of our +The ``view_page``, ``add_page`` and ``edit_page`` views that we've added +reference a :term:`template`. Each template is a :term:`Chameleon` :term:`ZPT` +template. These templates will live in the ``templates`` directory of our tutorial package. Chameleon templates must have a ``.pt`` extension to be recognized as such. The ``view.pt`` Template ------------------------ -The ``view.pt`` template is used for viewing a single Page. It is used by -the ``view_page`` view function. It should have a div that is "structure -replaced" with the ``content`` value provided by the view. It should also -have a link on the rendered page that points at the "edit" URL (the URL which -invokes the ``edit_page`` view for the page being viewed). - -Once we're done with the ``view.pt`` template, it will look a lot like -the below: +Create ``tutorial/tutorial/templates/view.pt`` and add the following +content: .. literalinclude:: src/views/tutorial/templates/view.pt + :linenos: :language: xml -.. note:: +This template is used by ``view_page()`` for displaying a single +wiki page. It includes: - The names available for our use in a template are always those that - are present in the dictionary returned by the view callable. But our - templates make use of a ``request`` object that none of our tutorial views - return in their dictionary. This value appears as if "by magic". - However, ``request`` is one of several names that are available "by - default" in a template when a template renderer is used. See - :ref:`chameleon_template_renderers` for more information about other names - that are available by default in a template when a template is used as a - renderer. +- A ``div`` element that is replaced with the ``content`` + value provided by the view (rows 45-47). ``content`` + contains HTML, so the ``structure`` keyword is used + to prevent escaping it (i.e. changing ">" to >, etc.) +- A link that points + at the "edit" URL which invokes the ``edit_page`` view for + the page being viewed (rows 49-51). The ``edit.pt`` Template ------------------------ -The ``edit.pt`` template is used for adding and editing a Page. It is used -by the ``add_page`` and ``edit_page`` view functions. It should display a -page containing a form that POSTs back to the "save_url" argument supplied by -the view. The form should have a "body" textarea field (the page data), and -a submit button that has the name "form.submitted". The textarea in the form -should be filled with any existing page data when it is rendered. - -Once we're done with the ``edit.pt`` template, it will look a lot like the -below: +Create ``tutorial/tutorial/templates/edit.pt`` and add the following +content: .. literalinclude:: src/views/tutorial/templates/edit.pt + :linenos: :language: xml +This template is used by ``add_page()`` and ``edit_page()`` for adding +and editing a wiki page. It displays +a page containing a form that includes: + +- A 10 row by 60 column ``textarea`` field named ``body`` that is filled + with any existing page data when it is rendered (rows 46-47). +- A submit button that has the name ``form.submitted`` (row 48). + +The form POSTs back to the "save_url" argument supplied +by the view (row 45). The view will use the ``body`` and +``form.submitted`` values. + +.. note:: Our templates use a ``request`` object that + none of our tutorial views return in their dictionary. + ``request`` is one of several + names that are available "by default" in a template when a template + renderer is used. See :ref:`chameleon_template_renderers` for + information about other names that are available by default + when a Chameleon template is used as a renderer. + Static Assets ------------- @@ -302,24 +305,25 @@ Viewing the Application in a Browser ==================================== We can finally examine our application in a browser (See -:ref:`wiki-start-the-application`). The views we'll try are as follows: +:ref:`wiki-start-the-application`). Launch a browser and visit +each of the following URLs, check that the result is as expected: -- Visiting ``http://localhost:6543/`` in a browser invokes the ``view_wiki`` +- ``http://localhost:6543/`` invokes the ``view_wiki`` view. This always redirects to the ``view_page`` view of the ``FrontPage`` Page resource. -- Visiting ``http://localhost:6543/FrontPage/`` in a browser invokes +- ``http://localhost:6543/FrontPage/`` invokes the ``view_page`` view of the front page resource. This is - because it's the *default view* (a view without a ``name``) for Page + because it's the :term:`default view` (a view without a ``name``) for Page resources. -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser +- ``http://localhost:6543/FrontPage/edit_page`` invokes the edit view for the ``FrontPage`` Page resource. -- Visiting ``http://localhost:6543/add_page/SomePageName`` in a - browser invokes the add view for a Page. +- ``http://localhost:6543/add_page/SomePageName`` + invokes the add view for a Page. - To generate an error, visit ``http://localhost:6543/add_page`` which - will generate an ``IndexError`` for the expression - ``request.subpath[0]``. You'll see an interactive traceback + will generate an ``IndexErrorr: tuple index out of range`` error. + You'll see an interactive traceback facility provided by :term:`pyramid_debugtoolbar`. diff --git a/docs/tutorials/wiki/design.rst b/docs/tutorials/wiki/design.rst index 2b613377a..c94612fb1 100644 --- a/docs/tutorials/wiki/design.rst +++ b/docs/tutorials/wiki/design.rst @@ -36,9 +36,16 @@ be used as the wiki home page. Views ----- -There will be four views to handle the normal operations of -viewing, editing and adding wiki pages. Two additional views -will handle the login and logout tasks related to security. +There will be three views to handle the normal operations of adding, +editing and viewing wiki pages, plus one view for the wiki front page. +Two templates will be used, one for viewing, and one for both for adding +and editing wiki pages. + +The default templating systems in :app:`Pyramid` are +:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of +:term:`ZPT`, which is an XML-based templating language. Mako is a +non-XML-based templating language. Because we had to pick one, +we chose Chameleon for this tutorial. Security -------- @@ -52,11 +59,11 @@ use to do this are below. - GROUPS, a dictionary mapping user names to a list of groups they belong to. -- *groupfinder*, an *authorization callback* that looks up +- ``groupfinder``, an *authorization callback* that looks up USERS and GROUPS. It will be provided in a new *security.py* file. -- An :term:`ACL` is attached to the root resource. Each +- An :term:`ACL` is attached to the root :term:`resource`. Each row below details an :term:`ACE`: +----------+----------------+----------------+ @@ -70,6 +77,8 @@ use to do this are below. - Permission declarations are added to the views to assert the security policies as each request is handled. +Two additional views and one template will handle the login and +logout tasks. Summary ------- diff --git a/docs/tutorials/wiki/installation.rst b/docs/tutorials/wiki/installation.rst index 63b30da5a..868c99dee 100644 --- a/docs/tutorials/wiki/installation.rst +++ b/docs/tutorials/wiki/installation.rst @@ -130,9 +130,10 @@ Preparation, Windows Make a Project ============== -Your next step is to create a project. :app:`Pyramid` supplies a variety of -scaffolds to generate sample projects. For this tutorial, we will use the -:term:`ZODB` -oriented scaffold named ``zodb``. +Your next step is to create a project. For this tutorial, we will use the +:term:`scaffold` named ``zodb``, which generates an application +that uses :term:`ZODB` and :term:`traversal`. :app:`Pyramid` +supplies a variety of scaffolds to generate sample projects. The below instructions assume your current working directory is the "virtualenv" named "pyramidtut". diff --git a/docs/tutorials/wiki/src/authorization/tutorial/models.py b/docs/tutorials/wiki/src/authorization/tutorial/models.py index 0a31c38be..582ff0d7e 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/models.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/models.py @@ -1,8 +1,10 @@ from persistent import Persistent from persistent.mapping import PersistentMapping -from pyramid.security import Allow -from pyramid.security import Everyone +from pyramid.security import ( + Allow, + Everyone, + ) class Wiki(PersistentMapping): __name__ = None diff --git a/docs/tutorials/wiki/src/authorization/tutorial/views.py b/docs/tutorials/wiki/src/authorization/tutorial/views.py index fcbe6fe25..21f12b31d 100644 --- a/docs/tutorials/wiki/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki/src/authorization/tutorial/views.py @@ -9,9 +9,9 @@ from pyramid.view import ( ) from pyramid.security import ( - authenticated_userid, remember, forget, + authenticated_userid, ) from .security import USERS @@ -20,12 +20,13 @@ from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='.models.Wiki', permission='view') +@view_config(context='.models.Wiki', + permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='.models.Page', - renderer='templates/view.pt', permission='view') +@view_config(context='.models.Page', renderer='templates/view.pt', + permission='view') def view_page(context, request): wiki = context.__parent__ @@ -43,10 +44,8 @@ def view_page(context, request): content = wikiwords.sub(check, content) edit_url = request.resource_url(context, 'edit_page') - logged_in = authenticated_userid(request) - return dict(page = context, content = content, edit_url = edit_url, - logged_in = logged_in) + logged_in = authenticated_userid(request)) @view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', @@ -65,9 +64,8 @@ def add_page(context, request): page.__name__ = name page.__parent__ = context - logged_in = authenticated_userid(request) - - return dict(page = page, save_url = save_url, logged_in = logged_in) + return dict(page = page, save_url = save_url, + logged_in = authenticated_userid(request)) @view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt', @@ -77,11 +75,9 @@ def edit_page(context, request): context.data = request.params['body'] return HTTPFound(location = request.resource_url(context)) - logged_in = authenticated_userid(request) - return dict(page = context, save_url = request.resource_url(context, 'edit_page'), - logged_in = logged_in) + logged_in = authenticated_userid(request)) @view_config(context='.models.Wiki', name='login', renderer='templates/login.pt') diff --git a/docs/tutorials/wiki/src/tests/tutorial/models.py b/docs/tutorials/wiki/src/tests/tutorial/models.py index 0a31c38be..582ff0d7e 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/models.py +++ b/docs/tutorials/wiki/src/tests/tutorial/models.py @@ -1,8 +1,10 @@ from persistent import Persistent from persistent.mapping import PersistentMapping -from pyramid.security import Allow -from pyramid.security import Everyone +from pyramid.security import ( + Allow, + Everyone, + ) class Wiki(PersistentMapping): __name__ = None diff --git a/docs/tutorials/wiki/src/tests/tutorial/views.py b/docs/tutorials/wiki/src/tests/tutorial/views.py index fcbe6fe25..21f12b31d 100644 --- a/docs/tutorials/wiki/src/tests/tutorial/views.py +++ b/docs/tutorials/wiki/src/tests/tutorial/views.py @@ -9,9 +9,9 @@ from pyramid.view import ( ) from pyramid.security import ( - authenticated_userid, remember, forget, + authenticated_userid, ) from .security import USERS @@ -20,12 +20,13 @@ from .models import Page # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(context='.models.Wiki', permission='view') +@view_config(context='.models.Wiki', + permission='view') def view_wiki(context, request): return HTTPFound(location=request.resource_url(context, 'FrontPage')) -@view_config(context='.models.Page', - renderer='templates/view.pt', permission='view') +@view_config(context='.models.Page', renderer='templates/view.pt', + permission='view') def view_page(context, request): wiki = context.__parent__ @@ -43,10 +44,8 @@ def view_page(context, request): content = wikiwords.sub(check, content) edit_url = request.resource_url(context, 'edit_page') - logged_in = authenticated_userid(request) - return dict(page = context, content = content, edit_url = edit_url, - logged_in = logged_in) + logged_in = authenticated_userid(request)) @view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt', @@ -65,9 +64,8 @@ def add_page(context, request): page.__name__ = name page.__parent__ = context - logged_in = authenticated_userid(request) - - return dict(page = page, save_url = save_url, logged_in = logged_in) + return dict(page = page, save_url = save_url, + logged_in = authenticated_userid(request)) @view_config(name='edit_page', context='.models.Page', renderer='templates/edit.pt', @@ -77,11 +75,9 @@ def edit_page(context, request): context.data = request.params['body'] return HTTPFound(location = request.resource_url(context)) - logged_in = authenticated_userid(request) - return dict(page = context, save_url = request.resource_url(context, 'edit_page'), - logged_in = logged_in) + logged_in = authenticated_userid(request)) @view_config(context='.models.Wiki', name='login', renderer='templates/login.pt') diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst index 8c1c50ff6..2ef55d15b 100644 --- a/docs/tutorials/wiki2/authorization.rst +++ b/docs/tutorials/wiki2/authorization.rst @@ -8,21 +8,31 @@ Adding Authorization :term:`authorization`. We'll make use of both features to provide security to our application. Our application currently allows anyone with access to the server to view, edit, and add pages to our wiki. We'll change that -to allow only people who possess a specific username (`editor`) -to add and edit wiki pages but we'll continue allowing anyone with access to -the server to view pages. +to allow only people who are members of a *group* named ``group:editors`` +to add and edit wiki pages but we'll continue allowing +anyone with access to the server to view pages. -We will do the following steps: +We will also add a login page and a logout link on all the +pages. The login page will be shown when a user is denied +access to any of the views that require a permission, instead of +a default "403 Forbidden" page. -* Add a :term:`root factory` with an :term:`ACL` (``models.py``). +We will implement the access control with the following steps: + +* Add users and groups (``security.py``, a new module). +* Add an :term:`ACL` (``models.py`` and + ``__init__.py``). * Add an :term:`authentication policy` and an :term:`authorization policy` (``__init__.py``). -* Add an authentication policy callback (new ``security.py`` module). -* Add ``login`` and ``logout`` views (``views.py``). * Add :term:`permission` declarations to the ``edit_page`` and ``add_page`` views (``views.py``). + +Then we will add the login and logout feature: + +* Add routes for /login and /logout (``__init__.py``). +* Add ``login`` and ``logout`` views (``views.py``). +* Add a login template (``login.pt``). * Make the existing views return a ``logged_in`` flag to the renderer (``views.py``). -* Add a login template (new ``login.pt``). * Add a "Logout" link to be shown when logged in and viewing or editing a page (``view.pt``, ``edit.pt``). @@ -30,39 +40,88 @@ The source code for this tutorial stage can be browsed at `http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki2/src/authorization/ <http://github.com/Pylons/pyramid/tree/1.3-branch/docs/tutorials/wiki2/src/authorization/>`_. -Adding A Root Factory -~~~~~~~~~~~~~~~~~~~~~ +Access Control +-------------- + +Add users and groups +~~~~~~~~~~~~~~~~~~~~ + +Create a new ``tutorial/tutorial/security.py`` module with the +following content: + +.. literalinclude:: src/authorization/tutorial/security.py + :linenos: + :language: python + +The ``groupfinder`` function accepts a userid and a request and +returns one of these values: + +- If the userid exists in the system, it will return a + sequence of group identifiers (or an empty sequence if the user + isn't a member of any groups). +- If the userid *does not* exist in the system, it will + return ``None``. -Open ``models.py`` and add the following statements: +For example, ``groupfinder('editor', request )`` returns ['group:editor'], +``groupfinder('viewer', request)`` returns [], and ``groupfinder('admin', request)`` +returns ``None``. We will use ``groupfinder()`` as an :term:`authentication policy` +"callback" that will provide the :term:`principal` or principals +for a user. + +In a production system, user and group +data will most often come from a database, but here we use "dummy" +data to represent user and groups sources. + +Add an ACL +~~~~~~~~~~ + +Open ``tutorial/tutorial/models.py`` and add the following import +statement at the head: .. literalinclude:: src/authorization/tutorial/models.py - :lines: 1-4,35-39 + :lines: 1-4 :linenos: :language: python -We're going to start to use a custom :term:`root factory` within our -``__init__.py`` file. The objects generated by the root factory will -be used as the :term:`context` of each request to our application. -Those context objects will be decorated with security -declarations. When we use a custom root factory to generate -our contexts, we can begin to make use of the declarative security -features of :app:`Pyramid`. - -We'll modify our ``__init__.py``, passing in a :term:`root factory` to our -:term:`Configurator` constructor. We'll point it at the new class we created -inside our ``models.py`` file. - -The ``RootFactory`` class we've just added will be used by :app:`Pyramid` to -construct a ``context`` object. The context is attached to the request -object passed to our view callables as the ``context`` attribute. - -The context object generated by our root factory will possess an ``__acl__`` -attribute that allows :data:`pyramid.security.Everyone` (a special principal) -to view all pages, while allowing only a :term:`principal` named -``group:editors`` to edit and add pages. The ``__acl__`` attribute attached -to a context is interpreted specially by :app:`Pyramid` as an access control -list during view callable execution. See :ref:`assigning_acls` for more -information about what an :term:`ACL` represents. +Add the following class definition: + +.. literalinclude:: src/authorization/tutorial/models.py + :lines: 35-39 + :linenos: + :language: python + +We import :data:`~pyramid.security.Allow`, an action that +means that permission is allowed:, and +:data:`~pyramid.security.Everyone`, a special :term:`principal` +that is associated to all requests. Both are used in the +:term:`ACE` entries that make up the ACL. + +The ACL is a list that needs to be named `__acl__` and be an +attribute of a class. We define an :term:`ACL` with two +:term:`ACE` entries: the first entry allows any user the `view` +permission. The second entry allows the ``group:editors`` +principal the `edit` permission. + +The ``RootFactory`` class that contains the ACL is a :term:`root factory`. +We need to associate it to our :app:`Pyramid` application, so the ACL is +provided to each view in the :term:`context` of the request, as +the ``context`` attribute. + +Open ``tutorial/tutorial/__init__.py`` and add a ``root_factory`` +parameter to our :term:`Configurator` constructor, that points to +the class we created above: + +.. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 19-20 + :linenos: + :emphasize-lines: 2 + :language: python + +(Only the highlighted line needs to be added.) + +We are now providing the ACL to the application. See +:ref:`assigning_acls` for more information about what an +:term:`ACL` represents. .. note:: @@ -71,23 +130,10 @@ information about what an :term:`ACL` represents. the ``factory`` argument to :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`. - -Add an Authorization Policy and an Authentication Policy -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We're going to be making several changes to our ``__init__.py`` file which -will help us configure an authorization policy. +Add Authentication and Authorization Policies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For any :app:`Pyramid` application to perform authorization, we need to add a -``security.py`` module (we'll do that shortly) and we'll need to change our -``__init__.py`` file to add an :term:`authentication policy` and an -:term:`authorization policy` which uses the ``security.py`` file for a -*callback*. - -We'll enable an ``AuthTktAuthenticationPolicy`` and an ``ACLAuthorizationPolicy`` -to implement declarative security checking. Open ``tutorial/__init__.py`` and +Open ``tutorial/__init__.py`` and add these import statements: .. literalinclude:: src/authorization/tutorial/__init__.py @@ -100,183 +146,173 @@ Now add those policies to the configuration: .. literalinclude:: src/authorization/tutorial/__init__.py :lines: 16-22 :linenos: + :emphasize-lines: 1-3,6-7 :language: python +(Only the highlighted lines need to be added.) + +We are enabling an ``AuthTktAuthenticationPolicy``, it is based in an auth +ticket that may be included in the request, and an ``ACLAuthorizationPolicy`` +that uses an ACL to determine the allow or deny outcome for a view. + Note that the :class:`pyramid.authentication.AuthTktAuthenticationPolicy` constructor accepts two arguments: ``secret`` and ``callback``. ``secret`` is a string representing an encryption key used by the "authentication ticket" machinery -represented by this policy: it is required. The ``callback`` is a -``groupfinder`` function in the current directory's ``security.py`` file. We -haven't added that module yet, but we're about to. +represented by this policy: it is required. The ``callback`` is the +``groupfinder()`` function that we created before. -Viewing Your Changes --------------------- +Add permission declarations +~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When we're done configuring a root factory, adding a authentication and -authorization policies, and adding routes for ``/login`` and ``/logout``, -your application's ``__init__.py`` will look like this: +Add a ``permission='edit'`` parameter to the ``@view_config`` +decorator for ``add_page()`` and ``edit_page()``, for example: -.. literalinclude:: src/authorization/tutorial/__init__.py +.. code-block:: python :linenos: - :emphasize-lines: 2-3,7,16-18,20-22,25-26 - :language: python + :emphasize-lines: 2 -Adding an authentication policy callback -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + @view_config(route_name='add_page', renderer='templates/edit.pt', + permission='edit') -Add a ``tutorial/security.py`` module within your package (in the same -directory as :file:`__init__.py`, :file:`views.py`, etc.) with the -following content: +(Only the highlighted line needs to be added.) -.. literalinclude:: src/authorization/tutorial/security.py +The result is that only users who possess the ``edit`` +permission at the time of the request may invoke those two views. + +Add a ``permission='view'`` parameter to the ``@view_config`` +decorator for ``view_wiki()`` and ``view_page()``, like this: + +.. code-block:: python + :linenos: + :emphasize-lines: 2 + + @view_config(route_name='view_page', renderer='templates/view.pt', + permission='view') + +(Only the highlighted line needs to be added.) + +This allows anyone to invoke these two views. + +We are done with the changes needed to control access. The +changes that follow will add the login and logout feature. + +Login, Logout +------------- + +Add routes for /login and /logout +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Go back to ``tutorial/tutorial/__init__.py`` and add these two +routes: + +.. literalinclude:: src/authorization/tutorial/__init__.py + :lines: 25-26 :linenos: :language: python -The ``groupfinder`` function defined here is an :term:`authentication policy` -"callback"; it is a callable that accepts a userid and a request. If -the userid exists in the system, the callback will return a sequence -of group identifiers (or an empty sequence if the user isn't a member -of any groups). If the userid *does not* exist in the system, the -callback will return ``None``. In a production system, user and group -data will most often come from a database, but here we use "dummy" -data to represent user and groups sources. Note that the ``editor`` -user is a member of the ``group:editors`` group in our dummy group -data (the ``GROUPS`` data structure). - -We've given the ``editor`` user membership to the ``group:editors`` by -mapping him to this group in the ``GROUPS`` data structure (``GROUPS = -{'editor':['group:editors']}``). Since the ``groupfinder`` function -consults the ``GROUPS`` data structure, this will mean that, as a -result of the ACL attached to the :term:`context` object returned by -the root factory, and the permission associated with the ``add_page`` -and ``edit_page`` views, the ``editor`` user should be able to add and -edit pages. - -Adding Login and Logout Views -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Add Login and Logout Views +~~~~~~~~~~~~~~~~~~~~~~~~~~ -To our ``views.py`` we'll add a ``login`` view callable which renders a login -form and processes the post from the login form, checking credentials. +We'll add a ``login`` view which renders a login form and processes +the post from the login form, checking credentials. We'll also add a ``logout`` view callable to our application and provide a link to it. This view will clear the credentials of the logged in user and redirect back to the front page. -The ``login`` view callable will look something like this: +Add the following import statements to the +head of ``tutorial/tutorial/views.py``: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 89-115 + :lines: 9-16,18,24-25 :linenos: + :emphasize-lines: 3,6-9,11 :language: python -The ``logout`` view callable will look something like this: +(Only the highlighted lines need to be added.) -.. literalinclude:: src/authorization/tutorial/views.py - :lines: 117-121 - :linenos: - :language: python +:meth:`~pyramid.view.forbidden_view_config` will be used +to customize the default 403 Forbidden page. +:meth:`~pyramid.security.remember` and +:meth:`~pyramid.security.forget` help to create and +expire an auth ticket cookie. -The ``login`` view callable is decorated with two decorators, a -``@view_config`` decorator, which associates it with the ``login`` -route, and a ``@forbidden_view_config`` decorator which turns it in to -an :term:`exception view`. The one which associates it with the -``login`` route makes it visible when we visit ``/login``. The other -one makes it a :term:`forbidden view`. The forbidden view is -displayed whenever Pyramid or your application raises an -:class:`pyramid.httpexceptions.HTTPForbidden` exception. In this -case, we'll be relying on the forbidden view to show the login form -whenver someone attempts to execute an action which they're not yet -authorized to perform. - -The ``logout`` view callable is decorated with a ``@view_config`` decorator -which associates it with the ``logout`` route. This makes it visible when we -visit ``/logout``. - -We'll need to import some stuff to service the needs of these two functions: -the ``pyramid.view.forbidden_view_config`` class, a number of values from the -``pyramid.security`` module, and a value from our newly added -``tutorial.security`` package. Add the following import statements to the -head of the ``views.py`` file: +Now add the ``login`` and ``logout`` views: .. literalinclude:: src/authorization/tutorial/views.py - :lines: 9-18,24-25 + :lines: 91-123 :linenos: :language: python -Changing Existing Views -~~~~~~~~~~~~~~~~~~~~~~~ +``login()`` is decorated with two decorators: -Add permision declarations --------------------------- +- a ``@view_config`` decorator which associates it with the + ``login`` route and makes it visible when we visit ``/login``, +- a ``@forbidden_view_config`` decorator which turns it into + an :term:`forbidden view`. ``login()`` will be invoked + when a users tries to execute a view callable that + they are not allowed to. For example, if a user has not logged in + and tries to add or edit a Wiki page, he will be shown the + login form before being allowed to continue on. -Then we need to change each of our ``view_page``, ``edit_page`` and -``add_page`` view callables in ``views.py``. Within each of these views, -we'll need to pass a "logged in" parameter to its template. We'll add -something like this to each view body: +The order of these two :term:`view configuration` decorators +is unimportant. -.. code-block:: python - :linenos: +``logout()`` is decorated with a ``@view_config`` decorator +which associates it with the ``logout`` route. It will be +invoked when we visit ``/logout``. - from pyramid.security import authenticated_userid - logged_in = authenticated_userid(request) +Add the ``login.pt`` Template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Return a logged_in flag to the renderer ---------------------------------------- +Create ``tutorial/tutorial/templates/login.pt`` with the following +content: -We'll then change the return value of these views to pass the resulting -``logged_in`` value to the template, e.g.: +.. literalinclude:: src/authorization/tutorial/templates/login.pt + :language: xml -.. code-block:: python - :linenos: +The above template is referred to within the login view we just +added to ``views.py``. - return dict(page = page, - content = content, - logged_in = logged_in, - edit_url = edit_url) +Return a logged_in flag to the renderer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We'll also need to add a ``permission`` value to the ``@view_config`` -decorator for each of the ``add_page`` and ``edit_page`` view callables. For -each, we'll add ``permission='edit'``, for example: +Add the following line to the import at the head of +``tutorial/tutorial/views.py``: -.. code-block:: python +.. literalinclude:: src/authorization/tutorial/views.py + :lines: 14-18 :linenos: + :emphasize-lines: 4 + :language: python - @view_config(route_name='edit_page', renderer='templates/edit.pt', - permission='edit') +(Only the highlighted line needs to be added.) -See the ``permission='edit'`` we added there? This indicates that the view -callables which these views reference cannot be invoked without the -authenticated user possessing the ``edit`` permission with respect to the -current :term:`context`. +Add a ``logged_in`` parameter to the return value of +``view_page()``, ``edit_page()`` and ``add_page()``, +like this: -Adding these ``permission`` arguments causes Pyramid to make the -assertion that only users who possess the effective ``edit`` -permission at the time of the request may invoke those two views. -We've granted the ``group:editors`` :term:`principal` the ``edit`` -permission in the :term:`root factory` via its ACL, so only a user who -is a member of the group named ``group:editors`` will be able to -invoke the views associated with the ``add_page`` or ``edit_page`` -routes. +.. code-block:: python + :linenos: + :emphasize-lines: 4 -Adding the ``login.pt`` Template -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + return dict(page = page, + content = content, + edit_url = edit_url, + logged_in = authenticated_userid(request)) -Add a ``login.pt`` template to your templates directory. It's -referred to within the login view we just added to ``views.py``. +(Only the highlighted line needs to be added.) -.. literalinclude:: src/authorization/tutorial/templates/login.pt - :language: xml +:meth:`~pyramid.security.authenticated_userid()` will return None +if the user is not authenticated, or some user id it the user +is authenticated. Add a "Logout" link when logged in ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We'll also need to change our ``edit.pt`` and ``view.pt`` templates to -display a "Logout" link if someone is logged in. This link will -invoke the logout view. - -To do so we'll add this to both templates within the ``<div id="right" -class="app-welcome align-right">`` div: +Open ``tutorial/tutorial/templates/edit.pt`` and +``tutorial/tutorial/templates/view.pt`` and add this within the +``<div id="right" class="app-welcome align-right">`` div: .. code-block:: xml @@ -284,36 +320,66 @@ class="app-welcome align-right">`` div: <a href="${request.application_url}/logout">Logout</a> </span> -Seeing Our Changes To ``views.py`` and our Templates -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The attribute ``tal:condition="logged_in"`` will make the element be +included when ``logged_in`` is any user id. The link will invoke +the logout view. The above element will not be included if ``logged_in`` +is ``None``, such as when a user is not authenticated. + +Seeing Our Changes +------------------ + +Our ``tutorial/tutorial/__init__.py`` will look something like this +when we're done: + +.. literalinclude:: src/authorization/tutorial/__init__.py + :linenos: + :emphasize-lines: 2-3,7,16-18,20-22,25-26 + :language: python + +(Only the highlighted lines need to be added.) -Our ``views.py`` module will look something like this when we're done: +Our ``tutorial/tutorial/models.py`` will look something like this +when we're done: + +.. literalinclude:: src/authorization/tutorial/models.py + :linenos: + :emphasize-lines: 1-4,35-39 + :language: python + +(Only the highlighted lines need to be added.) + +Our ``tutorial/tutorial/views.py`` will look something like this +when we're done: .. literalinclude:: src/authorization/tutorial/views.py :linenos: - :emphasize-lines: 11,14-18,56,59,71,74,89-115,117-121 + :emphasize-lines: 11,14-18,31,37,58,61,73,76,88,91-117,119-123 :language: python (Only the highlighted lines need to be added.) -Our ``edit.pt`` template will look something like this when we're done: +Our ``tutorial/tutorial/templates/edit.pt`` template will look +something like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/edit.pt + :linenos: :emphasize-lines: 41-43 :language: xml (Only the highlighted lines need to be added.) -Our ``view.pt`` template will look something like this when we're done: +Our ``tutorial/tutorial/templates/view.pt`` template will look +something like this when we're done: .. literalinclude:: src/authorization/tutorial/templates/view.pt + :linenos: :emphasize-lines: 41-43 :language: xml (Only the highlighted lines need to be added.) Viewing the Application in a Browser -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------------------ We can finally examine our application in a browser (See :ref:`wiki2-start-the-application`). Launch a browser and visit diff --git a/docs/tutorials/wiki2/definingviews.rst b/docs/tutorials/wiki2/definingviews.rst index ac58e1e46..efb72230e 100644 --- a/docs/tutorials/wiki2/definingviews.rst +++ b/docs/tutorials/wiki2/definingviews.rst @@ -226,52 +226,63 @@ of the wiki page. Adding Templates ================ -The views we've added all reference a :term:`template`. Each template is a -:term:`Chameleon` :term:`ZPT` template. These templates will live in the -``templates`` directory of our tutorial package. +The ``view_page``, ``add_page`` and ``edit_page`` views that we've added +reference a :term:`template`. Each template is a :term:`Chameleon` :term:`ZPT` +template. These templates will live in the ``templates`` directory of our +tutorial package. Chameleon templates must have a ``.pt`` extension to be +recognized as such. The ``view.pt`` Template ------------------------ -The ``view.pt`` template is used for viewing a single wiki page. It -is used by the ``view_page`` view function. It should have a ``div`` -that is "structure replaced" with the ``content`` value provided by -the view. It should also have a link on the rendered page that points -at the "edit" URL (the URL which invokes the ``edit_page`` view for -the page being viewed). - -Once we're done with the ``view.pt`` template, it will look a lot like the -below: +Create ``tutorial/tutorial/templates/view.pt`` and add the following +content: .. literalinclude:: src/views/tutorial/templates/view.pt + :linenos: :language: xml -.. note:: The names available for our use in a template are always - those that are present in the dictionary returned by the view - callable. But our templates make use of a ``request`` object that - none of our tutorial views return in their dictionary. This value - appears as if "by magic". However, ``request`` is one of several - names that are available "by default" in a template when a template - renderer is used. See :ref:`chameleon_template_renderers` for more - information about other names that are available by default in a - template when a Chameleon template is used as a renderer. +This template is used by ``view_page()`` for displaying a single +wiki page. It includes: + +- A ``div`` element that is replaced with the ``content`` + value provided by the view (rows 45-47). ``content`` + contains HTML, so the ``structure`` keyword is used + to prevent escaping it (i.e. changing ">" to >, etc.) +- A link that points + at the "edit" URL which invokes the ``edit_page`` view for + the page being viewed (rows 49-51). The ``edit.pt`` Template ------------------------ -The ``edit.pt`` template is used for adding and editing a wiki page. It is -used by the ``add_page`` and ``edit_page`` view functions. It should display -a page containing a form that POSTs back to the "save_url" argument supplied -by the view. The form should have a "body" textarea field (the page data), -and a submit button that has the name "form.submitted". The textarea in the -form should be filled with any existing page data when it is rendered. - -Once we're done with the ``edit.pt`` template, it will look a lot like -the following: +Create ``tutorial/tutorial/templates/edit.pt`` and add the following +content: .. literalinclude:: src/views/tutorial/templates/edit.pt + :linenos: :language: xml +This template is used by ``add_page()`` and ``edit_page()`` for adding +and editing a wiki page. It displays +a page containing a form that includes: + +- A 10 row by 60 column ``textarea`` field named ``body`` that is filled + with any existing page data when it is rendered (rows 46-47). +- A submit button that has the name ``form.submitted`` (row 48). + +The form POSTs back to the "save_url" argument supplied +by the view (row 45). The view will use the ``body`` and +``form.submitted`` values. + +.. note:: Our templates use a ``request`` object that + none of our tutorial views return in their dictionary. + ``request`` is one of several + names that are available "by default" in a template when a template + renderer is used. See :ref:`chameleon_template_renderers` for + information about other names that are available by default + when a Chameleon template is used as a renderer. + Static Assets ------------- @@ -339,25 +350,24 @@ Viewing the Application in a Browser ==================================== We can finally examine our application in a browser (See -:ref:`wiki2-start-the-application`). The views we'll try are -as follows: +:ref:`wiki2-start-the-application`). Launch a browser and visit +each of the following URLs, check that the result is as expected: -- Visiting ``http://localhost:6543`` in a browser invokes the +- ``http://localhost:6543`` in a browser invokes the ``view_wiki`` view. This always redirects to the ``view_page`` view of the FrontPage page object. -- Visiting ``http://localhost:6543/FrontPage`` in a browser invokes +- ``http://localhost:6543/FrontPage`` in a browser invokes the ``view_page`` view of the front page page object. -- Visiting ``http://localhost:6543/FrontPage/edit_page`` in a browser +- ``http://localhost:6543/FrontPage/edit_page`` in a browser invokes the edit view for the front page object. -- Visiting ``http://localhost:6543/add_page/SomePageName`` in a +- ``http://localhost:6543/add_page/SomePageName`` in a browser invokes the add view for a page. -Try generating an error within the body of a view by adding code to -the top of it that generates an exception (e.g. ``raise -Exception('Forced Exception')``). Then visit the error-raising view -in a browser. You should see an interactive exception handler in the -browser which allows you to examine values in a post-mortem mode. +- To generate an error, visit ``http://localhost:6543/add_page`` which + will generate a ``NoResultFound: No row was found for one()`` error. + You'll see an interactive traceback facility provided + by :term:`pyramid_debugtoolbar`. diff --git a/docs/tutorials/wiki2/design.rst b/docs/tutorials/wiki2/design.rst index 1ff000549..deaf32ef6 100644 --- a/docs/tutorials/wiki2/design.rst +++ b/docs/tutorials/wiki2/design.rst @@ -20,7 +20,7 @@ Models We'll be using a SQLite database to hold our wiki data, and we'll be using :term:`SQLAlchemy` to access the data in this database. -Within the database, we define a single table named `tables`, whose elements +Within the database, we define a single table named `pages`, whose elements will store the wiki pages. There are two columns: `name` and `data`. URLs like ``/PageName`` will try to find an element in @@ -36,9 +36,16 @@ page. Views ----- -There will be four views to handle the normal operations of adding and -editing wiki pages, and viewing pages and the wiki front page. Two -additional views will handle the login and logout tasks related to security. +There will be three views to handle the normal operations of adding, +editing and viewing wiki pages, plus one view for the wiki front page. +Two templates will be used, one for viewing, and one for both for adding +and editing wiki pages. + +The default templating systems in :app:`Pyramid` are +:term:`Chameleon` and :term:`Mako`. Chameleon is a variant of +:term:`ZPT`, which is an XML-based templating language. Mako is a +non-XML-based templating language. Because we had to pick one, +we chose Chameleon for this tutorial. Security -------- @@ -67,6 +74,8 @@ use to do this are below. - Permission declarations are added to the views to assert the security policies as each request is handled. +Two additional views and one template will handle the login and +logout tasks. Summary ------- @@ -87,7 +96,7 @@ listed in the following table: | | | | | | | | | | | | +----------------------+-----------------------+-------------+------------+------------+ -| /edit_page/PageName | Display edit form | edit_page | edit.pt | edit | +| /PageName/edit_page | Display edit form | edit_page | edit.pt | edit | | | with existing | | | | | | content. | | | | | | | | | | diff --git a/docs/tutorials/wiki2/installation.rst b/docs/tutorials/wiki2/installation.rst index 4ee2728c2..6589a1557 100644 --- a/docs/tutorials/wiki2/installation.rst +++ b/docs/tutorials/wiki2/installation.rst @@ -67,10 +67,10 @@ Preparation, Windows Making a Project ================ -Your next step is to create a project. :app:`Pyramid` supplies a -variety of scaffolds to generate sample projects. We will use the -``alchemy`` scaffold, which generates an application -that uses :term:`SQLAlchemy` and :term:`URL dispatch`. +Your next step is to create a project. For this tutorial, we will use the +:term:`scaffold` named ``alchemy``, which generates an application +that uses :term:`SQLAlchemy` and :term:`URL dispatch`. :app:`Pyramid` +supplies a variety of scaffolds to generate sample projects. The below instructions assume your current working directory is the "virtualenv" named "pyramidtut". @@ -254,7 +254,7 @@ The output to your console should be something like this:: 2011-11-26 14:42:25,140 INFO [sqlalchemy.engine.base.Engine][MainThread] COMMIT -Success! You should now have a ``tutorial.db`` file in your current working +Success! You should now have a ``tutorial.sqlite`` file in your current working directory. This will be a SQLite database with a single table defined in it (``models``). diff --git a/docs/tutorials/wiki2/src/authorization/development.ini b/docs/tutorials/wiki2/src/authorization/development.ini index 38738f3c6..eb2f878c5 100644 --- a/docs/tutorials/wiki2/src/authorization/development.ini +++ b/docs/tutorials/wiki2/src/authorization/development.ini @@ -10,7 +10,7 @@ pyramid.includes = pyramid_debugtoolbar pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/authorization/production.ini b/docs/tutorials/wiki2/src/authorization/production.ini index c4034abad..4684d2f7a 100644 --- a/docs/tutorials/wiki2/src/authorization/production.ini +++ b/docs/tutorials/wiki2/src/authorization/production.ini @@ -9,7 +9,7 @@ pyramid.default_locale_name = en pyramid.includes = pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/authorization/tutorial/views.py b/docs/tutorials/wiki2/src/authorization/tutorial/views.py index 1453cd2e6..c7670b049 100644 --- a/docs/tutorials/wiki2/src/authorization/tutorial/views.py +++ b/docs/tutorials/wiki2/src/authorization/tutorial/views.py @@ -27,12 +27,14 @@ from .security import USERS # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(route_name='view_wiki') +@view_config(route_name='view_wiki', + permission='view') def view_wiki(request): return HTTPFound(location = request.route_url('view_page', pagename='FrontPage')) -@view_config(route_name='view_page', renderer='templates/view.pt') +@view_config(route_name='view_page', renderer='templates/view.pt', + permission='view') def view_page(request): pagename = request.matchdict['pagename'] page = DBSession.query(Page).filter_by(name=pagename).first() diff --git a/docs/tutorials/wiki2/src/basiclayout/development.ini b/docs/tutorials/wiki2/src/basiclayout/development.ini index 38738f3c6..eb2f878c5 100644 --- a/docs/tutorials/wiki2/src/basiclayout/development.ini +++ b/docs/tutorials/wiki2/src/basiclayout/development.ini @@ -10,7 +10,7 @@ pyramid.includes = pyramid_debugtoolbar pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/basiclayout/production.ini b/docs/tutorials/wiki2/src/basiclayout/production.ini index c4034abad..4684d2f7a 100644 --- a/docs/tutorials/wiki2/src/basiclayout/production.ini +++ b/docs/tutorials/wiki2/src/basiclayout/production.ini @@ -9,7 +9,7 @@ pyramid.default_locale_name = en pyramid.includes = pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/models/development.ini b/docs/tutorials/wiki2/src/models/development.ini index 38738f3c6..eb2f878c5 100644 --- a/docs/tutorials/wiki2/src/models/development.ini +++ b/docs/tutorials/wiki2/src/models/development.ini @@ -10,7 +10,7 @@ pyramid.includes = pyramid_debugtoolbar pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/models/production.ini b/docs/tutorials/wiki2/src/models/production.ini index c4034abad..4684d2f7a 100644 --- a/docs/tutorials/wiki2/src/models/production.ini +++ b/docs/tutorials/wiki2/src/models/production.ini @@ -9,7 +9,7 @@ pyramid.default_locale_name = en pyramid.includes = pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini index 38738f3c6..eb2f878c5 100644 --- a/docs/tutorials/wiki2/src/tests/development.ini +++ b/docs/tutorials/wiki2/src/tests/development.ini @@ -10,7 +10,7 @@ pyramid.includes = pyramid_debugtoolbar pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini index c4034abad..4684d2f7a 100644 --- a/docs/tutorials/wiki2/src/tests/production.ini +++ b/docs/tutorials/wiki2/src/tests/production.ini @@ -9,7 +9,7 @@ pyramid.default_locale_name = en pyramid.includes = pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views.py b/docs/tutorials/wiki2/src/tests/tutorial/views.py index 465d98ae1..f2a33af1e 100644 --- a/docs/tutorials/wiki2/src/tests/tutorial/views.py +++ b/docs/tutorials/wiki2/src/tests/tutorial/views.py @@ -27,12 +27,14 @@ from .security import USERS # regular expression used to find WikiWords wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)") -@view_config(route_name='view_wiki') +@view_config(route_name='view_wiki', + permission='view') def view_wiki(request): return HTTPFound(location = request.route_url('view_page', pagename='FrontPage')) -@view_config(route_name='view_page', renderer='templates/view.pt') +@view_config(route_name='view_page', renderer='templates/view.pt', + permission='view') def view_page(request): pagename = request.matchdict['pagename'] session = DBSession() diff --git a/docs/tutorials/wiki2/src/views/development.ini b/docs/tutorials/wiki2/src/views/development.ini index 38738f3c6..eb2f878c5 100644 --- a/docs/tutorials/wiki2/src/views/development.ini +++ b/docs/tutorials/wiki2/src/views/development.ini @@ -10,7 +10,7 @@ pyramid.includes = pyramid_debugtoolbar pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main diff --git a/docs/tutorials/wiki2/src/views/production.ini b/docs/tutorials/wiki2/src/views/production.ini index c4034abad..4684d2f7a 100644 --- a/docs/tutorials/wiki2/src/views/production.ini +++ b/docs/tutorials/wiki2/src/views/production.ini @@ -9,7 +9,7 @@ pyramid.default_locale_name = en pyramid.includes = pyramid_tm -sqlalchemy.url = sqlite:///%(here)s/tutorial.db +sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite [server:main] use = egg:waitress#main |
