├── .gitignore ├── .gitmodules ├── CHANGES ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── docs ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── examples ├── chat.py ├── comet.py ├── hello.py ├── templates │ ├── chat.html │ ├── comet.html │ ├── hello.html │ └── upload.html └── upload.py ├── flask_sijax.py ├── setup.cfg ├── setup.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | env 4 | env* 5 | dist 6 | *.egg-info 7 | *.egg 8 | *.swp 9 | examples/static 10 | docs/_build 11 | /.eggs 12 | /build 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/_themes"] 2 | path = docs/_themes 3 | url = git://github.com/mitsuhiko/flask-sphinx-themes 4 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | Flask-Sijax Changelog 2 | ===================== 3 | 4 | Version 0.4.1 5 | ------------- 6 | 7 | Adds CSRF protection to the examples and docs. 8 | 9 | Version 0.4.0 10 | ------------- 11 | 12 | Adds compatibility with Python 3. 13 | 14 | 15 | Version 0.3.3 16 | ------------- 17 | 18 | Fixes a problem with request.files being inaccessible 19 | from upload callbacks when running under Werkzeug 0.9+ 20 | ("ValueError: I/O operation on closed file"). 21 | 22 | 23 | Version 0.3.0 24 | ------------- 25 | 26 | The extension now lives in the flask_sijax package, 27 | instead of the flaskext.sijax namespaced package. 28 | 29 | Minor backwards incompatible API changes that make Flask-Sijax 30 | more consistent with Flask extension patterns. 31 | 32 | 33 | Version 0.2.0 34 | ------------- 35 | 36 | Adds compatibility with Sijax 0.2.0, which introduced some API changes. 37 | The Flask-Sijax API however remains unchanged. 38 | 39 | 40 | Version 0.1.x 41 | ------------- 42 | 43 | Initial release. 44 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Slavi Pantaleev 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | * Neither the name of flask-sijax nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt README.rst tests.py 2 | recursive-include docs * 3 | recursive-include examples *.py 4 | recursive-include examples/templates *.html 5 | 6 | recursive-exclude * *.swp 7 | recursive-exclude * *.pyc 8 | recursive-exclude examples/static * 9 | recursive-exclude docs/_build * 10 | recursive-exclude docs/_themes/.git * 11 | exclude docs/_themes/.gitignore 12 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Flask-Sijax 2 | #################################### 3 | 4 | Flask-Sijax is an extension for the `Flask `_ microframework 5 | to simplify Sijax setup and usage for Flask users. 6 | 7 | `Sijax `_ is a Python/jQuery library 8 | providing easy to use AJAX capabilities to web applications. 9 | 10 | Take a look at the documentation_ (examples included) to learn more. 11 | 12 | .. _documentation: http://packages.python.org/Flask-Sijax 13 | 14 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flask-Sijax.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flask-Sijax.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Flask-Sijax" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flask-Sijax" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Flask-Sijax documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Mar 6 02:01:07 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | sys.path.append(os.path.abspath('_themes')) 22 | 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be extensions 29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 30 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix of source filenames. 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = u'Flask-Sijax' 46 | copyright = u'2011, Slavi Pantaleev' 47 | 48 | # The version info for the project you're documenting, acts as replacement for 49 | # |version| and |release|, also used in various other places throughout the 50 | # built documents. 51 | # 52 | # The short X.Y version. 53 | version = '0.4.1' 54 | # The full version, including alpha/beta/rc tags. 55 | release = '0.4.1' 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | #language = None 60 | 61 | # There are two options for replacing |today|: either, you set today to some 62 | # non-false value, then it is used: 63 | #today = '' 64 | # Else, today_fmt is used as the format for a strftime call. 65 | #today_fmt = '%B %d, %Y' 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | exclude_patterns = ['_build'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | html_theme = 'flask_small' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | html_theme_options = {'github_fork': 'spantaleev/flask-sijax', 'index_logo': False} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | html_theme_path = ['_themes'] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_domain_indices = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 155 | #html_show_sphinx = True 156 | 157 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 158 | #html_show_copyright = True 159 | 160 | # If true, an OpenSearch description file will be output, and all pages will 161 | # contain a tag referring to it. The value of this option must be the 162 | # base URL from which the finished HTML is served. 163 | #html_use_opensearch = '' 164 | 165 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 166 | #html_file_suffix = None 167 | 168 | # Output file base name for HTML help builder. 169 | htmlhelp_basename = 'Flask-Sijaxdoc' 170 | 171 | 172 | # -- Options for LaTeX output -------------------------------------------------- 173 | 174 | # The paper size ('letter' or 'a4'). 175 | #latex_paper_size = 'letter' 176 | 177 | # The font size ('10pt', '11pt' or '12pt'). 178 | #latex_font_size = '10pt' 179 | 180 | # Grouping the document tree into LaTeX files. List of tuples 181 | # (source start file, target name, title, author, documentclass [howto/manual]). 182 | latex_documents = [ 183 | ('index', 'Flask-Sijax.tex', u'Flask-Sijax Documentation', 184 | u'Slavi Pantaleev', 'manual'), 185 | ] 186 | 187 | # The name of an image file (relative to this directory) to place at the top of 188 | # the title page. 189 | #latex_logo = None 190 | 191 | # For "manual" documents, if this is true, then toplevel headings are parts, 192 | # not chapters. 193 | #latex_use_parts = False 194 | 195 | # If true, show page references after internal links. 196 | #latex_show_pagerefs = False 197 | 198 | # If true, show URL addresses after external links. 199 | #latex_show_urls = False 200 | 201 | # Additional stuff for the LaTeX preamble. 202 | #latex_preamble = '' 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'flask-sijax', u'Flask-Sijax Documentation', 217 | [u'Slavi Pantaleev'], 1) 218 | ] 219 | 220 | # Example configuration for intersphinx: refer to the Python standard library. 221 | intersphinx_mapping = { 222 | 'sijax': ('http://packages.python.org/Sijax/', None), 223 | } 224 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Flask-Sijax documentation master file, created by 2 | sphinx-quickstart on Sun Mar 6 02:01:07 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Flask-Sijax 7 | =========== 8 | 9 | :Author: Slavi Pantaleev 10 | :Version: |release| 11 | :Source: github.com_ 12 | :Bug tracker: `github.com/issues `_ 13 | 14 | Flask-Sijax helps you add Sijax_ support to your Flask_ applications. 15 | 16 | Sijax is a Python/jQuery_ library that makes AJAX easy to use in web applications. 17 | 18 | .. _github.com: https://github.com/spantaleev/flask-sijax 19 | .. _Flask: http://flask.pocoo.org/ 20 | .. _jQuery: http://jquery.com/ 21 | .. _Sijax: http://pypi.python.org/pypi/Sijax 22 | 23 | Installing Flask-Sijax 24 | ---------------------- 25 | 26 | Flask-Sijax is available on PyPI_ and can be installed using **easy_install**:: 27 | 28 | easy_install flask-sijax 29 | 30 | or using **pip**:: 31 | 32 | pip install flask-sijax 33 | 34 | 35 | .. _PyPI: http://pypi.python.org/pypi/Flask-Sijax/ 36 | 37 | Setting it up 38 | ------------- 39 | 40 | Here's an example of how Flask-Sijax is typically initialized and configured:: 41 | 42 | import os 43 | from flask import Flask, g 44 | import flask_sijax 45 | 46 | path = os.path.join('.', os.path.dirname(__file__), 'static/js/sijax/') 47 | 48 | app = Flask(__name__) 49 | app.config['SIJAX_STATIC_PATH'] = path 50 | app.config['SIJAX_JSON_URI'] = '/static/js/sijax/json2.js' 51 | flask_sijax.Sijax(app) 52 | 53 | 54 | Configuration options 55 | --------------------- 56 | 57 | **Flask-Sijax** is configured via the standard Flask config API. 58 | Here are the available configuration options: 59 | 60 | * **SIJAX_STATIC_PATH** - the static path where you want the Sijax javascript files to be mirrored. 61 | 62 | Flask-Sijax takes care of keeping the Sijax javascript files ``sijax.js`` and ``json2.js`` 63 | up to date in this directory (even between version changes). 64 | 65 | Don't put anything else in that directory - it should be dedicated to Sijax for hosting the files. 66 | 67 | 68 | * **SIJAX_JSON_URI** - the URI to load the ``json2.js`` static file from (if needed). 69 | 70 | Sijax uses JSON to pass data between the browser and server. This means that browsers either need to support 71 | JSON natively or get JSON support from the ``json2.js`` file. Such browsers include IE <= 7. 72 | If you've set a URI to ``json2.js`` and Sijax detects that the browser needs to load it, it will do so on demand. 73 | The URI could be relative or absolute. 74 | 75 | 76 | Making your Flask functions Sijax-aware 77 | ---------------------------------------------- 78 | 79 | Registering view functions with Flask is usually done using ``@app.route`` or ``@blueprint.route``. 80 | Functions registered that way cannot provide Sijax functionality, because they cannot be accessed 81 | using a POST method by default (and Sijax uses POST requests). 82 | 83 | To make a view function capable of handling Sijax requests, 84 | make it accessible via POST using ``@app.route('/url', methods=['GET', 'POST'])`` 85 | or use the ``@flask_sijax.route`` helper decorator like this:: 86 | 87 | # Initialization code for Flask and Flask-Sijax 88 | # See above.. 89 | 90 | # Functions registered with @app.route CANNOT use Sijax 91 | @app.route('/') 92 | def index(): 93 | return 'Index' 94 | 95 | # Functions registered with @flask_sijax.route can use Sijax 96 | @flask_sijax.route(app, '/hello') 97 | def hello(): 98 | # Every Sijax handler function (like this one) receives at least 99 | # one parameter automatically, much like Python passes `self` 100 | # to object methods. 101 | # The `obj_response` parameter is the function's way of talking 102 | # back to the browser 103 | def say_hi(obj_response): 104 | obj_response.alert('Hi there!') 105 | 106 | if g.sijax.is_sijax_request: 107 | # Sijax request detected - let Sijax handle it 108 | g.sijax.register_callback('say_hi', say_hi) 109 | return g.sijax.process_request() 110 | 111 | # Regular (non-Sijax request) - render the page template 112 | return _render_template() 113 | 114 | Let's assume ``_render_template()`` renders the following page:: 115 | 116 | 117 | 118 | 120 | 122 | 125 | 126 | 127 | Click here 128 | 129 | 130 | 131 | Clicking on the link will fire a Sijax request (a special ``jQuery.ajax()`` request) to the server. 132 | 133 | This request is detected on the server by ``g.sijax.is_sijax_request()``, in which case you let Sijax handle the request. 134 | 135 | All functions registered using ``g.sijax.register_callback()`` (see :meth:`flask_sijax.Sijax.register_callback`) are exposed for calling from the browser. 136 | 137 | Calling ``g.sijax.process_request()`` tells Sijax to execute the appropriate (previously registered) function and return the response to the browser. 138 | 139 | To learn more on ``obj_response`` and what it provides, see :class:`sijax.response.BaseResponse`. 140 | 141 | 142 | Setting up the client (browser) 143 | ------------------------------- 144 | 145 | The browser needs to talk to the server and that's done using `jQuery`_ (``jQuery.ajax``) 146 | and the Sijax javascript file (``sijax.js``). 147 | You'll have to load those on each page that needs to use Sijax. 148 | 149 | The ``sijax.js`` file is part of the `Sijax`_ project, but can be mirrored to a directory 150 | of your choosing if you use the ``SIJAX_STATIC_PATH`` configuration option (see above). 151 | There is no need to download Sijax separately and extract the file from it manually. 152 | 153 | Once both files are loaded, you can put the javascript init code (``g.sijax.get_js()``) somewhere on the page. 154 | That code is page-specific and needs to be executed after the ``sijax.js`` file has loaded. 155 | 156 | Assuming you've used the above configuration here's the HTML markup you need to add to your template:: 157 | 158 | 160 | 162 | 165 | 166 | You can then invoke a Sijax function using javascript like this:: 167 | 168 | Sijax.request('function_name', ['argument 1', 150, 'argument 3']); 169 | 170 | provided the function has been defined and registered with Sijax on the server-side:: 171 | 172 | def function_name(obj_response, arg1, arg2, arg3): 173 | obj_response.alert('You called the function successfully!') 174 | 175 | g.sijax.register_callback('function_name', function_name) 176 | 177 | To learn more on ``Sijax.request()`` see :ref:`Sijax:clientside-sijax-request`. 178 | 179 | Learn more on how it all fits together from the **Examples**. 180 | 181 | CSRF protection 182 | --------------- 183 | 184 | Learn more about `cross-site request forgery `_. In a nutshell, you want to ensure that you are indeed communicating with the same trusted user and not an imposter. 185 | 186 | In the following, we will see how to implement basic CSRF protection for your 187 | Sijax calls. The basic idea is that you send out a unique token when 188 | communitcating with your unique, trusted user and expect the same token when that user communicates with your server. 189 | 190 | There are a number of packages out there that can help you with token 191 | generation and validation. You might be using some of them already so it would 192 | be easy for you to implement this code. 193 | 194 | * Flask-SeaSurf_ 195 | * Flask-WTF_ 196 | * Flask-Security_ 197 | * even Django_ 198 | 199 | .. _Flask-SeaSurf: https://flask-seasurf.readthedocs.org/en/latest/ 200 | .. _Flask-WTF: https://flask-wtf.readthedocs.org/en/latest/csrf.html 201 | .. _Flask-Security: https://pythonhosted.org/Flask-Security/index.html 202 | .. _Django: https://docs.djangoproject.com/en/1.7/ref/contrib/csrf/ 203 | 204 | In the following, we rely on some adapted code from a `Flask Snippet `_ and 205 | `this gist `_. We do this as an 206 | example here in order to not add more dependencies but it's probably better to 207 | rely on the above packages to keep up with the development. 208 | 209 | The first step is to generate a unique token. The following function illustrates 210 | a simple way of doing this. Keep in mind that in order to use the session, the 211 | Flask application needs to have its secret key set.:: 212 | 213 | import os 214 | import hmac 215 | from hashlib import sha1 216 | from flask import session 217 | 218 | @app.template_global('csrf_token') 219 | def csrf_token(): 220 | """ 221 | Generate a token string from bytes arrays. The token in the session is user 222 | specific. 223 | """ 224 | if "_csrf_token" not in session: 225 | session["_csrf_token"] = os.urandom(128) 226 | return hmac.new(app.secret_key, session["_csrf_token"], 227 | digestmod=sha1).hexdigest() 228 | 229 | 230 | 231 | Due to the decorator you can now use this function in any of your templates. 232 | There are at least four ways to embed the token in your page. Say you have a 233 | Jinja2 template, then you either put it in a meta tag,:: 234 | 235 | 236 | 237 | as part of a form into a hidden field,:: 238 | 239 | 240 | 241 | directly store it in a Javascript variable:: 242 | 243 | 246 | 247 | or directly as part of your Sijax call:: 248 | 249 | 250 | 251 | In the first two cases, you will have to use Javascript to extract the token 252 | from the HTML element. 253 | 254 | Sijax merges any object of the `data` field of the third argument to 255 | `Sijax.request` with the form data of the request. We can retrieve the token 256 | from there and check its consistency. You can automatically do this for any 257 | request to a view function using the following piece of code:: 258 | 259 | @app.before_request 260 | def check_csrf_token(): 261 | """Checks that token is correct, aborting if not""" 262 | if request.method in ("GET",): # not exhaustive list 263 | return 264 | token = request.form.get("csrf_token") 265 | if token is None: 266 | app.logger.warning("Expected CSRF Token: not present") 267 | abort(400) 268 | if not safe_str_cmp(token, csrf_token()): 269 | app.logger.warning("CSRF Token incorrect") 270 | abort(400) 271 | 272 | This will check for the presence of a token for any request that is not `GET` 273 | and compare the delivered with the known token. 274 | 275 | Examples 276 | -------- 277 | 278 | We have provided complete examples which you can run directly from the source distribution (if you've installed the dependencies - Flask_ and Sijax_ - both available on PyPI). 279 | 280 | * **Hello** - a Hello world project 281 | 282 | - `Hello Code `_ 283 | - `Hello Template (jinja2) `_ 284 | 285 | 286 | * **Chat** - a simple Chat/Shoutbox 287 | 288 | - `Chat Code `_ 289 | - `Chat Template (jinja2) `_ 290 | 291 | 292 | * **Comet** - a demonstration of the :ref:`Comet Plugin ` 293 | 294 | - `Comet Code `_ 295 | - `Comet Template (jinja2) `_ 296 | 297 | 298 | * **Upload** - a demonstration of the :ref:`Upload Plugin ` 299 | 300 | - `Upload Code `_ 301 | - `Upload Template (jinja2) `_ 302 | 303 | 304 | Sijax documentation 305 | ------------------- 306 | 307 | The `documentation for Sijax `_ is very exhaustive and even though you're using the Flask-Sijax extension, which hides some of the details for you, you can still learn a lot from it. 308 | 309 | * :ref:`Using Sijax ` 310 | * :ref:`Comet Plugin ` 311 | * :ref:`Upload Plugin ` 312 | * :ref:`Sijax.request() ` 313 | * :ref:`Sijax.getFormValues() ` 314 | * :ref:`FAQ ` 315 | 316 | 317 | API 318 | --- 319 | 320 | .. autofunction:: flask_sijax.route 321 | .. autoclass:: flask_sijax.Sijax 322 | :members: 323 | 324 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | if NOT "%PAPER%" == "" ( 11 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 12 | ) 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "help" ( 17 | :help 18 | echo.Please use `make ^` where ^ is one of 19 | echo. html to make standalone HTML files 20 | echo. dirhtml to make HTML files named index.html in directories 21 | echo. singlehtml to make a single large HTML file 22 | echo. pickle to make pickle files 23 | echo. json to make JSON files 24 | echo. htmlhelp to make HTML files and a HTML help project 25 | echo. qthelp to make HTML files and a qthelp project 26 | echo. devhelp to make HTML files and a Devhelp project 27 | echo. epub to make an epub 28 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 29 | echo. text to make text files 30 | echo. man to make manual pages 31 | echo. changes to make an overview over all changed/added/deprecated items 32 | echo. linkcheck to check all external links for integrity 33 | echo. doctest to run all doctests embedded in the documentation if enabled 34 | goto end 35 | ) 36 | 37 | if "%1" == "clean" ( 38 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 39 | del /q /s %BUILDDIR%\* 40 | goto end 41 | ) 42 | 43 | if "%1" == "html" ( 44 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 45 | if errorlevel 1 exit /b 1 46 | echo. 47 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 48 | goto end 49 | ) 50 | 51 | if "%1" == "dirhtml" ( 52 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 53 | if errorlevel 1 exit /b 1 54 | echo. 55 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 56 | goto end 57 | ) 58 | 59 | if "%1" == "singlehtml" ( 60 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 61 | if errorlevel 1 exit /b 1 62 | echo. 63 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 64 | goto end 65 | ) 66 | 67 | if "%1" == "pickle" ( 68 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 69 | if errorlevel 1 exit /b 1 70 | echo. 71 | echo.Build finished; now you can process the pickle files. 72 | goto end 73 | ) 74 | 75 | if "%1" == "json" ( 76 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished; now you can process the JSON files. 80 | goto end 81 | ) 82 | 83 | if "%1" == "htmlhelp" ( 84 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished; now you can run HTML Help Workshop with the ^ 88 | .hhp project file in %BUILDDIR%/htmlhelp. 89 | goto end 90 | ) 91 | 92 | if "%1" == "qthelp" ( 93 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 94 | if errorlevel 1 exit /b 1 95 | echo. 96 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 97 | .qhcp project file in %BUILDDIR%/qthelp, like this: 98 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flask-Sijax.qhcp 99 | echo.To view the help file: 100 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flask-Sijax.ghc 101 | goto end 102 | ) 103 | 104 | if "%1" == "devhelp" ( 105 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 106 | if errorlevel 1 exit /b 1 107 | echo. 108 | echo.Build finished. 109 | goto end 110 | ) 111 | 112 | if "%1" == "epub" ( 113 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 117 | goto end 118 | ) 119 | 120 | if "%1" == "latex" ( 121 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 122 | if errorlevel 1 exit /b 1 123 | echo. 124 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 125 | goto end 126 | ) 127 | 128 | if "%1" == "text" ( 129 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished. The text files are in %BUILDDIR%/text. 133 | goto end 134 | ) 135 | 136 | if "%1" == "man" ( 137 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 141 | goto end 142 | ) 143 | 144 | if "%1" == "changes" ( 145 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.The overview file is in %BUILDDIR%/changes. 149 | goto end 150 | ) 151 | 152 | if "%1" == "linkcheck" ( 153 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Link check complete; look for any errors in the above output ^ 157 | or in %BUILDDIR%/linkcheck/output.txt. 158 | goto end 159 | ) 160 | 161 | if "%1" == "doctest" ( 162 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 163 | if errorlevel 1 exit /b 1 164 | echo. 165 | echo.Testing of doctests in the sources finished, look at the ^ 166 | results in %BUILDDIR%/doctest/output.txt. 167 | goto end 168 | ) 169 | 170 | :end 171 | -------------------------------------------------------------------------------- /examples/chat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """A chat/shoutbox using Sijax.""" 4 | 5 | import os 6 | import hmac 7 | from hashlib import sha1 8 | 9 | from flask import Flask, g, render_template, session, abort, request 10 | from werkzeug.security import safe_str_cmp 11 | import flask_sijax 12 | 13 | app = Flask(__name__) 14 | app.secret_key = os.urandom(128) 15 | 16 | @app.template_global('csrf_token') 17 | def csrf_token(): 18 | """ 19 | Generate a token string from bytes arrays. The token in the session is user 20 | specific. 21 | """ 22 | if "_csrf_token" not in session: 23 | session["_csrf_token"] = os.urandom(128) 24 | return hmac.new(app.secret_key, session["_csrf_token"], 25 | digestmod=sha1).hexdigest() 26 | 27 | @app.before_request 28 | def check_csrf_token(): 29 | """Checks that token is correct, aborting if not""" 30 | if request.method in ("GET",): # not exhaustive list 31 | return 32 | token = request.form.get("csrf_token") 33 | if token is None: 34 | app.logger.warning("Expected CSRF Token: not present") 35 | abort(400) 36 | if not safe_str_cmp(token, csrf_token()): 37 | app.logger.warning("CSRF Token incorrect") 38 | abort(400) 39 | 40 | # The path where you want the extension to create the needed javascript files 41 | # DON'T put any of your files in this directory, because they'll be deleted! 42 | app.config["SIJAX_STATIC_PATH"] = os.path.join('.', os.path.dirname(__file__), 'static/js/sijax/') 43 | 44 | # You need to point Sijax to the json2.js library if you want to support 45 | # browsers that don't support JSON natively (like IE <= 7) 46 | app.config["SIJAX_JSON_URI"] = '/static/js/sijax/json2.js' 47 | 48 | flask_sijax.Sijax(app) 49 | 50 | class SijaxHandler(object): 51 | """A container class for all Sijax handlers. 52 | 53 | Grouping all Sijax handler functions in a class 54 | (or a Python module) allows them all to be registered with 55 | a single line of code. 56 | """ 57 | 58 | @staticmethod 59 | def save_message(obj_response, message): 60 | 61 | message = message.strip() 62 | if message == '': 63 | return obj_response.alert("Empty messages are not allowed!") 64 | 65 | # Save message to database or whatever.. 66 | 67 | import time, hashlib 68 | time_txt = time.strftime("%H:%M:%S", time.gmtime(time.time())) 69 | message_id = 'message_%s' % hashlib.sha256(time_txt.encode("utf-8")).hexdigest() 70 | 71 | message = """ 72 |
73 | [%s] %s 74 |
75 | """ % (message_id, time_txt, message) 76 | 77 | # Add message to the end of the container 78 | obj_response.html_append('#messages', message) 79 | 80 | # Clear the textbox and give it focus in case it has lost it 81 | obj_response.attr('#message', 'value', '') 82 | obj_response.script("$('#message').focus();") 83 | 84 | # Scroll down the messages area 85 | obj_response.script("$('#messages').attr('scrollTop', $('#messages').attr('scrollHeight'));") 86 | 87 | # Make the new message appear in 400ms 88 | obj_response.script("$('#%s').animate({opacity: 1}, 400);" % message_id) 89 | 90 | 91 | @staticmethod 92 | def clear_messages(obj_response): 93 | 94 | # Delete all messages from the database 95 | 96 | # Clear the messages container 97 | obj_response.html('#messages', '') 98 | 99 | # Clear the textbox 100 | obj_response.attr('#message', 'value', '') 101 | 102 | # Ensure the texbox has focus 103 | obj_response.script("$('#message').focus();") 104 | 105 | 106 | @flask_sijax.route(app, "/") 107 | def index(): 108 | if g.sijax.is_sijax_request: 109 | # The request looks like a valid Sijax request 110 | # Let's register the handlers and tell Sijax to process it 111 | g.sijax.register_object(SijaxHandler) 112 | return g.sijax.process_request() 113 | 114 | return render_template('chat.html') 115 | 116 | if __name__ == '__main__': 117 | app.run(debug=True, port=8080) 118 | 119 | -------------------------------------------------------------------------------- /examples/comet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """A demonstration of Comet streaming functionality using Sijax.""" 4 | 5 | import os, sys 6 | 7 | path = os.path.join('.', os.path.dirname(__file__), '../') 8 | sys.path.append(path) 9 | 10 | from flask import Flask, g, render_template 11 | import flask_sijax 12 | 13 | app = Flask(__name__) 14 | 15 | # The path where you want the extension to create the needed javascript files 16 | # DON'T put any of your files in this directory, because they'll be deleted! 17 | app.config["SIJAX_STATIC_PATH"] = os.path.join('.', os.path.dirname(__file__), 'static/js/sijax/') 18 | 19 | # You need to point Sijax to the json2.js library if you want to support 20 | # browsers that don't support JSON natively (like IE <= 7) 21 | app.config["SIJAX_JSON_URI"] = '/static/js/sijax/json2.js' 22 | 23 | flask_sijax.Sijax(app) 24 | 25 | def comet_do_work_handler(obj_response, sleep_time): 26 | import time 27 | 28 | for i in range(6): 29 | width = '%spx' % (i * 80) 30 | obj_response.css('#progress', 'width', width) 31 | obj_response.html('#progress', width) 32 | 33 | # Yielding tells Sijax to flush the data to the browser. 34 | # This only works for Streaming functions (Comet or Upload) 35 | # and would not work for normal Sijax functions 36 | yield obj_response 37 | 38 | if i != 5: 39 | time.sleep(sleep_time) 40 | 41 | 42 | @flask_sijax.route(app, "/") 43 | def index(): 44 | if g.sijax.is_sijax_request: 45 | # The request looks like a valid Sijax request 46 | # Let's register the handlers and tell Sijax to process it 47 | g.sijax.register_comet_callback('do_work', comet_do_work_handler) 48 | return g.sijax.process_request() 49 | 50 | return render_template('comet.html') 51 | 52 | if __name__ == '__main__': 53 | app.run(debug=True, port=8080) 54 | -------------------------------------------------------------------------------- /examples/hello.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """A very simple example demonstrating Sijax and the Flask-Sijax extension.""" 4 | 5 | import os, sys 6 | 7 | path = os.path.join('.', os.path.dirname(__file__), '../') 8 | sys.path.append(path) 9 | 10 | 11 | from flask import Flask, g, render_template 12 | import flask_sijax 13 | 14 | app = Flask(__name__) 15 | 16 | # The path where you want the extension to create the needed javascript files 17 | # DON'T put any of your files in this directory, because they'll be deleted! 18 | app.config["SIJAX_STATIC_PATH"] = os.path.join('.', os.path.dirname(__file__), 'static/js/sijax/') 19 | 20 | # You need to point Sijax to the json2.js library if you want to support 21 | # browsers that don't support JSON natively (like IE <= 7) 22 | app.config["SIJAX_JSON_URI"] = '/static/js/sijax/json2.js' 23 | 24 | flask_sijax.Sijax(app) 25 | 26 | # Regular flask view function - Sijax is unavailable here 27 | @app.route("/") 28 | def hello(): 29 | return "Hello World!
Go to Sijax test" 30 | 31 | # Sijax enabled function - notice the `@Sijax.route` decorator 32 | # used instead of `@app.route` (above). 33 | @flask_sijax.route(app, "/sijax") 34 | def hello_sijax(): 35 | # Sijax handler function receiving 2 arguments from the browser 36 | # The first argument (obj_response) is passed automatically 37 | # by Sijax (much like Python passes `self` to object methods) 38 | def hello_handler(obj_response, hello_from, hello_to): 39 | obj_response.alert('Hello from %s to %s' % (hello_from, hello_to)) 40 | obj_response.css('a', 'color', 'green') 41 | 42 | # Another Sijax handler function which receives no arguments 43 | def goodbye_handler(obj_response): 44 | obj_response.alert('Goodbye, whoever you are.') 45 | obj_response.css('a', 'color', 'red') 46 | 47 | if g.sijax.is_sijax_request: 48 | # The request looks like a valid Sijax request 49 | # Let's register the handlers and tell Sijax to process it 50 | g.sijax.register_callback('say_hello', hello_handler) 51 | g.sijax.register_callback('say_goodbye', goodbye_handler) 52 | return g.sijax.process_request() 53 | 54 | return render_template('hello.html') 55 | 56 | if __name__ == '__main__': 57 | app.run(debug=True, port=8080) 58 | -------------------------------------------------------------------------------- /examples/templates/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | Message: 18 | 19 | 20 |
21 | 22 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/templates/comet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 |
14 |
15 |   16 |
17 |
18 | 19 | 20 | 21 | 22 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/templates/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | Say hello from John to Greg! 14 | 15 |
16 | 17 | Say goodbye! 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/templates/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 13 | 14 |
15 |
16 | Text field: 17 |
18 | 19 | Drop-down: 20 | 25 |
26 | 27 | Chechbox: 28 |
29 | 30 | File: 31 |
32 | 33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | Text field: 42 |
43 | 44 | Chechbox: 45 |
46 | 47 | Chechbox 2: 48 |
49 | 50 | File: 51 |
52 | 53 | 54 |
55 | 56 |
57 |
58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /examples/upload.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Demonstration of the Sijax Upload plugin. 4 | 5 | The Sijax Upload plugin allows you to easily transform any form 6 | on your page into a "Sijax-enabled" form. Such forms will be submitted 7 | using Sijax (ajax upload) and the upload will be handled by your Sijax handler 8 | function in the same way regular Sijax handler functions are called. 9 | 10 | """ 11 | 12 | import os, sys 13 | 14 | path = os.path.join('.', os.path.dirname(__file__), '../') 15 | sys.path.append(path) 16 | 17 | from flask import Flask, g, render_template 18 | import flask_sijax 19 | 20 | app = Flask(__name__) 21 | 22 | # The path where you want the extension to create the needed javascript files 23 | # DON'T put any of your files in this directory, because they'll be deleted! 24 | app.config["SIJAX_STATIC_PATH"] = os.path.join('.', os.path.dirname(__file__), 'static/js/sijax/') 25 | 26 | # You need to point Sijax to the json2.js library if you want to support 27 | # browsers that don't support JSON natively (like IE <= 7) 28 | app.config["SIJAX_JSON_URI"] = '/static/js/sijax/json2.js' 29 | 30 | flask_sijax.Sijax(app) 31 | 32 | class SijaxHandler(object): 33 | """A container class for all Sijax handlers. 34 | 35 | Grouping all Sijax handler functions in a class 36 | (or a Python module) allows them all to be registered with 37 | a single line of code. 38 | """ 39 | 40 | @staticmethod 41 | def _dump_data(obj_response, files, form_values, container_id): 42 | def dump_files(): 43 | if 'file' not in files: 44 | return 'Bad upload' 45 | 46 | file_data = files['file'] 47 | file_name = file_data.filename 48 | if file_name is None: 49 | return 'Nothing uploaded' 50 | 51 | file_type = file_data.content_type 52 | file_size = len(file_data.read()) 53 | return 'Uploaded file %s (%s) - %sB' % (file_name, file_type, file_size) 54 | 55 | html = """Form values: %s
Files: %s""" 56 | html = html % (str(form_values), dump_files()) 57 | 58 | obj_response.html('#%s' % container_id, html) 59 | 60 | @staticmethod 61 | def form_one_handler(obj_response, files, form_values): 62 | SijaxHandler._dump_data(obj_response, files, form_values, 'formOneResponse') 63 | 64 | @staticmethod 65 | def form_two_handler(obj_response, files, form_values): 66 | SijaxHandler._dump_data(obj_response, files, form_values, 'formTwoResponse') 67 | 68 | obj_response.reset_form() 69 | obj_response.html_append('#formTwoResponse', '
Form elements were reset!
Doing some more work (2 seconds)..') 70 | 71 | # Send the data to the browser now 72 | yield obj_response 73 | 74 | from time import sleep 75 | sleep(2) 76 | 77 | obj_response.html_append('#formTwoResponse', '
Finished!') 78 | 79 | 80 | @flask_sijax.route(app, "/") 81 | def index(): 82 | # Notice how we're doing callback registration on each request, 83 | # instead of only when needed (when the request is a Sijax request). 84 | # This is because `register_upload_callback` returns some javascript 85 | # that prepares the form on the page. 86 | form_init_js = '' 87 | form_init_js += g.sijax.register_upload_callback('formOne', SijaxHandler.form_one_handler) 88 | form_init_js += g.sijax.register_upload_callback('formTwo', SijaxHandler.form_two_handler) 89 | 90 | if g.sijax.is_sijax_request: 91 | # The request looks like a valid Sijax request 92 | # The handlers are already registered above.. we can process the request 93 | return g.sijax.process_request() 94 | 95 | return render_template('upload.html', form_init_js=form_init_js) 96 | 97 | if __name__ == '__main__': 98 | app.run(debug=True, port=8080) 99 | -------------------------------------------------------------------------------- /flask_sijax.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import 4 | 5 | from werkzeug.wsgi import ClosingIterator 6 | 7 | from flask import g, request, Response, _request_ctx_stack 8 | 9 | import sijax 10 | 11 | class Sijax(object): 12 | """Helper class that you'll use to interact with Sijax. 13 | 14 | This class tries to look like :class:`sijax.Sijax`, 15 | although the API differs slightly in order to make things easier for you. 16 | """ 17 | 18 | def __init__(self, app=None): 19 | self._request_uri = None 20 | 21 | #: Reference to the underlying :class:`sijax.Sijax` object 22 | self._sijax = None 23 | 24 | #: The URI to json2.js (JSON support for browsers without native one) 25 | self._json_uri = None 26 | 27 | if app is not None: 28 | self.init_app(app) 29 | 30 | def init_app(self, app): 31 | app.before_request(self._on_before_request) 32 | 33 | static_path = app.config.get('SIJAX_STATIC_PATH', None) 34 | if static_path is not None: 35 | sijax.helper.init_static_path(static_path) 36 | 37 | self._json_uri = app.config.get('SIJAX_JSON_URI', None) 38 | 39 | app.extensions = getattr(app, 'extensions', {}) 40 | app.extensions['sijax'] = self 41 | 42 | def _on_before_request(self): 43 | g.sijax = self 44 | 45 | self._sijax = sijax.Sijax() 46 | self._sijax.set_data(request.form) 47 | 48 | url_relative = request.url[len(request.host_url) - 1:] 49 | self._sijax.set_request_uri(url_relative) 50 | 51 | if self._json_uri is not None: 52 | self._sijax.set_json_uri(self._json_uri) 53 | 54 | def set_request_uri(self, uri): 55 | """Changes the request URI from the automatically detected one. 56 | 57 | The automatically detected URI is the relative URI of the 58 | current request, as detected by Flask/Werkzeug. 59 | 60 | You can override the detected URI with another one 61 | (for the current request only), by using this function. 62 | """ 63 | self._sijax.set_request_uri(uri) 64 | 65 | def register_callback(self, *args, **kwargs): 66 | """Registers a single callback function. 67 | 68 | Refer to :meth:`sijax.Sijax.register_callback` 69 | for more details - this is a direct proxy to it. 70 | """ 71 | self._sijax.register_callback(*args, **kwargs) 72 | 73 | def register_object(self, *args, **kwargs): 74 | """Registers all "public" callable attributes of the given object. 75 | 76 | The object could be anything (module, class, class instance, etc.) 77 | 78 | This makes mass registration of functions a lot easier. 79 | 80 | Refer to :meth:`sijax.Sijax.register_object` 81 | for more details - this is a direct proxy to it. 82 | """ 83 | self._sijax.register_object(*args, **kwargs) 84 | 85 | def register_comet_callback(self, *args, **kwargs): 86 | """Registers a single Comet callback function 87 | (see :ref:`comet-plugin`). 88 | 89 | Refer to :func:`sijax.plugin.comet.register_comet_callback` 90 | for more details - its signature differs slightly. 91 | 92 | This method's signature is the same, except that the first 93 | argument that :func:`sijax.plugin.comet.register_comet_callback` 94 | expects is the Sijax instance, and this method 95 | does that automatically, so you don't have to do it. 96 | """ 97 | sijax.plugin.comet.register_comet_callback(self._sijax, *args, **kwargs) 98 | 99 | def register_comet_object(self, *args, **kwargs): 100 | """Registers all functions from the object as Comet functions 101 | (see :ref:`comet-plugin`). 102 | 103 | This makes mass registration of functions a lot easier. 104 | 105 | Refer to :func:`sijax.plugin.comet.register_comet_object` 106 | for more details -ts signature differs slightly. 107 | 108 | This method's signature is the same, except that the first 109 | argument that :func:`sijax.plugin.comet.register_comet_object` 110 | expects is the Sijax instance, and this method 111 | does that automatically, so you don't have to do it. 112 | """ 113 | sijax.plugin.comet.register_comet_object(self._sijax, *args, **kwargs) 114 | 115 | def register_upload_callback(self, *args, **kwargs): 116 | """Registers an Upload function (see :ref:`upload-plugin`) 117 | to handle a certain form. 118 | 119 | Refer to :func:`sijax.plugin.upload.register_upload_callback` 120 | for more details. 121 | 122 | This method passes some additional arguments to your handler 123 | functions - the ``flask.request.files`` object. 124 | 125 | Your upload handler function's signature should look like this:: 126 | 127 | def func(obj_response, files, form_values) 128 | 129 | :return: string - javascript code that initializes the form 130 | """ 131 | if 'args_extra' not in kwargs: 132 | kwargs['args_extra'] = [request.files] 133 | return sijax.plugin.upload.register_upload_callback(self._sijax, *args, **kwargs) 134 | 135 | def register_event(self, *args, **kwargs): 136 | """Registers a new event handler. 137 | 138 | Refer to :meth:`sijax.Sijax.register_event` 139 | for more details - this is a direct proxy to it. 140 | """ 141 | self._sijax.register_event(*args, **kwargs) 142 | 143 | @property 144 | def is_sijax_request(self): 145 | """Tells whether the current request is meant to be handled by Sijax. 146 | 147 | Refer to :attr:`sijax.Sijax.is_sijax_request` for more details - 148 | this is a direct proxy to it. 149 | """ 150 | return self._sijax.is_sijax_request 151 | 152 | def process_request(self): 153 | """Processes the Sijax request and returns the proper response. 154 | 155 | Refer to :meth:`sijax.Sijax.process_request` for more details. 156 | """ 157 | response = self._sijax.process_request() 158 | return _make_response(response) 159 | 160 | def execute_callback(self, *args, **kwargs): 161 | """Executes a callback and returns the proper response. 162 | 163 | Refer to :meth:`sijax.Sijax.execute_callback` for more details. 164 | """ 165 | response = self._sijax.execute_callback(*args, **kwargs) 166 | return _make_response(response) 167 | 168 | def get_js(self): 169 | """Returns the javascript code that sets up the client for this request. 170 | 171 | This code is request-specific, be sure to put it on each page that needs 172 | to use Sijax. 173 | """ 174 | return self._sijax.get_js() 175 | 176 | 177 | def route(app_or_blueprint, rule, **options): 178 | """An alternative to :meth:`flask.Flask.route` or :meth:`flask.Blueprint.route` that 179 | always adds the ``POST`` method to the allowed endpoint request methods. 180 | 181 | You should use this for all your view functions that would need to use Sijax. 182 | 183 | We're doing this because Sijax uses ``POST`` for data passing, 184 | which means that every endpoint that wants Sijax support 185 | would have to accept ``POST`` requests. 186 | 187 | Registering functions that would use Sijax should happen like this:: 188 | 189 | @flask_sijax.route(app, '/') 190 | def index(): 191 | pass 192 | 193 | If you remember to make your view functions accessible via POST 194 | like this, you can avoid using this decorator:: 195 | 196 | @app.route('/', methods=['GET', 'POST']) 197 | def index(): 198 | pass 199 | """ 200 | def decorator(f): 201 | methods = options.pop('methods', ('GET', 'POST')) 202 | if 'POST' not in methods: 203 | methods = tuple(methods) + ('POST',) 204 | options['methods'] = methods 205 | app_or_blueprint.add_url_rule(rule, None, f, **options) 206 | return f 207 | return decorator 208 | 209 | 210 | def _make_response(sijax_response): 211 | """Takes a Sijax response object and returns a 212 | valid Flask response object.""" 213 | from types import GeneratorType 214 | 215 | if isinstance(sijax_response, GeneratorType): 216 | # Streaming response using a generator (non-JSON response). 217 | # Upon returning a response, Flask would automatically destroy 218 | # the request data and uploaded files - done by `flask.ctx.RequestContext.auto_pop()` 219 | # We can't allow that, since the user-provided callback we're executing 220 | # from within the generator may want to access request data/files. 221 | # That's why we'll tell Flask to preserve the context and we'll clean up ourselves. 222 | 223 | request.environ['flask._preserve_context'] = True 224 | 225 | # Clean-up code taken from `flask.testing.TestingClient` 226 | def clean_up_context(): 227 | top = _request_ctx_stack.top 228 | if top is not None and top.preserved: 229 | top.pop() 230 | 231 | # As per the WSGI specification, `close()` would be called on iterator responses. 232 | # Let's wrap the iterator in another one, which will forward that `close()` call to our clean-up callback. 233 | response = Response(ClosingIterator(sijax_response, clean_up_context), direct_passthrough=True) 234 | else: 235 | # Non-streaming response - a single JSON string 236 | response = Response(sijax_response) 237 | 238 | return response 239 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source-dir = docs 3 | build-dir = docs/_build 4 | all_files = 1 5 | 6 | [upload_sphinx] 7 | upload-dir = docs/_build/html 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flask-Sijax 3 | =========== 4 | 5 | Flask-Sijax is an extension for the `Flask `_ microframework, 6 | to simplify Sijax (`PyPi `_, `GitHub `_) setup and usage for Flask users. 7 | 8 | Sijax is a Python/jQuery library that makes AJAX easy to use in web applications. 9 | 10 | Links 11 | ----- 12 | 13 | * `source `_ 14 | * `documentation `_ 15 | * `development version 16 | `_ 17 | * `Sijax source `_ 18 | * `Sijax documentation `_ 19 | """ 20 | 21 | from setuptools import setup 22 | 23 | setup( 24 | name = "Flask-Sijax", 25 | version = '0.4.1', 26 | description = "An extension for the Flask microframework that adds Sijax support.", 27 | long_description = __doc__, 28 | author = "Slavi Pantaleev", 29 | author_email = "s.pantaleev@gmail.com", 30 | url = "https://github.com/spantaleev/flask-sijax", 31 | keywords = ["ajax", "jQuery", "flask"], 32 | platforms = "any", 33 | license = "BSD", 34 | py_modules = ['flask_sijax'], 35 | install_requires = ['Flask>=0.7.0', 'Sijax>=0.3.0'], 36 | test_suite = 'tests', 37 | zip_safe = False, 38 | classifiers = [ 39 | "Programming Language :: Python", 40 | "Programming Language :: Python :: 2.6", 41 | "Programming Language :: Python :: 2.7", 42 | "Programming Language :: Python :: 3.3", 43 | "Programming Language :: Python :: 3.4", 44 | "Development Status :: 5 - Production/Stable", 45 | "Environment :: Web Environment", 46 | "Intended Audience :: Developers", 47 | "License :: OSI Approved :: BSD License", 48 | "Operating System :: OS Independent", 49 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 50 | "Topic :: Software Development :: Libraries :: Python Modules", 51 | ] 52 | ) 53 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import with_statement 4 | import unittest 5 | 6 | import flask 7 | import flask_sijax 8 | 9 | 10 | class SijaxFlaskTestCase(unittest.TestCase): 11 | 12 | def test_route_always_adds_post_method(self): 13 | class FlaskMock(object): 14 | def __init__(self): 15 | self.methods = None 16 | 17 | def add_url_rule(self_mock, rule, view_func, endpoint, **options): 18 | self.assertEqual('/rule', rule) 19 | self_mock.methods = options.pop('methods', None) 20 | 21 | def try_route(methods_expected, **options): 22 | app = FlaskMock() 23 | decorator = flask_sijax.route(app, '/rule', **options) 24 | decorator(lambda a: a) 25 | self.assertEqual(tuple(methods_expected), app.methods) 26 | 27 | try_route(('GET', 'POST')) 28 | try_route(('GET', 'POST'), methods=('GET',)) 29 | try_route(('POST', ), methods=()) 30 | try_route(('GET', 'PUT', 'POST'), methods=('GET', 'PUT')) 31 | try_route(('GET', 'DELETE', 'POST'), methods=('GET', 'DELETE')) 32 | 33 | def test_sijax_helper_object_is_only_bound_to_g_in_a_request_context(self): 34 | app = flask.Flask(__name__) 35 | 36 | helper = flask_sijax.Sijax(app) 37 | 38 | with app.test_request_context(): 39 | app.preprocess_request() 40 | self.assertEqual(id(helper), id(flask.g.sijax)) 41 | 42 | # Make sure that access fails when outside of a request context 43 | try: 44 | flask.g.sijax 45 | except RuntimeError: 46 | # RuntimeError('working outside of request context') 47 | pass 48 | else: 49 | self.fail('Bound to g in a non-request context!') 50 | 51 | def test_delayed_app_initialization_works(self): 52 | # Makes sure that an app object can be provided at a later point 53 | # and that Sijax would still be registered correctly. 54 | app = flask.Flask(__name__) 55 | helper = flask_sijax.Sijax() 56 | helper.init_app(app) 57 | with app.test_request_context(): 58 | app.preprocess_request() 59 | self.assertEqual(id(helper), id(flask.g.sijax)) 60 | 61 | def test_json_uri_config_is_used(self): 62 | uri = '/some/json_uri.here' 63 | 64 | app = flask.Flask(__name__) 65 | app.config['SIJAX_JSON_URI'] = uri 66 | helper = flask_sijax.Sijax(app) 67 | with app.test_request_context(): 68 | app.preprocess_request() 69 | 70 | js = helper.get_js() 71 | self.assertTrue(uri in js) 72 | 73 | def test_request_uri_changing_works(self): 74 | # The request URI is automatically detected, 75 | # but could be changed to something else on each request 76 | # Changes should not be preserved throughout different requests though 77 | 78 | app = flask.Flask(__name__) 79 | 80 | helper = flask_sijax.Sijax(app) 81 | 82 | with app.test_request_context(): 83 | app.preprocess_request() 84 | 85 | js = helper.get_js() 86 | self.assertTrue('Sijax.setRequestUri("/");' in js) 87 | 88 | helper.set_request_uri('http://something.else/') 89 | 90 | js = helper.get_js() 91 | self.assertFalse('Sijax.setRequestUri("/");' in js) 92 | self.assertTrue('Sijax.setRequestUri("http://something.else/");' in js) 93 | 94 | # Ensure that the changed request uri was valid for the previous request only 95 | with app.test_request_context(): 96 | app.preprocess_request() 97 | 98 | js = helper.get_js() 99 | self.assertTrue('Sijax.setRequestUri("/");' in js) 100 | self.assertFalse('Sijax.setRequestUri("http://something.else/");' in js) 101 | 102 | # Test that a more complex request url (with query string, etc) works 103 | with app.test_request_context('/relative/url?query=string&is=here'): 104 | app.preprocess_request() 105 | 106 | js = helper.get_js() 107 | self.assertTrue('Sijax.setRequestUri("/relative/url?query=string&is=here");' in js) 108 | 109 | def test_registering_callbacks_in_a_non_request_context_fails(self): 110 | app = flask.Flask(__name__) 111 | helper = flask_sijax.Sijax(app) 112 | 113 | try: 114 | helper.register_callback('test', lambda r: r) 115 | self.fail('Callback registered, but failure was expected!') 116 | except AttributeError: 117 | # helper._sijax (and flask.g.sijax) 118 | pass 119 | 120 | def test_registering_callbacks_in_a_request_context_with_no_preprocessing_fails(self): 121 | app = flask.Flask(__name__) 122 | helper = flask_sijax.Sijax(app) 123 | 124 | with app.test_request_context(): 125 | try: 126 | helper.register_callback('test', lambda r: r) 127 | self.fail('Callback registered, but failure was expected!') 128 | except AttributeError: 129 | # helper._sijax (and flask.g.sijax) 130 | pass 131 | 132 | def test_register_callback_works(self): 133 | from sijax.helper import json 134 | 135 | call_history = [] 136 | 137 | def callback(obj_response): 138 | call_history.append('callback') 139 | obj_response.alert('test') 140 | 141 | 142 | app = flask.Flask(__name__) 143 | helper = flask_sijax.Sijax(app) 144 | 145 | with app.test_request_context(): 146 | app.preprocess_request() 147 | 148 | helper.register_callback('test', callback) 149 | 150 | # no data, cannot determine request as a sijax request 151 | self.assertFalse(helper.is_sijax_request) 152 | 153 | cls_sijax = helper._sijax.__class__ 154 | helper._sijax.set_data({cls_sijax.PARAM_REQUEST: 'test', cls_sijax.PARAM_ARGS: '[]'}) 155 | 156 | self.assertTrue(helper.is_sijax_request) 157 | response = helper.process_request() 158 | self.assertEqual(['callback'], call_history) 159 | 160 | try: 161 | obj = json.loads(response.get_data(True)) 162 | except: 163 | self.fail('Cannot decode JSON!') 164 | 165 | def test_upload_callbacks_receive_the_expected_arguments(self): 166 | # Upload callbacks should have the following signature: 167 | # def function(obj_response, flask_request_files, form_values) 168 | # The extension should ensure that the proper arguments are passed 169 | import sijax 170 | from types import GeneratorType 171 | 172 | app = flask.Flask(__name__) 173 | helper = flask_sijax.Sijax(app) 174 | 175 | call_history = [] 176 | 177 | def callback(obj_response, files, form_values): 178 | call_history.append(form_values) 179 | call_history.append(id(files)) 180 | 181 | with app.test_request_context(): 182 | app.preprocess_request() 183 | 184 | helper.register_upload_callback('form_id', callback) 185 | func_name = sijax.plugin.upload.func_name_by_form_id('form_id') 186 | 187 | cls_sijax = helper._sijax.__class__ 188 | 189 | post = {cls_sijax.PARAM_REQUEST: func_name, cls_sijax.PARAM_ARGS: '["form_id"]', 'post_key': 'val'} 190 | helper._sijax.set_data(post) 191 | self.assertTrue(helper.is_sijax_request) 192 | response = helper._sijax.process_request() 193 | self.assertTrue(isinstance(response, GeneratorType)) 194 | for r in response: pass 195 | 196 | expected_history = [{'post_key': 'val'}, id(flask.request.files)] 197 | self.assertEqual(expected_history, call_history) 198 | 199 | def test_sijax_helper_passes_correct_post_data(self): 200 | # It's expected that the Sijax Helper class passes `flask.request.form` 201 | # as post data in the "on before request" stage 202 | app = flask.Flask(__name__) 203 | helper = flask_sijax.Sijax(app) 204 | 205 | with app.test_request_context(): 206 | app.preprocess_request() 207 | self.assertEqual(id(helper._sijax.get_data()), id(flask.request.form)) 208 | 209 | def test_process_request_returns_a_flask_response_object(self): 210 | # flask_sijax.Sijax.process_request should return a string for regular functions 211 | # and a Flask.Response object for functions that use a generator (streaming functions) 212 | from sijax.response import StreamingIframeResponse 213 | 214 | app = flask.Flask(__name__) 215 | helper = flask_sijax.Sijax(app) 216 | 217 | with app.test_request_context(): 218 | app.preprocess_request() 219 | 220 | cls_sijax = helper._sijax.__class__ 221 | 222 | post = {cls_sijax.PARAM_REQUEST: 'callback', cls_sijax.PARAM_ARGS: '[]'} 223 | helper._sijax.set_data(post) 224 | helper.register_callback('callback', lambda r: r) 225 | response = helper.process_request() 226 | self.assertTrue(isinstance(response, flask.Response)) 227 | 228 | helper.register_callback('callback', lambda r: r, response_class=StreamingIframeResponse) 229 | response = helper.process_request() 230 | self.assertTrue(isinstance(response, flask.Response)) 231 | 232 | if __name__ == '__main__': 233 | unittest.main() 234 | --------------------------------------------------------------------------------