├── .bowerrc ├── .gitignore ├── .rstcheck.cfg ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── bower.json ├── docs ├── Makefile ├── _static │ └── img │ │ ├── example.png │ │ └── sacrud_pyramid_pages.png ├── api.rst ├── conf.py ├── configuration.rst ├── index.rst ├── install.rst ├── make.bat └── tutorial.rst ├── example ├── .gitignore ├── README.rst ├── development.ini ├── fixtures │ ├── country.json │ ├── gallery.json │ ├── news.json │ ├── pages.json │ └── photos.json ├── pyramid_pages_example.py ├── requirements.txt ├── setup.py ├── static │ ├── css │ │ ├── __main.css │ │ └── vendor │ │ │ └── fotorama.css │ ├── img │ │ └── vendor │ │ │ ├── fotorama.png │ │ │ └── fotorama@2x.png │ └── js │ │ ├── __main.js │ │ ├── gallery.js │ │ ├── main.js │ │ ├── popup.js │ │ └── vendor │ │ └── fotorama.js └── templates │ ├── gallery │ └── index.jinja2 │ ├── news │ └── index.jinja2 │ └── pyramid_pages │ ├── base.jinja2 │ └── index.jinja2 ├── gulpfile.js ├── package.json ├── pyramid_pages ├── __init__.py ├── assets.py ├── models.py ├── resources.py ├── routes.py ├── security.py ├── static │ ├── css │ │ ├── __pyramid_pages.css │ │ ├── content │ │ │ └── content.css │ │ ├── gallery │ │ │ ├── gallery-list.css │ │ │ └── gallery.css │ │ ├── main-menu │ │ │ ├── main-menu-title.css │ │ │ ├── main-menu.css │ │ │ ├── main-menu_type_right.css │ │ │ └── main-menu_type_vertical.css │ │ ├── main.css │ │ ├── nav │ │ │ └── nav.css │ │ ├── page │ │ │ ├── page-note.css │ │ │ ├── page-title.css │ │ │ └── page.css │ │ ├── popup │ │ │ ├── content-gallery │ │ │ │ └── content-gallery.css │ │ │ └── popup.css │ │ ├── vendor │ │ │ ├── font-awesome.min.css │ │ │ ├── font-awesome │ │ │ │ └── font-awesome.min.css │ │ │ ├── html5-boilerplate │ │ │ │ └── main.css │ │ │ ├── main.css │ │ │ └── normalize.css │ │ └── wrapper │ │ │ └── wrapper.css │ ├── favicon.ico │ ├── fonts │ │ ├── font-awesome │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── img │ │ └── logo.png │ └── js │ │ ├── __pyramid_pages.js │ │ ├── main.js │ │ └── vendor │ │ ├── jquery.min.js │ │ └── jquery │ │ └── jquery.min.js ├── templates │ └── pyramid_pages │ │ ├── breadcrumbs.jinja2 │ │ ├── index.jinja2 │ │ ├── menu │ │ ├── flat.jinja2 │ │ └── mptt.jinja2 │ │ ├── meta.jinja2 │ │ └── title.jinja2 ├── tests │ ├── __init__.py │ ├── integration_test.py │ └── unit_tests.py └── views.py ├── requirements-test.txt ├── requirements.txt ├── setup.cfg ├── setup.py └── test.sh /.bowerrc: -------------------------------------------------------------------------------- 1 | { "strict-ssl": false, "https-proxy": "" } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.map 3 | *.pyc 4 | *.sqlite* 5 | *.sublime* 6 | *.swo 7 | *.swp 8 | *~ 9 | *.DS_Store 10 | .cache 11 | .coverage 12 | .project 13 | .pydevproject 14 | .ropeproject 15 | .settings 16 | _build 17 | _themes 18 | bower_components 19 | build 20 | cover 21 | dist 22 | node_modules 23 | nohup.out 24 | phantomjsdriver.log 25 | pyramid_pages/.coverage 26 | pyramid_pages/cover 27 | pyramid_pages/TODO 28 | *.sqlite 29 | TODO 30 | TODO.txt 31 | -------------------------------------------------------------------------------- /.rstcheck.cfg: -------------------------------------------------------------------------------- 1 | [directives] 2 | ignore=automodule 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | dist: xenial 10 | sudo: required 11 | language: python 12 | 13 | notifications: 14 | email: "sacrud@uralbash.ru" 15 | email: "arkadiy@bk.ru" 16 | irc: 17 | channels: 18 | - "chat.freenode.net#sacrud" 19 | on_success: change 20 | on_failure: always 21 | 22 | python: 23 | - "3.4" 24 | - "3.5" 25 | - "3.6" 26 | - "3.7" 27 | 28 | matrix: 29 | include: 30 | - python: "2.7" 31 | env: LINK_CHECK=1 32 | 33 | install: 34 | - pip install -r example/requirements.txt 35 | - python setup.py develop 36 | - if [ -n "$LINK_CHECK" ];then pip install linkchecker; fi 37 | 38 | - pip install flake8 rstcheck 39 | 40 | script: 41 | # Nosetests 42 | - python setup.py nosetests 43 | 44 | # Styles check 45 | # - if [ -z "$NO_RSTLINT" ]; then ./test.sh; fi 46 | 47 | # BDD 48 | - cd example 49 | - nohup python pyramid_pages_example.py& 50 | - sleep 5 51 | - cd .. 52 | 53 | # Link checker 54 | - if [ -n "$LINK_CHECK" ];then linkchecker http://localhost:6543/ --ignore-url="unquote\(%*" --no-status; fi 55 | 56 | after_success: 57 | - pip install coveralls 58 | - coveralls 59 | 60 | after_failure: 61 | cat example/nohup.out 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright uralbash (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | include requirements-test.txt 3 | include README.rst 4 | recursive-include pyramid_pages/templates *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.jinja2 *.js *.html *.xml 5 | recursive-include pyramid_pages/static *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.jinja2 *.js *.html *.xml 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test 2 | 3 | test: 4 | nosetests --with-coverage --cover-package pyramid_pages --cover-erase --with-doctest --nocapture 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |Build Status| |Coverage Status| 2 | 3 | pyramid_pages 4 | ============= 5 | 6 | **pyramid_pages** provides a collections of pages to your Pyramid application. 7 | This is very similar to **django.contrib.flatpages** but with a tree structure 8 | and traversal algorithm in URL dispatch. 9 | 10 | See documentation http://pyramid-pages.readthedocs.io 11 | 12 | .. image:: https://raw.githubusercontent.com/uralbash/pyramid_pages/master/docs/_static/img/example.png 13 | :alt: pyramid_pages - example of website pages tree 14 | :width: 800px 15 | :align: center 16 | 17 | Support and Development 18 | ======================= 19 | 20 | To report bugs, use the `issue tracker `_ 21 | or `waffle board `_. 22 | 23 | We welcome any contribution: suggestions, ideas, commits with new futures, bug 24 | fixes, refactoring, docs, tests, translations etc 25 | 26 | If you have question, contact me sacrud@uralbash.ru or IRC channel #sacrud 27 | 28 | License 29 | ======= 30 | 31 | The project is licensed under the MIT license. 32 | 33 | .. |Build Status| image:: https://travis-ci.org/uralbash/pyramid_pages.svg?branch=master 34 | :target: https://travis-ci.org/uralbash/pyramid_pages 35 | .. |Coverage Status| image:: https://coveralls.io/repos/uralbash/pyramid_pages/badge.png 36 | :target: https://coveralls.io/r/uralbash/pyramid_pages 37 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pyramid_pages", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/uralbash/pyramid_pages", 5 | "authors": [ 6 | "uralbash" 7 | ], 8 | "moduleType": [ 9 | "amd", 10 | "node" 11 | ], 12 | "license": "MIT", 13 | "dependencies": { 14 | "font-awesome": "~4.3.0", 15 | "html5-boilerplate": "~5.2.0", 16 | "jquery": "~2.1.4", 17 | "normalize.css": "~3.0.2" 18 | }, 19 | "overrides": { 20 | "font-awesome": { 21 | "main": [ 22 | "css/font-awesome.min.css", 23 | "fonts/fontawesome-webfont.eot", 24 | "fonts/fontawesome-webfont.otf", 25 | "fonts/fontawesome-webfont.svg", 26 | "fonts/fontawesome-webfont.ttf", 27 | "fonts/fontawesome-webfont.woff", 28 | "fonts/fontawesome-webfont.woff2" 29 | ] 30 | }, 31 | "html5-boilerplate": { 32 | "main": "dist/css/main.css" 33 | }, 34 | "jquery": { 35 | "main": [ 36 | "dist/jquery.min.js", 37 | "dist/jquery.min.map" 38 | ] 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /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 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ps_pages.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ps_pages.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/ps_pages" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ps_pages" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_static/img/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/docs/_static/img/example.png -------------------------------------------------------------------------------- /docs/_static/img/sacrud_pyramid_pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/docs/_static/img/sacrud_pyramid_pages.png -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | common 5 | ------ 6 | 7 | .. automodule:: pyramid_pages.common 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | resources 13 | --------- 14 | 15 | .. automodule:: pyramid_pages.resources 16 | :members: 17 | :exclude-members: or_ 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | models 22 | ------ 23 | 24 | .. automodule:: pyramid_pages.models 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | routes 30 | ------ 31 | 32 | .. automodule:: pyramid_pages.routes 33 | :members: 34 | :exclude-members: or_ 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | 39 | 40 | views 41 | ----- 42 | 43 | .. automodule:: pyramid_pages.views 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | 48 | security 49 | -------- 50 | 51 | .. automodule:: pyramid_pages.security 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | 56 | Module contents 57 | --------------- 58 | 59 | .. automodule:: pyramid_pages 60 | :members: 61 | :undoc-members: 62 | :show-inheritance: 63 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from docutils.parsers.rst import directives 3 | from sphinx.directives.code import CodeBlock 4 | 5 | directives.register_directive('no-code-block', CodeBlock) 6 | 7 | # -- General configuration ------------------------------------------------ 8 | 9 | # Add any Sphinx extension module names here, as strings. They can be 10 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 11 | # ones. 12 | extensions = [ 13 | 'sphinx.ext.autodoc', 14 | 'sphinx.ext.viewcode', 15 | 'sphinx.ext.intersphinx', 16 | ] 17 | 18 | intersphinx_mapping = { 19 | 'https://docs.python.org/3': None, 20 | 'http://docs.sqlalchemy.org/en/latest/': None, 21 | 'http://sqlalchemy-mptt.readthedocs.org/en/master/': None 22 | } 23 | 24 | # Add any paths that contain templates here, relative to this directory. 25 | templates_path = ['_templates'] 26 | 27 | # The suffix of source filenames. 28 | source_suffix = '.rst' 29 | 30 | # The master toctree document. 31 | master_doc = 'index' 32 | 33 | # General information about the project. 34 | project = u'pyramid_pages' 35 | copyright = u'2014, uralbash' 36 | 37 | # List of patterns, relative to source directory, that match files and 38 | # directories to ignore when looking for source files. 39 | exclude_patterns = ['_build'] 40 | 41 | # The name of the Pygments (syntax highlighting) style to use. 42 | pygments_style = 'sphinx' 43 | 44 | # -- Options for HTML output ---------------------------------------------- 45 | # Add any paths that contain custom static files (such as style sheets) here, 46 | # relative to this directory. They are copied after the builtin static files, 47 | # so a file named "default.css" will overwrite the builtin "default.css". 48 | html_static_path = ['_static'] 49 | 50 | # Output file base name for HTML help builder. 51 | htmlhelp_basename = 'pyramid_pagesdoc' 52 | 53 | html_theme_options = { 54 | 'travis_button': True, 55 | 'github_button': True, 56 | 'github_user': 'uralbash', 57 | 'github_repo': 'pyramid_pages', 58 | } 59 | -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | Custom model for tree pages 5 | --------------------------- 6 | 7 | To build a tree, using the model from `sqlalchemy_mptt 8 | `_. 9 | 10 | .. note:: 11 | 12 | | Otherwise, it will look like flat pages. 13 | | Plans to add a recursive model with only `parent_id` field. 14 | 15 | Create model of tree pages. For more detail see example `pyramid_pages_example 16 | `_. 17 | 18 | .. no-code-block:: python 19 | 20 | from pyramid_pages.models import FlatPageMixin, MpttPageMixin, RedirectMixin 21 | 22 | ... 23 | 24 | class WebPage(Base, MpttPageMixin, RedirectMixin): 25 | __tablename__ = 'mptt_pages' 26 | 27 | id = Column('id', Integer, primary_key=True) 28 | 29 | 30 | class NewsPage(Base, FlatPageMixin): 31 | __tablename__ = 'flat_news' 32 | 33 | id = Column('id', Integer, primary_key=True) 34 | date = Column(Date, default=func.now()) 35 | 36 | If you use `pyramid_sacrud `_, you 37 | can inherited from :class:`~pyramid_pages.models.BaseSacrudMpttPage` or 38 | :class:`~pyramid_pages.models.BaseSacrudFlatPage` or just use 39 | :class:`~pyramid_pages.models.SacrudOptions`. 40 | 41 | It's look likes this: 42 | 43 | .. image:: _static/img/sacrud_pyramid_pages.png 44 | :alt: Integration pyramid_pages with pyramid_sacrud 45 | :align: center 46 | 47 | Configure `pyramid_pages` 48 | ------------------------- 49 | 50 | Then add settings of :mod:`pyramid_pages`. 51 | 52 | .. no-code-block:: python 53 | 54 | from youproject.models import WebPage, NewsPage 55 | 56 | ... 57 | 58 | settings['pyramid_pages.models'] = { 59 | '': WebPage, 60 | 'pages': WebPage, # available with prefix '/pages/' 61 | 'news': NewsPage 62 | } 63 | 64 | # pyramid_pages - put it after all routes 65 | # and after pyramid_pages configuration. 66 | config.include("pyramid_pages") 67 | 68 | If you use version of pyramid >= 1.6a1, there is a possibility put 69 | ``config.include("pyramid_pages")`` before pyramid_pages Configuration. 70 | 71 | .. no-code-block:: python 72 | 73 | from youproject.models import WebPage, NewsPage 74 | 75 | ... 76 | 77 | config.include("pyramid_pages") 78 | settings['pyramid_pages.models'] = { 79 | '': WebPage, 80 | 'pages': WebPage, # available with prefix '/pages/' 81 | 'news': NewsPage 82 | } 83 | 84 | Custom resource 85 | --------------- 86 | 87 | Base resource for pages can be found in the module :mod:`pyramid_pages.routes`. 88 | 89 | .. literalinclude:: /../pyramid_pages/routes.py 90 | :language: python 91 | :linenos: 92 | :caption: Base resource for pages. 93 | :pyobject: PageResource 94 | 95 | Just inherit your resource from :class:`~pyramid_pages.routes.PageResource`. 96 | 97 | .. literalinclude:: /../example/pyramid_pages_example.py 98 | :language: python 99 | :linenos: 100 | :caption: Custom resource for gallery. 101 | :pyobject: GalleryResource 102 | 103 | .. literalinclude:: /../example/pyramid_pages_example.py 104 | :language: python 105 | :linenos: 106 | :caption: Model for GalleryResource. 107 | :pyobject: Gallery 108 | 109 | .. literalinclude:: /../example/pyramid_pages_example.py 110 | :language: python 111 | :linenos: 112 | :caption: Model photo for Gallery. 113 | :pyobject: Photo 114 | 115 | And add it to config. 116 | 117 | .. code-block:: python 118 | :emphasize-lines: 5 119 | 120 | settings['pyramid_pages.models'] = { 121 | '': WebPage, 122 | 'pages': WebPage, # available with prefix '/pages/' 123 | 'news': NewsPage, 124 | 'gallery': GalleryResource 125 | } 126 | 127 | Generate menu 128 | ------------- 129 | 130 | Make menu object and pass it in template context. 131 | 132 | .. code-block:: python 133 | 134 | from pyramid_pages.common import Menu 135 | 136 | 137 | class Gallery(Base, MpttPageMixin): 138 | __tablename__ = 'mptt_gallery' 139 | 140 | menu_template = 'myproject/templates/my_custom_menu.mako' 141 | 142 | id = Column('id', Integer, primary_key=True) 143 | 144 | page_menu = Menu(DBSession, WebPage).mptt 145 | news_menu = Menu(DBSession, NewsPage).flat 146 | gallery_menu = Menu(DBSession, Gallery).mptt 147 | 148 | Just include menu template. 149 | 150 | .. code-block:: jinja 151 | 152 | {% with menu=news_menu() %} 153 | {% include menu.template with context %} 154 | {% endwith %} 155 | 156 | {% with menu=page_menu(from_lvl=1, to_lvl=6, trees=(1, 2, 3)) %} 157 | {% include menu.template with context %} 158 | {% endwith %} 159 | 160 | Or write your own. 161 | 162 | .. literalinclude:: /../pyramid_pages/templates/pyramid_pages/menu/flat.jinja2 163 | :language: html+jinja 164 | :linenos: 165 | :caption: Flat menu template. 166 | 167 | .. literalinclude:: /../pyramid_pages/templates/pyramid_pages/menu/mptt.jinja2 168 | :language: html+jinja 169 | :linenos: 170 | :caption: MPTT menu template. 171 | 172 | If you want show mptt menu with ``to_lvl==max-2`` or similar, just use ``to_lvl=-2``. 173 | 174 | .. code-block:: jinja 175 | 176 | {% with menu=page_menu(from_lvl=1, to_lvl=-2, trees=(1, 2, 3)) %} 177 | {% include menu.template with context %} 178 | {% endwith %} 179 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyramid_pages documentation master file, created by 2 | sphinx-quickstart on Tue Oct 7 16:14:25 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | pyramid_pages 7 | ============= 8 | 9 | **pyramid_pages** provides a collections of pages to your Pyramid application. 10 | This is very similar to **django.contrib.flatpages** but with a tree structure 11 | and traversal algorithm in URL dispatch. 12 | 13 | .. image:: _static/img/example.png 14 | :alt: pyramid_pages - example of website pages tree 15 | :align: center 16 | 17 | .. toctree:: 18 | :maxdepth: 4 19 | 20 | install 21 | configuration 22 | api 23 | 24 | Tutorials and Cookbook Recipes 25 | ============================== 26 | 27 | .. toctree:: 28 | :maxdepth: 4 29 | 30 | tutorial 31 | 32 | Support and Development 33 | ======================= 34 | 35 | To report bugs, use the `issue tracker 36 | `_. 37 | 38 | We welcome any contribution: suggestions, ideas, commits with new futures, bug 39 | fixes, refactoring, docs, tests, translations etc 40 | 41 | If you have question, contact me sacrud@uralbash.ru or IRC channel #sacrud 42 | 43 | Indices and tables 44 | ================== 45 | 46 | * :ref:`genindex` 47 | * :ref:`modindex` 48 | * :ref:`search` 49 | 50 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installing 2 | ========== 3 | 4 | github 5 | ------ 6 | 7 | .. code-block:: bash 8 | 9 | pip install git+http://github.com/uralbash/pyramid_pages.git 10 | 11 | PyPi 12 | ---- 13 | 14 | .. code-block:: bash 15 | 16 | pip install pyramid_pages 17 | 18 | source 19 | ------ 20 | 21 | .. code-block:: bash 22 | 23 | git clone git+http://github.com/uralbash/pyramid_pages.git 24 | cd pyramid_pages 25 | pip install -e . 26 | -------------------------------------------------------------------------------- /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 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ps_pages.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ps_pages.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/tutorial.rst: -------------------------------------------------------------------------------- 1 | .. _simple_tutorial: 2 | 3 | Simple Web-site with tree pages 4 | =============================== 5 | 6 | This is an example of using `pyramid_pages` in just one file 7 | (`pyramid_pages_example.py 8 | `_). 9 | Full code you can see `there `_. 10 | 11 | .. literalinclude:: /../example/pyramid_pages_example.py 12 | :language: python 13 | :linenos: 14 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | *.sqlite-* 3 | *.pid 4 | -------------------------------------------------------------------------------- /example/README.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | .. code-block:: bash 5 | 6 | $ cd example 7 | $ python pyramid_pages_example.py 8 | 9 | or 10 | 11 | .. code-block:: bash 12 | 13 | $ cd example 14 | $ python setup.py develop 15 | $ pserve development.ini --reload 16 | 17 | and goto http://localhost:6543/ 18 | -------------------------------------------------------------------------------- /example/development.ini: -------------------------------------------------------------------------------- 1 | [app:main] 2 | use = egg:pyramid_pages_example 3 | pyramid.reload_templates = true 4 | 5 | index_view = True 6 | 7 | [server:main] 8 | use = egg:pyramid#wsgiref 9 | host = 127.0.0.1 10 | port = 6543 11 | -------------------------------------------------------------------------------- /example/fixtures/country.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "100", 4 | "in_menu": 1, 5 | "name": "Countries", 6 | "slug": "countries", 7 | "tree_id": "2", 8 | "visible": 1 9 | }, 10 | { 11 | "id": "101", 12 | "in_menu": 1, 13 | "name": "Africa", 14 | "parent_id": "100", 15 | "slug": "africa", 16 | "tree_id": "2", 17 | "visible": 1 18 | }, 19 | { 20 | "id": "102", 21 | "in_menu": 1, 22 | "name": "Algeria", 23 | "parent_id": "101", 24 | "slug": "algeria", 25 | "tree_id": "2", 26 | "visible": 1 27 | }, 28 | { 29 | "id": "103", 30 | "in_menu": 1, 31 | "name": "Marocco", 32 | "parent_id": "101", 33 | "slug": "marocco", 34 | "tree_id": "2", 35 | "visible": 1 36 | }, 37 | { 38 | "id": "104", 39 | "in_menu": 1, 40 | "name": "Libya", 41 | "parent_id": "101", 42 | "slug": "libya", 43 | "tree_id": "2", 44 | "visible": 1 45 | }, 46 | { 47 | "id": "105", 48 | "in_menu": 1, 49 | "name": "Somalia", 50 | "parent_id": "101", 51 | "slug": "somalia", 52 | "tree_id": "2", 53 | "visible": 1 54 | }, 55 | { 56 | "id": "106", 57 | "in_menu": 1, 58 | "name": "Kenya", 59 | "parent_id": "101", 60 | "slug": "kenya", 61 | "tree_id": "2", 62 | "visible": 1 63 | }, 64 | { 65 | "id": "107", 66 | "in_menu": 1, 67 | "name": "Mauritania", 68 | "parent_id": "101", 69 | "slug": "mauritania", 70 | "tree_id": "2", 71 | "visible": 1 72 | }, 73 | { 74 | "id": "108", 75 | "in_menu": 1, 76 | "name": "South Africa", 77 | "parent_id": "101", 78 | "slug": "foo19", 79 | "tree_id": "2", 80 | "visible": 1 81 | }, 82 | { 83 | "id": "200", 84 | "in_menu": 1, 85 | "name": "America", 86 | "parent_id": "100", 87 | "slug": "america", 88 | "tree_id": "2", 89 | "visible": 1 90 | }, 91 | { 92 | "id": "201", 93 | "in_menu": 1, 94 | "name": "North-America", 95 | "parent_id": "200", 96 | "slug": "north-america", 97 | "tree_id": "2", 98 | "visible": 1 99 | }, 100 | { 101 | "id": "202", 102 | "in_menu": 1, 103 | "name": "Canada", 104 | "parent_id": "201", 105 | "slug": "Canada", 106 | "tree_id": "2", 107 | "visible": 1 108 | }, 109 | { 110 | "id": "203", 111 | "in_menu": 1, 112 | "name": "USA", 113 | "parent_id": "201", 114 | "slug": "usa", 115 | "tree_id": "2", 116 | "visible": 1 117 | }, 118 | { 119 | "id": "300", 120 | "in_menu": 1, 121 | "name": "Middle-America", 122 | "parent_id": "200", 123 | "slug": "middle-america", 124 | "tree_id": "2", 125 | "visible": 1 126 | }, 127 | { 128 | "id": "301", 129 | "in_menu": 1, 130 | "name": "Mexico", 131 | "parent_id": "300", 132 | "slug": "mexico", 133 | "tree_id": "2", 134 | "visible": 1 135 | }, 136 | { 137 | "id": "302", 138 | "in_menu": 1, 139 | "name": "Honduras", 140 | "parent_id": "300", 141 | "slug": "honduras", 142 | "tree_id": "2", 143 | "visible": 1 144 | }, 145 | { 146 | "id": "303", 147 | "in_menu": 1, 148 | "name": "Guatemala", 149 | "parent_id": "300", 150 | "slug": "guatemala", 151 | "tree_id": "2", 152 | "visible": 1 153 | }, 154 | { 155 | "id": "400", 156 | "in_menu": 1, 157 | "name": "South-America", 158 | "parent_id": "200", 159 | "slug": "south-america", 160 | "tree_id": "2", 161 | "visible": 1 162 | }, 163 | { 164 | "id": "401", 165 | "in_menu": 1, 166 | "name": "Brazil", 167 | "parent_id": "400", 168 | "slug": "brazil", 169 | "tree_id": "2", 170 | "visible": 1 171 | }, 172 | { 173 | "id": "402", 174 | "in_menu": 1, 175 | "name": "Argentina", 176 | "parent_id": "400", 177 | "slug": "argentina", 178 | "tree_id": "2", 179 | "visible": 1 180 | }, 181 | { 182 | "id": "403", 183 | "in_menu": 1, 184 | "name": "Uruguay", 185 | "parent_id": "400", 186 | "slug": "uruguay", 187 | "tree_id": "2", 188 | "visible": 1 189 | }, 190 | { 191 | "id": "404", 192 | "in_menu": 1, 193 | "name": "Chile", 194 | "parent_id": "400", 195 | "slug": "chile", 196 | "tree_id": "2", 197 | "visible": 1 198 | }, 199 | { 200 | "id": "500", 201 | "in_menu": 1, 202 | "name": "Asia", 203 | "parent_id": "100", 204 | "slug": "asia", 205 | "tree_id": "2", 206 | "visible": 1 207 | }, 208 | { 209 | "id": "501", 210 | "in_menu": 1, 211 | "name": "China", 212 | "parent_id": "500", 213 | "slug": "china", 214 | "tree_id": "2", 215 | "visible": 1 216 | }, 217 | { 218 | "id": "502", 219 | "in_menu": 1, 220 | "name": "India", 221 | "parent_id": "500", 222 | "slug": "india", 223 | "tree_id": "2", 224 | "visible": 1 225 | }, 226 | { 227 | "id": "503", 228 | "in_menu": 1, 229 | "name": "Malaysia", 230 | "parent_id": "500", 231 | "slug": "malaysia", 232 | "tree_id": "2", 233 | "visible": 1 234 | }, 235 | { 236 | "id": "504", 237 | "in_menu": 1, 238 | "name": "Thailand", 239 | "parent_id": "500", 240 | "slug": "thailand", 241 | "tree_id": "2", 242 | "visible": 1 243 | }, 244 | { 245 | "id": "505", 246 | "in_menu": 1, 247 | "name": "Vietnam", 248 | "parent_id": "500", 249 | "slug": "vietnam", 250 | "tree_id": "2", 251 | "visible": 1 252 | }, 253 | { 254 | "id": "506", 255 | "in_menu": 1, 256 | "name": "Singapore", 257 | "parent_id": "500", 258 | "slug": "singapore", 259 | "tree_id": "2", 260 | "visible": 1 261 | }, 262 | { 263 | "id": "507", 264 | "in_menu": 1, 265 | "name": "Indonesia", 266 | "parent_id": "500", 267 | "slug": "indonesia", 268 | "tree_id": "2", 269 | "visible": 1 270 | }, 271 | { 272 | "id": "508", 273 | "in_menu": 1, 274 | "name": "Mongolia", 275 | "parent_id": "500", 276 | "slug": "mongolia", 277 | "tree_id": "2", 278 | "visible": 1 279 | } 280 | ] 281 | -------------------------------------------------------------------------------- /example/fixtures/gallery.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Egypt, officially the Arab Republic of Egypt, is a transcontinental country spanning the northeast corner of Africa and southwest corner of Asia, via a land bridge formed by the Sinai Peninsula", 4 | "id": 1001, 5 | "in_menu": 1, 6 | "name": "Egypt", 7 | "slug": "egypt", 8 | "visible": 1, 9 | "tree_id": 3 10 | }, 11 | { 12 | "description": "The Egyptian pyramids are ancient pyramid-shaped masonry structures located in Egypt. As of November 2008, there are sources citing both 118 and 138 as the number of identified Egyptian pyramids. Most were built as tombs for the country's pharaohs and their consorts during the Old and Middle Kingdom periods.", 13 | "id": 1002, 14 | "in_menu": 1, 15 | "name": "Pyramid", 16 | "parent_id": 1001, 17 | "slug": "pyramid", 18 | "visible": 1, 19 | "tree_id": 3 20 | }, 21 | { 22 | "description": "Ziggurats were massive structures built in the ancient Mesopotamian valley and western Iranian plateau, having the form of a terraced step pyramid of successively receding stories or levels.", 23 | "id": 1003, 24 | "in_menu": 1, 25 | "name": "Ziggurats", 26 | "parent_id": 1002, 27 | "slug": "ziggurats", 28 | "visible": 1, 29 | "tree_id": 3 30 | }, 31 | { 32 | "id": 1004, 33 | "in_menu": 1, 34 | "name": "Unsorted", 35 | "slug": "unsorted", 36 | "visible": 1, 37 | "tree_id": 4 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /example/fixtures/news.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Pyramid - Hacker T-Shirt, designed by artist Felix Laflamme, is now available for online purchase via the Pylons Project Store - Hacker page.", 4 | "in_menu": 1, 5 | "name": "Pyramid - Hacker T-Shirt", 6 | "slug": "pyramid-hacker-t-shirt", 7 | "visible": 1 8 | }, 9 | { 10 | "description": "Pyramid 1.5 was released on April, 8 2014. Read the What's New In Pyramid 1.5 document for an overview of changes. Thanks to everyone who contributed to making this release possible.", 11 | "in_menu": 1, 12 | "name": "Pyramid 1.5 released", 13 | "slug": "pyramid-1-5-released", 14 | "visible": 1 15 | }, 16 | { 17 | "description": "Pyramid - Dashboard T-Shirt, designed by artist Felix Laflamme, is now available for online purchase via the Pylons Project Store - Dashboard page.", 18 | "in_menu": 1, 19 | "name": "Pyramid - Dashboard T-Shirt", 20 | "slug": "pyramid-dashboard-t-shirt", 21 | "visible": 1 22 | }, 23 | { 24 | "description": "The Pylons framework developers have merged efforts with the repoze.bfg framework under the Pylons Project Organization. Read about the Pylons Project and the joint effort, the Pyramid web framework, and notes on the merger.", 25 | "in_menu": 1, 26 | "name": "Pylons merges with repoze.bfg", 27 | "slug": "pylons-merges-with-repoze-bfg", 28 | "visible": 1 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /example/fixtures/pages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "0", 4 | "in_menu": 1, 5 | "name": "Default home page", 6 | "slug": "/", 7 | "visible": 1, 8 | "tree_id": 1 9 | }, 10 | { 11 | "description": "Hello Traversal World! Hello Traversal World! Hello Traversal World!", 12 | "id": "1", 13 | "in_menu": 1, 14 | "name": "Hello Traversal World!", 15 | "slug": "about-company", 16 | "tree_id": 1, 17 | "visible": 1 18 | }, 19 | { 20 | "id": "2", 21 | "in_menu": 1, 22 | "name": "We \u2665 gevent", 23 | "parent_id": "1", 24 | "slug": "we-love-gevent", 25 | "tree_id": 1, 26 | "visible": 1 27 | }, 28 | { 29 | "id": "3", 30 | "in_menu": 1, 31 | "name": "And Pyramid", 32 | "parent_id": "2", 33 | "slug": "and-pyramid", 34 | "tree_id": 1, 35 | "visible": 1 36 | }, 37 | { 38 | "id": "4", 39 | "in_menu": 1, 40 | "name": "And ziggurats", 41 | "parent_id": "2", 42 | "slug": "and-ziggurats", 43 | "tree_id": 1, 44 | "visible": 1, 45 | "redirect_type": 200, 46 | "redirect_page": 1003 47 | }, 48 | { 49 | "id": "5", 50 | "in_menu": 1, 51 | "name": "Redirect 301 to we-love-gevent", 52 | "parent_id": "1", 53 | "redirect_page": 2, 54 | "redirect_type": "301", 55 | "slug": "redirect-301", 56 | "tree_id": 1, 57 | "visible": 1 58 | }, 59 | { 60 | "id": "6", 61 | "in_menu": 1, 62 | "name": "Redirect 200 to about-company", 63 | "parent_id": "5", 64 | "redirect_page": 1, 65 | "redirect_type": "200", 66 | "slug": "redirect-200", 67 | "tree_id": 1, 68 | "visible": 1 69 | }, 70 | { 71 | "id": "7", 72 | "in_menu": 1, 73 | "name": "Фонд Город Без Наркотиков", 74 | "parent_id": "5", 75 | "redirect_type": "302", 76 | "redirect_url": "http://nobf.ru/", 77 | "slug": "fond-gbn", 78 | "tree_id": 1, 79 | "visible": 1 80 | }, 81 | { 82 | "id": "8", 83 | "name": "Our strategy", 84 | "parent_id": "1", 85 | "slug": "our-strategy", 86 | "tree_id": 1, 87 | "visible": 1 88 | }, 89 | { 90 | "id": "9", 91 | "name": "Wordwide", 92 | "parent_id": "8", 93 | "slug": "wordwide", 94 | "tree_id": 1, 95 | "visible": 1 96 | }, 97 | { 98 | "id": "10", 99 | "name": "Technology", 100 | "parent_id": "9", 101 | "slug": "technology", 102 | "tree_id": 1, 103 | "visible": 0 104 | }, 105 | { 106 | "id": "11", 107 | "name": "What we do", 108 | "parent_id": "8", 109 | "slug": "what-we-do", 110 | "tree_id": 1, 111 | "visible": 1 112 | }, 113 | { 114 | "id": "12", 115 | "name": "at a glance", 116 | "parent_id": "11", 117 | "slug": "at-a-glance", 118 | "tree_id": 1, 119 | "visible": 1 120 | }, 121 | { 122 | "id": "13", 123 | "in_menu": 1, 124 | "name": "and aiohttp!", 125 | "parent_id": "3", 126 | "slug": "and-aiohttp", 127 | "tree_id": 1, 128 | "visible": 1 129 | }, 130 | { 131 | "id": "14", 132 | "in_menu": 1, 133 | "name": "and asyncio!", 134 | "parent_id": "3", 135 | "slug": "and-asyncio", 136 | "tree_id": 1, 137 | "visible": 1 138 | }, 139 | { 140 | "id": "15", 141 | "in_menu": 1, 142 | "name": "and beer!", 143 | "parent_id": "13", 144 | "slug": "and-beer", 145 | "tree_id": 1, 146 | "visible": 1 147 | }, 148 | { 149 | "id": "16", 150 | "in_menu": 1, 151 | "name": "and bear to!", 152 | "parent_id": "15", 153 | "slug": "and-bear-to", 154 | "tree_id": 1, 155 | "visible": 1 156 | } 157 | ] 158 | -------------------------------------------------------------------------------- /example/fixtures/photos.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "http://i.imgur.com/FmVpuqM.jpg", 4 | "gallery_id": 1001 5 | }, 6 | { 7 | "path": "http://i.imgur.com/ZM0EjlB.jpg", 8 | "gallery_id": 1002 9 | }, 10 | { 11 | "path": "http://i.imgur.com/ns2TyAE.jpg", 12 | "gallery_id": 1002 13 | }, 14 | { 15 | "path": "http://i.imgur.com/GtZq6ps.jpg", 16 | "gallery_id": 1002 17 | }, 18 | { 19 | "path": "http://i.imgur.com/85U2RS9.jpg", 20 | "gallery_id": 1002 21 | }, 22 | { 23 | "path": "http://i.imgur.com/ZVTBpjV.jpg", 24 | "gallery_id": 1003 25 | }, 26 | { 27 | "path": "http://i.imgur.com/1RHsVNy.jpg", 28 | "gallery_id": 1003 29 | }, 30 | { 31 | "path": "http://i.imgur.com/fmck85z.jpg", 32 | "gallery_id": 1003 33 | }, 34 | { 35 | "path": "http://i.imgur.com/xNmuUcf.jpg", 36 | "gallery_id": 1004 37 | }, 38 | { 39 | "path": "http://i.imgur.com/MNUosfw.jpg", 40 | "gallery_id": 1004 41 | }, 42 | { 43 | "path": "http://i.imgur.com/8EfAn3t.jpg", 44 | "gallery_id": 1004 45 | } 46 | ] 47 | -------------------------------------------------------------------------------- /example/pyramid_pages_example.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Main for example 11 | """ 12 | import os 13 | import json 14 | 15 | import transaction 16 | from sqlalchemy import ( 17 | Date, 18 | Text, 19 | Column, 20 | String, 21 | Integer, 22 | ForeignKey, 23 | engine_from_config 24 | ) 25 | from pyramid.config import Configurator 26 | from pyramid.events import BeforeRender 27 | from sqlalchemy.orm import relationship, sessionmaker, scoped_session 28 | from sqlalchemy.sql import func 29 | from pyramid.session import SignedCookieSessionFactory 30 | from sqlalchemy_mptt import mptt_sessionmaker 31 | from zope.sqlalchemy import register 32 | from sqlalchemy.ext.declarative import declarative_base 33 | 34 | from pyramid_pages.models import FlatPageMixin, MpttPageMixin, RedirectMixin 35 | from pyramid_pages.resources import ( 36 | BasePageResource, 37 | resource_of_node, 38 | resources_of_config 39 | ) 40 | 41 | Base = declarative_base() 42 | DBSession = scoped_session( 43 | mptt_sessionmaker( 44 | sessionmaker() 45 | ) 46 | ) 47 | register(DBSession) 48 | 49 | CONFIG_SQLALCHEMY_URL = 'sqlalchemy.url' 50 | CONFIG_PYRAMID_PAGES_MODELS = 'pyramid_pages.models' 51 | CONFIG_PYRAMID_PAGES_DBSESSION = 'pyramid_pages.dbsession' 52 | 53 | 54 | class BasePage(Base, RedirectMixin): 55 | __tablename__ = 'base_pages' 56 | id = Column(Integer, primary_key=True) 57 | page_type = Column(String(50)) 58 | 59 | __mapper_args__ = { 60 | 'polymorphic_identity': 'base_page', 61 | 'polymorphic_on': page_type, 62 | 'with_polymorphic': '*' 63 | } 64 | 65 | @classmethod 66 | def get_pk_name(cls): 67 | return 'id' 68 | 69 | @classmethod 70 | def get_pk_with_class_name(cls): 71 | return 'BasePage.id' 72 | 73 | 74 | class WebPage(BasePage, MpttPageMixin): 75 | __tablename__ = 'mptt_pages' 76 | 77 | id = Column(Integer, ForeignKey('base_pages.id'), primary_key=True) 78 | 79 | __mapper_args__ = { 80 | 'polymorphic_identity': 'web_page', 81 | } 82 | 83 | 84 | class NewsPage(BasePage, FlatPageMixin): 85 | __tablename__ = 'flat_news' 86 | 87 | id = Column(Integer, ForeignKey('base_pages.id'), primary_key=True) 88 | date = Column(Date, default=func.now()) 89 | 90 | __mapper_args__ = { 91 | 'polymorphic_identity': 'news_page', 92 | } 93 | 94 | 95 | class Gallery(BasePage, MpttPageMixin): 96 | __tablename__ = 'mptt_gallery' 97 | 98 | id = Column(Integer, ForeignKey('base_pages.id'), primary_key=True) 99 | 100 | __mapper_args__ = { 101 | 'polymorphic_identity': 'gallery_page', 102 | } 103 | 104 | 105 | class Photo(Base): 106 | __tablename__ = 'photos' 107 | 108 | id = Column('id', Integer, primary_key=True) 109 | path = Column('path', Text) 110 | gallery_id = Column(Integer, ForeignKey('mptt_gallery.id')) 111 | gallery = relationship('Gallery', backref='photos') 112 | 113 | 114 | class GalleryResource(BasePageResource): 115 | model = Gallery 116 | template = 'gallery/index.jinja2' 117 | 118 | 119 | class NewsResource(BasePageResource): 120 | model = NewsPage 121 | template = 'news/index.jinja2' 122 | 123 | 124 | class Fixtures(object): 125 | 126 | def __init__(self, session): 127 | self.session = session 128 | 129 | def add(self, model, fixtures): 130 | here = os.path.dirname(os.path.realpath(__file__)) 131 | file = open(os.path.join(here, fixtures)) 132 | fixtures = json.loads(file.read(), encoding='utf-8') 133 | for fixture in fixtures: 134 | self.session.add(model(**fixture)) 135 | self.session.flush() 136 | transaction.commit() 137 | 138 | models = { 139 | '': WebPage, 140 | 'pages': WebPage, 141 | 'news': NewsResource, 142 | 'gallery': GalleryResource, 143 | } 144 | 145 | 146 | def add_globals(event): 147 | 148 | class Menu(object): 149 | resources = resources_of_config(models) 150 | 151 | def __init__(self, model): 152 | self.nodes = DBSession.query(model) 153 | self.template = 'pyramid_pages/menu/flat.jinja2' 154 | if hasattr(model, 'parent'): 155 | self.nodes = self.nodes.filter_by(parent=None)\ 156 | .order_by(model.tree_id) 157 | self.template = 'pyramid_pages/menu/mptt.jinja2' 158 | 159 | def __iter__(self): 160 | for node in self.nodes: 161 | yield resource_of_node(self.resources, node)(node) 162 | 163 | event['pages_menu'] = Menu(WebPage) 164 | event['news_menu'] = Menu(NewsPage) 165 | event['gallery_menu'] = Menu(Gallery) 166 | 167 | 168 | def main(global_settings, **settings): 169 | config = Configurator( 170 | settings=settings, 171 | session_factory=SignedCookieSessionFactory('itsaseekreet') 172 | ) 173 | config.include('pyramid_jinja2') 174 | config.add_jinja2_search_path('pyramid_pages_example:templates') 175 | config.add_static_view('pyramid_pages_example_static', 176 | 'static') 177 | 178 | # Database 179 | settings = config.get_settings() 180 | settings[CONFIG_SQLALCHEMY_URL] =\ 181 | settings.get(CONFIG_SQLALCHEMY_URL, 182 | 'sqlite:///example.sqlite') 183 | engine = engine_from_config(settings) 184 | DBSession.configure(bind=engine) 185 | Base.metadata.drop_all(engine) 186 | Base.metadata.create_all(engine) 187 | fixture = Fixtures(DBSession) 188 | fixture.add(WebPage, 'fixtures/pages.json') 189 | fixture.add(WebPage, 'fixtures/country.json') 190 | fixture.add(NewsPage, 'fixtures/news.json') 191 | fixture.add(Gallery, 'fixtures/gallery.json') 192 | fixture.add(Photo, 'fixtures/photos.json') 193 | 194 | # pyramid_pages 195 | settings[CONFIG_PYRAMID_PAGES_DBSESSION] =\ 196 | settings.get(CONFIG_PYRAMID_PAGES_DBSESSION, 197 | DBSession) 198 | settings[CONFIG_PYRAMID_PAGES_MODELS] =\ 199 | settings.get(CONFIG_PYRAMID_PAGES_MODELS, models) 200 | config.include("pyramid_pages") 201 | config.add_subscriber(add_globals, BeforeRender) 202 | return config.make_wsgi_app() 203 | 204 | if __name__ == '__main__': 205 | settings = {} 206 | app = main({}, **settings) 207 | 208 | from wsgiref.simple_server import make_server 209 | httpd = make_server('0.0.0.0', 6543, app) 210 | httpd.serve_forever() 211 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | pyramid 2 | transaction 3 | zope.sqlalchemy 4 | sqlalchemy_mptt 5 | pyramid_jinja2 6 | -------------------------------------------------------------------------------- /example/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup 4 | 5 | version = '1.0' 6 | 7 | here = os.path.dirname(os.path.realpath(__file__)) 8 | 9 | 10 | def read(name): 11 | with open(os.path.join(here, name)) as f: 12 | return f.read() 13 | 14 | setup( 15 | name='pyramid_pages_example', 16 | version=version, 17 | py_modules=['pyramid_pages_example'], 18 | install_requires=read('requirements.txt'), 19 | entry_points=""" 20 | [paste.app_factory] 21 | main = pyramid_pages_example:main 22 | """, 23 | ) 24 | -------------------------------------------------------------------------------- /example/static/css/__main.css: -------------------------------------------------------------------------------- 1 | .fotorama__arr:focus:after,.fotorama__fullscreen-icon:focus:after,.fotorama__html,.fotorama__img,.fotorama__nav__frame:focus .fotorama__dot:after,.fotorama__nav__frame:focus .fotorama__thumb:after,.fotorama__stage__frame,.fotorama__stage__shaft,.fotorama__video iframe{position:absolute;width:100%;height:100%;top:0;right:0;left:0;bottom:0}.fotorama--fullscreen,.fotorama__img{max-width:99999px!important;max-height:99999px!important;min-width:0!important;min-height:0!important;-webkit-border-radius:0!important;-moz-border-radius:0!important;border-radius:0!important;-webkit-box-shadow:none!important;-moz-box-shadow:none!important;box-shadow:none!important;padding:0!important}.fotorama__wrap .fotorama__grab{cursor:move;cursor:-webkit-grab;cursor:-o-grab;cursor:-ms-grab;cursor:-moz-grab;cursor:grab}.fotorama__grabbing *{cursor:move;cursor:-webkit-grabbing;cursor:-o-grabbing;cursor:-ms-grabbing;cursor:-moz-grabbing;cursor:grabbing}.fotorama__spinner{position:absolute!important;top:50%!important;left:50%!important}.fotorama__wrap--css3 .fotorama__arr,.fotorama__wrap--css3 .fotorama__fullscreen-icon,.fotorama__wrap--css3 .fotorama__nav__shaft,.fotorama__wrap--css3 .fotorama__stage__shaft,.fotorama__wrap--css3 .fotorama__thumb-border,.fotorama__wrap--css3 .fotorama__video-close,.fotorama__wrap--css3 .fotorama__video-play{-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.fotorama__caption,.fotorama__nav:after,.fotorama__nav:before,.fotorama__stage:after,.fotorama__stage:before,.fotorama__wrap--css3 .fotorama__html,.fotorama__wrap--css3 .fotorama__nav,.fotorama__wrap--css3 .fotorama__spinner,.fotorama__wrap--css3 .fotorama__stage,.fotorama__wrap--css3 .fotorama__stage .fotorama__img,.fotorama__wrap--css3 .fotorama__stage__frame{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);transform:translateZ(0)}.fotorama__arr:focus,.fotorama__fullscreen-icon:focus,.fotorama__nav__frame{outline:0}.fotorama__arr:focus:after,.fotorama__fullscreen-icon:focus:after,.fotorama__nav__frame:focus .fotorama__dot:after,.fotorama__nav__frame:focus .fotorama__thumb:after{content:'';-webkit-border-radius:inherit;-moz-border-radius:inherit;border-radius:inherit;background-color:rgba(0,175,234,.5)}.fotorama__wrap--video .fotorama__stage,.fotorama__wrap--video .fotorama__stage__frame--video,.fotorama__wrap--video .fotorama__stage__frame--video .fotorama__html,.fotorama__wrap--video .fotorama__stage__frame--video .fotorama__img,.fotorama__wrap--video .fotorama__stage__shaft{-webkit-transform:none!important;-moz-transform:none!important;-ms-transform:none!important;-o-transform:none!important;transform:none!important}.fotorama__wrap--css3 .fotorama__nav__shaft,.fotorama__wrap--css3 .fotorama__stage__shaft,.fotorama__wrap--css3 .fotorama__thumb-border{-webkit-transition-property:-webkit-transform,width;-moz-transition-property:-moz-transform,width;-o-transition-property:-o-transform,width;transition-property:transform,width;-webkit-transition-timing-function:cubic-bezier(.1,0,.25,1);-moz-transition-timing-function:cubic-bezier(.1,0,.25,1);-o-transition-timing-function:cubic-bezier(.1,0,.25,1);transition-timing-function:cubic-bezier(.1,0,.25,1);-webkit-transition-duration:0ms;-moz-transition-duration:0ms;-o-transition-duration:0ms;transition-duration:0ms}.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__no-select,.fotorama__video-close,.fotorama__video-play,.fotorama__wrap{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fotorama__nav,.fotorama__nav__frame{margin:auto;padding:0}.fotorama--fullscreen,.fullscreen{width:100%!important;height:100%!important;margin:0!important}.fotorama__caption__wrap,.fotorama__nav__frame,.fotorama__nav__shaft{-moz-box-orient:vertical;display:inline-block;vertical-align:middle}.fotorama:not(.fotorama--unobtrusive)>:not(:first-child),.fotorama__wrap--fade .fotorama__stage__frame{display:none}.fotorama__nav__frame,.fotorama__thumb-border{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}.fotorama--hidden,.fotorama__load{position:absolute;left:-99999px;top:-99999px;z-index:-1}.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__nav,.fotorama__nav__frame,.fotorama__nav__shaft,.fotorama__stage__frame,.fotorama__stage__shaft,.fotorama__video-close,.fotorama__video-play{-webkit-tap-highlight-color:transparent}.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__video-close,.fotorama__video-play{background:url(../img/vendor/fotorama.png) no-repeat}@media (-webkit-min-device-pixel-ratio:1.5),(-webkit-min-device-pixel-ratio:2),(-o-min-device-pixel-ratio:2/1),(min-resolution:2dppx){.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__video-close,.fotorama__video-play{background:url(../img/vendor/fotorama@2x.png) 0 0/96px 160px no-repeat}}.fotorama__thumb{background-color:#7f7f7f;background-color:rgba(127,127,127,.2)}@media print{.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__thumb-border,.fotorama__video-close,.fotorama__video-play{background:0 0!important}}.fotorama--fullscreen .fotorama__nav,.fotorama--fullscreen .fotorama__stage,.fullscreen{background:#000}.fotorama{min-width:1px;overflow:hidden}.fullscreen{max-width:100%!important;max-height:100%!important;padding:0!important;overflow:hidden!important}.fotorama--fullscreen{position:absolute!important;top:0!important;left:0!important;right:0!important;bottom:0!important;float:none!important;z-index:2147483647!important;background:#000}.fotorama__wrap{-webkit-text-size-adjust:100%;position:relative;direction:ltr;z-index:0}.fotorama__stage__frame.fotorama__active,.fotorama__wrap--fade .fotorama__fade-front{z-index:8}.fotorama__wrap--rtl .fotorama__stage__frame{direction:rtl}.fotorama__nav,.fotorama__stage{overflow:hidden;position:relative;max-width:100%}.fotorama__wrap--pan-y{-ms-touch-action:pan-y}.fotorama__wrap .fotorama__pointer{cursor:pointer}.fotorama__wrap--slide .fotorama__stage__frame{opacity:1!important;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"}.fotorama__stage__frame{overflow:hidden}.fotorama__wrap--fade .fotorama__fade-front,.fotorama__wrap--fade .fotorama__fade-rear,.fotorama__wrap--fade .fotorama__stage__frame.fotorama__active{display:block;left:0;top:0}.fotorama--fullscreen .fotorama__loaded--full .fotorama__img,.fotorama__img--full,.fotorama__wrap--fade .fotorama__stage .fotorama__shadow{display:none}.fotorama__wrap--fade .fotorama__fade-rear{z-index:7}.fotorama__wrap--fade .fotorama__fade-rear.fotorama__active{z-index:9}.fotorama__img{filter:alpha(opacity=0);opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";border:none!important}.fotorama__error .fotorama__img,.fotorama__loaded .fotorama__img{filter:alpha(opacity=100);opacity:1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"}.fotorama--fullscreen .fotorama__loaded--full .fotorama__img--full{display:block}.fotorama__wrap--only-active .fotorama__nav,.fotorama__wrap--only-active .fotorama__stage{max-width:99999px!important}.fotorama__wrap--only-active .fotorama__stage__frame{visibility:hidden}.fotorama__wrap--only-active .fotorama__stage__frame.fotorama__active{visibility:visible}.fotorama__nav{font-size:0;line-height:0;text-align:center;display:none;white-space:nowrap;z-index:5}.fotorama__nav__shaft{position:relative;left:0;top:0;text-align:left}.fotorama__nav__frame{position:relative;cursor:pointer}.fotorama__nav--dots{display:block}.fotorama__nav--dots .fotorama__nav__frame{width:18px;height:30px}.fotorama__nav--dots .fotorama__nav__frame--thumb,.fotorama__nav--dots .fotorama__thumb-border{display:none}.fotorama__nav--thumbs{display:block}.fotorama__nav--thumbs .fotorama__nav__frame{padding-left:0!important}.fotorama__nav--thumbs .fotorama__nav__frame:last-child{padding-right:0!important}.fotorama__nav--thumbs .fotorama__nav__frame--dot{display:none}.fotorama__dot{display:block;width:4px;height:4px;position:relative;top:12px;left:6px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;border:1px solid #7f7f7f}.fotorama__wrap--css2 .fotorama__video-play,.fotorama__wrap--no-captions .fotorama__caption,.fotorama__wrap--video .fotorama__caption,.fotorama__wrap--video .fotorama__stage .fotorama__video-play{display:none}.fotorama__nav__frame:focus .fotorama__dot:after{padding:1px;top:-1px;left:-1px}.fotorama__nav__frame.fotorama__active .fotorama__dot{width:0;height:0;border-width:3px}.fotorama__nav__frame.fotorama__active .fotorama__dot:after{padding:3px;top:-3px;left:-3px}.fotorama__thumb{overflow:hidden;position:relative;width:100%;height:100%}.fotorama__nav__frame:focus .fotorama__thumb{z-index:2}.fotorama__thumb-border{position:absolute;z-index:9;top:0;left:0;border-style:solid;border-color:#00afea;background-image:-webkit-gradient(linear,left top,right bottom,from(rgba(255,255,255,.25)),to(rgba(64,64,64,.1)));background-image:-webkit-linear-gradient(top left,rgba(255,255,255,.25),rgba(64,64,64,.1));background-image:-moz-linear-gradient(top left,rgba(255,255,255,.25),rgba(64,64,64,.1));background-image:-o-linear-gradient(top left,rgba(255,255,255,.25),rgba(64,64,64,.1));background-image:linear-gradient(to bottom right,rgba(255,255,255,.25),rgba(64,64,64,.1))}.fotorama__caption{position:absolute;z-index:12;bottom:0;left:0;right:0;font-family:'Helvetica Neue',Arial,sans-serif;font-size:14px;line-height:1.5;color:#000}.fotorama__caption a{text-decoration:none;color:#000;border-bottom:1px solid;border-color:rgba(0,0,0,.5)}.fotorama__caption a:hover{color:#333;border-color:rgba(51,51,51,.5)}.fotorama__wrap--rtl .fotorama__caption{left:auto;right:0}.fotorama__caption__wrap{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;background-color:#fff;background-color:rgba(255,255,255,.9);padding:5px 10px}@-webkit-keyframes spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes spinner{0%{-webkit-transform:rotate(0);-moz-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes spinner{0%{-webkit-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner{0%{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}.fotorama__wrap--css3 .fotorama__spinner{-webkit-animation:spinner 24s infinite linear;-moz-animation:spinner 24s infinite linear;-o-animation:spinner 24s infinite linear;animation:spinner 24s infinite linear}.fotorama__wrap--css3 .fotorama__html,.fotorama__wrap--css3 .fotorama__stage .fotorama__img{-webkit-transition-property:opacity;-moz-transition-property:opacity;-o-transition-property:opacity;transition-property:opacity;-webkit-transition-timing-function:linear;-moz-transition-timing-function:linear;-o-transition-timing-function:linear;transition-timing-function:linear;-webkit-transition-duration:.3s;-moz-transition-duration:.3s;-o-transition-duration:.3s;transition-duration:.3s}.fotorama__wrap--video .fotorama__stage__frame--video .fotorama__html,.fotorama__wrap--video .fotorama__stage__frame--video .fotorama__img{filter:alpha(opacity=0);opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"}.fotorama__select{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;cursor:auto}.fotorama__video{top:32px;right:0;bottom:0;left:0;position:absolute;z-index:10}@-moz-document url-prefix(){.fotorama__active{-moz-box-shadow:0 0 0 transparent;box-shadow:0 0 0 transparent}}.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__video-close,.fotorama__video-play{position:absolute;z-index:11;cursor:pointer}.fotorama__arr{position:absolute;width:32px;height:32px;top:50%;margin-top:-16px}.fotorama__arr--prev{left:2px;background-position:0 0}.fotorama__arr--next{right:2px;background-position:-32px 0}.fotorama__arr--disabled{pointer-events:none;cursor:default;opacity:.1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=10)"}.fotorama__fullscreen-icon{width:32px;height:32px;top:2px;right:2px;background-position:0 -32px;z-index:20}.fotorama__arr:focus,.fotorama__fullscreen-icon:focus{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%}.fotorama--fullscreen .fotorama__fullscreen-icon{background-position:-32px -32px}.fotorama__video-play{width:96px;height:96px;left:50%;top:50%;margin-left:-48px;margin-top:-48px;background-position:0 -64px;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"}.fotorama__error .fotorama__video-play,.fotorama__loaded .fotorama__video-play,.fotorama__nav__frame .fotorama__video-play{opacity:1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";display:block}.fotorama__nav__frame .fotorama__video-play{width:32px;height:32px;margin-left:-16px;margin-top:-16px;background-position:-64px -32px}.fotorama__video-close{width:32px;height:32px;top:0;right:0;background-position:-64px 0;z-index:20;opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"}.fotorama__wrap--css2 .fotorama__video-close{display:none}.fotorama__wrap--css3 .fotorama__video-close{-webkit-transform:translate3d(32px,-32px,0);-moz-transform:translate3d(32px,-32px,0);transform:translate3d(32px,-32px,0)}.fotorama__wrap--video .fotorama__video-close{display:block;opacity:1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"}.fotorama__wrap--css3.fotorama__wrap--video .fotorama__video-close{-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__arr,.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon{opacity:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"}.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__arr:focus,.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon:focus{opacity:1;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"}.fotorama__wrap--video .fotorama__arr,.fotorama__wrap--video .fotorama__fullscreen-icon{opacity:0!important;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"}.fotorama__wrap--css2.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__arr,.fotorama__wrap--css2.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon{display:none}.fotorama__wrap--css2.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__arr:focus,.fotorama__wrap--css2.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon:focus{display:block}.fotorama__wrap--css2.fotorama__wrap--video .fotorama__arr,.fotorama__wrap--css2.fotorama__wrap--video .fotorama__fullscreen-icon{display:none!important}.fotorama__wrap--css3.fotorama__wrap--no-controls.fotorama__wrap--slide.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon:not(:focus){-webkit-transform:translate3d(32px,-32px,0);-moz-transform:translate3d(32px,-32px,0);transform:translate3d(32px,-32px,0)}.fotorama__wrap--css3.fotorama__wrap--no-controls.fotorama__wrap--slide.fotorama__wrap--toggle-arrows .fotorama__arr--prev:not(:focus){-webkit-transform:translate3d(-48px,0,0);-moz-transform:translate3d(-48px,0,0);transform:translate3d(-48px,0,0)}.fotorama__wrap--css3.fotorama__wrap--no-controls.fotorama__wrap--slide.fotorama__wrap--toggle-arrows .fotorama__arr--next:not(:focus){-webkit-transform:translate3d(48px,0,0);-moz-transform:translate3d(48px,0,0);transform:translate3d(48px,0,0)}.fotorama__wrap--css3.fotorama__wrap--video .fotorama__fullscreen-icon{-webkit-transform:translate3d(32px,-32px,0)!important;-moz-transform:translate3d(32px,-32px,0)!important;transform:translate3d(32px,-32px,0)!important}.fotorama__wrap--css3.fotorama__wrap--video .fotorama__arr--prev{-webkit-transform:translate3d(-48px,0,0)!important;-moz-transform:translate3d(-48px,0,0)!important;transform:translate3d(-48px,0,0)!important}.fotorama__wrap--css3.fotorama__wrap--video .fotorama__arr--next{-webkit-transform:translate3d(48px,0,0)!important;-moz-transform:translate3d(48px,0,0)!important;transform:translate3d(48px,0,0)!important}.fotorama__wrap--css3 .fotorama__arr:not(:focus),.fotorama__wrap--css3 .fotorama__fullscreen-icon:not(:focus),.fotorama__wrap--css3 .fotorama__video-close:not(:focus),.fotorama__wrap--css3 .fotorama__video-play:not(:focus){-webkit-transition-property:-webkit-transform,opacity;-moz-transition-property:-moz-transform,opacity;-o-transition-property:-o-transform,opacity;transition-property:transform,opacity;-webkit-transition-duration:.3s;-moz-transition-duration:.3s;-o-transition-duration:.3s;transition-duration:.3s}.fotorama__nav:after,.fotorama__nav:before,.fotorama__stage:after,.fotorama__stage:before{content:"";display:block;position:absolute;text-decoration:none;top:0;bottom:0;width:10px;height:auto;z-index:10;pointer-events:none;background-repeat:no-repeat;-moz-background-size:1px 100%,5px 100%;-o-background-size:1px 100%,5px 100%;background-size:1px 100%,5px 100%}.fotorama__nav:before,.fotorama__stage:before{background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),-webkit-radial-gradient(0 50%,farthest-side,rgba(0,0,0,.4),transparent);background-image:-moz-linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),-moz-radial-gradient(0 50%,farthest-side,rgba(0,0,0,.4),transparent);background-image:-o-linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),-o-radial-gradient(0 50%,farthest-side,rgba(0,0,0,.4),transparent);background-image:linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),radial-gradient(farthest-side at 0 50%,rgba(0,0,0,.4),transparent);background-position:0 0,0 0;left:-10px}.fotorama__nav.fotorama__shadows--left:before,.fotorama__stage.fotorama__shadows--left:before{left:0}.fotorama__nav:after,.fotorama__stage:after{background-image:-webkit-linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),-webkit-radial-gradient(100% 50%,farthest-side,rgba(0,0,0,.4),transparent);background-image:-moz-linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),-moz-radial-gradient(100% 50%,farthest-side,rgba(0,0,0,.4),transparent);background-image:-o-linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),-o-radial-gradient(100% 50%,farthest-side,rgba(0,0,0,.4),transparent);background-image:linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),radial-gradient(farthest-side at 100% 50%,rgba(0,0,0,.4),transparent);background-position:100% 0,100% 0;right:-10px}.fotorama__nav.fotorama__shadows--right:after,.fotorama__stage.fotorama__shadows--right:after{right:0}.fotorama--fullscreen .fotorama__nav:after,.fotorama--fullscreen .fotorama__nav:before,.fotorama--fullscreen .fotorama__stage:after,.fotorama--fullscreen .fotorama__stage:before,.fotorama__wrap--fade .fotorama__stage:after,.fotorama__wrap--fade .fotorama__stage:before,.fotorama__wrap--no-shadows .fotorama__nav:after,.fotorama__wrap--no-shadows .fotorama__nav:before,.fotorama__wrap--no-shadows .fotorama__stage:after,.fotorama__wrap--no-shadows .fotorama__stage:before{display:none} -------------------------------------------------------------------------------- /example/static/css/vendor/fotorama.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Fotorama 4.6.4 | http://fotorama.io/license/ 3 | */ 4 | .fotorama__arr:focus:after,.fotorama__fullscreen-icon:focus:after,.fotorama__html,.fotorama__img,.fotorama__nav__frame:focus .fotorama__dot:after,.fotorama__nav__frame:focus .fotorama__thumb:after,.fotorama__stage__frame,.fotorama__stage__shaft,.fotorama__video iframe{position:absolute;width:100%;height:100%;top:0;right:0;left:0;bottom:0}.fotorama--fullscreen,.fotorama__img{max-width:99999px!important;max-height:99999px!important;min-width:0!important;min-height:0!important;border-radius:0!important;box-shadow:none!important;padding:0!important}.fotorama__wrap .fotorama__grab{cursor:move;cursor:-webkit-grab;cursor:-o-grab;cursor:-ms-grab;cursor:grab}.fotorama__grabbing *{cursor:move;cursor:-webkit-grabbing;cursor:-o-grabbing;cursor:-ms-grabbing;cursor:grabbing}.fotorama__spinner{position:absolute!important;top:50%!important;left:50%!important}.fotorama__wrap--css3 .fotorama__arr,.fotorama__wrap--css3 .fotorama__fullscreen-icon,.fotorama__wrap--css3 .fotorama__nav__shaft,.fotorama__wrap--css3 .fotorama__stage__shaft,.fotorama__wrap--css3 .fotorama__thumb-border,.fotorama__wrap--css3 .fotorama__video-close,.fotorama__wrap--css3 .fotorama__video-play{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.fotorama__caption,.fotorama__nav:after,.fotorama__nav:before,.fotorama__stage:after,.fotorama__stage:before,.fotorama__wrap--css3 .fotorama__html,.fotorama__wrap--css3 .fotorama__nav,.fotorama__wrap--css3 .fotorama__spinner,.fotorama__wrap--css3 .fotorama__stage,.fotorama__wrap--css3 .fotorama__stage .fotorama__img,.fotorama__wrap--css3 .fotorama__stage__frame{-webkit-transform:translateZ(0);transform:translateZ(0)}.fotorama__arr:focus,.fotorama__fullscreen-icon:focus,.fotorama__nav__frame{outline:0}.fotorama__arr:focus:after,.fotorama__fullscreen-icon:focus:after,.fotorama__nav__frame:focus .fotorama__dot:after,.fotorama__nav__frame:focus .fotorama__thumb:after{content:'';border-radius:inherit;background-color:rgba(0,175,234,.5)}.fotorama__wrap--video .fotorama__stage,.fotorama__wrap--video .fotorama__stage__frame--video,.fotorama__wrap--video .fotorama__stage__frame--video .fotorama__html,.fotorama__wrap--video .fotorama__stage__frame--video .fotorama__img,.fotorama__wrap--video .fotorama__stage__shaft{-webkit-transform:none!important;transform:none!important}.fotorama__wrap--css3 .fotorama__nav__shaft,.fotorama__wrap--css3 .fotorama__stage__shaft,.fotorama__wrap--css3 .fotorama__thumb-border{transition-property:-webkit-transform,width;transition-property:transform,width;transition-timing-function:cubic-bezier(0.1,0,.25,1);transition-duration:0ms}.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__no-select,.fotorama__video-close,.fotorama__video-play,.fotorama__wrap{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.fotorama__select{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.fotorama__nav,.fotorama__nav__frame{margin:auto;padding:0}.fotorama__caption__wrap,.fotorama__nav__frame,.fotorama__nav__shaft{-moz-box-orient:vertical;display:inline-block;vertical-align:middle;*display:inline;*zoom:1}.fotorama__nav__frame,.fotorama__thumb-border{box-sizing:content-box}.fotorama__caption__wrap{box-sizing:border-box}.fotorama--hidden,.fotorama__load{position:absolute;left:-99999px;top:-99999px;z-index:-1}.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__nav,.fotorama__nav__frame,.fotorama__nav__shaft,.fotorama__stage__frame,.fotorama__stage__shaft,.fotorama__video-close,.fotorama__video-play{-webkit-tap-highlight-color:transparent}.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__video-close,.fotorama__video-play{background:url(fotorama.png) no-repeat}@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:2dppx){.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__video-close,.fotorama__video-play{background:url(fotorama@2x.png) 0 0/96px 160px no-repeat}}.fotorama__thumb{background-color:#7f7f7f;background-color:rgba(127,127,127,.2)}@media print{.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__thumb-border,.fotorama__video-close,.fotorama__video-play{background:none!important}}.fotorama{min-width:1px;overflow:hidden}.fotorama:not(.fotorama--unobtrusive)>*:not(:first-child){display:none}.fullscreen{width:100%!important;height:100%!important;max-width:100%!important;max-height:100%!important;margin:0!important;padding:0!important;overflow:hidden!important;background:#000}.fotorama--fullscreen{position:absolute!important;top:0!important;left:0!important;right:0!important;bottom:0!important;float:none!important;z-index:2147483647!important;background:#000;width:100%!important;height:100%!important;margin:0!important}.fotorama--fullscreen .fotorama__nav,.fotorama--fullscreen .fotorama__stage{background:#000}.fotorama__wrap{-webkit-text-size-adjust:100%;position:relative;direction:ltr;z-index:0}.fotorama__wrap--rtl .fotorama__stage__frame{direction:rtl}.fotorama__nav,.fotorama__stage{overflow:hidden;position:relative;max-width:100%}.fotorama__wrap--pan-y{-ms-touch-action:pan-y}.fotorama__wrap .fotorama__pointer{cursor:pointer}.fotorama__wrap--slide .fotorama__stage__frame{opacity:1!important}.fotorama__stage__frame{overflow:hidden}.fotorama__stage__frame.fotorama__active{z-index:8}.fotorama__wrap--fade .fotorama__stage__frame{display:none}.fotorama__wrap--fade .fotorama__fade-front,.fotorama__wrap--fade .fotorama__fade-rear,.fotorama__wrap--fade .fotorama__stage__frame.fotorama__active{display:block;left:0;top:0}.fotorama__wrap--fade .fotorama__fade-front{z-index:8}.fotorama__wrap--fade .fotorama__fade-rear{z-index:7}.fotorama__wrap--fade .fotorama__fade-rear.fotorama__active{z-index:9}.fotorama__wrap--fade .fotorama__stage .fotorama__shadow{display:none}.fotorama__img{-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);opacity:0;border:none!important}.fotorama__error .fotorama__img,.fotorama__loaded .fotorama__img{-ms-filter:"alpha(Opacity=100)";filter:alpha(opacity=100);opacity:1}.fotorama--fullscreen .fotorama__loaded--full .fotorama__img,.fotorama__img--full{display:none}.fotorama--fullscreen .fotorama__loaded--full .fotorama__img--full{display:block}.fotorama__wrap--only-active .fotorama__nav,.fotorama__wrap--only-active .fotorama__stage{max-width:99999px!important}.fotorama__wrap--only-active .fotorama__stage__frame{visibility:hidden}.fotorama__wrap--only-active .fotorama__stage__frame.fotorama__active{visibility:visible}.fotorama__nav{font-size:0;line-height:0;text-align:center;display:none;white-space:nowrap;z-index:5}.fotorama__nav__shaft{position:relative;left:0;top:0;text-align:left}.fotorama__nav__frame{position:relative;cursor:pointer}.fotorama__nav--dots{display:block}.fotorama__nav--dots .fotorama__nav__frame{width:18px;height:30px}.fotorama__nav--dots .fotorama__nav__frame--thumb,.fotorama__nav--dots .fotorama__thumb-border{display:none}.fotorama__nav--thumbs{display:block}.fotorama__nav--thumbs .fotorama__nav__frame{padding-left:0!important}.fotorama__nav--thumbs .fotorama__nav__frame:last-child{padding-right:0!important}.fotorama__nav--thumbs .fotorama__nav__frame--dot{display:none}.fotorama__dot{display:block;width:4px;height:4px;position:relative;top:12px;left:6px;border-radius:6px;border:1px solid #7f7f7f}.fotorama__nav__frame:focus .fotorama__dot:after{padding:1px;top:-1px;left:-1px}.fotorama__nav__frame.fotorama__active .fotorama__dot{width:0;height:0;border-width:3px}.fotorama__nav__frame.fotorama__active .fotorama__dot:after{padding:3px;top:-3px;left:-3px}.fotorama__thumb{overflow:hidden;position:relative;width:100%;height:100%}.fotorama__nav__frame:focus .fotorama__thumb{z-index:2}.fotorama__thumb-border{position:absolute;z-index:9;top:0;left:0;border-style:solid;border-color:#00afea;background-image:linear-gradient(to bottom right,rgba(255,255,255,.25),rgba(64,64,64,.1))}.fotorama__caption{position:absolute;z-index:12;bottom:0;left:0;right:0;font-family:'Helvetica Neue',Arial,sans-serif;font-size:14px;line-height:1.5;color:#000}.fotorama__caption a{text-decoration:none;color:#000;border-bottom:1px solid;border-color:rgba(0,0,0,.5)}.fotorama__caption a:hover{color:#333;border-color:rgba(51,51,51,.5)}.fotorama__wrap--rtl .fotorama__caption{left:auto;right:0}.fotorama__wrap--no-captions .fotorama__caption,.fotorama__wrap--video .fotorama__caption{display:none}.fotorama__caption__wrap{background-color:#fff;background-color:rgba(255,255,255,.9);padding:5px 10px}@-webkit-keyframes spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.fotorama__wrap--css3 .fotorama__spinner{-webkit-animation:spinner 24s infinite linear;animation:spinner 24s infinite linear}.fotorama__wrap--css3 .fotorama__html,.fotorama__wrap--css3 .fotorama__stage .fotorama__img{transition-property:opacity;transition-timing-function:linear;transition-duration:.3s}.fotorama__wrap--video .fotorama__stage__frame--video .fotorama__html,.fotorama__wrap--video .fotorama__stage__frame--video .fotorama__img{-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);opacity:0}.fotorama__select{cursor:auto}.fotorama__video{top:32px;right:0;bottom:0;left:0;position:absolute;z-index:10}@-moz-document url-prefix(){.fotorama__active{box-shadow:0 0 0 transparent}}.fotorama__arr,.fotorama__fullscreen-icon,.fotorama__video-close,.fotorama__video-play{position:absolute;z-index:11;cursor:pointer}.fotorama__arr{position:absolute;width:32px;height:32px;top:50%;margin-top:-16px}.fotorama__arr--prev{left:2px;background-position:0 0}.fotorama__arr--next{right:2px;background-position:-32px 0}.fotorama__arr--disabled{pointer-events:none;cursor:default;*display:none;opacity:.1}.fotorama__fullscreen-icon{width:32px;height:32px;top:2px;right:2px;background-position:0 -32px;z-index:20}.fotorama__arr:focus,.fotorama__fullscreen-icon:focus{border-radius:50%}.fotorama--fullscreen .fotorama__fullscreen-icon{background-position:-32px -32px}.fotorama__video-play{width:96px;height:96px;left:50%;top:50%;margin-left:-48px;margin-top:-48px;background-position:0 -64px;opacity:0}.fotorama__wrap--css2 .fotorama__video-play,.fotorama__wrap--video .fotorama__stage .fotorama__video-play{display:none}.fotorama__error .fotorama__video-play,.fotorama__loaded .fotorama__video-play,.fotorama__nav__frame .fotorama__video-play{opacity:1;display:block}.fotorama__nav__frame .fotorama__video-play{width:32px;height:32px;margin-left:-16px;margin-top:-16px;background-position:-64px -32px}.fotorama__video-close{width:32px;height:32px;top:0;right:0;background-position:-64px 0;z-index:20;opacity:0}.fotorama__wrap--css2 .fotorama__video-close{display:none}.fotorama__wrap--css3 .fotorama__video-close{-webkit-transform:translate3d(32px,-32px,0);transform:translate3d(32px,-32px,0)}.fotorama__wrap--video .fotorama__video-close{display:block;opacity:1}.fotorama__wrap--css3.fotorama__wrap--video .fotorama__video-close{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__arr,.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon{opacity:0}.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__arr:focus,.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon:focus{opacity:1}.fotorama__wrap--video .fotorama__arr,.fotorama__wrap--video .fotorama__fullscreen-icon{opacity:0!important}.fotorama__wrap--css2.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__arr,.fotorama__wrap--css2.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon{display:none}.fotorama__wrap--css2.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__arr:focus,.fotorama__wrap--css2.fotorama__wrap--no-controls.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon:focus{display:block}.fotorama__wrap--css2.fotorama__wrap--video .fotorama__arr,.fotorama__wrap--css2.fotorama__wrap--video .fotorama__fullscreen-icon{display:none!important}.fotorama__wrap--css3.fotorama__wrap--no-controls.fotorama__wrap--slide.fotorama__wrap--toggle-arrows .fotorama__fullscreen-icon:not(:focus){-webkit-transform:translate3d(32px,-32px,0);transform:translate3d(32px,-32px,0)}.fotorama__wrap--css3.fotorama__wrap--no-controls.fotorama__wrap--slide.fotorama__wrap--toggle-arrows .fotorama__arr--prev:not(:focus){-webkit-transform:translate3d(-48px,0,0);transform:translate3d(-48px,0,0)}.fotorama__wrap--css3.fotorama__wrap--no-controls.fotorama__wrap--slide.fotorama__wrap--toggle-arrows .fotorama__arr--next:not(:focus){-webkit-transform:translate3d(48px,0,0);transform:translate3d(48px,0,0)}.fotorama__wrap--css3.fotorama__wrap--video .fotorama__fullscreen-icon{-webkit-transform:translate3d(32px,-32px,0)!important;transform:translate3d(32px,-32px,0)!important}.fotorama__wrap--css3.fotorama__wrap--video .fotorama__arr--prev{-webkit-transform:translate3d(-48px,0,0)!important;transform:translate3d(-48px,0,0)!important}.fotorama__wrap--css3.fotorama__wrap--video .fotorama__arr--next{-webkit-transform:translate3d(48px,0,0)!important;transform:translate3d(48px,0,0)!important}.fotorama__wrap--css3 .fotorama__arr:not(:focus),.fotorama__wrap--css3 .fotorama__fullscreen-icon:not(:focus),.fotorama__wrap--css3 .fotorama__video-close:not(:focus),.fotorama__wrap--css3 .fotorama__video-play:not(:focus){transition-property:-webkit-transform,opacity;transition-property:transform,opacity;transition-duration:.3s}.fotorama__nav:after,.fotorama__nav:before,.fotorama__stage:after,.fotorama__stage:before{content:"";display:block;position:absolute;text-decoration:none;top:0;bottom:0;width:10px;height:auto;z-index:10;pointer-events:none;background-repeat:no-repeat;background-size:1px 100%,5px 100%}.fotorama__nav:before,.fotorama__stage:before{background-image:linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),radial-gradient(farthest-side at 0 50%,rgba(0,0,0,.4),transparent);background-position:0 0,0 0;left:-10px}.fotorama__nav.fotorama__shadows--left:before,.fotorama__stage.fotorama__shadows--left:before{left:0}.fotorama__nav:after,.fotorama__stage:after{background-image:linear-gradient(transparent,rgba(0,0,0,.2) 25%,rgba(0,0,0,.3) 75%,transparent),radial-gradient(farthest-side at 100% 50%,rgba(0,0,0,.4),transparent);background-position:100% 0,100% 0;right:-10px}.fotorama__nav.fotorama__shadows--right:after,.fotorama__stage.fotorama__shadows--right:after{right:0}.fotorama--fullscreen .fotorama__nav:after,.fotorama--fullscreen .fotorama__nav:before,.fotorama--fullscreen .fotorama__stage:after,.fotorama--fullscreen .fotorama__stage:before,.fotorama__wrap--fade .fotorama__stage:after,.fotorama__wrap--fade .fotorama__stage:before,.fotorama__wrap--no-shadows .fotorama__nav:after,.fotorama__wrap--no-shadows .fotorama__nav:before,.fotorama__wrap--no-shadows .fotorama__stage:after,.fotorama__wrap--no-shadows .fotorama__stage:before{display:none} -------------------------------------------------------------------------------- /example/static/img/vendor/fotorama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/example/static/img/vendor/fotorama.png -------------------------------------------------------------------------------- /example/static/img/vendor/fotorama@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/example/static/img/vendor/fotorama@2x.png -------------------------------------------------------------------------------- /example/static/js/gallery.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Gallery = function(item) { 4 | if (!(this instanceof Gallery)) { 5 | return new Gallery('.gallery-list'); 6 | } 7 | this.gallery = $(item); 8 | this.getImageId($(item)); 9 | }; 10 | 11 | 12 | Gallery.prototype.getImageId = function() { 13 | $(document).on('click', 14 | '.gallery-list__item-link', function(event) { 15 | 16 | var popup = require('./popup.js').Popup(); 17 | popup.showPopup(); 18 | 19 | $('.fotorama').fotorama({ 20 | width: '100%', 21 | maxheight: 550, 22 | fit: 'contain', 23 | loop: true, 24 | nav: 'thumbs', 25 | thumbwidth: 80, 26 | thumbheight: 55, 27 | thumbfit: 'contain', 28 | data: IMAGES_LIST 29 | }); 30 | 31 | var fotorama = $('.fotorama').data('fotorama'), 32 | target = $(this).data('fotorama-image-id'); 33 | 34 | fotorama.show(target - 1); 35 | 36 | return event.preventDefault(); 37 | }); 38 | }; 39 | 40 | module.exports.Gallery = Gallery; -------------------------------------------------------------------------------- /example/static/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof $ === 'undefined') { require('jquery'); } 4 | 5 | var fotorama = require('./vendor/fotorama.js'), 6 | gallery = require('./gallery.js').Gallery(); -------------------------------------------------------------------------------- /example/static/js/popup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Popup = function(item) { 4 | if (!(this instanceof Popup)) { 5 | return new Popup('.popup'); 6 | } 7 | this.popup = $(item); 8 | this.popupContainer = $('.popup-inner__content-gallery'); 9 | this._bindEvents(); 10 | }; 11 | 12 | Popup.prototype._bindEvents = function() { 13 | $(document).on('click', 14 | '.popup-inner__content-link', this.hidePopup.bind(this) 15 | ); 16 | 17 | $(document).on('click', 18 | '.popup-background', this.hidePopup.bind(this) 19 | ); 20 | }; 21 | 22 | Popup.prototype.showPopup = function() { 23 | this.popup.css({ display: 'table' }); 24 | }; 25 | 26 | Popup.prototype.hidePopup = function(event) { 27 | this.popup.css({ display: 'none' }); 28 | if ($(event.target).closest('.popup-background').length > 0){ 29 | this.popup.css({ display: 'none' }); 30 | } 31 | }; 32 | 33 | module.exports.Popup = Popup; -------------------------------------------------------------------------------- /example/templates/gallery/index.jinja2: -------------------------------------------------------------------------------- 1 | {% extends 'pyramid_pages/base.jinja2' %} 2 | 3 | {% block content %} 4 | {{ super() }} 5 | 6 | 19 | 20 | 31 | 32 | 41 | {% endblock %} 42 | -------------------------------------------------------------------------------- /example/templates/news/index.jinja2: -------------------------------------------------------------------------------- 1 | {% extends 'pyramid_pages/base.jinja2' %} 2 | 3 | {% block content %} 4 | {{ page.date }} 5 | {{ super() }} 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /example/templates/pyramid_pages/base.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% include 'pyramid_pages/title.jinja2' %} 6 | {% include 'pyramid_pages/meta.jinja2' %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 25 |
26 |
27 | 33 |
34 |
35 | {% block breadcrumbs %} 36 | {% include "breadcrumbs.jinja2" %} 37 | {% endblock %} 38 | {% block content %} 39 |
{{ page.name }}
40 |
{{ page.description|safe or '' }}
41 | {% endblock %} 42 |
43 |
44 | 50 |
51 |
52 |
53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /example/templates/pyramid_pages/index.jinja2: -------------------------------------------------------------------------------- 1 | {% extends 'pyramid_pages/base.jinja2' %} 2 | 3 | {% block content %} 4 |
{{ page.name }}
5 |
{{ page.description|safe or '' }}
6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'), 4 | plugins = require('gulp-load-plugins')({ pattern: ['gulp-*', 'gulp.*'] }); 5 | 6 | var browserify = require('browserify'), 7 | browserSync = require('browser-sync'), 8 | mainBowerFiles = require('main-bower-files'), 9 | minimist = require('minimist'); 10 | 11 | var map = require('vinyl-map'), 12 | buffer = require('vinyl-buffer'), 13 | source = require('vinyl-source-stream'); 14 | 15 | var TARGET_CSS_FILE = '__pyramid_pages.css', 16 | TARGET_JS_FILE = '__pyramid_pages.js', 17 | BROWSERIFY_FILE = 'main.js'; 18 | 19 | var CSS_PATH = './pyramid_pages/static/css/', 20 | JS_PATH = './pyramid_pages/static/js/', 21 | IMG_PATH = './pyramid_pages/static/img/', 22 | FONT_PATH = './pyramid_pages/static/fonts/'; 23 | 24 | var TARGET_CSS_PATH = CSS_PATH + TARGET_CSS_FILE, 25 | TARGET_JS_PATH = JS_PATH + TARGET_JS_FILE; 26 | 27 | var CSS_FILES = [ 28 | './pyramid_pages/static/css/*.css', 29 | './pyramid_pages/static/css/**/*.css', 30 | '!pyramid_pages/static/css/no-js.css', 31 | '!pyramid_pages/static/css/' + TARGET_CSS_FILE 32 | ]; 33 | 34 | var JS_FILES = [ 35 | './pyramid_pages/static/js/*.js', 36 | './pyramid_pages/static/js/**/*.js', 37 | '!pyramid_pages/static/js/' + TARGET_JS_FILE, 38 | '!pyramid_pages/static/js/vendor/*.js' 39 | ]; 40 | 41 | var TEMPLATES_FILES = [ 42 | './pyramid_pages/**/*.jinja2' 43 | ]; 44 | 45 | var EXAMPLE_TARGET_CSS_FILE = '__main.css', 46 | EXAMPLE_CSS_PATH = './example/static/css/', 47 | EXAMPLE_TARGET_CSS_PATH = EXAMPLE_CSS_PATH + EXAMPLE_TARGET_CSS_FILE; 48 | 49 | var EXAMPLE_TARGET_JS_FILE = '__main.js', 50 | EXAMPLE_JS_PATH = './example/static/js/', 51 | EXAMPLE_TARGET_JS_PATH = EXAMPLE_JS_PATH + EXAMPLE_TARGET_JS_FILE; 52 | 53 | var EXAMPLE_CSS_FILES = [ 54 | './example/static/css/*.css', 55 | './example/static/css/**/*.css', 56 | '!example/static/css/' + EXAMPLE_TARGET_CSS_FILE 57 | ]; 58 | 59 | var EXAMPLE_JS_FILES = [ 60 | './example/static/js/*.js', 61 | './example/static/js/**/*.js', 62 | '!example/static/js/' + EXAMPLE_TARGET_JS_FILE, 63 | '!example/static/js/vendor/*.js' 64 | ]; 65 | 66 | var EXAMPLE_TEMPLATES_FILES = [ 67 | './example/**/*.jinja2' 68 | ]; 69 | 70 | var knownOptions = { 71 | string: 'env', 72 | default: { env: process.env.NODE_ENV || 'development' } 73 | }; 74 | 75 | var options = minimist(process.argv.slice(2), knownOptions); 76 | 77 | var processors = [ 78 | require('postcss-nested'), 79 | require('autoprefixer-core')({ 80 | browsers: [ 81 | 'Firefox >= 3', 82 | 'Explorer >= 6', 83 | 'Opera >= 9', 84 | 'Chrome >= 15', 85 | 'Safari >= 4', 86 | '> 1%' 87 | ], 88 | cascade: false 89 | }), 90 | require('postcss-css-variables'), 91 | require('postcss-opacity') 92 | ]; 93 | 94 | 95 | gulp.task('browser-sync', function() { 96 | browserSync({ 97 | proxy: '127.0.0.1:6543', 98 | logLevel: 'info', 99 | open: false 100 | }); 101 | }); 102 | 103 | 104 | gulp.task('bower-js', function() { 105 | return gulp.src(mainBowerFiles({ filter: (/.*\.(js|map)$/i) }), 106 | { base: 'bower_components' }) 107 | .pipe(plugins.rename(function (path) { 108 | path.dirname = path.dirname.slice(0, path.dirname.indexOf('/') + 1); 109 | })) 110 | .pipe(gulp.dest(JS_PATH + 'vendor/')) 111 | .pipe(map(function(code, filename) { 112 | plugins.util.log('Bower JS ' + 113 | plugins.util.colors.green(filename)); 114 | })); 115 | }); 116 | 117 | 118 | gulp.task('bower-css', function() { 119 | return gulp.src(mainBowerFiles({ filter: (/.*\.(css)$/i) }), 120 | { base: 'bower_components' }) 121 | .pipe(plugins.rename(function (path) { 122 | path.dirname = path.dirname.slice(0, path.dirname.indexOf('/') + 1); 123 | })) 124 | .pipe(gulp.dest(CSS_PATH + 'vendor/')) 125 | .pipe(map(function(code, filename) { 126 | plugins.util.log('Bower CSS ' + 127 | plugins.util.colors.green(filename)); 128 | })); 129 | }); 130 | 131 | 132 | gulp.task('bower-img', function() { 133 | return gulp.src(mainBowerFiles({ filter: (/.*\.(png|jpg|gif)$/i) }), 134 | { base: 'bower_components' }) 135 | .pipe(plugins.rename(function (path) { 136 | path.dirname = path.dirname.slice(0, path.dirname.indexOf('/') + 1); 137 | })) 138 | .pipe(gulp.dest(IMG_PATH)) 139 | .pipe(map(function(code, filename) { 140 | plugins.util.log('Bower Images ' + 141 | plugins.util.colors.green(filename)); 142 | })); 143 | }); 144 | 145 | 146 | gulp.task('bower-font', function() { 147 | return gulp.src(mainBowerFiles( 148 | { filter: (/.*\.(eot|otf|svg|ttf|woff|woff2)$/i) }), { base: 'bower_components' }) 149 | .pipe(plugins.rename(function (path) { 150 | path.dirname = path.dirname.slice(0, path.dirname.indexOf('/') + 1); 151 | })) 152 | .pipe(gulp.dest(FONT_PATH)) 153 | .pipe(map(function(code, filename) { 154 | plugins.util.log('Bower Fonts ' + 155 | plugins.util.colors.green(filename)); 156 | })); 157 | }); 158 | 159 | 160 | gulp.task('browserify', function() { 161 | 162 | var b = browserify({ 163 | entries: JS_PATH + BROWSERIFY_FILE, 164 | debug: true 165 | }); 166 | return b.bundle() 167 | .pipe(source(TARGET_JS_PATH)) 168 | .pipe(buffer()) 169 | .pipe(plugins.sourcemaps.init({loadMaps: true})) 170 | .pipe(plugins.if(options.env === 'production', 171 | plugins.uglify())) 172 | .pipe(plugins.if(options.env === 'development', 173 | plugins.sourcemaps.write('./'))) 174 | .pipe(gulp.dest('./')) 175 | .pipe(map(function(code, filename) { 176 | plugins.util.log('Browserify ' + 177 | plugins.util.colors.green(filename)); 178 | })) 179 | .pipe(browserSync.reload({ stream:true })); 180 | }); 181 | 182 | 183 | gulp.task('css', function() { 184 | return gulp.src(CSS_FILES) 185 | .pipe(plugins.newer(TARGET_CSS_PATH)) 186 | .pipe(plugins.sourcemaps.init()) 187 | .pipe(plugins.postcss(processors)) 188 | .on('error', function(err) { 189 | plugins.util.log(plugins.util.colors.red('PostCSS Error'), 190 | plugins.util.colors.yellow(err.message)); 191 | }) 192 | .pipe(plugins.cssBase64({ 193 | extensions: ['png', 'jpg', 'gif'], 194 | maxWeightResource: 100, 195 | })) 196 | .on('error', function(err) { 197 | plugins.util.log(plugins.util.colors.red('Base64 Error'), 198 | plugins.util.colors.yellow(err.message)); 199 | }) 200 | .pipe(plugins.modifyCssUrls({ 201 | modify: function (url, filePath) { 202 | if(filePath.indexOf('vendor') > -1) { 203 | if(url.indexOf('./font') > -1) { 204 | url = './../' + url.substring(url.indexOf('font')); 205 | } else if(url.indexOf('./img') > -1) { 206 | url = './../img/vendor/' + url.substring(url.indexOf('img')); 207 | } 208 | if(url.match(/.*\.(png|jpg|gif)$/i)) { 209 | url = './../img/vendor/' + url.substring(url.indexOf('/')); 210 | } 211 | return url; 212 | } else { 213 | return url; 214 | } 215 | } 216 | })) 217 | .pipe(plugins.concat(TARGET_CSS_FILE)) 218 | .pipe(plugins.if(options.env === 'development', 219 | plugins.sourcemaps.write('.'))) 220 | .pipe(plugins.if(options.env === 'production', 221 | plugins.minifyCss({ keepSpecialComments: 0 }))) 222 | .pipe(gulp.dest(CSS_PATH)) 223 | .on('error', plugins.util.log) 224 | .pipe(plugins.filter('*.css')) 225 | .pipe(map(function(code, filename) { 226 | plugins.util.log('CSS ' + 227 | plugins.util.colors.green(filename)); 228 | })) 229 | .pipe(browserSync.reload({ stream:true })); 230 | }); 231 | 232 | 233 | gulp.task('html', function() { 234 | return gulp.src(TEMPLATES_FILES) 235 | .pipe(browserSync.reload({ stream:true })); 236 | }); 237 | 238 | 239 | gulp.task('dev-browserify', function() { 240 | 241 | var b = browserify({ 242 | entries: EXAMPLE_JS_PATH + BROWSERIFY_FILE, 243 | debug: true 244 | }); 245 | 246 | return b.bundle() 247 | .pipe(source(EXAMPLE_TARGET_JS_PATH)) 248 | .pipe(buffer()) 249 | .pipe(plugins.sourcemaps.init({loadMaps: true})) 250 | .pipe(plugins.if(options.env === 'production', 251 | plugins.uglify())) 252 | .pipe(plugins.if(options.env === 'development', 253 | plugins.sourcemaps.write('./'))) 254 | .pipe(gulp.dest('./')) 255 | .pipe(map(function(code, filename) { 256 | plugins.util.log('Browserify ' + 257 | plugins.util.colors.green(filename)); 258 | })) 259 | .pipe(browserSync.reload({ stream:true })); 260 | }); 261 | 262 | 263 | gulp.task('dev-css', function() { 264 | return gulp.src(EXAMPLE_CSS_FILES) 265 | .pipe(plugins.newer(EXAMPLE_TARGET_CSS_PATH)) 266 | .pipe(plugins.sourcemaps.init()) 267 | .pipe(plugins.postcss(processors)) 268 | .on('error', function(err) { 269 | plugins.util.log(plugins.util.colors.red('PostCSS Error'), 270 | plugins.util.colors.yellow(err.message)); 271 | }) 272 | .pipe(plugins.cssBase64({ 273 | extensions: ['png', 'jpg', 'gif'], 274 | maxWeightResource: 100, 275 | })) 276 | .on('error', function(err) { 277 | plugins.util.log(plugins.util.colors.red('Base64 Error'), 278 | plugins.util.colors.yellow(err.message)); 279 | }) 280 | .pipe(plugins.modifyCssUrls({ 281 | modify: function (url, filePath) { 282 | if(filePath.indexOf('vendor') > -1) { 283 | if(url.indexOf('./font') > -1) { 284 | url = './../' + url.substring(url.indexOf('font')); 285 | } else if(url.indexOf('./img') > -1) { 286 | url = './../img/vendor/' + url.substring(url.indexOf('img')); 287 | } 288 | if(url.match(/.*\.(png|jpg|gif)$/i)) { 289 | url = './../img/vendor/' + url.substring(url.indexOf('/')); 290 | } 291 | return url; 292 | } else { 293 | return url; 294 | } 295 | } 296 | })) 297 | .pipe(plugins.concat(EXAMPLE_TARGET_CSS_FILE)) 298 | .pipe(plugins.if(options.env === 'development', 299 | plugins.sourcemaps.write('.'))) 300 | .pipe(plugins.if(options.env === 'production', 301 | plugins.minifyCss({ keepSpecialComments: 0 }))) 302 | .pipe(gulp.dest(EXAMPLE_CSS_PATH)) 303 | .on('error', plugins.util.log) 304 | .pipe(plugins.filter('*.css')) 305 | .pipe(map(function(code, filename) { 306 | plugins.util.log('CSS ' + 307 | plugins.util.colors.green(filename)); 308 | })) 309 | .pipe(browserSync.reload({ stream:true })); 310 | }); 311 | 312 | 313 | gulp.task('dev-html', function() { 314 | return gulp.src(EXAMPLE_TEMPLATES_FILES) 315 | .pipe(browserSync.reload({ stream:true })); 316 | }); 317 | 318 | gulp.task('watch', function() { 319 | plugins.watch(CSS_FILES,{ verbose: true }, 320 | plugins.batch(function (cb) { 321 | gulp.start('css'); 322 | cb(); 323 | })); 324 | 325 | plugins.watch(JS_FILES, { verbose: true }, 326 | plugins.batch(function (cb) { 327 | gulp.start('browserify'); 328 | cb(); 329 | })); 330 | 331 | plugins.watch(TEMPLATES_FILES, { verbose: true }, 332 | plugins.batch(function (cb) { 333 | gulp.start('html'); 334 | cb(); 335 | })); 336 | 337 | if(options.env === 'development') { 338 | plugins.watch(EXAMPLE_CSS_FILES,{ verbose: true }, 339 | plugins.batch(function (cb) { 340 | gulp.start('dev-css'); 341 | cb(); 342 | })); 343 | 344 | plugins.watch(EXAMPLE_JS_FILES,{ verbose: true }, 345 | plugins.batch(function (cb) { 346 | gulp.start('dev-browserify'); 347 | cb(); 348 | })); 349 | 350 | plugins.watch(EXAMPLE_TEMPLATES_FILES, { verbose: true }, 351 | plugins.batch(function (cb) { 352 | gulp.start('dev-html'); 353 | cb(); 354 | })); 355 | } 356 | }); 357 | 358 | 359 | gulp.task('default', ['browser-sync', 'watch']); 360 | gulp.task('bower', ['bower-js', 'bower-css', 'bower-img', 'bower-font']); 361 | gulp.task('build', ['bower', 'css', 'browserify']); 362 | gulp.task('dev-build', ['dev-css', 'dev-browserify']); 363 | gulp.task('build-all', ['bower', 'build', 'dev-build']); 364 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pyramid_pages", 3 | "title": "Pyramid Pages", 4 | "description": "pyramid_pages provides a collections of pages to your Pyramid application. This is very similar to django.contrib.flatpages but with a tree structure and traversal algorithm in URL dispath.", 5 | "version": "0.0.1", 6 | "homepage": "https://github.com/uralbash/pyramid_pages", 7 | "author": { 8 | "name": "uralbash", 9 | "email": "sacrud@uralbash.ru" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:uralbash/pyramid_pages.git" 14 | }, 15 | "bugs": "https://github.com/uralbash/pyramid_pages/issues", 16 | "licenses": [ 17 | { 18 | "type": "MIT", 19 | "url": "http://opensource.org/licenses/MIT" 20 | } 21 | ], 22 | "engines": { 23 | "node": ">= 0.8.0" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer-core": "^6.0.1", 27 | "browser-sync": "^2.7.12", 28 | "browserify": "^11.1.0", 29 | "browserify-shim": "^3.8.9", 30 | "gulp": "^3.9.0", 31 | "gulp-batch": "~1.0.5", 32 | "gulp-concat": "^2.5.2", 33 | "gulp-css-base64": "^1.3.2", 34 | "gulp-filter": "^3.0.1", 35 | "gulp-if": "^1.2.5", 36 | "gulp-load-plugins": "^1.0.0-rc", 37 | "gulp-minify-css": "^1.1.6", 38 | "gulp-modify-css-urls": "^0.2.0", 39 | "gulp-newer": "^0.5.1", 40 | "gulp-postcss": "^6.0.0", 41 | "gulp-rename": "^1.2.2", 42 | "gulp-sourcemaps": "^1.5.2", 43 | "gulp-uglify": "^1.2.0", 44 | "gulp-util": "^3.0.5", 45 | "gulp-watch": "^4.2.4", 46 | "main-bower-files": "^2.8.0", 47 | "minimist": "^1.1.1", 48 | "postcss-css-variables": "^0.5.0", 49 | "postcss-nested": "^1.0.0", 50 | "postcss-opacity": "^3.0.0", 51 | "vinyl-buffer": "^1.0.0", 52 | "vinyl-map": "^1.0.1", 53 | "vinyl-source-stream": "^1.1.0" 54 | }, 55 | "browser": { 56 | "jquery": "./pyramid_pages/static/js/vendor/jquery.min.js" 57 | }, 58 | "browserify": { 59 | "transform": [ 60 | "browserify-shim" 61 | ] 62 | }, 63 | "browserify-shim": { 64 | "jquery": "$" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /pyramid_pages/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | CONFIG_MODELS = 'pyramid_pages.models' 9 | CONFIG_DBSESSION = 'pyramid_pages.dbsession' 10 | 11 | 12 | def includeme(config): 13 | config.include('.assets') 14 | config.include('.routes') 15 | -------------------------------------------------------------------------------- /pyramid_pages/assets.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Assets 11 | """ 12 | 13 | 14 | def includeme(config): 15 | config.include('pyramid_jinja2') 16 | config.add_jinja2_extension('jinja2.ext.with_') 17 | config.add_jinja2_search_path("pyramid_pages:templates") 18 | config.add_static_view('pyramid_pages_static', 'pyramid_pages:static') 19 | -------------------------------------------------------------------------------- /pyramid_pages/models.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Models for page. 11 | """ 12 | from sqlalchemy import ( 13 | Column, 14 | String, 15 | Boolean, 16 | Integer, 17 | ForeignKey, 18 | UnicodeText 19 | ) 20 | from sacrud.common import ClassProperty 21 | from saexttype import SlugType, ChoiceType 22 | from sqlalchemy.orm import foreign, relationship 23 | from sqlalchemy_mptt import BaseNestedSets 24 | from sqlalchemy.ext.declarative import declared_attr 25 | 26 | REDIRECT_CHOICES = ( 27 | ('200', 'OK (200)'), 28 | ('301', 'Moved Permanently (301)'), 29 | ('302', 'Moved Temporarily (302)'), 30 | ) 31 | 32 | 33 | class PageMixin(object): 34 | 35 | name = Column(String, nullable=False) 36 | visible = Column(Boolean) 37 | in_menu = Column(Boolean) 38 | slug = Column(SlugType('name', False), unique=True, nullable=False) 39 | description = Column(UnicodeText) 40 | 41 | def __repr__(self): 42 | return self.name or '' 43 | 44 | def __json__(self, request): 45 | return self.id 46 | 47 | 48 | class MpttPageMixin(BaseNestedSets, PageMixin): 49 | pass 50 | 51 | 52 | class FlatPageMixin(PageMixin): 53 | pass 54 | 55 | 56 | class RecursionPageMixin(PageMixin): 57 | """ 58 | Model with single parent_id field 59 | """ 60 | 61 | 62 | class SeoMixin(object): 63 | 64 | seo_title = Column(String) 65 | seo_keywords = Column(String) 66 | seo_description = Column(String) 67 | seo_metatags = Column(UnicodeText) 68 | 69 | 70 | class RedirectMixin(object): 71 | 72 | redirect_url = Column(String) 73 | redirect_type = Column(ChoiceType(choices=REDIRECT_CHOICES)) 74 | 75 | @declared_attr 76 | def redirect_page(cls): 77 | return Column( 78 | Integer, 79 | ForeignKey( 80 | '{}.{}'.format(cls.__tablename__, cls.get_pk_name()), 81 | ondelete='CASCADE') 82 | ) 83 | 84 | @declared_attr 85 | def redirect(cls): 86 | pk = getattr(cls, cls.get_pk_name()) 87 | return relationship( 88 | cls, foreign_keys=[cls.redirect_page], 89 | remote_side='{}.{}'.format(cls.__name__, cls.get_pk_name()), 90 | primaryjoin=lambda: foreign(cls.redirect_page) == pk, 91 | ) 92 | 93 | 94 | class SacrudOptions(object): 95 | 96 | @ClassProperty 97 | def sacrud_detail_col(cls): 98 | options = [ 99 | ('', [cls.name, cls.slug, cls.visible, cls.in_menu, 100 | cls.description, getattr(cls, 'parent', None)]) 101 | ] 102 | if all(hasattr(cls, name) 103 | for name in ('redirect_url', 'redirect', 'redirect_type')): 104 | options.append( 105 | ('Redirection', [cls.redirect_url, cls.redirect, 106 | cls.redirect_type]) 107 | ) 108 | if all(hasattr(cls, name) 109 | for name in ('seo_title', 'seo_keywords', 'seo_description', 110 | 'seo_metatags')): 111 | options.append( 112 | ('SEO', [cls.seo_title, cls.seo_keywords, cls.seo_description, 113 | cls.seo_metatags]) 114 | ) 115 | return options 116 | 117 | 118 | class BaseSacrudMpttPage( 119 | SacrudOptions, 120 | SeoMixin, 121 | RedirectMixin, 122 | MpttPageMixin 123 | ): 124 | """ 125 | Base mptt page class for :mod:`pyramid_sacrud`. 126 | """ 127 | 128 | 129 | class BaseSacrudFlatPage( 130 | SacrudOptions, 131 | SeoMixin, 132 | RedirectMixin, 133 | FlatPageMixin 134 | ): 135 | """ 136 | Base flat page class for :mod:`pyramid_sacrud`. 137 | """ 138 | -------------------------------------------------------------------------------- /pyramid_pages/resources.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Base pages resources. 11 | """ 12 | import re 13 | 14 | from pyramid.threadlocal import get_current_registry 15 | 16 | from . import CONFIG_MODELS, CONFIG_DBSESSION 17 | from .views import PageView 18 | 19 | 20 | class BasePageResource(object): 21 | 22 | """ Base resource tree class for pages. 23 | 24 | :view: view of resource. 25 | :attr: The view machinery defaults to using the ``__call__`` method of the 26 | view callable (or the function itself, if the view callable is a 27 | function) to obtain a response. The attr value allows you to vary the 28 | method attribute used to obtain the response. For example, if your view 29 | was a class, and the class has a method named index and you wanted to 30 | use this method instead of the class's ``__call__`` method to return 31 | the response, you'd say attr="index" in the view configuration for the 32 | view. This is most useful when the view definition is a class. 33 | :template: template for view of resource. 34 | """ 35 | 36 | view = PageView 37 | attr = 'page_with_redirect' 38 | template = 'pyramid_pages/index.jinja2' 39 | 40 | def __init__(self, 41 | node, prefix=None, request=None, parent=None, 42 | *args, **kwargs): 43 | """ Make resource of node for traversal URL dispatch. 44 | 45 | :node: instance of page 46 | :pages_config: config for pyramid_pages from your app 47 | :resources: list of all resources in config 48 | :prefix: URL prefix for current node 49 | :parent: if exist it assignet to ``__parent__`` attribute 50 | """ 51 | self.settings = get_current_registry().settings 52 | self.node = node 53 | self.dbsession = self.settings[CONFIG_DBSESSION] 54 | self.pages_config = self.settings[CONFIG_MODELS] 55 | self.resources = resources_of_config(self.pages_config) 56 | self.prefix = prefix or self.get_prefix() 57 | self.parent = parent 58 | 59 | def get_prefix(self): 60 | """ Each resource defined in config for pages as dict. This method 61 | returns key from config where located current resource. 62 | """ 63 | for key, value in self.pages_config.items(): 64 | if not hasattr(value, '__iter__'): 65 | value = (value, ) 66 | for item in value: 67 | if type(self.node) == item\ 68 | or type(self.node) == getattr(item, 'model', None): 69 | return key 70 | 71 | def resource_of_node(self, node, parent=None): 72 | return resource_of_node(self.resources, node)( 73 | node, parent=parent or self.parent) 74 | 75 | @property 76 | def __name__(self): 77 | """ Returns ``slug`` attribute of node or ``prefix``. 78 | """ 79 | if self.node and not self.node.slug == '/': 80 | return self.node.slug 81 | elif self.node and self.node.slug == '/': 82 | return '' 83 | elif self.prefix: 84 | return self.prefix 85 | return None 86 | 87 | @property 88 | def name(self): 89 | return self.node.name 90 | 91 | @property 92 | def __parent__(self): 93 | if self.parent: 94 | return self.parent 95 | elif hasattr(self.node, 'parent'): 96 | parent = self.node.parent 97 | return resource_of_node(self.resources, parent)(parent) 98 | elif self.node: 99 | return self.__class__(None) 100 | return None 101 | 102 | def __getitem__(self, name): 103 | for child in self.children: 104 | if child.slug == name: 105 | return self.resource_of_node(child.node) 106 | 107 | @property 108 | def slug(self): 109 | return getattr(self.node, 'slug', None) 110 | 111 | @property 112 | def children(self): 113 | for child in self.node.children: 114 | yield self.resource_of_node(child) 115 | 116 | @property 117 | def children_qty(self): 118 | return len([child for child in self.children 119 | if child.node.visible and child.node.in_menu]) 120 | 121 | def __resource_url__(self, request, info): 122 | """ Some hook for prefix and duplication root slash. 123 | """ 124 | separator = '/{}'.format(self.prefix) if self.prefix else '' 125 | # XXX: I feel a dissonance here 126 | info['virtual_path'] = re.sub('/+', '/', info['virtual_path']) 127 | return info['app_url'] + separator + info['virtual_path'] 128 | 129 | def __repr__(self): 130 | return self.name 131 | 132 | 133 | def resource_of_node(resources, node): 134 | """ Returns resource of node. 135 | """ 136 | for resource in resources: 137 | model = getattr(resource, 'model', None) 138 | if type(node) == model: 139 | return resource 140 | return BasePageResource 141 | 142 | 143 | def resources_of_config(config): 144 | """ Returns all resources and models from config. 145 | """ 146 | return set( # unique values 147 | sum([ # join lists to flat list 148 | list(value) # if value is iter (ex: list of resources) 149 | if hasattr(value, '__iter__') 150 | else [value, ] # if value is not iter (ex: model or resource) 151 | for value in config.values() 152 | ], []) 153 | ) 154 | 155 | 156 | def models_of_config(config): 157 | """ Return list of models from all resources in config. 158 | """ 159 | resources = resources_of_config(config) 160 | models = [] 161 | for resource in resources: 162 | if not hasattr(resource, '__table__') and hasattr(resource, 'model'): 163 | models.append(resource.model) 164 | else: 165 | models.append(resource) 166 | return models 167 | -------------------------------------------------------------------------------- /pyramid_pages/routes.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Routes for pyramid_pages 11 | """ 12 | from sqlalchemy import or_ 13 | from pyramid.events import BeforeRender 14 | from pyramid.location import lineage 15 | from pyramid.httpexceptions import HTTPNotFound 16 | 17 | from . import CONFIG_MODELS, CONFIG_DBSESSION 18 | from .security import HOME_PAGE, PREFIX_PAGE 19 | from .resources import ( 20 | BasePageResource, 21 | models_of_config, 22 | resource_of_node, 23 | resources_of_config 24 | ) 25 | 26 | 27 | def add_globals(event): 28 | event['lineage'] = lineage 29 | 30 | 31 | def page_factory(request): 32 | """ Page factory. 33 | 34 | Config models example: 35 | 36 | .. code-block:: python 37 | 38 | models = { 39 | '': [WebPage, CatalogResource], 40 | 'catalogue': CatalogResource, 41 | 'news': NewsResource, 42 | } 43 | """ 44 | prefix = request.matchdict['prefix'] # /{prefix}/page1/page2/page3... 45 | settings = request.registry.settings 46 | dbsession = settings[CONFIG_DBSESSION] 47 | config = settings[CONFIG_MODELS] 48 | 49 | if prefix not in config: 50 | # prepend {prefix} to *traverse 51 | request.matchdict['traverse'] =\ 52 | tuple([prefix] + list(request.matchdict['traverse'])) 53 | prefix = None 54 | 55 | # Get all resources and models from config with the same prefix. 56 | resources = config.get( 57 | prefix, config.get( # 1. get resources with prefix same as URL prefix 58 | '', config.get( # 2. if not, then try to get empty prefix 59 | '/', None))) # 3. else try to get prefix '/' otherwise None 60 | 61 | if not hasattr(resources, '__iter__'): 62 | resources = (resources, ) 63 | 64 | tree = {} 65 | 66 | if not resources: 67 | return tree 68 | 69 | # Add top level nodes of resources in the tree 70 | for resource in resources: 71 | table = None 72 | if not hasattr(resource, '__table__')\ 73 | and hasattr(resource, 'model'): 74 | table = resource.model 75 | else: 76 | table = resource 77 | 78 | if not hasattr(table, 'slug'): 79 | continue 80 | 81 | nodes = dbsession.query(table) 82 | if hasattr(table, 'parent_id'): 83 | nodes = nodes.filter(or_( 84 | table.parent_id == None, # noqa 85 | table.parent.has(table.slug == '/') 86 | )) 87 | for node in nodes: 88 | if not node.slug: 89 | continue 90 | resource = resource_of_node(resources, node) 91 | tree[node.slug] = resource(node, prefix=prefix) 92 | return tree 93 | 94 | 95 | def home_page_factory(request): 96 | settings = request.registry.settings 97 | dbsession = settings[CONFIG_DBSESSION] 98 | config = settings[CONFIG_MODELS] 99 | models = models_of_config(config) 100 | resources = resources_of_config(config) 101 | for table in models: 102 | if not hasattr(table, 'slug'): 103 | continue 104 | node = dbsession.query(table).filter(table.slug == '/').first() 105 | if node: 106 | return resource_of_node(resources, node)(node) 107 | raise HTTPNotFound 108 | 109 | 110 | def register_views(*args): 111 | """ Registration view for each resource from config. 112 | """ 113 | config = args[0] 114 | settings = config.get_settings() 115 | pages_config = settings[CONFIG_MODELS] 116 | resources = resources_of_config(pages_config) 117 | for resource in resources: 118 | if hasattr(resource, '__table__')\ 119 | and not hasattr(resource, 'model'): 120 | continue 121 | resource.model.pyramid_pages_template = resource.template 122 | config.add_view(resource.view, 123 | attr=resource.attr, 124 | route_name=PREFIX_PAGE, 125 | renderer=resource.template, 126 | context=resource, 127 | permission=PREFIX_PAGE) 128 | 129 | 130 | def includeme(config): 131 | config.add_subscriber(add_globals, BeforeRender) 132 | 133 | # Home page factory 134 | config.add_route(HOME_PAGE, '/', factory=home_page_factory) 135 | config.add_view(BasePageResource.view, 136 | attr=BasePageResource.attr, 137 | route_name=HOME_PAGE, 138 | renderer=BasePageResource.template, 139 | context=BasePageResource, 140 | permission=HOME_PAGE) 141 | 142 | # Default page factory 143 | config.add_route(PREFIX_PAGE, '/{prefix}*traverse', factory=page_factory) 144 | config.add_view(BasePageResource.view, 145 | attr=BasePageResource.attr, 146 | route_name=PREFIX_PAGE, 147 | renderer=BasePageResource.template, 148 | context=BasePageResource, 149 | permission=PREFIX_PAGE) 150 | 151 | import pkg_resources 152 | pyramid_version = pkg_resources.get_distribution("pyramid").parsed_version 153 | # if pyramid_version >= pkg_resources.SetuptoolsVersion('1.6a1'): 154 | # Allow you to change settings after including this function. This 155 | # fuature works only in version 1.6 or above. 156 | config.action('pyramid_pages_routes', register_views, args=(config, )) 157 | # else: 158 | # config.include(register_views) 159 | -------------------------------------------------------------------------------- /pyramid_pages/security.py: -------------------------------------------------------------------------------- 1 | HOME_PAGE = 'pyramid_pages_home_page_view' 2 | PREFIX_PAGE = 'pyramid_pages_prefix_page_view' 3 | -------------------------------------------------------------------------------- /pyramid_pages/static/css/content/content.css: -------------------------------------------------------------------------------- 1 | .content h1 2 | { 3 | font: 1.2em Arial, Helvetica, sans-serif; 4 | color: #444; 5 | padding: 0 0 0.3em 0; 6 | } 7 | .content h2 8 | { 9 | font: 1.1em Arial, Helvetica, sans-serif; 10 | color: #444; 11 | padding: 0 0 0.3em 0; 12 | } 13 | .content h3 14 | { 15 | font: 1em Arial, Helvetica, sans-serif; 16 | color: #444; 17 | padding: 0 0 0.3em 0; 18 | } 19 | .content h5 20 | { 21 | width: 100%; 22 | font: 1em Arial, Helvetica, sans-serif; 23 | color: #444; 24 | padding: 0.3em 0 0.2em 0.5em; 25 | margin: 0 0 0.5em 0; 26 | background: #EFEFEF; 27 | position: relative; 28 | left: -0.5em; 29 | } 30 | .content a 31 | { 32 | color: #336699; 33 | } 34 | .content a:hover 35 | { 36 | color: #EF7F1B; 37 | } 38 | .content p 39 | { 40 | font: 0.8em Arial, Helvetica, sans-serif; 41 | color: #444; 42 | padding: 0 0 1.3em 0; 43 | line-height: 1.4em; 44 | } 45 | .content em 46 | { 47 | font-style: italic; 48 | } 49 | .content strong 50 | { 51 | font-weight: bold; 52 | } 53 | .content i 54 | { 55 | font-style: italic; 56 | } 57 | .content ol 58 | { 59 | counter-reset: list1; 60 | padding: 0 0 1em 1em; 61 | } 62 | .content ol li 63 | { 64 | position: relative; 65 | display: block; 66 | font: 0.8em Arial; 67 | color: #444; 68 | padding: 0 0 1em 1.8em; 69 | line-height: 1.4em; 70 | /*list-style-type:expression(function(t){ t.runtimeStyle.listStyleType = 'none'; t.insertAdjacentHTML('afterBegin','(' + (++t.parentNode.IEcounter || (t.parentNode.IEcounter = 1)) + ') '); }(this));*/ 71 | } 72 | .content ol li:before 73 | { 74 | content: counter(list1)'.'; 75 | counter-increment: list1; 76 | position: absolute; 77 | left: 0; 78 | top: 0; 79 | } 80 | .content ol li .content-list-item-number 81 | { 82 | color: #555; 83 | } 84 | .content ul 85 | { 86 | padding: 0 0 1em 2em; 87 | list-style-type: none; 88 | text-indent: -1.8em; 89 | } 90 | .content ul li 91 | { 92 | 93 | position: relative; 94 | display: block; 95 | font: 0.8em Arial; 96 | color: #444; 97 | padding: 0 0 1em 0; 98 | } 99 | .content ul li:before 100 | { 101 | content: "\2014\a0"; 102 | padding: 0 0.5em 0 0; 103 | } 104 | .content ul li::before 105 | { 106 | content: "\2014\a0"; 107 | padding: 0 0.5em 0 0; 108 | } 109 | 110 | .content table 111 | { 112 | border-collapse: collapse; 113 | margin: 0 0 1em 0; 114 | } 115 | .content tr 116 | { 117 | color: #444; 118 | } 119 | .content tr:hover 120 | { 121 | background: #efefef; 122 | } 123 | .content th 124 | { 125 | font: bold 0.7em Arial; 126 | color: #444; 127 | text-align: left; 128 | border: 1px solid #ccc; 129 | padding: 0.5em 1em; 130 | background: #eee; 131 | } 132 | .content th p 133 | { 134 | font: bold 1em Arial; 135 | padding: 0; 136 | line-height: 1em; 137 | } 138 | .content td 139 | { 140 | font: 0.8em Arial; 141 | color: #444; 142 | line-height: 1.4em; 143 | padding: 0.5em 1em; 144 | border: 1px solid #ccc; 145 | vertical-align: top; 146 | } 147 | .content td p 148 | { 149 | font: 1em Arial; 150 | color: #444; 151 | line-height: 1.4em; 152 | padding: 0; 153 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/gallery/gallery-list.css: -------------------------------------------------------------------------------- 1 | .gallery-list 2 | { 3 | display: table; 4 | } 5 | .gallery-list__item 6 | { 7 | min-height: 100px; 8 | display: inline-block; 9 | vertical-align: top; 10 | zoom: 1; 11 | *display: inline; 12 | _height: 100px; 13 | margin: 0 0 2em 0; 14 | } 15 | .gallery-list__item-link 16 | { 17 | display: inline-block; 18 | text-decoration: none; 19 | border: solid 3px #fff; 20 | padding: 0.1em; 21 | position: relative; 22 | } 23 | .gallery-list__item-image 24 | { 25 | display: inline-block; 26 | position: relative; 27 | box-shadow: #bbb 0 1px 3px; 28 | vertical-align: bottom; 29 | max-height: 100px; 30 | } 31 | .gallery-list__item-link:hover 32 | { 33 | border: solid 3px #bc2131; 34 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/gallery/gallery.css: -------------------------------------------------------------------------------- 1 | .gallery 2 | { 3 | width: 100%; 4 | padding: 1em 0 0 0; 5 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/main-menu/main-menu-title.css: -------------------------------------------------------------------------------- 1 | .main-menu-title 2 | { 3 | width: 24%; 4 | font: 1.3em Arial, Helvetica, sans-serif; 5 | position: relative; 6 | float: left; 7 | color: #fff; 8 | padding: 0.4em 0 0 0; 9 | } 10 | .main-menu-title__link 11 | { 12 | color: #fff; 13 | display: inline-block; 14 | text-align: center; 15 | position: relative; 16 | padding: 0 0 0 1.8em; 17 | text-decoration: none; 18 | margin: 0 0 0 1.4em; 19 | } 20 | .main-menu-title__link:before 21 | { 22 | width: 2.2em; 23 | height: 1.2em; 24 | background: url('../img/logo.png') no-repeat; 25 | background-size: contain; 26 | content: ''; 27 | display: block; 28 | position: absolute; 29 | left: 0; 30 | top: 0; 31 | } 32 | .main-menu-title__link:hover 33 | { 34 | color: #ffcc00; 35 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/main-menu/main-menu.css: -------------------------------------------------------------------------------- 1 | .main-menu 2 | { 3 | width: 100%; 4 | position: relative; 5 | background: #bc2131; 6 | z-index: 1000; 7 | } 8 | .main-menu-list 9 | { 10 | padding: 0; 11 | margin: 0; 12 | display: table; 13 | transition: all 0.2s linear; 14 | position: relative; 15 | float: left; 16 | z-index: 10000; 17 | } 18 | .main-menu-list__item 19 | { 20 | position: relative; 21 | margin: 0; 22 | vertical-align: top; 23 | text-decoration: none; 24 | display: inline-block; 25 | z-index: 100; 26 | } 27 | .main-menu-list__item-link 28 | { 29 | font: 0.95em Arial, Helvetica, sans-serif; 30 | position: relative; 31 | display: block; 32 | color: #fff; 33 | padding: 0.7em 1.5em 0.75em 0.8em; 34 | transition: all 0.1s linear; 35 | } 36 | .main-menu-list__item-link:hover 37 | { 38 | transition: all 0.1s linear; 39 | background: #811621; 40 | color: #fff; 41 | } 42 | 43 | .main-menu-list__item-link:visited 44 | { 45 | color: #fff; 46 | } 47 | .main-menu-list__item:hover > .main-menu-list 48 | { 49 | visibility: visible; 50 | opacity: 100; 51 | } 52 | 53 | .main-menu-list__item > .main-menu-list 54 | { 55 | position: absolute; 56 | background: #bc2131; 57 | left: 0; 58 | top: auto; 59 | visibility: hidden; 60 | opacity: 0; 61 | box-shadow: 0 0.1em 0.5em #540E15; 62 | margin: -0.1em 0 0 0; 63 | } 64 | .main-menu-list__item > .main-menu-list .main-menu-list__item 65 | { 66 | display: block; 67 | width: 100%; 68 | } 69 | .main-menu-list__item > .main-menu-list .main-menu-list__item-link 70 | { 71 | padding-right: 2em; 72 | padding-left: 1em; 73 | min-width: 130px; 74 | } 75 | .main-menu-list__item > .main-menu-list .main-menu-list 76 | { 77 | left: 100%; 78 | top: 0.5em; 79 | margin: 0 0 0 -0.5em; 80 | } 81 | 82 | .main-menu-list__item_state_current 83 | { 84 | background: #540E15; 85 | } 86 | .main-menu-list__item_state_current > .main-menu-list__item-link 87 | { 88 | text-decoration: none; 89 | content: #fff; 90 | } 91 | .main-menu-list__item_state_has-child > .main-menu-list__item-link:after 92 | { 93 | display: inline-block; 94 | content: ""; 95 | position: absolute; 96 | right: 0.5em; 97 | top: 1.2em; 98 | color: lime; 99 | width: 0; 100 | height: 0; 101 | border-style: solid; 102 | border-width: 5px 5px 0 5px; 103 | border-color: #fff transparent transparent transparent; 104 | } 105 | .main-menu-list__item_state_has-child .main-menu-list > 106 | .main-menu-list__item_state_has-child > .main-menu-list__item-link:after 107 | { 108 | display: inline-block; 109 | content: ""; 110 | position: absolute; 111 | right: 1em; 112 | top: 1em; 113 | color: lime; 114 | width: 0; 115 | height: 0; 116 | border-style: solid; 117 | border-width: 5px 0 5px 5px; 118 | border-color: transparent transparent transparent #fff; 119 | } 120 | 121 | .main-menu-list__item_in_current_branch 122 | { 123 | background: #992635; 124 | z-index: 6000; 125 | } 126 | 127 | .main-menu-list__item_state_active 128 | { 129 | background: #811621; 130 | } 131 | .main-menu-list__item_type_login 132 | { 133 | background: #4183c4; 134 | } 135 | .main-menu-list__item_type_login .main-menu-list__item-link 136 | { 137 | padding-right: 0.9em; 138 | } 139 | .main-menu-list__item_type_login:hover .main-menu-list__item-link 140 | { 141 | background: #4078c0; 142 | } 143 | -------------------------------------------------------------------------------- /pyramid_pages/static/css/main-menu/main-menu_type_right.css: -------------------------------------------------------------------------------- 1 | .main-menu_type_right 2 | { 3 | background: none; 4 | padding: 0; 5 | z-index: 500; 6 | } 7 | .main-menu_type_right .main-menu-title 8 | { 9 | float: none; 10 | color: #000; 11 | padding: 0.7em 1.5em 0.2em 1.4em; 12 | } 13 | .main-menu_type_right .main-menu-list 14 | { 15 | width: 100%; 16 | display: block; 17 | float: none; 18 | } 19 | .main-menu_type_right .main-menu-list__item 20 | { 21 | display: block; 22 | padding: 0; 23 | width: 100%; 24 | } 25 | .main-menu_type_right .main-menu-list__item-link 26 | { 27 | display: block; 28 | color: #000; 29 | padding-left: 2em; 30 | } 31 | .main-menu_type_right .main-menu-list__item-link:hover 32 | { 33 | background: #bbb; 34 | color: #fff; 35 | } 36 | .main-menu_type_right .main-menu-list__item > .main-menu-list 37 | { 38 | background: #fff; 39 | box-shadow: 0 0.1em 0.5em #333; 40 | } 41 | .main-menu_type_right .main-menu-list__item > .main-menu-list 42 | { 43 | position: absolute; 44 | left: -100%; 45 | top: 1em; 46 | margin: 0 0 0 0; 47 | } 48 | .main-menu_type_right .main-menu-list__item > .main-menu-list .main-menu-list__item-link 49 | { 50 | min-width: 50px; 51 | } 52 | .main-menu_type_right .main-menu-list__item_state_has-child > .main-menu-list__item-link:after, 53 | .main-menu_type_right .main-menu-list__item_state_has-child .main-menu-list > 54 | .main-menu_type_right .main-menu-list__item_state_has-child > .main-menu-list__item-link:after 55 | { 56 | left: 1em; 57 | top: 1em; 58 | color: #000; 59 | border-style: solid; 60 | border-width: 5px 5px 5px 0; 61 | border-color: transparent #000 transparent transparent; 62 | } 63 | .main-menu_type_right .main-menu-list__item_state_has-child .main-menu-list > 64 | .main-menu-list__item_state_has-child > .main-menu-list__item-link 65 | { 66 | padding-left: 2em; 67 | } 68 | .main-menu_type_right .main-menu-list__item_state_has-child .main-menu-list > 69 | .main-menu-list__item_state_has-child > .main-menu-list__item-link:after 70 | { 71 | display: inline-block; 72 | content: ""; 73 | position: absolute; 74 | right: 1em; 75 | top: 1em; 76 | color: lime; 77 | width: 0; 78 | height: 0; 79 | border-style: solid; 80 | border-width: 5px 5px 5px 0; 81 | border-color: transparent #000 transparent transparent; 82 | } 83 | .main-menu_type_right .main-menu-list__item_state_current 84 | { 85 | background: #ccc; 86 | } 87 | 88 | .main-menu_type_right .main-menu-list__item_state_active 89 | { 90 | background: #ccc; 91 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/main-menu/main-menu_type_vertical.css: -------------------------------------------------------------------------------- 1 | .main-menu_type_vertical 2 | { 3 | background: none; 4 | padding: 0; 5 | z-index: 500; 6 | } 7 | .main-menu_type_vertical .main-menu-title 8 | { 9 | float: none; 10 | color: #000; 11 | padding: 0.7em 1.5em 0.2em 1.5em; 12 | } 13 | .main-menu_type_vertical .main-menu-title:before 14 | { 15 | display: none; 16 | } 17 | .main-menu_type_vertical .main-menu-list 18 | { 19 | width: 100%; 20 | display: block; 21 | float: none; 22 | } 23 | .main-menu_type_vertical .main-menu-list__item 24 | { 25 | display: block; 26 | padding: 0; 27 | width: 100%; 28 | } 29 | 30 | .main-menu_type_vertical .main-menu-list__item-link 31 | { 32 | display: block; 33 | color: #000; 34 | padding-left: 2em; 35 | } 36 | .main-menu_type_vertical .main-menu-list__item-link:hover 37 | { 38 | background: #bbb; 39 | color: #fff; 40 | } 41 | .main-menu_type_vertical .main-menu-list__item > .main-menu-list 42 | { 43 | background: #fff; 44 | box-shadow: 0 0.1em 0.5em #333; 45 | } 46 | .main-menu_type_vertical .main-menu-list__item > .main-menu-list 47 | { 48 | position: absolute; 49 | left: 100%; 50 | top: 1em; 51 | margin: 0 0 0 -2em; 52 | } 53 | .main-menu_type_vertical .main-menu-list__item_state_has-child > .main-menu-list__item-link:after, 54 | .main-menu_type_vertical .main-menu-list__item_state_has-child .main-menu-list > 55 | .main-menu_type_vertical .main-menu-list__item_state_has-child > .main-menu-list__item-link:after 56 | { 57 | display: inline-block; 58 | content: ""; 59 | position: absolute; 60 | right: 1em; 61 | top: 1em; 62 | color: lime; 63 | width: 0; 64 | height: 0; 65 | border-style: solid; 66 | border-width: 5px 0 5px 5px; 67 | border-color: transparent transparent transparent #000; 68 | } 69 | 70 | .main-menu_type_vertical .main-menu-list__item_state_current 71 | { 72 | background: #ccc; 73 | } 74 | 75 | .main-menu_type_vertical .main-menu-list__item_state_active 76 | { 77 | background: #ccc; 78 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/main.css: -------------------------------------------------------------------------------- 1 | body, html 2 | { 3 | height: 100%; 4 | min-height: 100%; 5 | } 6 | 7 | .hoverclass2 8 | { 9 | border: solid 5px lime; 10 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/nav/nav.css: -------------------------------------------------------------------------------- 1 | .nav 2 | { 3 | position: relative; 4 | vertical-align: top; 5 | font: 0.8em 'Arial', Helvetica, sans-serif; 6 | margin: 0 0 1em 0; 7 | display: table; 8 | } 9 | .nav__link 10 | { 11 | color: #333333; 12 | display: inline-block; 13 | margin: 0 0.5em 0.5em 0; 14 | } 15 | .nav__link:hover 16 | { 17 | color: #ffcc00; 18 | } 19 | .nav__separator 20 | { 21 | position: relative; 22 | display: inline-block; 23 | margin: 0 0.5em 0 0; 24 | } 25 | .nav__separator .fa-chevron-right 26 | { 27 | font-size: 0.5em; 28 | padding: 0 0.1em 0 0.5em; 29 | position: relative; 30 | top: -0.2em; 31 | display: inline-block; 32 | color: #555555; 33 | } 34 | .nav__item 35 | { 36 | color: #005197; 37 | } 38 | .nav__item-state-current 39 | { 40 | position: relative; 41 | } 42 | -------------------------------------------------------------------------------- /pyramid_pages/static/css/page/page-note.css: -------------------------------------------------------------------------------- 1 | .page-note 2 | { 3 | font: 0.9em Arial, Helvetica, sans-serif; 4 | color: #000; 5 | padding: 0.5em 1em; 6 | background: #f9f5e4; 7 | margin: 0 0 1em -1em; 8 | display: inline-block; 9 | position: relative; 10 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/page/page-title.css: -------------------------------------------------------------------------------- 1 | .page-title 2 | { 3 | font: 1.8em Arial, Helvetica, sans-serif; 4 | color: #000; 5 | padding: 0 0 0.5em 0; 6 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/page/page.css: -------------------------------------------------------------------------------- 1 | .page 2 | { 3 | border: solid 1px #bbb; 4 | position: relative; 5 | z-index: 90; 6 | box-shadow: 0 0 1.5em #ccc; 7 | } 8 | .page-inner 9 | { 10 | width: 100%; 11 | display: table; 12 | } 13 | .page__left 14 | { 15 | width: 23%; 16 | display: table-cell; 17 | background: #eee; 18 | vertical-align: top; 19 | } 20 | .page__middle 21 | { 22 | width: 53%; 23 | display: table-cell; 24 | padding: 2em 2% 2em 2%; 25 | vertical-align: top; 26 | } 27 | .page__right 28 | { 29 | width: 20%; 30 | display: table-cell; 31 | vertical-align: top; 32 | background: #eee; 33 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/popup/content-gallery/content-gallery.css: -------------------------------------------------------------------------------- 1 | .popup-inner__content-gallery 2 | { 3 | width: 800px; 4 | min-height: 450px; 5 | max-width: 800px; 6 | padding: 1.5em 1.1em 1.2em 1.1em; 7 | background: #fff; 8 | box-shadow: 0 0 30px 0 #555; 9 | } -------------------------------------------------------------------------------- /pyramid_pages/static/css/popup/popup.css: -------------------------------------------------------------------------------- 1 | .popup 2 | { 3 | width: 100%; 4 | height: 100%; 5 | min-height: 100%; 6 | position: absolute; 7 | z-index: 100000; 8 | top: 0; 9 | left: 0; 10 | display: none; 11 | } 12 | .popup-background 13 | { 14 | background: #eee; 15 | display: block; 16 | filter:progid:DXImageTransform.Microsoft.Alpha(opacity=50); 17 | height: 100%; 18 | left: 0; 19 | min-height: 100%; 20 | opacity: .50; 21 | position: fixed; 22 | top: 0; 23 | width: 100%; 24 | z-index: 100001; 25 | } 26 | .popup-inner 27 | { 28 | //top: 50%; 29 | //vertical-align: baseline; 30 | display: table-cell; 31 | position: relative; 32 | vertical-align: middle; 33 | width: 100%; 34 | } 35 | .popup-inner__ie 36 | { 37 | position: relative; 38 | width: 100%; 39 | text-align: center; 40 | //top: -50%; 41 | } 42 | .popup-inner__content 43 | { 44 | position: relative; 45 | z-index: 100002; 46 | display: inline-block; 47 | margin: 2em 0; 48 | text-align: left; 49 | text-decoration: none; 50 | } 51 | .popup-inner__content-link 52 | { 53 | position: absolute; 54 | z-index: 100003; 55 | top: .1em; 56 | right: 1.3em; 57 | text-decoration: none; 58 | } 59 | .popup-inner__content-link-text 60 | { 61 | font: .9em 'Arial', Helvetica, sans-serif; 62 | cursor: pointer; 63 | color: #000; 64 | border-bottom: dashed 1px #000; 65 | } 66 | .popup-inner__content-link:hover .popup-inner__content-link-text 67 | { 68 | color: #ff6702; 69 | border-bottom: dashed 1px #ff6702; 70 | } 71 | -------------------------------------------------------------------------------- /pyramid_pages/static/css/vendor/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.3.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.3.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.3.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.3.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.3.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0, 0)}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-genderless:before,.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"} -------------------------------------------------------------------------------- /pyramid_pages/static/css/vendor/html5-boilerplate/main.css: -------------------------------------------------------------------------------- 1 | /*! HTML5 Boilerplate v5.2.0 | MIT License | https://html5boilerplate.com/ */ 2 | 3 | /* 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html { 14 | color: #222; 15 | font-size: 1em; 16 | line-height: 1.4; 17 | } 18 | 19 | /* 20 | * Remove text-shadow in selection highlight: 21 | * https://twitter.com/miketaylr/status/12228805301 22 | * 23 | * These selection rule sets have to be separate. 24 | * Customize the background color to match your design. 25 | */ 26 | 27 | ::-moz-selection { 28 | background: #b3d4fc; 29 | text-shadow: none; 30 | } 31 | 32 | ::selection { 33 | background: #b3d4fc; 34 | text-shadow: none; 35 | } 36 | 37 | /* 38 | * A better looking default horizontal rule 39 | */ 40 | 41 | hr { 42 | display: block; 43 | height: 1px; 44 | border: 0; 45 | border-top: 1px solid #ccc; 46 | margin: 1em 0; 47 | padding: 0; 48 | } 49 | 50 | /* 51 | * Remove the gap between audio, canvas, iframes, 52 | * images, videos and the bottom of their containers: 53 | * https://github.com/h5bp/html5-boilerplate/issues/440 54 | */ 55 | 56 | audio, 57 | canvas, 58 | iframe, 59 | img, 60 | svg, 61 | video { 62 | vertical-align: middle; 63 | } 64 | 65 | /* 66 | * Remove default fieldset styles. 67 | */ 68 | 69 | fieldset { 70 | border: 0; 71 | margin: 0; 72 | padding: 0; 73 | } 74 | 75 | /* 76 | * Allow only vertical resizing of textareas. 77 | */ 78 | 79 | textarea { 80 | resize: vertical; 81 | } 82 | 83 | /* ========================================================================== 84 | Browser Upgrade Prompt 85 | ========================================================================== */ 86 | 87 | .browserupgrade { 88 | margin: 0.2em 0; 89 | background: #ccc; 90 | color: #000; 91 | padding: 0.2em 0; 92 | } 93 | 94 | /* ========================================================================== 95 | Author's custom styles 96 | ========================================================================== */ 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | /* ========================================================================== 115 | Helper classes 116 | ========================================================================== */ 117 | 118 | /* 119 | * Hide visually and from screen readers: 120 | */ 121 | 122 | .hidden { 123 | display: none !important; 124 | } 125 | 126 | /* 127 | * Hide only visually, but have it available for screen readers: 128 | * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility 129 | */ 130 | 131 | .visuallyhidden { 132 | border: 0; 133 | clip: rect(0 0 0 0); 134 | height: 1px; 135 | margin: -1px; 136 | overflow: hidden; 137 | padding: 0; 138 | position: absolute; 139 | width: 1px; 140 | } 141 | 142 | /* 143 | * Extends the .visuallyhidden class to allow the element 144 | * to be focusable when navigated to via the keyboard: 145 | * https://www.drupal.org/node/897638 146 | */ 147 | 148 | .visuallyhidden.focusable:active, 149 | .visuallyhidden.focusable:focus { 150 | clip: auto; 151 | height: auto; 152 | margin: 0; 153 | overflow: visible; 154 | position: static; 155 | width: auto; 156 | } 157 | 158 | /* 159 | * Hide visually and from screen readers, but maintain layout 160 | */ 161 | 162 | .invisible { 163 | visibility: hidden; 164 | } 165 | 166 | /* 167 | * Clearfix: contain floats 168 | * 169 | * For modern browsers 170 | * 1. The space content is one way to avoid an Opera bug when the 171 | * `contenteditable` attribute is included anywhere else in the document. 172 | * Otherwise it causes space to appear at the top and bottom of elements 173 | * that receive the `clearfix` class. 174 | * 2. The use of `table` rather than `block` is only necessary if using 175 | * `:before` to contain the top-margins of child elements. 176 | */ 177 | 178 | .clearfix:before, 179 | .clearfix:after { 180 | content: " "; /* 1 */ 181 | display: table; /* 2 */ 182 | } 183 | 184 | .clearfix:after { 185 | clear: both; 186 | } 187 | 188 | /* ========================================================================== 189 | EXAMPLE Media Queries for Responsive Design. 190 | These examples override the primary ('mobile first') styles. 191 | Modify as content requires. 192 | ========================================================================== */ 193 | 194 | @media only screen and (min-width: 35em) { 195 | /* Style adjustments for viewports that meet the condition */ 196 | } 197 | 198 | @media print, 199 | (-webkit-min-device-pixel-ratio: 1.25), 200 | (min-resolution: 1.25dppx), 201 | (min-resolution: 120dpi) { 202 | /* Style adjustments for high resolution devices */ 203 | } 204 | 205 | /* ========================================================================== 206 | Print styles. 207 | Inlined to avoid the additional HTTP request: 208 | http://www.phpied.com/delay-loading-your-print-css/ 209 | ========================================================================== */ 210 | 211 | @media print { 212 | *, 213 | *:before, 214 | *:after { 215 | background: transparent !important; 216 | color: #000 !important; /* Black prints faster: 217 | http://www.sanbeiji.com/archives/953 */ 218 | box-shadow: none !important; 219 | text-shadow: none !important; 220 | } 221 | 222 | a, 223 | a:visited { 224 | text-decoration: underline; 225 | } 226 | 227 | a[href]:after { 228 | content: " (" attr(href) ")"; 229 | } 230 | 231 | abbr[title]:after { 232 | content: " (" attr(title) ")"; 233 | } 234 | 235 | /* 236 | * Don't show links that are fragment identifiers, 237 | * or use the `javascript:` pseudo protocol 238 | */ 239 | 240 | a[href^="#"]:after, 241 | a[href^="javascript:"]:after { 242 | content: ""; 243 | } 244 | 245 | pre, 246 | blockquote { 247 | border: 1px solid #999; 248 | page-break-inside: avoid; 249 | } 250 | 251 | /* 252 | * Printing Tables: 253 | * http://css-discuss.incutio.com/wiki/Printing_Tables 254 | */ 255 | 256 | thead { 257 | display: table-header-group; 258 | } 259 | 260 | tr, 261 | img { 262 | page-break-inside: avoid; 263 | } 264 | 265 | img { 266 | max-width: 100% !important; 267 | } 268 | 269 | p, 270 | h2, 271 | h3 { 272 | orphans: 3; 273 | widows: 3; 274 | } 275 | 276 | h2, 277 | h3 { 278 | page-break-after: avoid; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /pyramid_pages/static/css/vendor/main.css: -------------------------------------------------------------------------------- 1 | /*! HTML5 Boilerplate v5.2.0 | MIT License | https://html5boilerplate.com/ */ 2 | 3 | /* 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html { 14 | color: #222; 15 | font-size: 1em; 16 | line-height: 1.4; 17 | } 18 | 19 | /* 20 | * Remove text-shadow in selection highlight: 21 | * https://twitter.com/miketaylr/status/12228805301 22 | * 23 | * These selection rule sets have to be separate. 24 | * Customize the background color to match your design. 25 | */ 26 | 27 | ::-moz-selection { 28 | background: #b3d4fc; 29 | text-shadow: none; 30 | } 31 | 32 | ::selection { 33 | background: #b3d4fc; 34 | text-shadow: none; 35 | } 36 | 37 | /* 38 | * A better looking default horizontal rule 39 | */ 40 | 41 | hr { 42 | display: block; 43 | height: 1px; 44 | border: 0; 45 | border-top: 1px solid #ccc; 46 | margin: 1em 0; 47 | padding: 0; 48 | } 49 | 50 | /* 51 | * Remove the gap between audio, canvas, iframes, 52 | * images, videos and the bottom of their containers: 53 | * https://github.com/h5bp/html5-boilerplate/issues/440 54 | */ 55 | 56 | audio, 57 | canvas, 58 | iframe, 59 | img, 60 | svg, 61 | video { 62 | vertical-align: middle; 63 | } 64 | 65 | /* 66 | * Remove default fieldset styles. 67 | */ 68 | 69 | fieldset { 70 | border: 0; 71 | margin: 0; 72 | padding: 0; 73 | } 74 | 75 | /* 76 | * Allow only vertical resizing of textareas. 77 | */ 78 | 79 | textarea { 80 | resize: vertical; 81 | } 82 | 83 | /* ========================================================================== 84 | Browser Upgrade Prompt 85 | ========================================================================== */ 86 | 87 | .browserupgrade { 88 | margin: 0.2em 0; 89 | background: #ccc; 90 | color: #000; 91 | padding: 0.2em 0; 92 | } 93 | 94 | /* ========================================================================== 95 | Author's custom styles 96 | ========================================================================== */ 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | /* ========================================================================== 115 | Helper classes 116 | ========================================================================== */ 117 | 118 | /* 119 | * Hide visually and from screen readers: 120 | */ 121 | 122 | .hidden { 123 | display: none !important; 124 | } 125 | 126 | /* 127 | * Hide only visually, but have it available for screen readers: 128 | * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility 129 | */ 130 | 131 | .visuallyhidden { 132 | border: 0; 133 | clip: rect(0 0 0 0); 134 | height: 1px; 135 | margin: -1px; 136 | overflow: hidden; 137 | padding: 0; 138 | position: absolute; 139 | width: 1px; 140 | } 141 | 142 | /* 143 | * Extends the .visuallyhidden class to allow the element 144 | * to be focusable when navigated to via the keyboard: 145 | * https://www.drupal.org/node/897638 146 | */ 147 | 148 | .visuallyhidden.focusable:active, 149 | .visuallyhidden.focusable:focus { 150 | clip: auto; 151 | height: auto; 152 | margin: 0; 153 | overflow: visible; 154 | position: static; 155 | width: auto; 156 | } 157 | 158 | /* 159 | * Hide visually and from screen readers, but maintain layout 160 | */ 161 | 162 | .invisible { 163 | visibility: hidden; 164 | } 165 | 166 | /* 167 | * Clearfix: contain floats 168 | * 169 | * For modern browsers 170 | * 1. The space content is one way to avoid an Opera bug when the 171 | * `contenteditable` attribute is included anywhere else in the document. 172 | * Otherwise it causes space to appear at the top and bottom of elements 173 | * that receive the `clearfix` class. 174 | * 2. The use of `table` rather than `block` is only necessary if using 175 | * `:before` to contain the top-margins of child elements. 176 | */ 177 | 178 | .clearfix:before, 179 | .clearfix:after { 180 | content: " "; /* 1 */ 181 | display: table; /* 2 */ 182 | } 183 | 184 | .clearfix:after { 185 | clear: both; 186 | } 187 | 188 | /* ========================================================================== 189 | EXAMPLE Media Queries for Responsive Design. 190 | These examples override the primary ('mobile first') styles. 191 | Modify as content requires. 192 | ========================================================================== */ 193 | 194 | @media only screen and (min-width: 35em) { 195 | /* Style adjustments for viewports that meet the condition */ 196 | } 197 | 198 | @media print, 199 | (-webkit-min-device-pixel-ratio: 1.25), 200 | (min-resolution: 1.25dppx), 201 | (min-resolution: 120dpi) { 202 | /* Style adjustments for high resolution devices */ 203 | } 204 | 205 | /* ========================================================================== 206 | Print styles. 207 | Inlined to avoid the additional HTTP request: 208 | http://www.phpied.com/delay-loading-your-print-css/ 209 | ========================================================================== */ 210 | 211 | @media print { 212 | *, 213 | *:before, 214 | *:after { 215 | background: transparent !important; 216 | color: #000 !important; /* Black prints faster: 217 | http://www.sanbeiji.com/archives/953 */ 218 | box-shadow: none !important; 219 | text-shadow: none !important; 220 | } 221 | 222 | a, 223 | a:visited { 224 | text-decoration: underline; 225 | } 226 | 227 | a[href]:after { 228 | content: " (" attr(href) ")"; 229 | } 230 | 231 | abbr[title]:after { 232 | content: " (" attr(title) ")"; 233 | } 234 | 235 | /* 236 | * Don't show links that are fragment identifiers, 237 | * or use the `javascript:` pseudo protocol 238 | */ 239 | 240 | a[href^="#"]:after, 241 | a[href^="javascript:"]:after { 242 | content: ""; 243 | } 244 | 245 | pre, 246 | blockquote { 247 | border: 1px solid #999; 248 | page-break-inside: avoid; 249 | } 250 | 251 | /* 252 | * Printing Tables: 253 | * http://css-discuss.incutio.com/wiki/Printing_Tables 254 | */ 255 | 256 | thead { 257 | display: table-header-group; 258 | } 259 | 260 | tr, 261 | img { 262 | page-break-inside: avoid; 263 | } 264 | 265 | img { 266 | max-width: 100% !important; 267 | } 268 | 269 | p, 270 | h2, 271 | h3 { 272 | orphans: 3; 273 | widows: 3; 274 | } 275 | 276 | h2, 277 | h3 { 278 | page-break-after: avoid; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /pyramid_pages/static/css/vendor/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS and IE text size adjust after device orientation change, 6 | * without disabling user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability of focused elements when they are also in an 95 | * active/hover state. 96 | */ 97 | 98 | a:active, 99 | a:hover { 100 | outline: 0; 101 | } 102 | 103 | /* Text-level semantics 104 | ========================================================================== */ 105 | 106 | /** 107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 108 | */ 109 | 110 | abbr[title] { 111 | border-bottom: 1px dotted; 112 | } 113 | 114 | /** 115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 116 | */ 117 | 118 | b, 119 | strong { 120 | font-weight: bold; 121 | } 122 | 123 | /** 124 | * Address styling not present in Safari and Chrome. 125 | */ 126 | 127 | dfn { 128 | font-style: italic; 129 | } 130 | 131 | /** 132 | * Address variable `h1` font-size and margin within `section` and `article` 133 | * contexts in Firefox 4+, Safari, and Chrome. 134 | */ 135 | 136 | h1 { 137 | font-size: 2em; 138 | margin: 0.67em 0; 139 | } 140 | 141 | /** 142 | * Address styling not present in IE 8/9. 143 | */ 144 | 145 | mark { 146 | background: #ff0; 147 | color: #000; 148 | } 149 | 150 | /** 151 | * Address inconsistent and variable font size in all browsers. 152 | */ 153 | 154 | small { 155 | font-size: 80%; 156 | } 157 | 158 | /** 159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 160 | */ 161 | 162 | sub, 163 | sup { 164 | font-size: 75%; 165 | line-height: 0; 166 | position: relative; 167 | vertical-align: baseline; 168 | } 169 | 170 | sup { 171 | top: -0.5em; 172 | } 173 | 174 | sub { 175 | bottom: -0.25em; 176 | } 177 | 178 | /* Embedded content 179 | ========================================================================== */ 180 | 181 | /** 182 | * Remove border when inside `a` element in IE 8/9/10. 183 | */ 184 | 185 | img { 186 | border: 0; 187 | } 188 | 189 | /** 190 | * Correct overflow not hidden in IE 9/10/11. 191 | */ 192 | 193 | svg:not(:root) { 194 | overflow: hidden; 195 | } 196 | 197 | /* Grouping content 198 | ========================================================================== */ 199 | 200 | /** 201 | * Address margin not present in IE 8/9 and Safari. 202 | */ 203 | 204 | figure { 205 | margin: 1em 40px; 206 | } 207 | 208 | /** 209 | * Address differences between Firefox and other browsers. 210 | */ 211 | 212 | hr { 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. 354 | */ 355 | 356 | input[type="search"] { 357 | -webkit-appearance: textfield; /* 1 */ 358 | box-sizing: content-box; /* 2 */ 359 | } 360 | 361 | /** 362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 363 | * Safari (but not Chrome) clips the cancel button when the search input has 364 | * padding (and `textfield` appearance). 365 | */ 366 | 367 | input[type="search"]::-webkit-search-cancel-button, 368 | input[type="search"]::-webkit-search-decoration { 369 | -webkit-appearance: none; 370 | } 371 | 372 | /** 373 | * Define consistent border, margin, and padding. 374 | */ 375 | 376 | fieldset { 377 | border: 1px solid #c0c0c0; 378 | margin: 0 2px; 379 | padding: 0.35em 0.625em 0.75em; 380 | } 381 | 382 | /** 383 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 385 | */ 386 | 387 | legend { 388 | border: 0; /* 1 */ 389 | padding: 0; /* 2 */ 390 | } 391 | 392 | /** 393 | * Remove default vertical scrollbar in IE 8/9/10/11. 394 | */ 395 | 396 | textarea { 397 | overflow: auto; 398 | } 399 | 400 | /** 401 | * Don't inherit the `font-weight` (applied by a rule above). 402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 403 | */ 404 | 405 | optgroup { 406 | font-weight: bold; 407 | } 408 | 409 | /* Tables 410 | ========================================================================== */ 411 | 412 | /** 413 | * Remove most spacing between table cells. 414 | */ 415 | 416 | table { 417 | border-collapse: collapse; 418 | border-spacing: 0; 419 | } 420 | 421 | td, 422 | th { 423 | padding: 0; 424 | } 425 | -------------------------------------------------------------------------------- /pyramid_pages/static/css/wrapper/wrapper.css: -------------------------------------------------------------------------------- 1 | .wrapper 2 | { 3 | min-width: 985px; 4 | max-width: 1100px; 5 | margin: 0 auto; 6 | position: relative; 7 | padding: 0 10px; 8 | } 9 | .wrapper__inner 10 | { 11 | padding: 20px 10px 50px 10px; 12 | position: relative; 13 | } -------------------------------------------------------------------------------- /pyramid_pages/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/favicon.ico -------------------------------------------------------------------------------- /pyramid_pages/static/fonts/font-awesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/fonts/font-awesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /pyramid_pages/static/fonts/font-awesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/fonts/font-awesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /pyramid_pages/static/fonts/font-awesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/fonts/font-awesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /pyramid_pages/static/fonts/font-awesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/fonts/font-awesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /pyramid_pages/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /pyramid_pages/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /pyramid_pages/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /pyramid_pages/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /pyramid_pages/static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uralbash/pyramid_pages/d916cfc78b03ad37b47b815575c64302361640cc/pyramid_pages/static/img/logo.png -------------------------------------------------------------------------------- /pyramid_pages/static/js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof $ === 'undefined') { require('jquery'); } 4 | 5 | (function($){ 6 | 7 | $(window).load(function(){ 8 | 9 | var ACTIVE = 'main-menu-list__item_state_active'; 10 | 11 | $('.main-menu-list__item') 12 | .hover(function() { 13 | var self = $(this); 14 | if(self.has('.main-menu-list').length > 0) { 15 | self.addClass(ACTIVE); 16 | self.siblings('.' + ACTIVE) 17 | .removeClass(ACTIVE); 18 | self.find('.main-menu-list .main-menu-list__item') 19 | .removeClass(ACTIVE); 20 | } 21 | }) 22 | .mouseleave(function() { 23 | var self = $(this); 24 | self.parent() 25 | .find('.main-menu-list__item_state_active') 26 | .removeClass(ACTIVE); 27 | }); 28 | 29 | }); 30 | })(jQuery); -------------------------------------------------------------------------------- /pyramid_pages/templates/pyramid_pages/breadcrumbs.jinja2: -------------------------------------------------------------------------------- 1 | {% set breadcrumbs = lineage(context)|reverse %} 2 | {% if breadcrumbs|length > 2 %} 3 | 16 | {% endif %} 17 | -------------------------------------------------------------------------------- /pyramid_pages/templates/pyramid_pages/index.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% include 'pyramid_pages/title.jinja2' %} 6 | {% include 'pyramid_pages/meta.jinja2' %} 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 23 |
24 |
25 |
Important: Redefine me in "/pyramid_pages/index.jinja2"
26 |
{{ page.name }}
27 |
{{ page.description|safe }}
28 |
29 |
30 |
31 |
32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /pyramid_pages/templates/pyramid_pages/menu/flat.jinja2: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /pyramid_pages/templates/pyramid_pages/menu/mptt.jinja2: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /pyramid_pages/templates/pyramid_pages/meta.jinja2: -------------------------------------------------------------------------------- 1 | {% if page %} 2 | 3 | 4 | {{ page.seo_metatags or ''|safe }} 5 | {% endif %} -------------------------------------------------------------------------------- /pyramid_pages/templates/pyramid_pages/title.jinja2: -------------------------------------------------------------------------------- 1 | {% if page %} 2 | {% if page.seo_title %} 3 | {{ page.seo_title }} 4 | {% else %} 5 | {{ page.name }} 6 | {% endif %} 7 | {% endif %} -------------------------------------------------------------------------------- /pyramid_pages/tests/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2015 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Base classes for tests 11 | http://www.sontek.net/blog/2011/12/01/writing_tests_for_pyramid_and_sqlalchemy.html 12 | """ 13 | import imp 14 | import unittest 15 | 16 | from pyramid import testing 17 | from webtest import TestApp 18 | from sqlalchemy import engine_from_config 19 | from pyramid_pages import CONFIG_MODELS, CONFIG_DBSESSION 20 | from sqlalchemy.orm import sessionmaker 21 | from sqlalchemy_mptt import mptt_sessionmaker 22 | from pyramid.threadlocal import get_current_registry 23 | 24 | imp.load_source('pyramid_pages_example', 'example/pyramid_pages_example.py') 25 | 26 | from pyramid_pages_example import Base, main, models, WebPage, NewsPage # noqa 27 | 28 | settings = { 29 | 'sqlalchemy.url': 'sqlite:///test.sqlite', 30 | 'pyramid_pages.models': models 31 | } 32 | 33 | 34 | class BaseTestCase(unittest.TestCase): 35 | 36 | @classmethod 37 | def setUpClass(cls): 38 | cls.engine = engine_from_config(settings, prefix='sqlalchemy.') 39 | cls.DBSession = mptt_sessionmaker(sessionmaker()) 40 | 41 | def setUp(self): 42 | # bind an individual Session to the connection 43 | self.dbsession = self.DBSession(bind=self.engine) 44 | self.create_db() 45 | 46 | def tearDown(self): 47 | # rollback - everything that happened with the 48 | # Session above (including calls to commit()) 49 | # is rolled back. 50 | testing.tearDown() 51 | self.drop_db() 52 | self.dbsession.close() 53 | 54 | def drop_db(self): 55 | Base.metadata.drop_all(bind=self.engine) 56 | self.dbsession.commit() 57 | 58 | def create_db(self): 59 | Base.metadata.create_all(bind=self.engine) 60 | self.dbsession.commit() 61 | 62 | 63 | class UnitTestBase(BaseTestCase): 64 | 65 | def setUp(self): 66 | self.request = testing.DummyRequest() 67 | self.config = testing.setUp(request=self.request) 68 | super(UnitTestBase, self).setUp() 69 | get_current_registry().settings[CONFIG_MODELS] = models 70 | get_current_registry().settings[CONFIG_DBSESSION] = self.dbsession 71 | 72 | 73 | class IntegrationTestBase(BaseTestCase): 74 | 75 | def setUp(self): 76 | self.app = TestApp(main({}, **settings)) 77 | self.config = testing.setUp() 78 | super(IntegrationTestBase, self).setUp() 79 | -------------------------------------------------------------------------------- /pyramid_pages/tests/integration_test.py: -------------------------------------------------------------------------------- 1 | from . import IntegrationTestBase 2 | 3 | 4 | class BasePageView(object): 5 | 6 | def test_get_root_page(self): 7 | self.app.get('/', status=200) 8 | 9 | def test_get_page_from_tree(self): 10 | self.app.get('/pages/about-company/', status=200) 11 | self.app.get('/about-company/', status=200) 12 | 13 | def test_redirect_301_page(self): 14 | self.app.get('/pages/about-company/redirect-301/', 15 | status=301) 16 | 17 | def test_redirect_302_page(self): 18 | self.app.get('/pages/about-company/redirect-301/fond-gbn/', 19 | status=302) 20 | 21 | def test_bad_page_source(self): 22 | self.app.get('/I-would-like-to-be-a-page-but-Im-just-a-404-error', 23 | status=404) 24 | 25 | 26 | class TestPageView(IntegrationTestBase, BasePageView): 27 | pass 28 | -------------------------------------------------------------------------------- /pyramid_pages/tests/unit_tests.py: -------------------------------------------------------------------------------- 1 | from pyramid.httpexceptions import HTTPNotFound 2 | 3 | from pyramid_pages.resources import BasePageResource 4 | from pyramid_pages.views import PageView 5 | 6 | from . import NewsPage, UnitTestBase, WebPage 7 | from pyramid_pages_example import Gallery 8 | 9 | 10 | class TestResource(UnitTestBase): 11 | pass 12 | 13 | 14 | class TestCommon(UnitTestBase): 15 | pass 16 | 17 | 18 | class TestPageView(UnitTestBase): 19 | 20 | """ VISIBLE 21 | """ 22 | 23 | def test_visible_node(self): 24 | """ Node is visible. 25 | """ 26 | 27 | def do_it(model): 28 | node = model(visible=True) 29 | context = BasePageResource(node) 30 | view = PageView(context, self.request).page_with_redirect() 31 | self.assertEqual(view['page'], node) 32 | 33 | do_it(NewsPage) 34 | do_it(WebPage) 35 | 36 | def test_not_visible_node(self): 37 | """ Node is not visible. 38 | """ 39 | 40 | def do_it(model): 41 | # visible is None 42 | node = model() 43 | context = BasePageResource(node) 44 | self.assertRaises(HTTPNotFound, PageView, context, self.request) 45 | 46 | # visible is False 47 | node = model(visible=False) 48 | context = BasePageResource(node) 49 | self.assertRaises(HTTPNotFound, PageView, context, self.request) 50 | 51 | do_it(NewsPage) 52 | do_it(WebPage) 53 | 54 | """ REDIRECT 55 | 56 | redirect_url: URI string e.g.: http://example.com/ 57 | redirect_page: node of tree 58 | redirect_type: [200, 301, 302] 59 | """ 60 | def test_redirect_200(self): 61 | """ Redirect 200 w/o page. 62 | 63 | redirect_type = 200 64 | redirect_url = None 65 | redirect_page = None 66 | """ 67 | node = WebPage(visible=True, redirect_type=200) 68 | context = BasePageResource(node) 69 | view = PageView(context, self.request).page_with_redirect() 70 | self.assertEqual(view['page'], node) 71 | 72 | """ 200 OK 73 | redirect to page 74 | """ 75 | def test_redirect_200_to_self(self): 76 | """ Redirect 200 to itself. 77 | 78 | redirect_type = 200 79 | redirect_url = None 80 | redirect_page = self 81 | """ 82 | node = WebPage( 83 | id=1, visible=True, 84 | redirect_type=200, redirect_page=1 85 | ) 86 | node.redirect = node 87 | context = BasePageResource(node) 88 | context.template = 'json' 89 | response = PageView(context, self.request).page_with_redirect() 90 | self.assertEqual(response.json['page'], 1) 91 | 92 | def test_redirect_page_with_out_type(self): 93 | """ Redirect to page w/o redirect type. 94 | 95 | redirect_type = None 96 | redirect_url = None 97 | redirect_page = 2 98 | """ 99 | node2 = WebPage(id=2) 100 | node = WebPage( 101 | id=1, visible=True, 102 | redirect_page=2, redirect=node2 103 | ) 104 | context = BasePageResource(node) 105 | view = PageView(context, self.request) 106 | self.assertRaises(HTTPNotFound, view.page_with_redirect) 107 | 108 | def test_redirect_200_to_not_visible_page(self): 109 | """ Redirect 200 to not visible page. 110 | 111 | redirect_type = 200 112 | redirect_url = None 113 | redirect_page = 2 114 | """ 115 | node2 = WebPage(id=2) 116 | node = WebPage( 117 | id=1, visible=True, 118 | redirect_type=200, redirect_page=2, redirect=node2 119 | ) 120 | context = BasePageResource(node) 121 | view = PageView(context, self.request) 122 | self.assertRaises(HTTPNotFound, view.page_with_redirect) 123 | 124 | def test_redirect_200_to_visible_page(self): 125 | """ Redirect 200 to visible page. 126 | 127 | redirect_type = 200 128 | redirect_url = None 129 | redirect_page = 2 130 | """ 131 | node2 = WebPage(id=2, visible=True) 132 | node = WebPage( 133 | id=1, visible=True, 134 | redirect_type=200, redirect_page=2, redirect=node2 135 | ) 136 | context = BasePageResource(node) 137 | context.template = 'json' 138 | response = PageView(context, self.request).page_with_redirect() 139 | self.assertEqual(response.json['page'], 2) 140 | 141 | """ 300 142 | redirect to page 143 | """ 144 | def test_redirect_300_to_self(self): 145 | """ Redirect 300 to itself. 146 | 147 | redirect_type = 300 148 | redirect_url = None 149 | redirect_page = self 150 | """ 151 | def do_it(redirect_code): 152 | node = WebPage( 153 | id=1, visible=True, 154 | redirect_type=redirect_code, redirect_page=1 155 | ) 156 | node.redirect = node 157 | context = BasePageResource(node) 158 | view = PageView(context, self.request) 159 | self.assertRaises(HTTPNotFound, view.page_with_redirect) 160 | 161 | do_it(301) 162 | do_it(302) 163 | 164 | def test_redirect_300_to_not_visible_page(self): 165 | """ Redirect 300 to not visible page. 166 | 167 | redirect_type = 300 168 | redirect_url = None 169 | redirect_page = 2 170 | """ 171 | def do_it(redirect_code): 172 | node2 = WebPage(id=2) 173 | node = WebPage( 174 | id=1, visible=True, 175 | redirect_type=redirect_code, redirect_page=2, redirect=node2 176 | ) 177 | context = BasePageResource(node) 178 | view = PageView(context, self.request) 179 | self.assertRaises(HTTPNotFound, view.page_with_redirect) 180 | 181 | do_it(301) 182 | do_it(302) 183 | 184 | def test_redirect_300_to_visible_page(self): 185 | """ Redirect 300 to visible page. 186 | 187 | redirect_type = 300 188 | redirect_url = None 189 | redirect_page = 2 190 | """ 191 | 192 | def do_it(redirect_code): 193 | self.drop_db() 194 | self.create_db() 195 | 196 | node2 = WebPage(id=2, visible=True, name='node2', slug='node2') 197 | node1 = WebPage( 198 | id=1, visible=True, name='node', slug='node', 199 | redirect_type=redirect_code, redirect=node2 200 | ) 201 | node3 = WebPage(id=3, visible=True, name='node3', slug='node3', 202 | parent_id=2) 203 | node4 = WebPage( 204 | id=4, visible=True, name='node4', slug='node4', 205 | redirect_type=redirect_code, redirect=node3 206 | ) 207 | node5_inheritance = Gallery( 208 | id=5, visible=True, name='node5', slug='node5') 209 | node6 = WebPage( 210 | id=6, visible=True, name='node6', slug='node6', 211 | redirect_type=redirect_code, redirect_page=node5_inheritance.id 212 | ) 213 | 214 | self.dbsession.add(node1) 215 | self.dbsession.add(node2) 216 | self.dbsession.flush() 217 | self.dbsession.add(node3) 218 | self.dbsession.add(node4) 219 | self.dbsession.add(node5_inheritance) 220 | self.dbsession.add(node6) 221 | self.dbsession.commit() 222 | 223 | # 301 224 | context = BasePageResource(node1) 225 | context.dbsession = self.dbsession 226 | view = PageView(context, self.request).page_with_redirect() 227 | self.assertEqual(view.status_code, redirect_code) 228 | self.assertEqual(view.location, 'http://example.com/node2/') 229 | 230 | context = BasePageResource(node4) 231 | view = PageView(context, self.request).page_with_redirect() 232 | self.assertEqual(view.status_code, redirect_code) 233 | self.assertEqual(view.location, 234 | 'http://example.com/node2/node3/') 235 | 236 | context = BasePageResource(node6) 237 | view = PageView(context, self.request).page_with_redirect() 238 | self.assertEqual(view.status_code, redirect_code) 239 | self.assertEqual(view.location, 240 | 'http://example.com/gallery/node5/') 241 | self.dbsession.close() 242 | 243 | do_it(301) 244 | do_it(302) 245 | 246 | """ 200 OK 247 | redirect to URL 248 | """ 249 | def test_redirect_200_to_url(self): 250 | """ Redirect 200 to external URL. 251 | 252 | redirect_type = 200 253 | redirect_url = http://example.org 254 | redirect_page = None 255 | """ 256 | URL = 'http://example.org' 257 | node = WebPage( 258 | visible=True, redirect_type=200, redirect_url=URL 259 | ) 260 | context = BasePageResource(node) 261 | view = PageView(context, self.request) 262 | self.assertRaises(HTTPNotFound, view.page_with_redirect) 263 | 264 | def test_redirect_url_with_out_type(self): 265 | """ Redirect to external URL w/o type. 266 | 267 | redirect_type = None 268 | redirect_url = http://example.org 269 | redirect_page = None 270 | """ 271 | URL = 'http://example.org' 272 | node = WebPage( 273 | visible=True, redirect_url=URL, 274 | ) 275 | 276 | context = BasePageResource(node) 277 | view = PageView(context, self.request).page_with_redirect() 278 | self.assertEqual(view.status_code, 302) 279 | self.assertEqual(view.location, 'http://example.org') 280 | 281 | """ 300 282 | redirect to URL 283 | """ 284 | def test_redirect_300_to_url(self): 285 | """ Redirect 300 to external URL. 286 | 287 | redirect_type = 300 288 | redirect_url = http://example.org 289 | redirect_page = None 290 | """ 291 | def do_it(redirect_code): 292 | URL = 'http://example.org' 293 | node = WebPage( 294 | visible=True, redirect_type=301, redirect_url=URL 295 | ) 296 | 297 | # 301 298 | context = BasePageResource(node) 299 | view = PageView(context, self.request).page_with_redirect() 300 | self.assertEqual(view.status_code, 301) 301 | self.assertEqual(view.location, 'http://example.org') 302 | 303 | do_it(301) 304 | do_it(302) 305 | 306 | """ Bad case. 307 | """ 308 | def test_redirect_simultaneously_url_and_page(self): 309 | """ Redirect simultaneously to URL and Page. 310 | 311 | redirect_type = None 312 | redirect_url = http://example.org 313 | redirect_page = 2 314 | """ 315 | URL = 'http://example.org' 316 | node2 = WebPage(id=2, visible=True) 317 | node = WebPage( 318 | visible=True, redirect_url=URL, 319 | redirect_page=2, redirect=node2 320 | ) 321 | 322 | context = BasePageResource(node) 323 | view = PageView(context, self.request) 324 | self.assertRaises(HTTPNotFound, view.page_with_redirect) 325 | -------------------------------------------------------------------------------- /pyramid_pages/views.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2014 uralbash 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | """ 10 | Views for pages 11 | """ 12 | from pyramid.response import Response 13 | from pyramid.renderers import render_to_response 14 | from pyramid.httpexceptions import HTTPNotFound 15 | 16 | from . import CONFIG_MODELS 17 | 18 | 19 | class PageView(object): 20 | 21 | def __init__(self, context, request): 22 | self.request = request 23 | self.context = context 24 | self.page = context.node 25 | 26 | if not getattr(self.page, 'visible', False): 27 | raise HTTPNotFound 28 | 29 | def page_with_redirect(self): 30 | 31 | if all([hasattr(self.page, attr) 32 | for attr in ('redirect', 'redirect_url', 'redirect_page')]): 33 | # Prohibit redirect both url and page 34 | if self.page.redirect_url and self.page.redirect_page: 35 | raise HTTPNotFound 36 | 37 | # check redirect type 38 | if not self.page.redirect_type and self.page.redirect_url: 39 | redirect_type = '302' 40 | elif not self.page.redirect_type and self.page.redirect_page: 41 | redirect_type = '200' 42 | else: 43 | redirect_type = str(self.page.redirect_type) 44 | 45 | # Prohibit redirect itself 46 | if self.page.redirect == self.page and redirect_type != '200': 47 | raise HTTPNotFound 48 | 49 | # Redirect to Page 50 | if self.page.redirect_page: 51 | if not self.page.redirect.visible: 52 | raise HTTPNotFound 53 | if redirect_type == '200': 54 | self.page = self.page.redirect 55 | return render_to_response( 56 | getattr(self.page, 57 | 'pyramid_pages_template', 58 | self.context.template), 59 | {'page': self.page}, 60 | request=self.request 61 | ) 62 | else: 63 | from .resources import ( 64 | resource_of_node, 65 | resources_of_config 66 | ) 67 | redirect = self.page.redirect 68 | pages_config = self.request.registry\ 69 | .settings[CONFIG_MODELS] 70 | resources = resources_of_config(pages_config) 71 | resource = resource_of_node(resources, redirect)(redirect) 72 | redirect_resource_url = self.request.resource_url(resource) 73 | return Response(status_code=int(redirect_type), 74 | location=redirect_resource_url) 75 | # Redirect to URL 76 | if self.page.redirect_url: 77 | if redirect_type == '200': 78 | raise HTTPNotFound 79 | return Response(status_code=int(redirect_type), 80 | location=self.page.redirect_url) 81 | return {'page': self.page} 82 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | nose 2 | coverage 3 | webtest 4 | zope.sqlalchemy 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyramid 2 | sacrud>=0.2.5 3 | saexttype 4 | sqlalchemy_mptt>=0.1.8 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = pyramid_pages/tests/* 3 | 4 | [nosetests] 5 | detailed-errors=1 6 | with-coverage=1 7 | cover-package=pyramid_pages 8 | cover-erase=1 9 | with-doctest=1 10 | verbosity=1 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import find_packages, setup 4 | 5 | this = os.path.dirname(os.path.realpath(__file__)) 6 | 7 | 8 | def read(name): 9 | with open(os.path.join(this, name)) as f: 10 | return f.read() 11 | 12 | setup( 13 | name='pyramid_pages', 14 | version='0.0.5', 15 | url='http://github.com/uralbash/pyramid_pages/', 16 | author='Svintsov Dmitry', 17 | author_email='sacrud@uralbash.ru', 18 | packages=find_packages(), 19 | include_package_data=True, 20 | zip_safe=False, 21 | test_suite="nose.collector", 22 | license="MIT", 23 | description='Tree pages for pyramid', 24 | long_description=read('README.rst'), 25 | install_requires=read('requirements.txt'), 26 | tests_require=read('requirements.txt') + read('requirements-test.txt'), 27 | classifiers=[ 28 | 'Development Status :: 5 - Production/Stable', 29 | 'Environment :: Web Environment', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Natural Language :: English', 33 | 'Operating System :: OS Independent', 34 | 'Programming Language :: Python', 35 | "Programming Language :: Python :: 2.7", 36 | "Programming Language :: Python :: 3", 37 | "Programming Language :: Python :: 3.3", 38 | "Programming Language :: Python :: 3.4", 39 | "Programming Language :: Python :: 3.5", 40 | "Framework :: Pyramid ", 41 | "Topic :: Internet", 42 | "Topic :: Database", 43 | ], 44 | ) 45 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # test.sh 4 | # Copyright (C) 2015 uralbash 5 | # 6 | # Distributed under terms of the MIT license. 7 | # 8 | RED='\033[0;31m' 9 | GREEN='\033[0;32m' 10 | NC='\033[0m' # No Color 11 | 12 | RST_FILES=`find . -name "*.rst" -printf "%p "` 13 | RST_CHECK=$(rstcheck $RST_FILES --report 2 3>&1 1>&2 2>&3 | tee >(cat - >&2)) # fd=STDERR_FILENO 14 | FLAKE8=$(flake8 ./pyramid_pages/) 15 | 16 | echo -e "${RED}" 17 | if [ -n "$RST_CHECK" ] || 18 | [ -n "$FLAKE8" ] 19 | then 20 | echo -e "RST_CHECK: ${RST_CHECK:-OK}" 21 | echo -e "FLAKE8: ${FLAKE8:-OK}" 22 | exit 1 23 | else 24 | echo -e "${GREEN}OK!" 25 | fi 26 | echo -e "${NC}" 27 | --------------------------------------------------------------------------------