summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTres Seaver <tseaver@palladion.com>2024-06-10 12:09:42 -0400
committerGitHub <noreply@github.com>2024-06-10 12:09:42 -0400
commitef0f6861e5b439afe43983f6c7437c30a413a34d (patch)
treede670102b0123f2eea2ef399fd1e61cdfc5676b4
parent72f61853beda8e21b669c3520e43fe3e5b224ba3 (diff)
parent1ebd9884e712463057de38fb4948a56c0c0982c5 (diff)
downloadpyramid-ef0f6861e5b439afe43983f6c7437c30a413a34d.tar.gz
pyramid-ef0f6861e5b439afe43983f6c7437c30a413a34d.tar.bz2
pyramid-ef0f6861e5b439afe43983f6c7437c30a413a34d.zip
Merge pull request #3760 from Pylons/tseaver-qt_cleanup
docs: quick tutorial cleanups
-rw-r--r--docs/quick_tutorial/authentication.rst2
-rw-r--r--docs/quick_tutorial/authentication/tutorial/home.pt6
-rw-r--r--docs/quick_tutorial/authentication/tutorial/login.pt2
-rw-r--r--docs/quick_tutorial/authentication/tutorial/views.py7
-rw-r--r--docs/quick_tutorial/authorization.rst14
-rw-r--r--docs/quick_tutorial/authorization/tutorial/__init__.py9
-rw-r--r--docs/quick_tutorial/authorization/tutorial/home.pt6
-rw-r--r--docs/quick_tutorial/authorization/tutorial/login.pt2
-rw-r--r--docs/quick_tutorial/authorization/tutorial/views.py43
-rw-r--r--docs/quick_tutorial/retail_forms/development.ini9
-rw-r--r--docs/quick_tutorial/retail_forms/setup.py20
-rw-r--r--docs/quick_tutorial/retail_forms/tutorial/__init__.py13
-rw-r--r--docs/quick_tutorial/retail_forms/tutorial/tests.py36
-rw-r--r--docs/quick_tutorial/retail_forms/tutorial/views.py96
-rw-r--r--docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt19
-rw-r--r--docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt37
-rw-r--r--docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt17
17 files changed, 62 insertions, 276 deletions
diff --git a/docs/quick_tutorial/authentication.rst b/docs/quick_tutorial/authentication.rst
index 3f6df17de..da76f3ec7 100644
--- a/docs/quick_tutorial/authentication.rst
+++ b/docs/quick_tutorial/authentication.rst
@@ -137,7 +137,7 @@ Subsequent requests return that cookie and identify the user.
In our template, we fetched the ``logged_in`` value from the view class. We use
this to calculate the logged-in user, if any. In the template we can then
choose to show a login link to anonymous visitors or a logout link to logged-in
-users.
+users, including their login name.
Extra credit
diff --git a/docs/quick_tutorial/authentication/tutorial/home.pt b/docs/quick_tutorial/authentication/tutorial/home.pt
index ed911b673..0e8508558 100644
--- a/docs/quick_tutorial/authentication/tutorial/home.pt
+++ b/docs/quick_tutorial/authentication/tutorial/home.pt
@@ -8,8 +8,10 @@
<div>
<a tal:condition="view.logged_in is None"
href="${request.application_url}/login">Log In</a>
- <a tal:condition="view.logged_in is not None"
- href="${request.application_url}/logout">Logout</a>
+ <span tal:condition="view.logged_in is not None">
+ <a href="${request.application_url}/logout">Logout</a>
+ as ${view.logged_in}
+ </span>
</div>
<h1>Hi ${name}</h1>
diff --git a/docs/quick_tutorial/authentication/tutorial/login.pt b/docs/quick_tutorial/authentication/tutorial/login.pt
index 9e5bfe2ad..db8080fc8 100644
--- a/docs/quick_tutorial/authentication/tutorial/login.pt
+++ b/docs/quick_tutorial/authentication/tutorial/login.pt
@@ -8,8 +8,6 @@
<span tal:replace="message"/>
<form action="${url}" method="post">
- <input type="hidden" name="came_from"
- value="${came_from}"/>
<label for="login">Username</label>
<input type="text" id="login"
name="login"
diff --git a/docs/quick_tutorial/authentication/tutorial/views.py b/docs/quick_tutorial/authentication/tutorial/views.py
index b2d9354ec..7c57d6371 100644
--- a/docs/quick_tutorial/authentication/tutorial/views.py
+++ b/docs/quick_tutorial/authentication/tutorial/views.py
@@ -33,10 +33,6 @@ class TutorialViews:
def login(self):
request = self.request
login_url = request.route_url('login')
- referrer = request.url
- if referrer == login_url:
- referrer = '/' # never use login form itself as came_from
- came_from = request.params.get('came_from', referrer)
message = ''
login = ''
password = ''
@@ -46,7 +42,7 @@ class TutorialViews:
hashed_pw = USERS.get(login)
if hashed_pw and check_password(password, hashed_pw):
headers = remember(request, login)
- return HTTPFound(location=came_from,
+ return HTTPFound(location=request.route_url("home"),
headers=headers)
message = 'Failed login'
@@ -54,7 +50,6 @@ class TutorialViews:
name='Login',
message=message,
url=request.application_url + '/login',
- came_from=came_from,
login=login,
password=password,
)
diff --git a/docs/quick_tutorial/authorization.rst b/docs/quick_tutorial/authorization.rst
index b1ef86a17..9a5b7c738 100644
--- a/docs/quick_tutorial/authorization.rst
+++ b/docs/quick_tutorial/authorization.rst
@@ -104,9 +104,17 @@ Of course, this only applies on ``Root``. Some other part of the site (a.k.a.
*context*) might have a different ACL.
If you are not logged in and visit ``/howdy``, you need to get shown the login
-screen. How does Pyramid know what is the login page to use? We explicitly told
-Pyramid that the ``login`` view should be used by decorating the view with
-``@forbidden_view_config``.
+screen. How does Pyramid know what is the login page to use? We defined an
+explicit "forbidden view", decorating that view with
+``@forbidden_view_config``, and then had it store the information about the
+route being protected in the request's session, before redirecting to the
+login view.
+
+.. note::
+
+ We use the session to store the ``came_from`` information, rather than a
+ hidden form input, in order to avoid trusting user-supplied data (from the
+ form or query string) when constructing redirect URLs.
Extra credit
diff --git a/docs/quick_tutorial/authorization/tutorial/__init__.py b/docs/quick_tutorial/authorization/tutorial/__init__.py
index 255bb35ac..f59d5ab6d 100644
--- a/docs/quick_tutorial/authorization/tutorial/__init__.py
+++ b/docs/quick_tutorial/authorization/tutorial/__init__.py
@@ -1,11 +1,16 @@
from pyramid.config import Configurator
+from pyramid.session import SignedCookieSessionFactory
from .security import SecurityPolicy
def main(global_config, **settings):
- config = Configurator(settings=settings,
- root_factory='.resources.Root')
+ my_session_factory = SignedCookieSessionFactory('itsaseekreet')
+ config = Configurator(
+ settings=settings,
+ root_factory='.resources.Root',
+ session_factory=my_session_factory,
+ )
config.include('pyramid_chameleon')
config.set_security_policy(
diff --git a/docs/quick_tutorial/authorization/tutorial/home.pt b/docs/quick_tutorial/authorization/tutorial/home.pt
index ed911b673..0e8508558 100644
--- a/docs/quick_tutorial/authorization/tutorial/home.pt
+++ b/docs/quick_tutorial/authorization/tutorial/home.pt
@@ -8,8 +8,10 @@
<div>
<a tal:condition="view.logged_in is None"
href="${request.application_url}/login">Log In</a>
- <a tal:condition="view.logged_in is not None"
- href="${request.application_url}/logout">Logout</a>
+ <span tal:condition="view.logged_in is not None">
+ <a href="${request.application_url}/logout">Logout</a>
+ as ${view.logged_in}
+ </span>
</div>
<h1>Hi ${name}</h1>
diff --git a/docs/quick_tutorial/authorization/tutorial/login.pt b/docs/quick_tutorial/authorization/tutorial/login.pt
index 9e5bfe2ad..db8080fc8 100644
--- a/docs/quick_tutorial/authorization/tutorial/login.pt
+++ b/docs/quick_tutorial/authorization/tutorial/login.pt
@@ -8,8 +8,6 @@
<span tal:replace="message"/>
<form action="${url}" method="post">
- <input type="hidden" name="came_from"
- value="${came_from}"/>
<label for="login">Username</label>
<input type="text" id="login"
name="login"
diff --git a/docs/quick_tutorial/authorization/tutorial/views.py b/docs/quick_tutorial/authorization/tutorial/views.py
index b9c828086..9b3bbe42a 100644
--- a/docs/quick_tutorial/authorization/tutorial/views.py
+++ b/docs/quick_tutorial/authorization/tutorial/views.py
@@ -30,33 +30,58 @@ class TutorialViews:
def hello(self):
return {'name': 'Hello View'}
+ @forbidden_view_config()
+ def forbidden(self):
+ request = self.request
+ session = request.session
+ if request.matched_route is not None:
+ session['came_from'] = {
+ 'route_name': request.matched_route.name,
+ 'route_kwargs': request.matchdict,
+ }
+ if request.authenticated_userid is not None:
+ session['message'] = (
+ f'User {request.authenticated_userid} is not allowed '
+ f'to see this resource. Please log in as another user.'
+ )
+ else:
+ if 'came_from' in session:
+ del session['came_from']
+
+ return HTTPFound(request.route_url('login'))
+
@view_config(route_name='login', renderer='login.pt')
- @forbidden_view_config(renderer='login.pt')
def login(self):
request = self.request
+ session = request.session
login_url = request.route_url('login')
- referrer = request.url
- if referrer == login_url:
- referrer = '/' # never use login form itself as came_from
- came_from = request.params.get('came_from', referrer)
- message = ''
+ came_from = session.get('came_from')
+ message = session.get('message', '')
login = ''
password = ''
+
if 'form.submitted' in request.params:
login = request.params['login']
password = request.params['password']
hashed_pw = USERS.get(login)
if hashed_pw and check_password(password, hashed_pw):
headers = remember(request, login)
- return HTTPFound(location=came_from,
- headers=headers)
+
+ if came_from is not None:
+ return_to = request.route_url(
+ came_from['route_name'], **came_from['route_kwargs'],
+ )
+ else:
+ return_to = request.route_url('home')
+
+ return HTTPFound(location=return_to, headers=headers)
+
message = 'Failed login'
return dict(
name='Login',
message=message,
url=request.application_url + '/login',
- came_from=came_from,
login=login,
password=password,
)
diff --git a/docs/quick_tutorial/retail_forms/development.ini b/docs/quick_tutorial/retail_forms/development.ini
deleted file mode 100644
index 78d7479e7..000000000
--- a/docs/quick_tutorial/retail_forms/development.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[app:main]
-use = egg:tutorial
-pyramid.reload_templates = true
-pyramid.includes =
- pyramid_debugtoolbar
-
-[server:main]
-use = egg:waitress#main
-listen = localhost:6543
diff --git a/docs/quick_tutorial/retail_forms/setup.py b/docs/quick_tutorial/retail_forms/setup.py
deleted file mode 100644
index dda0a2cc4..000000000
--- a/docs/quick_tutorial/retail_forms/setup.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from setuptools import setup
-
-# List of dependencies installed via `pip install -e .`
-# by virtue of the Setuptools `install_requires` value below.
-requires = [
- 'deform',
- 'pyramid',
- 'pyramid_chameleon',
- 'waitress',
-]
-
-setup(
- name='tutorial',
- install_requires=requires,
- entry_points={
- 'paste.app_factory': [
- 'main = tutorial:main'
- ],
- },
-)
diff --git a/docs/quick_tutorial/retail_forms/tutorial/__init__.py b/docs/quick_tutorial/retail_forms/tutorial/__init__.py
deleted file mode 100644
index dff7457cf..000000000
--- a/docs/quick_tutorial/retail_forms/tutorial/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from pyramid.config import Configurator
-
-
-def main(global_config, **settings):
- config = Configurator(settings=settings)
- config.include('pyramid_chameleon')
- config.add_route('wiki_view', '/')
- config.add_route('wikipage_add', '/add')
- config.add_route('wikipage_view', '/{uid}')
- config.add_route('wikipage_edit', '/{uid}/edit')
- config.add_static_view('deform_static', 'deform:static/')
- config.scan('.views')
- return config.make_wsgi_app()
diff --git a/docs/quick_tutorial/retail_forms/tutorial/tests.py b/docs/quick_tutorial/retail_forms/tutorial/tests.py
deleted file mode 100644
index 5a2c40904..000000000
--- a/docs/quick_tutorial/retail_forms/tutorial/tests.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import unittest
-
-from pyramid import testing
-
-
-class TutorialViewTests(unittest.TestCase):
- def setUp(self):
- self.config = testing.setUp()
-
- def tearDown(self):
- testing.tearDown()
-
- def test_home(self):
- from .views import WikiViews
-
- request = testing.DummyRequest()
- inst = WikiViews(request)
- response = inst.wiki_view()
- self.assertEqual(len(response['pages']), 3)
-
-
-class TutorialFunctionalTests(unittest.TestCase):
- def setUp(self):
- from tutorial import main
-
- app = main({})
- from webtest import TestApp
-
- self.testapp = TestApp(app)
-
- def tearDown(self):
- testing.tearDown()
-
- def test_home(self):
- res = self.testapp.get('/', status=200)
- self.assertIn(b'<title>Wiki: View</title>', res.body)
diff --git a/docs/quick_tutorial/retail_forms/tutorial/views.py b/docs/quick_tutorial/retail_forms/tutorial/views.py
deleted file mode 100644
index c6f4e3877..000000000
--- a/docs/quick_tutorial/retail_forms/tutorial/views.py
+++ /dev/null
@@ -1,96 +0,0 @@
-import colander
-import deform.widget
-
-from pyramid.httpexceptions import HTTPFound
-from pyramid.view import view_config
-
-pages = {
- '100': dict(uid='100', title='Page 100', body='<em>100</em>'),
- '101': dict(uid='101', title='Page 101', body='<em>101</em>'),
- '102': dict(uid='102', title='Page 102', body='<em>102</em>')
-}
-
-class WikiPage(colander.MappingSchema):
- title = colander.SchemaNode(colander.String())
- body = colander.SchemaNode(
- colander.String(),
- widget=deform.widget.RichTextWidget()
- )
-
-
-class WikiViews:
- def __init__(self, request):
- self.request = request
-
- @property
- def wiki_form(self):
- schema = WikiPage()
- return deform.Form(schema, buttons=('submit',))
-
- @property
- def reqts(self):
- return self.wiki_form.get_widget_resources()
-
- @view_config(route_name='wiki_view', renderer='wiki_view.pt')
- def wiki_view(self):
- return dict(pages=pages.values())
-
- @view_config(route_name='wikipage_add',
- renderer='wikipage_addedit.pt')
- def wikipage_add(self):
- form = self.wiki_form
-
- if 'submit' in self.request.params:
- controls = self.request.POST.items()
- try:
- appstruct = self.wiki_form.validate(controls)
- except deform.ValidationFailure as e:
- # Form is NOT valid
- return dict(form=e.render())
-
- # Form is valid, make a new identifier and add to list
- last_uid = int(sorted(pages.keys())[-1])
- new_uid = str(last_uid + 1)
- pages[new_uid] = dict(
- uid=new_uid, title=appstruct['title'],
- body=appstruct['body']
- )
-
- # Now visit new page
- url = self.request.route_url('wikipage_view', uid=new_uid)
- return HTTPFound(url)
-
- return dict(form=form)
-
- @view_config(route_name='wikipage_view', renderer='wikipage_view.pt')
- def wikipage_view(self):
- uid = self.request.matchdict['uid']
- page = pages[uid]
- return dict(page=page)
-
- @view_config(route_name='wikipage_edit',
- renderer='wikipage_addedit.pt')
- def wikipage_edit(self):
- uid = self.request.matchdict['uid']
- page = pages[uid]
-
- wiki_form = self.wiki_form
-
- if 'submit' in self.request.params:
- controls = self.request.POST.items()
- try:
- appstruct = wiki_form.validate(controls)
- except deform.ValidationFailure as e:
- return dict(page=page, form=e.render())
-
- # Change the content and redirect to the view
- page['title'] = appstruct['title']
- page['body'] = appstruct['body']
-
- url = self.request.route_url('wikipage_view',
- uid=page['uid'])
- return HTTPFound(url)
-
- form = wiki_form.render(page)
-
- return dict(page=page, form=form) \ No newline at end of file
diff --git a/docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt b/docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt
deleted file mode 100644
index 9e3afe495..000000000
--- a/docs/quick_tutorial/retail_forms/tutorial/wiki_view.pt
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <title>Wiki: View</title>
-</head>
-<body>
-<h1>Wiki</h1>
-
-<a href="${request.route_url('wikipage_add')}">Add
- WikiPage</a>
-<ul>
- <li tal:repeat="page pages">
- <a href="${request.route_url('wikipage_view', uid=page.uid)}">
- ${page.title}
- </a>
- </li>
-</ul>
-</body>
-</html> \ No newline at end of file
diff --git a/docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt b/docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt
deleted file mode 100644
index 586f4c44b..000000000
--- a/docs/quick_tutorial/retail_forms/tutorial/wikipage_addedit.pt
+++ /dev/null
@@ -1,37 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <title>WikiPage: Add/Edit</title>
- <tal:block tal:repeat="reqt view.reqts['css']">
- <link rel="stylesheet" type="text/css"
- href="${request.static_url('deform:static/' + reqt)}"/>
- </tal:block>
- <tal:block tal:repeat="reqt view.reqts['js']">
- <script src="${request.static_url('deform:static/' + reqt)}"
- type="text/javascript"></script>
- </tal:block>
-</head>
-<body>
-<h1>Wiki</h1>
-
-<div class="row"
- tal:repeat="field form">
- <div class="span2">
- ${structure:field.title}
- <span class="req" tal:condition="field.required">*</span>
- </div>
- <div class="span2">
- ${structure:field.serialize()}
- </div>
- <ul tal:condition="field.error">
- <li tal:repeat="error field.error.messages()">
- ${structure:error}
- </li>
- </ul>
-</div>
-
-<script type="text/javascript">
- deform.load()
-</script>
-</body>
-</html>
diff --git a/docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt b/docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt
deleted file mode 100644
index cb9ff526e..000000000
--- a/docs/quick_tutorial/retail_forms/tutorial/wikipage_view.pt
+++ /dev/null
@@ -1,17 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <title>WikiPage: View</title>
-</head>
-<body>
-<a href="${request.route_url('wiki_view')}">
- Up
-</a> |
-<a href="${request.route_url('wikipage_edit', uid=page.uid)}">
- Edit
-</a>
-
-<h1>${page.title}</h1>
-<p>${structure: page.body}</p>
-</body>
-</html> \ No newline at end of file