summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2016-02-18 02:32:08 -0600
committerMichael Merickel <michael@merickel.org>2016-02-18 02:32:08 -0600
commit66fabb4ac707b5b4289db0094756f1a1af7269cc (patch)
treea66d8ee8f8282a72a5ada4fc5d903486caac6a85
parent50e08a743d097616ef7f76c9689833eab215cb94 (diff)
downloadpyramid-66fabb4ac707b5b4289db0094756f1a1af7269cc.tar.gz
pyramid-66fabb4ac707b5b4289db0094756f1a1af7269cc.tar.bz2
pyramid-66fabb4ac707b5b4289db0094756f1a1af7269cc.zip
update tests chapter
-rw-r--r--docs/tutorials/wiki2/src/tests/development.ini2
-rw-r--r--docs/tutorials/wiki2/src/tests/production.ini2
-rw-r--r--docs/tutorials/wiki2/src/tests/setup.py2
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/__init__.py19
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/models/user.py6
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/routes.py50
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/security.py40
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/security/__init__.py0
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/security/default.py12
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja293
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja264
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2100
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja289
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py56
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py66
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/auth.py25
-rw-r--r--docs/tutorials/wiki2/src/tests/tutorial/views/default.py54
-rw-r--r--docs/tutorials/wiki2/tests.rst43
18 files changed, 354 insertions, 369 deletions
diff --git a/docs/tutorials/wiki2/src/tests/development.ini b/docs/tutorials/wiki2/src/tests/development.ini
index 99c4ff0fe..f3079727e 100644
--- a/docs/tutorials/wiki2/src/tests/development.ini
+++ b/docs/tutorials/wiki2/src/tests/development.ini
@@ -17,6 +17,8 @@ pyramid.includes =
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+auth.secret = seekrit
+
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
diff --git a/docs/tutorials/wiki2/src/tests/production.ini b/docs/tutorials/wiki2/src/tests/production.ini
index cb1db3211..686dba48a 100644
--- a/docs/tutorials/wiki2/src/tests/production.ini
+++ b/docs/tutorials/wiki2/src/tests/production.ini
@@ -14,6 +14,8 @@ pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/tutorial.sqlite
+auth.secret = real-seekrit
+
[server:main]
use = egg:waitress#main
host = 0.0.0.0
diff --git a/docs/tutorials/wiki2/src/tests/setup.py b/docs/tutorials/wiki2/src/tests/setup.py
index e06aa06e4..57538f2d0 100644
--- a/docs/tutorials/wiki2/src/tests/setup.py
+++ b/docs/tutorials/wiki2/src/tests/setup.py
@@ -43,8 +43,8 @@ setup(name='tutorial',
include_package_data=True,
zip_safe=False,
test_suite='tutorial',
- install_requires=requires,
tests_require=tests_require,
+ install_requires=requires,
entry_points="""\
[paste.app_factory]
main = tutorial:main
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
index a62c42378..f5c033b8b 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/__init__.py
@@ -1,28 +1,13 @@
from pyramid.config import Configurator
-from pyramid.authentication import AuthTktAuthenticationPolicy
-from pyramid.authorization import ACLAuthorizationPolicy
-
-from .security.default import groupfinder
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
- authn_policy = AuthTktAuthenticationPolicy(
- 'sosecret', callback=groupfinder, hashalg='sha512')
- authz_policy = ACLAuthorizationPolicy()
config = Configurator(settings=settings)
config.include('pyramid_jinja2')
config.include('.models')
- config.set_root_factory('.models.mymodel.RootFactory')
- config.set_authentication_policy(authn_policy)
- config.set_authorization_policy(authz_policy)
- config.add_static_view('static', 'static', cache_max_age=3600)
- config.add_route('view_wiki', '/')
- config.add_route('login', '/login')
- config.add_route('logout', '/logout')
- config.add_route('view_page', '/{pagename}')
- config.add_route('add_page', '/add_page/{pagename}')
- config.add_route('edit_page', '/{pagename}/edit_page')
+ config.include('.routes')
+ config.include('.security')
config.scan()
return config.make_wsgi_app()
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/models/user.py b/docs/tutorials/wiki2/src/tests/tutorial/models/user.py
index 25b0a8187..6fb32a1b2 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/models/user.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/models/user.py
@@ -18,10 +18,12 @@ class User(Base):
password_hash = Column(Text)
def set_password(self, pw):
- pwhash = bcrypt.hashpw(pw, bcrypt.gensalt())
+ pwhash = bcrypt.hashpw(pw.encode('utf8'), bcrypt.gensalt())
self.password_hash = pwhash
def check_password(self, pw):
if self.password_hash is not None:
- return bcrypt.hashpw(pw, self.password_hash) == self.password_hash
+ expected_hash = self.password_hash.encode('utf8')
+ actual_hash = bcrypt.hashpw(pw.encode('utf8'), expected_hash)
+ return expected_hash == actual_hash
return False
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/routes.py b/docs/tutorials/wiki2/src/tests/tutorial/routes.py
new file mode 100644
index 000000000..c7c3a2120
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/routes.py
@@ -0,0 +1,50 @@
+from pyramid.httpexceptions import HTTPNotFound
+from pyramid.security import (
+ Allow,
+ Everyone,
+)
+
+from .models import Page
+
+def includeme(config):
+ config.add_static_view('static', 'static', cache_max_age=3600)
+ config.add_route('view_wiki', '/')
+ config.add_route('login', '/login')
+ config.add_route('logout', '/logout')
+ config.add_route('view_page', '/{pagename}', factory=page_factory)
+ config.add_route('add_page', '/add_page/{pagename}',
+ factory=new_page_factory)
+ config.add_route('edit_page', '/{pagename}/edit_page',
+ factory=page_factory)
+
+def new_page_factory(request):
+ pagename = request.matchdict['pagename']
+ return NewPage(pagename)
+
+class NewPage(object):
+ def __init__(self, pagename):
+ self.pagename = pagename
+
+ def __acl__(self):
+ return [
+ (Allow, 'role:editor', 'create'),
+ (Allow, 'role:basic', 'create'),
+ ]
+
+def page_factory(request):
+ pagename = request.matchdict['pagename']
+ page = request.dbsession.query(Page).filter_by(name=pagename).first()
+ if page is None:
+ raise HTTPNotFound
+ return PageResource(page)
+
+class PageResource(object):
+ def __init__(self, page):
+ self.page = page
+
+ def __acl__(self):
+ return [
+ (Allow, Everyone, 'view'),
+ (Allow, 'role:editor', 'edit'),
+ (Allow, str(self.page.creator_id), 'edit'),
+ ]
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security.py b/docs/tutorials/wiki2/src/tests/tutorial/security.py
new file mode 100644
index 000000000..25cff7b05
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/security.py
@@ -0,0 +1,40 @@
+from pyramid.authentication import AuthTktAuthenticationPolicy
+from pyramid.authorization import ACLAuthorizationPolicy
+from pyramid.security import (
+ Authenticated,
+ Everyone,
+)
+
+from .models import User
+
+
+class MyAuthenticationPolicy(AuthTktAuthenticationPolicy):
+ def authenticated_userid(self, request):
+ user = request.user
+ if user is not None:
+ return user.id
+
+ def effective_principals(self, request):
+ principals = [Everyone]
+ user = request.user
+ if user is not None:
+ principals.append(Authenticated)
+ principals.append(str(user.id))
+ principals.append('role:' + user.role)
+ return principals
+
+def get_user(request):
+ user_id = request.unauthenticated_userid
+ if user_id is not None:
+ user = request.dbsession.query(User).get(user_id)
+ return user
+
+def includeme(config):
+ settings = config.get_settings()
+ authn_policy = MyAuthenticationPolicy(
+ settings['auth.secret'],
+ hashalg='sha512',
+ )
+ config.set_authentication_policy(authn_policy)
+ config.set_authorization_policy(ACLAuthorizationPolicy())
+ config.add_request_method(get_user, 'user', reify=True)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security/__init__.py b/docs/tutorials/wiki2/src/tests/tutorial/security/__init__.py
deleted file mode 100644
index e69de29bb..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/security/__init__.py
+++ /dev/null
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/security/default.py b/docs/tutorials/wiki2/src/tests/tutorial/security/default.py
deleted file mode 100644
index 7fc1ea7c8..000000000
--- a/docs/tutorials/wiki2/src/tests/tutorial/security/default.py
+++ /dev/null
@@ -1,12 +0,0 @@
-USERS = {
- 'editor': 'editor',
- 'viewer': 'viewer',
-}
-
-GROUPS = {
- 'editor': ['group:editors'],
-}
-
-def groupfinder(userid, request):
- if userid in USERS:
- return GROUPS.get(userid, [])
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2
index 4d767cfbe..7db25c674 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/edit.jinja2
@@ -1,73 +1,20 @@
-<!DOCTYPE html>
-<html lang="{{request.locale_name}}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
-
- <title>Edit{% if page.name %} {{page.name}}{% endif %} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- Custom styles for this scaffold -->
- <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
-
- <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- {% if request.authenticated_userid is not none %}
- <p class="pull-right">
- <a href="{{ request.route_url('logout') }}">Logout</a>
- </p>
- {% endif %}
- <p>
- Editing <strong>{% if page.name %}{{page.name}}{% else %}Page Name Goes Here{% endif %}</strong>
- </p>
- <p>You can return to the
- <a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
- </p>
- <form action="{{ save_url }}" method="post">
- <div class="form-group">
- <textarea class="form-control" name="body" rows="10" cols="60">{{ page.data }}</textarea>
- </div>
- <div class="form-group">
- <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
- </div>
- </form>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
- </body>
-</html>
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}Edit {{pagename}} - {% endblock subtitle %}
+
+{% block content %}
+<p>
+Editing <strong>{{pagename}}</strong>
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+<form action="{{ save_url }}" method="post">
+<div class="form-group">
+ <textarea class="form-control" name="body" rows="10" cols="60">{{ pagedata }}</textarea>
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Save" class="btn btn-default">Save</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
new file mode 100644
index 000000000..44d14304e
--- /dev/null
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/layout.jinja2
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="{{request.locale_name}}">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="pyramid web application">
+ <meta name="author" content="Pylons Project">
+ <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
+
+ <title>{% block subtitle %}{% endblock %}Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
+
+ <!-- Bootstrap core CSS -->
+ <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
+
+ <!-- Custom styles for this scaffold -->
+ <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
+
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
+ <!--[if lt IE 9]>
+ <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
+ <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
+ <![endif]-->
+ </head>
+
+ <body>
+
+ <div class="starter-template">
+ <div class="container">
+ <div class="row">
+ <div class="col-md-2">
+ <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
+ </div>
+ <div class="col-md-10">
+ <div class="content">
+ {% if request.user is none %}
+ <p class="pull-right">
+ <a href="{{ request.route_url('login') }}">Login</a>
+ </p>
+ {% else %}
+ <p class="pull-right">
+ {{request.user.name}} <a href="{{request.route_url('logout')}}">Logout</a>
+ </p>
+ {% endif %}
+ {% block content %}{% endblock %}
+ </div>
+ </div>
+ </div>
+ <div class="row">
+ <div class="copyright">
+ Copyright &copy; Pylons Project
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- Bootstrap core JavaScript
+ ================================================== -->
+ <!-- Placed at the end of the document so the pages load faster -->
+ <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
+ <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
+ </body>
+</html>
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2
index a80a2a165..1806de0ff 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/login.jinja2
@@ -1,74 +1,26 @@
-<!DOCTYPE html>
-<html lang="{{request.locale_name}}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
-
- <title>Login - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- Custom styles for this scaffold -->
- <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
-
- <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- <p>
- <strong>
- Login
- </strong><br>
- {{ message }}
- </p>
- <form action="{{ url }}" method="post">
- <input type="hidden" name="came_from" value="{{ came_from }}">
- <div class="form-group">
- <label for="login">Username</label>
- <input type="text" name="login" value="{{ login }}">
- </div>
- <div class="form-group">
- <label for="password">Password</label>
- <input type="password" name="password" value="{{ password }}">
- </div>
- <div class="form-group">
- <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
- </div>
- </form>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
- </body>
-</html>
+{% extends 'layout.jinja2' %}
+
+{% block title %}Login - {% endblock title %}
+
+{% block content %}
+<p>
+<strong>
+ Login
+</strong><br>
+{{ message }}
+</p>
+<form action="{{ url }}" method="post">
+<input type="hidden" name="next" value="{{ next_url }}">
+<div class="form-group">
+ <label for="login">Username</label>
+ <input type="text" name="login" value="{{ login }}">
+</div>
+<div class="form-group">
+ <label for="password">Password</label>
+ <input type="password" name="password">
+</div>
+<div class="form-group">
+ <button type="submit" name="form.submitted" value="Log In" class="btn btn-default">Log In</button>
+</div>
+</form>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2 b/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2
index 942b8479b..94419e228 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2
+++ b/docs/tutorials/wiki2/src/tests/tutorial/templates/view.jinja2
@@ -1,71 +1,18 @@
-<!DOCTYPE html>
-<html lang="{{request.locale_name}}">
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <meta name="description" content="pyramid web application">
- <meta name="author" content="Pylons Project">
- <link rel="shortcut icon" href="{{request.static_url('tutorial:static/pyramid-16x16.png')}}">
-
- <title>{{page.name}} - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
-
- <!-- Bootstrap core CSS -->
- <link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
-
- <!-- Custom styles for this scaffold -->
- <link href="{{request.static_url('tutorial:static/theme.css')}}" rel="stylesheet">
-
- <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
- <!--[if lt IE 9]>
- <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
- <script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
- <![endif]-->
- </head>
-
- <body>
-
- <div class="starter-template">
- <div class="container">
- <div class="row">
- <div class="col-md-2">
- <img class="logo img-responsive" src="{{request.static_url('tutorial:static/pyramid.png')}}" alt="pyramid web framework">
- </div>
- <div class="col-md-10">
- <div class="content">
- {% if request.authenticated_userid is not none %}
- <p class="pull-right">
- <a href="{{ request.route_url('logout') }}">Logout</a>
- </p>
- {% endif %}
- <p>{{ content|safe }}</p>
- <p>
- <a href="{{ edit_url }}">
- Edit this page
- </a>
- </p>
- <p>
- Viewing <strong>{% if page.name %}{{page.name}}{% else %}Page Name Goes Here{% endif %}</strong>
- </p>
- <p>You can return to the
- <a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
- </p>
- </div>
- </div>
- </div>
- <div class="row">
- <div class="copyright">
- Copyright &copy; Pylons Project
- </div>
- </div>
- </div>
- </div>
-
-
- <!-- Bootstrap core JavaScript
- ================================================== -->
- <!-- Placed at the end of the document so the pages load faster -->
- <script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js"></script>
- <script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js"></script>
- </body>
-</html>
+{% extends 'layout.jinja2' %}
+
+{% block subtitle %}{{page.name}} - {% endblock subtitle %}
+
+{% block content %}
+<p>{{ content|safe }}</p>
+<p>
+<a href="{{ edit_url }}">
+ Edit this page
+</a>
+</p>
+<p>
+ Viewing <strong>{{page.name}}</strong>, created by <strong>{{page.creator.name}}</strong>.
+</p>
+<p>You can return to the
+<a href="{{request.route_url('view_page', pagename='FrontPage')}}">FrontPage</a>.
+</p>
+{% endblock content %}
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
index c716537ae..b2c6e0975 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_functional.py
@@ -5,26 +5,30 @@ from webtest import TestApp
class FunctionalTests(unittest.TestCase):
- viewer_login = (
- '/login?login=viewer&password=viewer'
- '&came_from=FrontPage&form.submitted=Login')
- viewer_wrong_login = (
- '/login?login=viewer&password=incorrect'
- '&came_from=FrontPage&form.submitted=Login')
+ basic_login = (
+ '/login?login=basic&password=basic'
+ '&next=FrontPage&form.submitted=Login')
+ basic_wrong_login = (
+ '/login?login=basic&password=incorrect'
+ '&next=FrontPage&form.submitted=Login')
editor_login = (
'/login?login=editor&password=editor'
- '&came_from=FrontPage&form.submitted=Login')
+ '&next=FrontPage&form.submitted=Login')
@classmethod
def setUpClass(cls):
from tutorial.models.meta import Base
from tutorial.models import (
+ User,
Page,
get_tm_session,
)
from tutorial import main
- settings = {'sqlalchemy.url': 'sqlite://'}
+ settings = {
+ 'sqlalchemy.url': 'sqlite://',
+ 'auth.secret': 'seekrit',
+ }
app = main({}, **settings)
cls.testapp = TestApp(app)
@@ -34,8 +38,15 @@ class FunctionalTests(unittest.TestCase):
with transaction.manager:
dbsession = get_tm_session(session_factory, transaction.manager)
- model = Page(name='FrontPage', data='This is the front page')
- dbsession.add(model)
+ editor = User(name='editor', role='editor')
+ editor.set_password('editor')
+ basic = User(name='basic', role='basic')
+ basic.set_password('basic')
+ page1 = Page(name='FrontPage', data='This is the front page')
+ page1.creator = editor
+ page2 = Page(name='BackPage', data='This is the back page')
+ page2.creator = basic
+ dbsession.add_all([basic, editor, page1, page2])
@classmethod
def tearDownClass(cls):
@@ -54,20 +65,20 @@ class FunctionalTests(unittest.TestCase):
self.testapp.get('/SomePage', status=404)
def test_successful_log_in(self):
- res = self.testapp.get(self.viewer_login, status=302)
+ res = self.testapp.get(self.basic_login, status=302)
self.assertEqual(res.location, 'http://localhost/FrontPage')
def test_failed_log_in(self):
- res = self.testapp.get(self.viewer_wrong_login, status=200)
+ res = self.testapp.get(self.basic_wrong_login, status=200)
self.assertTrue(b'login' in res.body)
def test_logout_link_present_when_logged_in(self):
- self.testapp.get(self.viewer_login, status=302)
+ self.testapp.get(self.basic_login, status=302)
res = self.testapp.get('/FrontPage', status=200)
self.assertTrue(b'Logout' in res.body)
def test_logout_link_not_present_after_logged_out(self):
- self.testapp.get(self.viewer_login, status=302)
+ self.testapp.get(self.basic_login, status=302)
self.testapp.get('/FrontPage', status=200)
res = self.testapp.get('/logout', status=302)
self.assertTrue(b'Logout' not in res.body)
@@ -80,15 +91,20 @@ class FunctionalTests(unittest.TestCase):
res = self.testapp.get('/add_page/NewPage', status=302).follow()
self.assertTrue(b'Login' in res.body)
- def test_viewer_user_cannot_edit(self):
- self.testapp.get(self.viewer_login, status=302)
+ def test_basic_user_cannot_edit_front(self):
+ self.testapp.get(self.basic_login, status=302)
res = self.testapp.get('/FrontPage/edit_page', status=302).follow()
self.assertTrue(b'Login' in res.body)
- def test_viewer_user_cannot_add(self):
- self.testapp.get(self.viewer_login, status=302)
- res = self.testapp.get('/add_page/NewPage', status=302).follow()
- self.assertTrue(b'Login' in res.body)
+ def test_basic_user_can_edit_back(self):
+ self.testapp.get(self.basic_login, status=302)
+ res = self.testapp.get('/BackPage/edit_page', status=200)
+ self.assertTrue(b'Editing' in res.body)
+
+ def test_basic_user_can_add(self):
+ self.testapp.get(self.basic_login, status=302)
+ res = self.testapp.get('/add_page/NewPage', status=200)
+ self.assertTrue(b'Editing' in res.body)
def test_editors_member_user_can_edit(self):
self.testapp.get(self.editor_login, status=302)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py
index b2830d070..5253183df 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/tests/test_views.py
@@ -8,12 +8,6 @@ def dummy_request(dbsession):
return testing.DummyRequest(dbsession=dbsession)
-def _register_routes(config):
- config.add_route('view_page', '{pagename}')
- config.add_route('add_page', 'add_page/{pagename}')
- config.add_route('edit_page', '{pagename}/edit_page')
-
-
class BaseTest(unittest.TestCase):
def setUp(self):
from ..models import get_tm_session
@@ -21,7 +15,7 @@ class BaseTest(unittest.TestCase):
'sqlalchemy.url': 'sqlite:///:memory:'
})
self.config.include('..models')
- self.config.include(_register_routes)
+ self.config.include('..routes')
session_factory = self.config.registry['dbsession_factory']
self.session = get_tm_session(session_factory, transaction.manager)
@@ -38,11 +32,21 @@ class BaseTest(unittest.TestCase):
testing.tearDown()
transaction.abort()
+ def makeUser(self, name, role, password='dummy'):
+ from ..models import User
+ user = User(name=name, role=role)
+ user.set_password(password)
+ return user
+
+ def makePage(self, name, data, creator):
+ from ..models import Page
+ return Page(name=name, data=data, creator=creator)
+
class ViewWikiTests(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
- _register_routes(self.config)
+ self.config.include('..routes')
def tearDown(self):
testing.tearDown()
@@ -63,14 +67,16 @@ class ViewPageTests(BaseTest):
return view_page(request)
def test_it(self):
+ from ..routes import PageResource
+
# add a page to the db
- from ..models.mymodel import Page
- page = Page(name='IDoExist', data='Hello CruelWorld IDoExist')
- self.session.add(page)
+ user = self.makeUser('foo', 'editor')
+ page = self.makePage('IDoExist', 'Hello CruelWorld IDoExist', user)
+ self.session.add_all([page, user])
# create a request asking for the page we've created
request = dummy_request(self.session)
- request.matchdict['pagename'] = 'IDoExist'
+ request.context = PageResource(page)
# call the view we're testing and check its behavior
info = self._callFUT(request)
@@ -93,19 +99,23 @@ class AddPageTests(BaseTest):
return add_page(request)
def test_it_notsubmitted(self):
+ from ..routes import NewPage
request = dummy_request(self.session)
- request.matchdict = {'pagename': 'AnotherPage'}
+ request.user = self.makeUser('foo', 'editor')
+ request.context = NewPage('AnotherPage')
info = self._callFUT(request)
- self.assertEqual(info['page'].data, '')
+ self.assertEqual(info['pagedata'], '')
self.assertEqual(info['save_url'],
'http://example.com/add_page/AnotherPage')
def test_it_submitted(self):
- from ..models.mymodel import Page
+ from ..models import Page
+ from ..routes import NewPage
request = testing.DummyRequest({'form.submitted': True,
'body': 'Hello yo!'},
dbsession=self.session)
- request.matchdict = {'pagename': 'AnotherPage'}
+ request.user = self.makeUser('foo', 'editor')
+ request.context = NewPage('AnotherPage')
self._callFUT(request)
page = self.session.query(Page).filter_by(name='AnotherPage').one()
self.assertEqual(page.data, 'Hello yo!')
@@ -116,25 +126,31 @@ class EditPageTests(BaseTest):
from tutorial.views.default import edit_page
return edit_page(request)
+ def makeContext(self, page):
+ from ..routes import PageResource
+ return PageResource(page)
+
def test_it_notsubmitted(self):
- from ..models.mymodel import Page
+ user = self.makeUser('foo', 'editor')
+ page = self.makePage('abc', 'hello', user)
+ self.session.add_all([page, user])
+
request = dummy_request(self.session)
- request.matchdict = {'pagename': 'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
+ request.context = self.makeContext(page)
info = self._callFUT(request)
- self.assertEqual(info['page'], page)
+ self.assertEqual(info['pagename'], 'abc')
self.assertEqual(info['save_url'],
'http://example.com/abc/edit_page')
def test_it_submitted(self):
- from ..models.mymodel import Page
+ user = self.makeUser('foo', 'editor')
+ page = self.makePage('abc', 'hello', user)
+ self.session.add_all([page, user])
+
request = testing.DummyRequest({'form.submitted': True,
'body': 'Hello yo!'},
dbsession=self.session)
- request.matchdict = {'pagename': 'abc'}
- page = Page(name='abc', data='hello')
- self.session.add(page)
+ request.context = self.makeContext(page)
response = self._callFUT(request)
self.assertEqual(response.location, 'http://example.com/abc')
self.assertEqual(page.data, 'Hello yo!')
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py b/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py
index 08aa2bfad..2b993b430 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/auth.py
@@ -8,33 +8,30 @@ from pyramid.view import (
view_config,
)
-from ..security.default import USERS
+from ..models import User
-@view_config(route_name='login', renderer='templates/login.jinja2')
+@view_config(route_name='login', renderer='../templates/login.jinja2')
def login(request):
- login_url = request.route_url('login')
- referrer = request.url
- if referrer == login_url:
- referrer = '/' # never use the login form itself as came_from
- came_from = request.params.get('came_from', referrer)
+ next_url = request.params.get('next', request.referrer)
+ if not next_url:
+ next_url = request.route_url('view_wiki')
message = ''
login = ''
- password = ''
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
- if USERS.get(login) == password:
- headers = remember(request, login)
- return HTTPFound(location=came_from, headers=headers)
+ user = request.dbsession.query(User).filter_by(name=login).first()
+ if user is not None and user.check_password(password):
+ headers = remember(request, user.id)
+ return HTTPFound(location=next_url, headers=headers)
message = 'Failed login'
return dict(
message=message,
url=request.route_url('login'),
- came_from=came_from,
+ next_url=next_url,
login=login,
- password=password,
)
@view_config(route_name='logout')
@@ -45,5 +42,5 @@ def logout(request):
@forbidden_view_config()
def forbidden_view(request):
- next_url = request.route_url('login', _query={'came_from': request.url})
+ next_url = request.route_url('login', _query={'next': request.url})
return HTTPFound(location=next_url)
diff --git a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
index 6fb3c8744..9358993ea 100644
--- a/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
+++ b/docs/tutorials/wiki2/src/tests/tutorial/views/default.py
@@ -2,10 +2,7 @@ import cgi
import re
from docutils.core import publish_parts
-from pyramid.httpexceptions import (
- HTTPFound,
- HTTPNotFound,
- )
+from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from ..models import Page
@@ -13,7 +10,7 @@ from ..models import Page
# regular expression used to find WikiWords
wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
-@view_config(route_name='view_wiki', permission='view')
+@view_config(route_name='view_wiki')
def view_wiki(request):
next_url = request.route_url('view_page', pagename='FrontPage')
return HTTPFound(location=next_url)
@@ -21,12 +18,9 @@ def view_wiki(request):
@view_config(route_name='view_page', renderer='../templates/view.jinja2',
permission='view')
def view_page(request):
- pagename = request.matchdict['pagename']
- page = request.dbsession.query(Page).filter_by(name=pagename).first()
- if page is None:
- return HTTPNotFound('No such page')
+ page = request.context.page
- def check(match):
+ def add_link(match):
word = match.group(1)
exists = request.dbsession.query(Page).filter_by(name=word).all()
if exists:
@@ -37,34 +31,34 @@ def view_page(request):
return '<a href="%s">%s</a>' % (add_url, cgi.escape(word))
content = publish_parts(page.data, writer_name='html')['html_body']
- content = wikiwords.sub(check, content)
- edit_url = request.route_url('edit_page', pagename=pagename)
+ content = wikiwords.sub(add_link, content)
+ edit_url = request.route_url('edit_page', pagename=page.name)
return dict(page=page, content=content, edit_url=edit_url)
-@view_config(route_name='add_page', renderer='../templates/edit.jinja2',
+@view_config(route_name='edit_page', renderer='../templates/edit.jinja2',
permission='edit')
+def edit_page(request):
+ page = request.context.page
+ if 'form.submitted' in request.params:
+ page.data = request.params['body']
+ next_url = request.route_url('view_page', pagename=page.name)
+ return HTTPFound(location=next_url)
+ return dict(
+ pagename=page.name,
+ pagedata=page.data,
+ save_url=request.route_url('edit_page', pagename=page.name),
+ )
+
+@view_config(route_name='add_page', renderer='../templates/edit.jinja2',
+ permission='create')
def add_page(request):
- pagename = request.matchdict['pagename']
+ pagename = request.context.pagename
if 'form.submitted' in request.params:
body = request.params['body']
page = Page(name=pagename, data=body)
+ page.creator = request.user
request.dbsession.add(page)
next_url = request.route_url('view_page', pagename=pagename)
return HTTPFound(location=next_url)
save_url = request.route_url('add_page', pagename=pagename)
- page = Page(name='', data='')
- return dict(page=page, save_url=save_url)
-
-@view_config(route_name='edit_page', renderer='../templates/edit.jinja2',
- permission='edit')
-def edit_page(request):
- pagename = request.matchdict['pagename']
- page = request.dbsession.query(Page).filter_by(name=pagename).one()
- if 'form.submitted' in request.params:
- page.data = request.params['body']
- next_url = request.route_url('view_page', pagename=pagename)
- return HTTPFound(location=next_url)
- return dict(
- page=page,
- save_url=request.route_url('edit_page', pagename=pagename),
- )
+ return dict(pagename=pagename, pagedata='', save_url=save_url)
diff --git a/docs/tutorials/wiki2/tests.rst b/docs/tutorials/wiki2/tests.rst
index a99cd68cc..667550467 100644
--- a/docs/tutorials/wiki2/tests.rst
+++ b/docs/tutorials/wiki2/tests.rst
@@ -43,7 +43,7 @@ Functional tests
We'll test the whole application, covering security aspects that are not
tested in the unit tests, like logging in, logging out, checking that
-the ``viewer`` user cannot add or edit pages, but the ``editor`` user
+the ``basic`` user cannot edit pages it didn't create, but the ``editor`` user
can, and so on.
@@ -65,39 +65,20 @@ follows:
:language: python
-Running the tests
-=================
-
-We can run these tests by using ``setup.py test`` in the same way we did in
-:ref:`running_tests`. However, first we must edit our ``setup.py`` to include
-a dependency on `WebTest
-<http://docs.pylonsproject.org/projects/webtest/en/latest/>`_, which we've used
-in our ``tests.py``. Change the ``requires`` list in ``setup.py`` to include
-``WebTest``.
-
-.. literalinclude:: src/tests/setup.py
- :linenos:
- :language: python
- :lines: 11-22
- :emphasize-lines: 11
+.. note::
-After we've added a dependency on WebTest in ``setup.py``, we need to run
-``setup.py develop`` to get WebTest installed into our virtualenv. Assuming
-our shell's current working directory is the "tutorial" distribution directory:
+ We're utilizing the excellent WebTest_ package to do functional testing
+ of the application. This is defined in the ``tests_require`` section of
+ our ``setup.py``. Any other dependencies needed only for testing purposes
+ can be added there and will be installed automatically when running
+ ``setup.py test``.
-On UNIX:
-
-.. code-block:: bash
- $ $VENV/bin/python setup.py develop
-
-On Windows:
-
-.. code-block:: text
-
- c:\pyramidtut\tutorial> %VENV%\Scripts\python setup.py develop
+Running the tests
+=================
-Once that command has completed successfully, we can run the tests themselves:
+We can run these tests by using ``setup.py test`` in the same way we did in
+:ref:`running_tests`:
On UNIX:
@@ -122,3 +103,5 @@ The expected result should look like the following:
OK
Process finished with exit code 0
+
+.. _webtest: http://docs.pylonsproject.org/projects/webtest/en/latest/