Merge branch 'master' of github.com:Pylons/pyramid
| | |
| | | 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 |
| | |
| | | - Donald Stufft, 2015/03/15 |
| | | |
| | | - Karen Dalton, 2015/06/01 |
| | | |
| | | - Igor Stroh, 2015/06/10 |
| | |
| | | .. 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 |
| | |
| | | ``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 |
| | |
| | | @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 |
| | | |
| | |
| | | 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? |
| | |
| | | #. 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`, |
| | |
| | | :ref:`environment_chapter`, |
| | | :ref:`paste_chapter` |
| | | |
| | | Extra Credit |
| | | ============ |
| | | |
| | | #. What is the purpose of ``**settings``? What does the ``**`` signify? |
| | |
| | | ==================== |
| | | |
| | | :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 |
| | |
| | | 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): |
| | | """ |
| | |
| | | |
| | | 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): |
| | | """ |
| | |
| | | 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): |
| | | """ |
| | |
| | | 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': |
| | |
| | | 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 |
| | |
| | | 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") |
| | | |
| | |
| | | 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() |
| | | |
| | | if not self.validate_input(): |
| | | 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)) |
| | | 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('-', '_')) |
| | |
| | | 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): |
| | |
| | | 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) |
| | |
| | | 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')]) |
| | |
| | | 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'} |
| | |
| | | import unittest |
| | | |
| | | |
| | | class TestPCreateCommand(unittest.TestCase): |
| | | def setUp(self): |
| | | from pyramid.compat import NativeIO |
| | |
| | | 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 |
| | | |
| | |
| | | '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): |