summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt6
-rw-r--r--CONTRIBUTORS.txt2
-rw-r--r--docs/narr/assets.rst87
-rw-r--r--docs/narr/viewconfig.rst4
-rw-r--r--docs/quick_tutorial/debugtoolbar.rst24
-rw-r--r--docs/quick_tutorial/ini.rst6
-rw-r--r--docs/tutorials/wiki2/authorization.rst12
-rw-r--r--pyramid/httpexceptions.py16
-rw-r--r--pyramid/scaffolds/__init__.py4
-rw-r--r--pyramid/scripts/pcreate.py96
-rw-r--r--pyramid/tests/test_httpexceptions.py13
-rw-r--r--pyramid/tests/test_scaffolds/test_init.py5
-rw-r--r--pyramid/tests/test_scripts/test_pcreate.py46
13 files changed, 256 insertions, 65 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 87e9f1f3a..9ed486b26 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -4,6 +4,12 @@
Features
--------
+- pcreate will now ask for confirmation if invoked with
+ an argument for a project name that already exists or
+ is importable in the current environment.
+ See https://github.com/Pylons/pyramid/issues/1357 and
+ https://github.com/Pylons/pyramid/pull/1837
+
- Make it possible to subclass ``pyramid.request.Request`` and also use
``pyramid.request.Request.add_request.method``. See
https://github.com/Pylons/pyramid/issues/1529
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index 8eadbeecf..3405612ef 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -248,3 +248,5 @@ Contributors
- Donald Stufft, 2015/03/15
- Karen Dalton, 2015/06/01
+
+- Igor Stroh, 2015/06/10
diff --git a/docs/narr/assets.rst b/docs/narr/assets.rst
index d6bc8cbb8..fc02b3f7d 100644
--- a/docs/narr/assets.rst
+++ b/docs/narr/assets.rst
@@ -512,6 +512,93 @@ time at start up as a cachebust token:
.. index::
single: static assets view
+CSS and JavaScript source and cache busting
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Often one needs to refer to images and other static assets inside CSS and
+JavaScript files. If cache busting is active, the final static asset URL is
+not available until the static assets have been assembled. These URLs cannot
+be handwritten. Thus, when having static asset references in CSS and
+JavaScript, one needs to perform one of the following tasks.
+
+* Process the files by using a precompiler which rewrites URLs to their final
+ cache busted form.
+
+* Templatize JS and CSS, and call ``request.static_url()`` inside their
+ template code.
+
+* Pass static URL references to CSS and JavaScript via other means.
+
+Below are some simple approaches for CSS and JS programming which consider
+asset cache busting. These approaches do not require additional tools or
+packages.
+
+Relative cache busted URLs in CSS
++++++++++++++++++++++++++++++++++
+
+Consider a CSS file ``/static/theme/css/site.css`` which contains the
+following CSS code.
+
+.. code-block:: css
+
+ body {
+ background: url(/static/theme/img/background.jpg);
+ }
+
+Any changes to ``background.jpg`` would not appear to the visitor because the
+URL path is not cache busted as it is. Instead we would have to construct an
+URL to the background image with the default ``PathSegmentCacheBuster`` cache
+busting mechanism::
+
+ https://site/static/1eeb262c717/theme/img/background.jpg
+
+Every time the image is updated, the URL would need to be changed. It is not
+practical to write this non-human readable URL into a CSS file.
+
+However, the CSS file itself is cache busted and is located under the path for
+static assets. This lets us use relative references in our CSS to cache bust
+the image.
+
+.. code-block:: css
+
+ body {
+ background: url(../img/background.jpg);
+ }
+
+The browser would interpret this as having the CSS file hash in URL::
+
+ https://site/static/ab234b262c71/theme/css/../img/background.jpg
+
+The downside of this approach is that if the background image changes, one
+needs to bump the CSS file. The CSS file hash change signals the caches that
+the relative URL to the image in the CSS has been changed. When updating CSS
+and related image assets, updates usually happen hand in hand, so this does
+not add extra effort to theming workflow.
+
+Passing cache busted URLs to JavaScript
++++++++++++++++++++++++++++++++++++++++
+
+For JavaScript, one can pass static asset URLs as function arguments or
+globals. The globals can be generated in page template code, having access to
+the ``request.static_url()`` function.
+
+Below is a simple example of passing a cached busted image URL in the Jinja2
+template language. Put the following code into the ``<head>`` section of the
+relevant page.
+
+.. code-block:: html
+
+ <script>
+ window.assets.backgroundImage =
+ "{{ '/theme/img/background.jpg'|static_url() }}";
+ </script>
+
+Then in your main ``site.js`` file put the following code.
+
+.. code-block:: javascript
+
+ var image = new Image(window.assets.backgroundImage);
+
.. _advanced_static:
Advanced: Serving Static Assets Using a View Callable
diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst
index fc5ae6dc6..46b2c4f76 100644
--- a/docs/narr/viewconfig.rst
+++ b/docs/narr/viewconfig.rst
@@ -119,7 +119,7 @@ Non-Predicate Arguments
``renderer``
Denotes the :term:`renderer` implementation which will be used to construct
a :term:`response` from the associated view callable's return value.
-
+
.. seealso:: See also :ref:`renderers_chapter`.
This is either a single string term (e.g. ``json``) or a string implying a
@@ -1020,7 +1020,7 @@ there's a ``should_cache`` GET or POST variable:
@view_config(http_cache=3600)
def view(request):
response = Response()
- if not 'should_cache' in request.params:
+ if 'should_cache' not in request.params:
response.cache_control.prevent_auto = True
return response
diff --git a/docs/quick_tutorial/debugtoolbar.rst b/docs/quick_tutorial/debugtoolbar.rst
index a5623ae2a..f11abc493 100644
--- a/docs/quick_tutorial/debugtoolbar.rst
+++ b/docs/quick_tutorial/debugtoolbar.rst
@@ -89,24 +89,24 @@ temporarily.
Extra Credit
============
-# Why don't we add ``pyramid_debugtoolbar`` to the list of
- ``install_requires`` dependencies in ``debugtoolbar/setup.py``?
+#. Why don't we add ``pyramid_debugtoolbar`` to the list of
+ ``install_requires`` dependencies in ``debugtoolbar/setup.py``?
-# Introduce a bug into your application: Change:
+#. Introduce a bug into your application: Change:
- .. code-block:: python
+ .. code-block:: python
- def hello_world(request):
- return Response('<body><h1>Hello World!</h1></body>')
+ def hello_world(request):
+ return Response('<body><h1>Hello World!</h1></body>')
- to:
+ to:
- .. code-block:: python
+ .. code-block:: python
def hello_world(request):
return xResponse('<body><h1>Hello World!</h1></body>')
- Save, and visit http://localhost:6543/ again. Notice the nice
- traceback display. On the lowest line, click the "screen" icon to the
- right, and try typing the variable names ``request`` and ``Response``.
- What else can you discover?
+ Save, and visit http://localhost:6543/ again. Notice the nice
+ traceback display. On the lowest line, click the "screen" icon to the
+ right, and try typing the variable names ``request`` and ``Response``.
+ What else can you discover?
diff --git a/docs/quick_tutorial/ini.rst b/docs/quick_tutorial/ini.rst
index 4e062e575..36942c767 100644
--- a/docs/quick_tutorial/ini.rst
+++ b/docs/quick_tutorial/ini.rst
@@ -131,6 +131,8 @@ Extra Credit
#. The entry point in ``setup.py`` didn't mention ``__init__.py`` when
it declared ``tutorial:main`` function. Why not?
+#. What is the purpose of ``**settings``? What does the ``**`` signify?
+
.. seealso::
:ref:`project_narr`,
:ref:`scaffolding_chapter`,
@@ -138,7 +140,3 @@ Extra Credit
:ref:`environment_chapter`,
:ref:`paste_chapter`
-Extra Credit
-============
-
-#. What is the purpose of ``**settings``? What does the ``**`` signify?
diff --git a/docs/tutorials/wiki2/authorization.rst b/docs/tutorials/wiki2/authorization.rst
index d2ad7a9ca..1d810b05b 100644
--- a/docs/tutorials/wiki2/authorization.rst
+++ b/docs/tutorials/wiki2/authorization.rst
@@ -5,12 +5,12 @@ Adding authorization
====================
: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.
+: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
diff --git a/pyramid/httpexceptions.py b/pyramid/httpexceptions.py
index 465769834..93d06e0d6 100644
--- a/pyramid/httpexceptions.py
+++ b/pyramid/httpexceptions.py
@@ -562,10 +562,7 @@ class HTTPClientError(HTTPError):
a bug. A server-side traceback is not warranted. Unless specialized,
this is a '400 Bad Request'
"""
- code = 400
- title = 'Bad Request'
- explanation = ('The server could not comply with the request since '
- 'it is either malformed or otherwise incorrect.')
+ pass
class HTTPBadRequest(HTTPClientError):
"""
@@ -576,7 +573,10 @@ class HTTPBadRequest(HTTPClientError):
code: 400, title: Bad Request
"""
- pass
+ code = 400
+ title = 'Bad Request'
+ explanation = ('The server could not comply with the request since '
+ 'it is either malformed or otherwise incorrect.')
class HTTPUnauthorized(HTTPClientError):
"""
@@ -988,15 +988,15 @@ class HTTPServerError(HTTPError):
This is an error condition in which the server is presumed to be
in-error. Unless specialized, this is a '500 Internal Server Error'.
"""
+ pass
+
+class HTTPInternalServerError(HTTPServerError):
code = 500
title = 'Internal Server Error'
explanation = (
'The server has either erred or is incapable of performing '
'the requested operation.')
-class HTTPInternalServerError(HTTPServerError):
- pass
-
class HTTPNotImplemented(HTTPServerError):
"""
subclass of :class:`~HTTPServerError`
diff --git a/pyramid/scaffolds/__init__.py b/pyramid/scaffolds/__init__.py
index c993ce5f9..4e811a42b 100644
--- a/pyramid/scaffolds/__init__.py
+++ b/pyramid/scaffolds/__init__.py
@@ -18,10 +18,6 @@ class PyramidTemplate(Template):
misnamings (such as naming a package "site" or naming a package
logger "root".
"""
- if vars['package'] == 'site':
- raise ValueError('Sorry, you may not name your package "site". '
- 'The package name "site" has a special meaning in '
- 'Python. Please name it anything except "site".')
vars['random_string'] = native_(binascii.hexlify(os.urandom(20)))
package_logger = vars['package']
if package_logger == 'root':
diff --git a/pyramid/scripts/pcreate.py b/pyramid/scripts/pcreate.py
index f6376f575..1e8074fc5 100644
--- a/pyramid/scripts/pcreate.py
+++ b/pyramid/scripts/pcreate.py
@@ -8,12 +8,17 @@ import os.path
import pkg_resources
import re
import sys
+from pyramid.compat import input_
_bad_chars_re = re.compile('[^a-zA-Z0-9_]')
def main(argv=sys.argv, quiet=False):
command = PCreateCommand(argv, quiet)
- return command.run()
+ try:
+ return command.run()
+ except KeyboardInterrupt: # pragma: no cover
+ return 1
+
class PCreateCommand(object):
verbosity = 1 # required
@@ -52,6 +57,13 @@ class PCreateCommand(object):
dest='interactive',
action='store_true',
help='When a file would be overwritten, interrogate')
+ parser.add_option('--ignore-conflicting-name',
+ dest='force_bad_name',
+ action='store_true',
+ default=False,
+ help='Do create a project even if the chosen name '
+ 'is the name of an already existing / importable '
+ 'package.')
pyramid_dist = pkg_resources.get_distribution("pyramid")
@@ -69,25 +81,19 @@ class PCreateCommand(object):
self.out('')
self.show_scaffolds()
return 2
- if not self.options.scaffold_name:
- self.out('You must provide at least one scaffold name: -s <scaffold name>')
- self.out('')
- self.show_scaffolds()
- return 2
- if not self.args:
- self.out('You must provide a project name')
- return 2
- available = [x.name for x in self.scaffolds]
- diff = set(self.options.scaffold_name).difference(available)
- if diff:
- self.out('Unavailable scaffolds: %s' % list(diff))
+
+ if not self.validate_input():
return 2
+
return self.render_scaffolds()
- def render_scaffolds(self):
- options = self.options
- args = self.args
- output_dir = os.path.abspath(os.path.normpath(args[0]))
+ @property
+ def output_path(self):
+ return os.path.abspath(os.path.normpath(self.args[0]))
+
+ @property
+ def project_vars(self):
+ output_dir = self.output_path
project_name = os.path.basename(os.path.split(output_dir)[1])
pkg_name = _bad_chars_re.sub(
'', project_name.lower().replace('-', '_'))
@@ -111,17 +117,22 @@ class PCreateCommand(object):
else:
pyramid_docs_branch = 'latest'
- vars = {
+ return {
'project': project_name,
'package': pkg_name,
'egg': egg_name,
'pyramid_version': pyramid_version,
'pyramid_docs_branch': pyramid_docs_branch,
- }
- for scaffold_name in options.scaffold_name:
+ }
+
+
+ def render_scaffolds(self):
+ props = self.project_vars
+ output_dir = self.output_path
+ for scaffold_name in self.options.scaffold_name:
for scaffold in self.scaffolds:
if scaffold.name == scaffold_name:
- scaffold.run(self, output_dir, vars)
+ scaffold.run(self, output_dir, props)
return 0
def show_scaffolds(self):
@@ -154,5 +165,48 @@ class PCreateCommand(object):
if not self.quiet:
print(msg)
+ def validate_input(self):
+ if not self.options.scaffold_name:
+ self.out('You must provide at least one scaffold name: -s <scaffold name>')
+ self.out('')
+ self.show_scaffolds()
+ return False
+ if not self.args:
+ self.out('You must provide a project name')
+ return False
+ available = [x.name for x in self.scaffolds]
+ diff = set(self.options.scaffold_name).difference(available)
+ if diff:
+ self.out('Unavailable scaffolds: %s' % ", ".join(sorted(diff)))
+ return False
+
+ pkg_name = self.project_vars['package']
+
+ if pkg_name == 'site' and not self.options.force_bad_name:
+ self.out('The package name "site" has a special meaning in '
+ 'Python. Are you sure you want to use it as your '
+ 'project\'s name?')
+ return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name))
+
+ # check if pkg_name can be imported (i.e. already exists in current
+ # $PYTHON_PATH, if so - let the user confirm
+ pkg_exists = True
+ try:
+ __import__(pkg_name, globals(), locals(), [], 0) # use absolute imports
+ except ImportError as error:
+ pkg_exists = False
+ if not pkg_exists:
+ return True
+
+ if self.options.force_bad_name:
+ return True
+ self.out('A package named "{0}" already exists, are you sure you want '
+ 'to use it as your project\'s name?'.format(pkg_name))
+ return self.confirm_bad_name('Really use "{0}"?: '.format(pkg_name))
+
+ def confirm_bad_name(self, prompt): # pragma: no cover
+ answer = input_('{0} [y|N]: '.format(prompt))
+ return answer.strip().lower() == 'y'
+
if __name__ == '__main__': # pragma: no cover
sys.exit(main() or 0)
diff --git a/pyramid/tests/test_httpexceptions.py b/pyramid/tests/test_httpexceptions.py
index d0779e080..c700dc80e 100644
--- a/pyramid/tests/test_httpexceptions.py
+++ b/pyramid/tests/test_httpexceptions.py
@@ -10,13 +10,22 @@ class Test_exception_response(unittest.TestCase):
from pyramid.httpexceptions import exception_response
return exception_response(*arg, **kw)
+ def test_status_400(self):
+ from pyramid.httpexceptions import HTTPBadRequest
+ self.assertTrue(isinstance(self._callFUT(400), HTTPBadRequest))
+
def test_status_404(self):
from pyramid.httpexceptions import HTTPNotFound
- self.assertEqual(self._callFUT(404).__class__, HTTPNotFound)
+ self.assertTrue(isinstance(self._callFUT(404), HTTPNotFound))
+
+ def test_status_500(self):
+ from pyramid.httpexceptions import HTTPInternalServerError
+ self.assertTrue(isinstance(self._callFUT(500),
+ HTTPInternalServerError))
def test_status_201(self):
from pyramid.httpexceptions import HTTPCreated
- self.assertEqual(self._callFUT(201).__class__, HTTPCreated)
+ self.assertTrue(isinstance(self._callFUT(201), HTTPCreated))
def test_extra_kw(self):
resp = self._callFUT(404, headers=[('abc', 'def')])
diff --git a/pyramid/tests/test_scaffolds/test_init.py b/pyramid/tests/test_scaffolds/test_init.py
index 4988e66ff..f4d1b287a 100644
--- a/pyramid/tests/test_scaffolds/test_init.py
+++ b/pyramid/tests/test_scaffolds/test_init.py
@@ -12,11 +12,6 @@ class TestPyramidTemplate(unittest.TestCase):
self.assertTrue(vars['random_string'])
self.assertEqual(vars['package_logger'], 'one')
- def test_pre_site(self):
- inst = self._makeOne()
- vars = {'package':'site'}
- self.assertRaises(ValueError, inst.pre, 'command', 'output dir', vars)
-
def test_pre_root(self):
inst = self._makeOne()
vars = {'package':'root'}
diff --git a/pyramid/tests/test_scripts/test_pcreate.py b/pyramid/tests/test_scripts/test_pcreate.py
index 63e5e6368..eaa7c1464 100644
--- a/pyramid/tests/test_scripts/test_pcreate.py
+++ b/pyramid/tests/test_scripts/test_pcreate.py
@@ -1,5 +1,6 @@
import unittest
+
class TestPCreateCommand(unittest.TestCase):
def setUp(self):
from pyramid.compat import NativeIO
@@ -15,7 +16,8 @@ class TestPCreateCommand(unittest.TestCase):
def _makeOne(self, *args, **kw):
effargs = ['pcreate']
effargs.extend(args)
- cmd = self._getTargetClass()(effargs, **kw)
+ tgt_class = kw.pop('target_class', self._getTargetClass())
+ cmd = tgt_class(effargs, **kw)
cmd.out = self.out
return cmd
@@ -220,6 +222,48 @@ class TestPCreateCommand(unittest.TestCase):
'pyramid_version': '0.10.1dev',
'pyramid_docs_branch': 'master'})
+ def test_confirm_override_conflicting_name(self):
+ from pyramid.scripts.pcreate import PCreateCommand
+ class YahInputPCreateCommand(PCreateCommand):
+ def confirm_bad_name(self, pkg_name):
+ return True
+ cmd = self._makeOne('-s', 'dummy', 'Unittest', target_class=YahInputPCreateCommand)
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.10.1dev")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Unittest', 'egg': 'Unittest', 'package': 'unittest',
+ 'pyramid_version': '0.10.1dev',
+ 'pyramid_docs_branch': 'master'})
+
+ def test_force_override_conflicting_name(self):
+ cmd = self._makeOne('-s', 'dummy', 'Unittest', '--ignore-conflicting-name')
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.10.1dev")
+ result = cmd.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(
+ scaffold.vars,
+ {'project': 'Unittest', 'egg': 'Unittest', 'package': 'unittest',
+ 'pyramid_version': '0.10.1dev',
+ 'pyramid_docs_branch': 'master'})
+
+ def test_force_override_site_name(self):
+ from pyramid.scripts.pcreate import PCreateCommand
+ class NayInputPCreateCommand(PCreateCommand):
+ def confirm_bad_name(self, pkg_name):
+ return False
+ cmd = self._makeOne('-s', 'dummy', 'Site', target_class=NayInputPCreateCommand)
+ scaffold = DummyScaffold('dummy')
+ cmd.scaffolds = [scaffold]
+ cmd.pyramid_dist = DummyDist("0.10.1dev")
+ result = cmd.run()
+ self.assertEqual(result, 2)
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):