├── .coveragerc ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .tx └── config ├── AUTHORS ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── _static │ └── images │ │ ├── context-screenshot.png │ │ └── filter-screenshot.png ├── conf.py ├── contributing.rst ├── editor.rst ├── example.rst ├── index.rst ├── make.bat ├── plugins.rst ├── quick-start.rst ├── releases.rst ├── requirements.txt └── templatetags.rst ├── example ├── README.rst ├── dayslog │ ├── __init__.py │ ├── admin.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_alter_dayslog_happenings_alter_dayslog_id.py │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── example │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── manage.py ├── nginx.conf ├── postinstall ├── requirements.txt ├── static │ ├── fonts │ │ ├── cutive-regular-webfont.eot │ │ ├── cutive-regular-webfont.svg │ │ ├── cutive-regular-webfont.ttf │ │ └── cutive-regular-webfont.woff │ ├── img │ │ ├── favicon.png │ │ └── scribbler-logo.png │ └── less │ │ └── styles.less ├── templates │ ├── 404.html │ ├── 500.html │ └── home.html └── wsgi.py ├── package.json ├── runtests.py ├── scribbler ├── __init__.py ├── apps.py ├── conf.py ├── forms.py ├── locale │ ├── en │ │ └── LC_MESSAGES │ │ │ └── djangojs.po │ └── fr │ │ └── LC_MESSAGES │ │ └── djangojs.po ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── static │ └── scribbler │ │ ├── img │ │ └── btn-close.png │ │ ├── js │ │ ├── djangohint.js │ │ ├── plugins │ │ │ └── themes.js │ │ ├── scribbler-editor.js │ │ ├── scribbler-main.js │ │ └── scribbler-menu.js │ │ ├── less │ │ └── scribbler.less │ │ └── libs │ │ ├── Backbone-LICENSE │ │ ├── Browserify-LICENSE │ │ ├── CodeMirror-LICENSE │ │ ├── Underscore-LICENSE │ │ └── jQuery-LICENSE ├── templates │ └── scribbler │ │ ├── edit-form.html │ │ └── scribble-wrapper.html ├── templatetags │ ├── __init__.py │ └── scribbler_tags.py ├── tests │ ├── __init__.py │ ├── base.py │ ├── jinja2.py │ ├── migrations │ │ └── __init__.py │ ├── qunit │ │ ├── editor-test.js │ │ ├── index.html │ │ ├── main.js │ │ ├── menu-test.js │ │ ├── runner.js │ │ └── template.html │ ├── test.html │ ├── test_templatetags.py │ ├── test_utils.py │ ├── test_views.py │ └── urls.py ├── urls.py ├── utils.py └── views.py ├── setup.cfg ├── setup.py └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = scribbler 3 | omit = */tests*,*/migrations/* 4 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Upload Python Package 7 | 8 | on: 9 | release: 10 | types: [published] 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.x' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install build 25 | make dist 26 | - name: Build package 27 | run: python -m build 28 | - name: Publish package 29 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 30 | with: 31 | user: __token__ 32 | password: ${{ secrets.PYPI_API_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: lint-test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | push: 8 | branches: 9 | - master 10 | schedule: 11 | # run once a week on early monday mornings 12 | - cron: '22 2 * * 1' 13 | 14 | jobs: 15 | 16 | test: 17 | runs-on: ubuntu-20.04 18 | strategy: 19 | matrix: 20 | # tox-gh-actions will only run the tox environments which match the currently 21 | # running python-version. See [gh-actions] in tox.ini for the mapping 22 | # version 3.10 is explicitly a string because otherwise github actions truncates the zero 23 | python-version: [3.7, 3.8, 3.9, '3.10'] 24 | steps: 25 | - uses: actions/checkout@v2 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v2 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | - name: Install dependencies 31 | run: | 32 | python -m pip install --upgrade pip 33 | pip install tox tox-gh-actions 34 | - name: Test with tox 35 | run: tox 36 | env: 37 | PLATFORM: ${{ matrix.platform }} 38 | 39 | coverage: 40 | runs-on: ubuntu-20.04 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: actions/setup-python@v2 44 | with: 45 | python-version: '3.10' 46 | - name: Install dependencies 47 | run: | 48 | python -m pip install --upgrade pip 49 | pip install tox tox-gh-actions 50 | - name: Test with tox 51 | run: tox -e py310-coverage 52 | 53 | build-docs: 54 | runs-on: ubuntu-20.04 55 | steps: 56 | - uses: actions/checkout@v2 57 | - uses: actions/setup-python@v2 58 | with: 59 | python-version: '3.10' 60 | - name: Install dependencies 61 | run: | 62 | python -m pip install --upgrade pip 63 | pip install tox tox-gh-actions 64 | - name: Test with tox 65 | run: tox -e docs -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | .project 3 | .pydevproject 4 | *~ 5 | *.swp 6 | *.db 7 | *.orig 8 | *.DS_Store 9 | .coverage 10 | .tox 11 | *.egg-info/* 12 | docs/_build/* 13 | build 14 | dist/* 15 | *.mo 16 | scribbler/static/scribbler/css/scribbler.css 17 | scribbler/static/scribbler/js/scribbler.js 18 | scribbler/static/scribbler/js/scribbler-min.js 19 | scribbler/tests/qunit/bundle.js 20 | node_modules/* 21 | ghostdriver.log 22 | .direnv 23 | .envrc 24 | .gitignore 25 | package-lock.json 26 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "browser": true, 13 | "undef": true, 14 | "trailing": true, 15 | "jquery": false, 16 | "indent": 4 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: false 4 | 5 | python: 6 | - "3.4" 7 | - "3.5" 8 | - "3.6" 9 | - "3.7" 10 | - "3.8" 11 | - "3.9" 12 | - "3.10" 13 | 14 | services: 15 | - xvfb 16 | 17 | install: 18 | - pip install pip -U 19 | - pip install tox-travis 20 | - pip install coveralls 21 | 22 | script: 23 | - tox 24 | 25 | after_success: 26 | - coveralls 27 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [django-scribbler.djangojs] 5 | file_filter = scribbler/locale//LC_MESSAGES/djangojs.po 6 | source_file = scribbler/locale/en/LC_MESSAGES/djangojs.po 7 | source_lang = en 8 | type = PO 9 | 10 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Primary author: 2 | 3 | Mark Lavin 4 | 5 | 6 | Additional contributors: 7 | 8 | Julia Elman 9 | Omar Estrella 10 | Karen Tracey 11 | Caleb Smith 12 | Nicolas Ippolito 13 | David Ray 14 | Yaroslav Klyuyev 15 | Ben Phillips 16 | Petr Dlouhý 17 | Ronard Luna -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2020, Caktus Consulting Group, LLC 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include LICENSE 3 | include README.rst 4 | recursive-include scribbler/templates * 5 | recursive-include scribbler/static/scribbler/css * 6 | recursive-include scribbler/static/scribbler/img * 7 | recursive-include scribbler/static/scribbler/js * 8 | recursive-include scribbler/static/scribbler/less * 9 | recursive-include scribbler/locale * 10 | recursive-include scribbler/tests/qunit * 11 | include scribbler/static/scribbler/libs/*-LICENSE 12 | recursive-include scribbler/static/scribbler/libs/codemirror/addon * 13 | recursive-include scribbler/static/scribbler/libs/codemirror/keymap * 14 | recursive-include scribbler/static/scribbler/libs/codemirror/lib * 15 | recursive-include scribbler/static/scribbler/libs/codemirror/mode * 16 | recursive-include scribbler/static/scribbler/libs/codemirror/theme * 17 | include scribbler/static/scribbler/libs/jquery.js 18 | include scribbler/static/scribbler/libs/require.js 19 | include scribbler/static/scribbler/libs/backbone.js 20 | include scribbler/static/scribbler/libs/underscore.js 21 | recursive-exclude * __pycache__ 22 | recursive-exclude * *.py[co] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | STATIC_DIR = ./scribbler/static/scribbler 2 | PROJECT_FILES = ${STATIC_DIR}/js/scribbler-main.js ${STATIC_DIR}/js/scribbler-editor.js ${STATIC_DIR}/js/scribbler-menu.js ${STATIC_DIR}/js/djangohint.js 3 | TESTS_DIR = ./scribbler/tests/qunit 4 | TEST_FILES = ${TESTS_DIR}/menu-test.js ${TESTS_DIR}/editor-test.js 5 | 6 | fetch-static-libs: FORCE 7 | # Fetch JS library dependencies 8 | # Requires npm 9 | npm install 10 | npm update 11 | FORCE: 12 | 13 | ${STATIC_DIR}/css/scribbler.css: ${STATIC_DIR}/less/scribbler.less fetch-static-libs 14 | # Build CSS from LESS 15 | # Requires LESS 16 | mkdir -p ${STATIC_DIR}/css 17 | echo | node_modules/.bin/lessc -x node_modules/codemirror/lib/codemirror.css > $@ 18 | echo $^ $@ 19 | echo | node_modules/.bin/lessc ${STATIC_DIR}/less/scribbler.less >> $@ 20 | 21 | build-css: ${STATIC_DIR}/css/scribbler.css 22 | 23 | lint-js: fetch-static-libs 24 | # Check JS for any problems 25 | node_modules/.bin/jshint ${STATIC_DIR}/js/djangohint.js 26 | node_modules/.bin/jshint ${STATIC_DIR}/js/scribbler-main.js 27 | node_modules/.bin/jshint ${STATIC_DIR}/js/scribbler-editor.js 28 | node_modules/.bin/jshint ${STATIC_DIR}/js/scribbler-menu.js 29 | node_modules/.bin/jshint ${STATIC_DIR}/js/plugins/ 30 | 31 | ${STATIC_DIR}/js/scribbler.js: ${PROJECT_FILES} 32 | node_modules/.bin/browserify $< -o $@ 33 | 34 | ${STATIC_DIR}/js/scribbler-min.js: ${STATIC_DIR}/js/scribbler.js 35 | node_modules/.bin/uglifyjs $^ -o $@ 36 | 37 | build-js: ${STATIC_DIR}/js/scribbler-min.js 38 | 39 | ${TESTS_DIR}/bundle.js: ${TESTS_DIR}/main.js ${PROJECT_FILES} ${TEST_FILES} 40 | node_modules/.bin/browserify -t browserify-compile-templates --extension=.html $< -o $@ 41 | 42 | test-js: ${TESTS_DIR}/bundle.js 43 | # Run the QUnit tests 44 | # Requires PhantomJS 45 | node_modules/.bin/phantomjs ${TESTS_DIR}/runner.js ${TESTS_DIR}/index.html 46 | 47 | compile-messages: 48 | # Create compiled .mo files for source distribution 49 | cd scribbler && django-admin.py compilemessages 50 | 51 | make-messages: 52 | # Create .po message files 53 | cd scribbler && django-admin.py makemessages -a && django-admin.py makemessages -d djangojs -a 54 | 55 | push-messages: make-messages 56 | # Create messages and push them to Transifex 57 | # Requires Transifex client 58 | tx push -s -t 59 | 60 | pull-messages: 61 | # Pull the latest translations from Transifex 62 | # Requires Transifex client 63 | tx pull -a 64 | 65 | prep-release: lint-js build-css build-js pull-messages compile-messages 66 | # Prepare for upcoming release 67 | # Check JS, create CSS, compile translations, run the test suite 68 | tox 69 | 70 | dist: clean fetch-static-libs lint-js build-js build-css 71 | python setup.py sdist 72 | 73 | clean: 74 | rm -f ${STATIC_DIR}/js/scribbler.js 75 | rm -f ${STATIC_DIR}/js/scribbler-min.js 76 | rm -rf ${STATIC_DIR}/css 77 | rm -f ${TESTS_DIR}/bundle.js 78 | rm -rf example/example/static/ 79 | rm -rf dist 80 | rm -rf .tox 81 | rm -rf node_modules 82 | 83 | .PHONY: build-css build-js lint-js test-js compile-messages make-messages push-messages pull-messages prep-release dist clean 84 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-scribbler 2 | =================== 3 | 4 | django-scribbler is an application for managing snippets of text for a Django website. 5 | Similar projects include django-flatblocks, django-chunks and django-pagelets. This 6 | project attempts to take some of the best concepts from those previous projects as 7 | well as focus on giving the users instant feedback inspired by Bret Victor's 8 | `Inventing on Principle `_ talk. 9 | 10 | .. image:: https://travis-ci.org/caktus/django-scribbler.svg?branch=master 11 | :alt: Build Status 12 | :target: https://travis-ci.org/caktus/django-scribbler 13 | 14 | 15 | Features 16 | -------------------------------------- 17 | 18 | - Simple template tag for defining snippet blocks with default text 19 | - Template tag for displaying and editing fields from arbitrary models 20 | - Front-end editing of snippets with the powerful `CodeMirror `_ editor 21 | - Live in-place preview of content while editing 22 | - The full power of the Django template language in the snippet blocks 23 | - Python 3 support 24 | 25 | 26 | Installation 27 | -------------------------------------- 28 | 29 | django-scribbler currently requires Django 2.2, 3.2, or 4.0 and Python >= 3.7. Older versions of 30 | django-scribbler may support older versions of Django and Python. 31 | 32 | To install from PyPi:: 33 | 34 | pip install django-scribbler 35 | 36 | .. note:: If you need to run an unreleased version from the repository, see the `Contributing Guide `_ for additional instructions. 37 | 38 | Documentation 39 | ----------------------------------- 40 | 41 | Documentation on using django-scribbler is available on 42 | `Read The Docs `_. 43 | 44 | 45 | License 46 | -------------------------------------- 47 | 48 | django-scribbler is released under the BSD License. See the 49 | `LICENSE `_ file for more details. 50 | 51 | 52 | Contributing 53 | -------------------------------------- 54 | 55 | If you think you've found a bug or are interested in contributing to this project 56 | check out `django-scribbler on Github `_. A 57 | full contributing guide can be found in the `online documentation `_. 58 | 59 | If you are interested in translating django-scribbler into your native language 60 | you can join the `Transifex project `_. 61 | 62 | Development sponsored by `Caktus Consulting Group, LLC 63 | `_. 64 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 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 " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-scribbler.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-scribbler.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/django-scribbler" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-scribbler" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/_static/images/context-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/docs/_static/images/context-screenshot.png -------------------------------------------------------------------------------- /docs/_static/images/filter-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/docs/_static/images/filter-screenshot.png -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # django-scribbler documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Jul 25 21:12:10 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | import scribbler 16 | import caktus_theme 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.insert(0, os.path.abspath('.')) 22 | 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be extensions 29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 30 | extensions = [] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix of source filenames. 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = u'django-scribbler' 46 | copyright = u'2012-2022, Caktus Consulting Group' 47 | 48 | # The version info for the project you're documenting, acts as replacement for 49 | # |version| and |release|, also used in various other places throughout the 50 | # built documents. 51 | # 52 | # The short X.Y version. 53 | version = '.'.join(scribbler.__version__.split('.')[0:2]) 54 | # The full version, including alpha/beta/rc tags. 55 | release = scribbler.__version__ 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | #language = None 60 | 61 | # There are two options for replacing |today|: either, you set today to some 62 | # non-false value, then it is used: 63 | #today = '' 64 | # Else, today_fmt is used as the format for a strftime call. 65 | #today_fmt = '%B %d, %Y' 66 | 67 | # List of patterns, relative to source directory, that match files and 68 | # directories to ignore when looking for source files. 69 | exclude_patterns = ['_build'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | #pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. See the documentation for 95 | # a list of builtin themes. 96 | html_theme = 'caktus' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | html_theme_options = { 102 | 'tagline': "An application for managing snippets of text for a Django website.", 103 | 'links': { 104 | 'pypi': 'http://pypi.python.org/pypi/django-scribbler', 105 | 'github': 'https://github.com/caktus/django-scribbler', 106 | 'demo': 'http://scribbler-mlavin.dotcloud.com/', 107 | } 108 | } 109 | 110 | # Add any paths that contain custom themes here, relative to this directory. 111 | html_theme_path = [caktus_theme.get_theme_dir()] 112 | 113 | # The name for this set of Sphinx documents. If None, it defaults to 114 | # " v documentation". 115 | #html_title = None 116 | 117 | # A shorter title for the navigation bar. Default is the same as html_title. 118 | #html_short_title = None 119 | 120 | # The name of an image file (relative to this directory) to place at the top 121 | # of the sidebar. 122 | #html_logo = None 123 | 124 | # The name of an image file (within the static path) to use as favicon of the 125 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 126 | # pixels large. 127 | #html_favicon = None 128 | 129 | # Add any paths that contain custom static files (such as style sheets) here, 130 | # relative to this directory. They are copied after the builtin static files, 131 | # so a file named "default.css" will overwrite the builtin "default.css". 132 | html_static_path = ['_static'] 133 | 134 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 135 | # using the given strftime format. 136 | #html_last_updated_fmt = '%b %d, %Y' 137 | 138 | # If true, SmartyPants will be used to convert quotes and dashes to 139 | # typographically correct entities. 140 | #html_use_smartypants = True 141 | 142 | # Custom sidebar templates, maps document names to template names. 143 | html_sidebars = caktus_theme.default_sidebars() 144 | 145 | # Additional templates that should be rendered to pages, maps page names to 146 | # template names. 147 | #html_additional_pages = {} 148 | 149 | # If false, no module index is generated. 150 | #html_domain_indices = True 151 | 152 | # If false, no index is generated. 153 | #html_use_index = True 154 | 155 | # If true, the index is split into individual pages for each letter. 156 | #html_split_index = False 157 | 158 | # If true, links to the reST sources are added to the pages. 159 | #html_show_sourcelink = True 160 | 161 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 162 | #html_show_sphinx = True 163 | 164 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 165 | #html_show_copyright = True 166 | 167 | # If true, an OpenSearch description file will be output, and all pages will 168 | # contain a tag referring to it. The value of this option must be the 169 | # base URL from which the finished HTML is served. 170 | #html_use_opensearch = '' 171 | 172 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 173 | #html_file_suffix = None 174 | 175 | # Output file base name for HTML help builder. 176 | htmlhelp_basename = 'django-scribblerdoc' 177 | 178 | 179 | # -- Options for LaTeX output -------------------------------------------------- 180 | 181 | latex_elements = { 182 | # The paper size ('letterpaper' or 'a4paper'). 183 | #'papersize': 'letterpaper', 184 | 185 | # The font size ('10pt', '11pt' or '12pt'). 186 | #'pointsize': '10pt', 187 | 188 | # Additional stuff for the LaTeX preamble. 189 | #'preamble': '', 190 | } 191 | 192 | # Grouping the document tree into LaTeX files. List of tuples 193 | # (source start file, target name, title, author, documentclass [howto/manual]). 194 | latex_documents = [ 195 | ('index', 'django-scribbler.tex', u'django-scribbler Documentation', 196 | u'Mark Lavin', 'manual'), 197 | ] 198 | 199 | # The name of an image file (relative to this directory) to place at the top of 200 | # the title page. 201 | #latex_logo = None 202 | 203 | # For "manual" documents, if this is true, then toplevel headings are parts, 204 | # not chapters. 205 | #latex_use_parts = False 206 | 207 | # If true, show page references after internal links. 208 | #latex_show_pagerefs = False 209 | 210 | # If true, show URL addresses after external links. 211 | #latex_show_urls = False 212 | 213 | # Documents to append as an appendix to all manuals. 214 | #latex_appendices = [] 215 | 216 | # If false, no module index is generated. 217 | #latex_domain_indices = True 218 | 219 | 220 | # -- Options for manual page output -------------------------------------------- 221 | 222 | # One entry per manual page. List of tuples 223 | # (source start file, name, description, authors, manual section). 224 | man_pages = [ 225 | ('index', 'django-scribbler', u'django-scribbler Documentation', 226 | [u'Mark Lavin'], 1) 227 | ] 228 | 229 | # If true, show URL addresses after external links. 230 | #man_show_urls = False 231 | 232 | 233 | # -- Options for Texinfo output ------------------------------------------------ 234 | 235 | # Grouping the document tree into Texinfo files. List of tuples 236 | # (source start file, target name, title, author, 237 | # dir menu entry, description, category) 238 | texinfo_documents = [ 239 | ('index', 'django-scribbler', u'django-scribbler Documentation', 240 | u'Mark Lavin', 'django-scribbler', 'One line description of project.', 241 | 'Miscellaneous'), 242 | ] 243 | 244 | # Documents to append as an appendix to all manuals. 245 | #texinfo_appendices = [] 246 | 247 | # If false, no module index is generated. 248 | #texinfo_domain_indices = True 249 | 250 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 251 | #texinfo_show_urls = 'footnote' 252 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | Contributing Guide 2 | ==================================== 3 | 4 | There are a number of ways to contribute to django-scribbler. If you are interested 5 | in making django-scribbler better then this guide will help you find a way to contribute. 6 | 7 | 8 | Ways to Contribute 9 | ------------------------------------ 10 | 11 | You can contribute to the project by submitting bug reports, feature requests 12 | or documentation updates through the Github `issues `_. 13 | 14 | 15 | Translate django-scribbler 16 | -------------------------------------- 17 | 18 | We are working towards translating django-scribbler into different languages. There 19 | are only a few strings to translate so it is a great way to be involved with the project. 20 | The translations are managed through `Transifex `_. 21 | Please do not submit translate requests/updates to the Github repo. 22 | 23 | 24 | Getting the Source 25 | ------------------------------------ 26 | 27 | You can clone the repository from Github:: 28 | 29 | git clone https://github.com/caktus/django-scribbler.git 30 | 31 | However this checkout will be read only. If you want to contribute code you should 32 | create a fork and clone your fork. You can then add the main repository as a remote:: 33 | 34 | git clone git@github.com:/django-scribbler.git 35 | cd django-scribbler 36 | git remote add upstream git://github.com/caktus/django-scribbler.git 37 | git fetch upstream 38 | 39 | django-scribbler requires a few static libraries which are not included in the repository. Before beginning 40 | development you should make sure you have these libraries with:: 41 | 42 | make fetch-static-libs 43 | 44 | 45 | Running the Tests 46 | ------------------------------------ 47 | 48 | When making changes to the code, either fixing bugs or adding features, you'll want to 49 | run the tests to ensure that you have not broken any of the existing functionality. 50 | With the code checked out and Django installed you can run the tests via:: 51 | 52 | python setup.py test 53 | 54 | or:: 55 | 56 | python runtests.py 57 | 58 | Note that the tests require the `mock `_ library. 59 | To test against multiple versions of Django you can use install and use ``tox>=1.4``. The 60 | ``tox`` command will run the tests against supported versions of Django and Python.:: 61 | 62 | # Build all environments 63 | tox 64 | # Build a single environment 65 | tox -e py37-2.2.X 66 | 67 | Building all environments will also build the documentation. More on that in the next 68 | section. 69 | 70 | The JS plugins are tested using the `QUnit ` testing framework. You can 71 | run the tests by opening ``scribbler\tests\qunit\index.html`` in your browser. You can also 72 | run the tests using the `PhantomJS ` headless runner. First install 73 | PhantomJS from NPM (requires at least 1.6):: 74 | 75 | # Install phantomjs from the NPM package 76 | npm install phantomjs -g 77 | # Run QUnit tests 78 | phantomjs scribbler/tests/qunit/runner.js scribbler/tests/qunit/index.html 79 | 80 | We've added a make command which you can use as well:: 81 | 82 | make test-js 83 | 84 | 85 | Building the Documentation 86 | ------------------------------------ 87 | 88 | This project aims to have a minimal core with hooks for customization. That makes documentation 89 | an important part of the project. Useful examples and notes on common use cases are a great 90 | way to contribute and improve the documentation. 91 | 92 | The docs are written in `ReST `_ 93 | and built using `Sphinx `_. As noted above you can use 94 | tox to build the documentation or you can build them on their own via:: 95 | 96 | tox -e docs 97 | 98 | or:: 99 | 100 | make html 101 | 102 | from inside the ``docs/`` directory. 103 | 104 | 105 | Building the CSS 106 | ------------------------------------ 107 | 108 | The CSS used by django-scribbler is built using `LESS `_. No changes 109 | should be made to the ``scribbler.css`` directly. Instead changes should be made to the ``scribbler.less`` 110 | file. After changes are made to ``scribbler.less`` you can create the new compressed CSS with the 111 | Node based complier. In addition, this inlines the required ``codemirror.css``:: 112 | 113 | make build-css 114 | 115 | The example project uses the client-side LESS compiler to make local development easier. 116 | 117 | 118 | Building the JS 119 | ------------------------------------ 120 | 121 | While it is not often needed for local development, the final released JS is bundled and minified 122 | using Browserify and UglifyJS2. To build ``bundle-min.js`` you should 123 | have the optimizer installed and run:: 124 | 125 | make build-js 126 | 127 | 128 | Coding Standards 129 | ------------------------------------ 130 | 131 | Code contributions should follow the `PEP8 `_ 132 | and `Django contributing style `_ 133 | standards. Please note that these are only guidelines. Overall code consistency 134 | and readability are more important than strict adherence to these guides. 135 | 136 | The Javascript is configured for some basic `JSHint `_ checks. Changes 137 | to the Javascript should pass without errors. You can check the Javascript file on the command line 138 | with Node based `CLI tool `_:: 139 | 140 | # Install jshint from the NPM package 141 | npm install jshint -g 142 | # Check the scribbler JS 143 | jshint scribbler/static/scribbler/js/ 144 | 145 | This can also be done with the ``make`` command:: 146 | 147 | make lint-js 148 | 149 | 150 | Submitting a Pull Request 151 | ------------------------------------ 152 | 153 | The easiest way to contribute code or documentation changes is through a pull request. 154 | For information on submitting a pull request you can read the Github help page 155 | https://help.github.com/articles/using-pull-requests. 156 | 157 | Pull requests are a place for the code to be reviewed before it is merged. This review 158 | will go over the coding style as well as if it solves the problem intended and fits 159 | in the scope of the project. It may be a long discussion or it might just be a simple 160 | thank you. 161 | 162 | Not necessarily every request will be merged but you should not take it personally 163 | if you change is not accepted. If you want to increase the chances of your change 164 | being incorporated then here are some tips. 165 | 166 | - Address a known issue. Preference is given to a request that fixes a currently open issue. 167 | - Include documentation and tests when appropriate. New features should be tested and documented. Bugfixes should include tests which demonstrate the problem. 168 | - Keep it simple. It's difficult to review a large block of code so try to keep the scope of the change small. 169 | 170 | You should also feel free to ask for help writing tests or writing documentation 171 | if you aren't sure how to go about it. 172 | 173 | 174 | Installing an Unstable Release 175 | ------------------------------------ 176 | 177 | Since the built CSS, JS and other static dependencies are not included in the repository, it is not 178 | possible to install django-scribbler directly from Github. If you want to install and unstable version 179 | of django-scribbler you have a few options. 180 | 181 | .. warning:: 182 | 183 | While we try to keep the ``master`` branch stable, there may be bugs or unfinished work there. It 184 | is recommended that you use a stable release of django-scribbler when possible. 185 | 186 | 187 | Install Local Build 188 | _____________________________________ 189 | 190 | The step overview for installing from a local build is: 191 | 192 | * Check out the repository 193 | * Install static libraries 194 | * Build CSS and JS 195 | * Install from local repository 196 | 197 | From the command line this would be:: 198 | 199 | git clone git://github.com/caktus/django-scribbler.git 200 | cd django-scribbler 201 | make fetch-static-libs build-css build-js 202 | 203 | 204 | Create an Unstable Package 205 | _____________________________________ 206 | 207 | Installing from a local build is probably a reasonable solution for a single person wanting 208 | to test out the current master or a feature branch in a large project. However, it isn't a good 209 | solution if you want to deploy this to a larger testing environment or multiple computers. The 210 | basic steps are more or less the same: 211 | 212 | * Check out the repository 213 | * Install static libraries 214 | * Build CSS and JS 215 | * Create a source distribution 216 | * Distribute .tar file 217 | * Install for packaged .tar 218 | 219 | From the command line this would be:: 220 | 221 | git clone git://github.com/caktus/django-scribbler.git 222 | cd django-scribbler 223 | make fetch-static-libs build-css build-js 224 | python setup.py sdist 225 | 226 | This will create a ``django-scribbler-X.X.X.tar.gz`` inside a ``dist/`` directory where 227 | ``X.X.X`` is the current ``scribbler.__version__``. This tar file would then be distributed 228 | using your favorite file hosting service (S3, Dropbox, etc). You can then install by using ``pip``:: 229 | 230 | pip install http://path-to-hostedfile/django-scribbler-X.X.X.tar.gz 231 | -------------------------------------------------------------------------------- /docs/editor.rst: -------------------------------------------------------------------------------- 1 | Using the Editor 2 | ==================================== 3 | 4 | django-scribbler makes use of `CodeMirror `_ to create 5 | a powerful client-side editor. We've added a couple features to make it easier 6 | when working with Django templates. 7 | 8 | 9 | Context Inspection 10 | ------------------------------------ 11 | 12 | When using the editor, you can inspect the current context by starting a variable 13 | node with ``{{`` and hitting tab. As noted in the quick start introduction, 14 | scribble content can be any valid Django template. The context provided when 15 | rendering the scribble includes anything added by the set of 16 | ``TEMPLATE_CONTEXT_PROCESSORS``. This would include ``STATIC_URL``, ``MEDIA_URL``, 17 | ``LANGUAGE_CODE``, the current ``user`` and others. Developers may choose to add 18 | context processors to include additional content for rendering scribbles. 19 | 20 | .. image:: /_static/images/context-screenshot.png 21 | 22 | 23 | Template Tag/Filter Completion 24 | ------------------------------------ 25 | 26 | Similar to how the editor can tab-complete the context variables, you can tab 27 | complete template tags when ``{%`` has been opened. The built-in filters can 28 | be tab-completed when the pipe ``|`` character is detected inside of a variable node. 29 | Currently this will only complete the built-in tags and filter and will not include any 30 | additional tags or filters which might be added by loading additional libraries inside the scribble. 31 | 32 | .. image:: /_static/images/filter-screenshot.png 33 | 34 | 35 | Saving Drafts 36 | ------------------------------------ 37 | 38 | While editing the scribble content, the editor will periodically save the current 39 | editor content as a draft. These drafts are saved on the client side using local storage 40 | (if supported by the browser) or a cookie. While that means you won't be able to see 41 | or use these drafts in another browser, it does mean that you work will not be lost 42 | if there is a failure on the server while saving changes or making edits. When the editor 43 | is opened it will restore any draft that is found. There is the option to discard a working draft. 44 | This will load the current scribble content into the editor. 45 | 46 | 47 | Full-Screen Mode 48 | ------------------------------------ 49 | 50 | Sometimes scribbles are rather long and it is useful to have more screen area to see the text. 51 | While the editor is in focus you may press `F11` to view the scribble in full-screen mode. To 52 | deactivate full-screen mode press either `F11` or `ESC`. 53 | -------------------------------------------------------------------------------- /docs/example.rst: -------------------------------------------------------------------------------- 1 | Example project 2 | =============== 3 | 4 | An example project is included that demonstrates basic usage of django-scribbler. 5 | Check out the source code and see the ``README.rst`` file in the ``example`` directory 6 | for more information. 7 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. django-scribbler documentation master file, created by 2 | sphinx-quickstart on Wed Jul 25 21:12:10 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. include:: ../README.rst 7 | 8 | Contents 9 | ------------------------------------ 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | example 15 | quick-start 16 | editor 17 | plugins 18 | contributing 19 | releases 20 | -------------------------------------------------------------------------------- /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. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-scribbler.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-scribbler.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/plugins.rst: -------------------------------------------------------------------------------- 1 | PLUGINS CURRENTLY BROKEN 2 | ==================================== 3 | It is possible that plugins may be added back in the future, but as of right now there is 4 | no working plugin feature. All following documentation only pertains to versions 0.6.0 5 | and earlier. 6 | 7 | 8 | Writing Editor Plugins 9 | ==================================== 10 | 11 | django-scribbler editor has some nice features like a live preview, auto-saving 12 | drafts and tab completion of template tags and template context. If you find yourself 13 | wanting to extend the functionality of the editor or top menu you can write a 14 | plugin. 15 | 16 | 17 | Basic Setup 18 | ------------------------------------ 19 | 20 | Client-side plugins to be added to a ``scribbler/js/plugins`` folder inside of 21 | static files folder. If you are writing this plugin for a reusable application 22 | then it would live inside of the app's ``static`` folder. The plugin name 23 | will be the name of the .js file. For example we might create a ``demo`` plugin 24 | by adding a ``scribbler/js/plugins/demo.js`` inside of our app ``static`` folder. 25 | 26 | 27 | Writing the Plugin 28 | ------------------------------------ 29 | 30 | django-scribbler uses `RequireJS `_ to load its Javascript 31 | requirements. Since this code is loaded in a closure, plugins need to be 32 | written in the `AMD `_ format. The 33 | basic format is:: 34 | 35 | define(function () { 36 | function plugin(editor, menu) { 37 | // Plugin code goes here... 38 | } 39 | return plugin; 40 | }); 41 | 42 | This has an advantage in that the plugin code can declare requirements as well as 43 | take advantage of existing modules used by the scribbler code. For instance if your 44 | plugin requires jQuery you can define the requirement:: 45 | 46 | define(['jquery'], function ($) { 47 | function plugin(editor, menu) { 48 | // Plugin code goes here... 49 | } 50 | return plugin; 51 | }); 52 | 53 | The plugin code itself should return a function which takes two arguments: ``editor`` 54 | and ``menu``. Each are the current instance of the editor and the menu respectively. 55 | Within the plugin you can add additional controls or event bindings. The APIs for 56 | both the editor and the menu are given below. 57 | 58 | .. note:: 59 | 60 | The plugins are executed after the editor and menu have been initialized but they 61 | are loaded asynchronously. That means the editor and menu may be fully rendered 62 | before the plugins are executed. 63 | 64 | 65 | Enabling the Plugin 66 | ------------------------------------ 67 | 68 | Plugins are enable by listing them in a ``data-scribbler-plugins`` attribute on the 69 | script tag:: 70 | 71 | 74 | 75 | If mutliple plugins used then they should be comma seperated as in 76 | ``data-scribbler-plugins="themes,other"``. 77 | 78 | .. note:: 79 | 80 | Since the plugins are loaded asynchronously they might not load in the same order 81 | they are listed. Plugins should be written and designed with that limitation in mind. 82 | 83 | 84 | Available Libraries 85 | ------------------------------------ 86 | 87 | As noted above you can use the ``define`` call to load additional dependencies/libraries 88 | for your plugin from the set of libraries used by django-scribbler. The available libraries 89 | are: 90 | 91 | - require: `RequireJS 2.1.4 `_ 92 | - jquery: `jQuery 1.8.3 `_ 93 | - codemirror: `CodeMirror 2.38 `_ 94 | - underscore: `Underscore 1.4.4 `_ 95 | - backbone: `Backbone 0.9.10 `_ 96 | 97 | 98 | Editor API 99 | ------------------------------------ 100 | 101 | The ``editor`` passed in the plugin is an instance of the ``ScribbleEditor`` defined 102 | in ``scribbler/js/scribbler-editor.js``. Below is a list of some of the most relevant 103 | functions and properties for controlling the editor. 104 | 105 | .. js:attribute:: editor.scribbles 106 | 107 | This is a jQuery object containing all of the scribble divs available on the page 108 | for editting. 109 | 110 | .. js:attribute:: editor.footerControls 111 | 112 | A jQuery object for the div wrapping all of the editor button controls (Save, 113 | Save Draft, Discard Draft, Close). If you want to add additional controls they 114 | should be appended here. 115 | 116 | .. js:attribute:: editor.editor 117 | 118 | ``editor.editor`` is the instance of the CodeMirror editor. You can manipulate this 119 | object to change the options with ``editor.editor.setOption``. See the CodeMirror 120 | usage manual for available options: http://codemirror.net/doc/manual.html 121 | 122 | .. js:function:: editor.open(scribble) 123 | 124 | Opens the editor to edit the given scribble. 125 | 126 | .. js:function:: editor.close() 127 | 128 | Closes the editor. 129 | 130 | .. js:function:: editor.submitPreview(force) 131 | 132 | Submits the current editor content to render the live preview. By default this 133 | will not submit if the editor is currently in the process of rendering a preview. 134 | Passing ``true`` into the call will force the submission. 135 | 136 | .. js:function:: editor.submitSave() 137 | 138 | Submits the editor content to save the current scribble content. By default 139 | the save will not be submitted if the last preview was not valid. 140 | 141 | .. js:function:: editor.getFormData() 142 | 143 | Prepares the current form data for preview/save submission. If you want to 144 | pass additional data to the server your plugin could extend this function. 145 | 146 | .. js:function:: editor.createDraft() 147 | 148 | Saves the current editor content as a local draft. 149 | 150 | .. js:function:: editor.restoreDraft() 151 | 152 | Restores the editor content from the last saved draft if available. 153 | 154 | .. js:function:: editor.deleteDraft() 155 | 156 | Discards last saved draft. 157 | 158 | .. js:function:: editor.setStatus(msg) 159 | 160 | Displays a status message to the user in the header of the editor. 161 | 162 | .. js:function:: editor.destroy() 163 | 164 | Removes the editor from the DOM and unbinds all event handling. 165 | 166 | 167 | Menu API 168 | ------------------------------------ 169 | 170 | The ``menu`` passed in the plugin is an instance of the ``ScribbleMenu`` defined 171 | in ``scribbler/js/scribbler-menu.js``. Below is a list of some of the most relevant 172 | functions and properties for controlling the menu. 173 | 174 | .. js:attribute:: menu.scribbles 175 | 176 | This is a jQuery object containing all of the scribble divs available on the page 177 | for editing. 178 | 179 | .. js:attribute:: menu.menuControls 180 | 181 | A jQuery object for the div wrapping all of the menu button controls. 182 | If you want to add additional controls they should be appended here. 183 | 184 | .. js:function:: menu.open() 185 | 186 | Opens the top menu bar. 187 | 188 | .. js:function:: menu.close() 189 | 190 | Closes the top menu bar. 191 | 192 | .. js:function:: menu.toggle() 193 | 194 | Toggles the open/close state of the top menu bar. 195 | 196 | .. js:function:: menu.highlight() 197 | 198 | Highlights all editable scribble areas on the page. 199 | 200 | .. js:function:: menu.destroy() 201 | 202 | Removes the menu from the DOM and unbinds all event handling. 203 | -------------------------------------------------------------------------------- /docs/quick-start.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | ==================================== 3 | 4 | Below are the basic steps need to get django-scribbler integrated into your 5 | Django project. 6 | 7 | Install Scribbler 8 | ------------------------------------ 9 | 10 | django-scribbler should be installed using ``pip``:: 11 | 12 | $ pip install django-scribbler 13 | 14 | .. note:: If you need to run an unreleased version from the repository, see :doc:`contributing` for additional instructions. 15 | 16 | Configure Settings 17 | ------------------------------------ 18 | 19 | You need to include ``scribbler`` to your installed apps. django-scribbler requires 20 | ``django.contrib.auth`` which in turn requires ``django.contrib.sessions`` 21 | which are enabled in Django by default. You will also need to include a context processor 22 | to include the current request in the template context. This is included by default 23 | in Django 1.8+ when using the ``startproject`` command. 24 | 25 | .. code-block:: python 26 | 27 | INSTALLED_APPS = ( 28 | # Required contrib apps 29 | 'django.contrib.auth', 30 | 'django.contrib.sessions', 31 | # Other installed apps would go here 32 | 'scribbler', 33 | ) 34 | 35 | ... 36 | 37 | TEMPLATES = [ # example config untill 'context_processors' your config maydiffer 38 | { 39 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 40 | 'DIRS': [ 41 | ], 42 | 'APP_DIRS': True, 43 | 'OPTIONS': { 44 | 'context_processors': [ 45 | # add required context processors here: 46 | 'django.template.context_processors.request', 47 | 'django.contrib.auth.context_processors.auth', 48 | # Other context processors would go here 49 | ], 50 | 'debug': False, 51 | }, 52 | }, 53 | ], 54 | 55 | 56 | Django 1.8+ also supports custom template engines 57 | but this is not supported at the moment by django-scribbler 58 | (only the Django and Jinja2 template engines are supported). 59 | 60 | For the context processor to have any effect you need to make sure that the template 61 | is rendered using a RequestContext. This is done for you with the 62 | `render `_ shortcut. 63 | 64 | django-scribbler aggressively caches the scribble content. By default the scribble 65 | content is cached for 12 hours. You have the option to configure this cache timeout 66 | with the ``SCRIBBLER_CACHE_TIMEOUT`` setting. The value should be the timeout in 67 | seconds. 68 | 69 | 70 | Configure Urls 71 | ------------------------------------ 72 | 73 | You should include the scribbler urls in your root url patterns. 74 | 75 | .. code-block:: python 76 | 77 | urlpatterns = [ 78 | # Other url patterns would go here 79 | url(r'^scribbler/', include('scribbler.urls')), 80 | ] 81 | 82 | 83 | Create Database Tables 84 | ------------------------------------ 85 | 86 | You'll need to create the necessary database tables for storing scribble content. 87 | To run migrations call:: 88 | 89 | python manage.py migrate scribbler 90 | 91 | 92 | User Permissions 93 | ------------------------------------ 94 | 95 | To edit scribbles on the front-end users must have the ``scribbler.add_scribble`` 96 | and ``scribbler.change_scribble`` permissions. You can configure uses to have 97 | these permissions through the users section of the Django admin. Superusers have 98 | all of these permissions by default. 99 | 100 | Similarly, to edit fields from models on the front-end, users must have "change" 101 | permission for the models being edited. Again these permissions can be configured 102 | through the users section of the Django admin. 103 | 104 | 105 | Include Static Resources 106 | ------------------------------------ 107 | 108 | django-scribbler includes both CSS and JS resources which need to be included in your 109 | templates to handle the front-end content management. Since you may want to include 110 | scribbles on any page on your site these should be included in your base template ````. 111 | 112 | .. code-block:: html 113 | 114 | 115 | 116 | 117 | This uses `Browserify `_ to load the additional JS resources. The front-end 118 | editor uses `CodeMirror `_ (currently using v5.10) which is included in the distribution. 119 | Both Browserify and CodeMirror are available a MIT-style license compatible with 120 | this project's BSD license. You can find the license files included in 121 | ``scribbler/static/scribbler/libs/``. 122 | 123 | 124 | Place Scribbles in Your Template 125 | ------------------------------------ 126 | 127 | You are now ready to place the scribble content blocks throughout your templates. 128 | This is done with the ``scribble`` block tag. The basic usage of the tag takes 129 | one argument which is the slug name for the scribble, and contains the default 130 | content for the scribble. 131 | 132 | Slugs must be unique per 133 | url/slug pair. That means you cannot use the same slug more than once in the 134 | template, but you can use the same slug in different templates, as long as they 135 | are rendered on different urls. 136 | 137 | .. code-block:: html 138 | 139 | {% load scribbler_tags %} 140 | {% scribble 'header' %} 141 |

Blip {% now 'Y' %} {{ STATIC_URL|upper }}

142 | {% endscribble %} 143 | 144 | The content inside the block is the default content that will be rendered if a 145 | matching scribble in the database is not found. 146 | 147 | The ``scribble`` tag can take an optional argument which allows for defining 148 | shared scribbles. 149 | 150 | .. code-block:: html 151 | 152 | {% load scribbler_tags %} 153 | {% scribble 'header' 'shared' %} 154 |

Blip {% now 'Y' %} {{ STATIC_URL|upper }}

155 | {% endscribble %} 156 | 157 | The second argument defines a lookup vector to a shared scribble. This overrides 158 | the url portion of the url/slug pair, and allows for reuse across multiple templates. 159 | 160 | In other words, ordinarily even scribbles with the same slug are considered 161 | different when they occur on pages with different URLs, but if you also give them 162 | the same second argument, they will be considered the same - they will share 163 | the same content. 164 | 165 | .. note:: 166 | 167 | Scribble content can be any valid Django template. However the content does 168 | not include all of the context of the template. Only the context provided 169 | by the set of ``context_processors`` from the ``TEMPLATES`` configuration. 170 | 171 | 172 | A second scribbler tag, ``scribble_field``, allows for editing fields of model instances. 173 | For example, suppose you have a ``DaysLog`` model with a field named ``happenings``. Suppose 174 | an instance of this model is passed into your template in the template variable ``days_log``. 175 | Then the ``happenings`` field of this ``DaysLog`` instance can be displayed and edited on the 176 | page by including this ``scribble_field`` template tag in the template for the page: 177 | 178 | .. code-block:: html 179 | 180 | {% load scribbler_tags %} 181 | {% scribble_field days_log 'happenings' %} 182 | 183 | .. note:: 184 | 185 | The logged-in user must have "change" permission for the model in order for 186 | the model instance to be editable on the page. 187 | 188 | That should be enough to get you up and running with django-scribbler. 189 | -------------------------------------------------------------------------------- /docs/releases.rst: -------------------------------------------------------------------------------- 1 | Release History 2 | ==================================== 3 | 4 | Release and change history for django-scribbler 5 | 6 | v1.1.0 (Release 2021-12-20) 7 | ------------------------------------ 8 | 9 | - Add support for Django 3.2 through 4.0 (Thanks Petr Dlouhý) 10 | 11 | 12 | v1.0.0 (Release 2019-10-28) 13 | ------------------------------------ 14 | 15 | - Add support for Django 2.0 through 2.2 (Thanks Petr Dlouhý) 16 | - Drop support for Django < 2 17 | - Drop support for Python 2 18 | 19 | v0.9.0 (Release 2017-12-11) 20 | ------------------------------------ 21 | 22 | - Add support for Django 1.11 and Python 3.6 23 | - Drop support for Django 1.9 and Python 3.2-3.3. 24 | - Resulting support: Django 1.8, 1.10, or 1.11, and Python 2.7 or >= 3.4 25 | - Fix how styles are loaded in the example project. 26 | 27 | v0.8.0 (Release 2016-12-14) 28 | ------------------------------------ 29 | 30 | - Add support for Django 1.10, Python 3.5 31 | - Improvements to docs 32 | - Fix: Disallow scribble previews if the user does not have permission. 33 | - Updated example project (including removing Twitter Bootstrap from it) 34 | 35 | 36 | v0.7.0 (Released 2016-01-18) 37 | ------------------------------------ 38 | 39 | The release removed the use of RequireJS for bundling the required assets 40 | and instead uses Browserify to create single required JS file. Updating 41 | from a previous version requires changing how the JS file is included. 42 | 43 | .. code-block:: html 44 | 45 | 46 | 47 | should be updated to 48 | 49 | .. code-block:: html 50 | 51 | 52 | 53 | 54 | Features 55 | _________________ 56 | 57 | - Added support for Django 1.9 58 | - Added support for full screen edits 59 | - Updated to CodeMirror 5.10 60 | - Updated to Backbone 1.2.3, Underscore 1.8.3, jQuery 2.2.0 61 | 62 | Backwards Incompatible Changes 63 | __________________________________ 64 | 65 | - The update to jQuery 2.2 drops support for IE < 9 66 | - The refactor to use browserify has dropped support for front-end plugins 67 | - Dropped support for Django<=1.7, Python2.6, and Python3.2 (#101) 68 | - South migrations have been removed since Django 1.6 support was removed 69 | 70 | 71 | v0.6.0 (Released 2015-10-07) 72 | ------------------------------------ 73 | 74 | This release fixes some lingering issues with Django 1.8 support and integrates 75 | Wheel support and features from the latest versions of Tox. 76 | 77 | Features 78 | _________________ 79 | 80 | - Added Wheel support (#96) 81 | - Updated Tox and Travis to work with Tox 2.0+ (#90) 82 | - Changed button color on editor 83 | - Confirmed Python 3.4 support 84 | 85 | Bug Fixes 86 | _________________ 87 | 88 | - Fixed issues with Django 1.8 compatibility (#84) 89 | 90 | 91 | v0.5.3 (Released 2014-06-13) 92 | ------------------------------------ 93 | 94 | - Fixed issues with Python 3 compatibility 95 | - Fixed issues with Django 1.7+ compatibility 96 | - Fixed issues with IE 8 compatibility 97 | 98 | 99 | v0.5.2 (Released 2013-05-02) 100 | ------------------------------------ 101 | 102 | - Fixed issue with scribbler styles overlapping/overriding site styles. See #73 103 | 104 | 105 | v0.5.1 (Released 2013-03-03) 106 | ------------------------------------ 107 | 108 | - Bug fix release for broken packaging 109 | 110 | 111 | v0.5.0 (Released 2013-03-03) 112 | ------------------------------------ 113 | 114 | This release includes a major refactor of the JS code and adds support for writing 115 | client-side plugins for customizing the editor. 116 | 117 | Features 118 | _________________ 119 | 120 | - Upgraded to CodeMirror 3.02 121 | - Additional build/development utilities and documentation 122 | - Started including a minified and optimized version of scribbler.js for production usage 123 | - CSS is now built to include the base CodeMirror CSS and does not need to be added to the template separately 124 | 125 | Bug Fixes 126 | _________________ 127 | 128 | - Fixed a bug where you could not follow an internal link in the scribble content. See #66 129 | 130 | Backwards Incompatible Changes 131 | __________________________________ 132 | 133 | The static dependencies (RequireJS, CodeMirror and jQuery) were originally included in the repository 134 | but have been removed. These are still included in the final distribution. However, if you installing 135 | django-scribbler directly from git these will no longer be available. See the :doc:`contributing guide ` 136 | for more information on building/installing an unstable version. 137 | 138 | 139 | v0.4.0 (Released 2013-01-01) 140 | ------------------------------------ 141 | 142 | The length of the slug field has been reduced to fix problems with the unique contraint 143 | on MySQL. Upgrading requires running a migration:: 144 | 145 | manage.py migrate scribbler 146 | 147 | Features 148 | _________________ 149 | 150 | - Top level menu to reveal all editable sections on the page 151 | - i18n support and initial French translation thanks to Nicolas Ippolito 152 | - Created Transifex group for translations 153 | - Added optional parameter to scribble tag to support shared scribbles thanks to David Ray 154 | - Added the ability to discard a saved draft 155 | 156 | Bug Fixes 157 | _________________ 158 | 159 | - Fixed bug with newly included jQuery overriding an existing version. See #53 160 | - Fixed bug with unique index on MySQL thanks to David Ray. See #61 161 | 162 | Backwards Incompatible Changes 163 | __________________________________ 164 | 165 | - The fix for #61 reduced the length of the slug field from 255 characters to 64 166 | 167 | 168 | v0.3.0 (Released 2012-10-26) 169 | ------------------------------------ 170 | 171 | Features 172 | _________________ 173 | 174 | - Autocomplete for Django template tags and filters 175 | - New scribble_field template tag to allow editing of fields in arbitrary models 176 | 177 | 178 | v0.2.1 (Released 2012-10-12) 179 | ------------------------------------ 180 | 181 | Bug Fixes 182 | _________________ 183 | 184 | - Preview was broken when scribble was saved due to unique constraint. See #34 185 | 186 | 187 | v0.2.0 (Released 2012-10-12) 188 | ------------------------------------ 189 | 190 | The editor now saves drafts on the client side by default. Python 3 support is 191 | added when using the lastest Django master. There is also some additional documentation. 192 | 193 | A unique constraint was added and upgrading from v0.1 does require a migration:: 194 | 195 | manage.py migrate scribbler 196 | 197 | - Added experimental Python >= 3.2 support when using Django 1.5dev 198 | - Caktus Consulting Group has taken over the primary development 199 | - Added the ability to save as a draft on the client side 200 | - Added an official contributing guide 201 | 202 | Bug Fixes 203 | _________________ 204 | 205 | - Added unique constraint for url/slug pair. South migration is included. 206 | 207 | 208 | v0.1.1 (Released 2012-08-25) 209 | ------------------------------------ 210 | 211 | Minor bug fix release for some JS and CSS issues. 212 | 213 | Bug Fixes 214 | _________________ 215 | 216 | - Fixed issue with the content editor z-index allowing content in front when open 217 | - Fixed issue where links within editable content could not be clicked by editors 218 | 219 | 220 | v0.1.0 (Released 2012-07-28) 221 | ------------------------------------ 222 | 223 | - Initial public release. 224 | 225 | Features 226 | _________________ 227 | 228 | - Template tag for rendering content blocks 229 | - CodeMirror editor integration 230 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # Requirements for ReadTheDocs to build the documentation 2 | caktus-sphinx-theme==0.1.0 3 | -------------------------------------------------------------------------------- /docs/templatetags.rst: -------------------------------------------------------------------------------- 1 | Template Tags 2 | ==================================== 3 | 4 | 5 | 6 | ``scribble`` 7 | ------------------------------------ 8 | 9 | 10 | 11 | ``scribble_field`` 12 | ------------------------------------ 13 | -------------------------------------------------------------------------------- /example/README.rst: -------------------------------------------------------------------------------- 1 | Example Project 2 | =============== 3 | 4 | This is a very simple example project to demonstrate the use of django-scribbler. 5 | 6 | Setup instructions: 7 | 8 | 1. Create a virtualenv and install requirements:: 9 | 10 | mkvirtualenv -p $(which python3) scribbler 11 | pip install -r requirements.txt 12 | 13 | #. Setup the SQLite database and a superuser (set password to "demo"):: 14 | 15 | python manage.py migrate 16 | python manage.py createsuperuser --username demo 17 | 18 | #. Run the server:: 19 | 20 | python manage.py runserver 21 | -------------------------------------------------------------------------------- /example/dayslog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/dayslog/__init__.py -------------------------------------------------------------------------------- /example/dayslog/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import DaysLog 4 | 5 | admin.site.register(DaysLog) 6 | -------------------------------------------------------------------------------- /example/dayslog/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models, migrations 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ] 9 | 10 | operations = [ 11 | migrations.CreateModel( 12 | name='DaysLog', 13 | fields=[ 14 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 15 | ('day', models.DateField(unique=True)), 16 | ('happenings', models.TextField(default=b'

Things that happened today

\n
    \n
  1. Clock ticked over midnight.
  2. \n
')), 17 | ], 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /example/dayslog/migrations/0002_alter_dayslog_happenings_alter_dayslog_id.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0 on 2021-12-20 21:31 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dayslog', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='dayslog', 15 | name='happenings', 16 | field=models.TextField(default='

Things that happened today

\n
    \n
  1. Clock ticked over midnight.
  2. \n
'), 17 | ), 18 | migrations.AlterField( 19 | model_name='dayslog', 20 | name='id', 21 | field=models.AutoField(primary_key=True, serialize=False), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /example/dayslog/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/dayslog/migrations/__init__.py -------------------------------------------------------------------------------- /example/dayslog/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | class DaysLog(models.Model): 4 | id = models.AutoField(primary_key=True) 5 | day = models.DateField(unique=True) 6 | happenings = models.TextField( 7 | default="

Things that happened today

\n" 8 | "
    \n
  1. Clock ticked over midnight.
  2. \n
") 9 | 10 | def __unicode__(self): 11 | return "Day's log for {0}".format(self.day) 12 | -------------------------------------------------------------------------------- /example/dayslog/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /example/dayslog/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /example/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/example/__init__.py -------------------------------------------------------------------------------- /example/example/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for example project. 2 | import os 3 | 4 | PROJECT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir)) 5 | 6 | DEBUG = True 7 | 8 | ADMINS = ( 9 | # ('Your Name', 'your_email@example.com'), 10 | ) 11 | 12 | MANAGERS = ADMINS 13 | 14 | DATABASES = { 15 | 'default': { 16 | 'ENGINE': 'django.db.backends.sqlite3', 17 | 'NAME': os.path.join(PROJECT_PATH, 'blip.db'), 18 | 'USER': '', # Not used with sqlite3. 19 | 'PASSWORD': '', # Not used with sqlite3. 20 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 21 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 22 | } 23 | } 24 | 25 | # Local time zone for this installation. Choices can be found here: 26 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 27 | # although not all choices may be available on all operating systems. 28 | # On Unix systems, a value of None will cause Django to use the same 29 | # timezone as the operating system. 30 | # If running in a Windows environment this must be set to the same as your 31 | # system time zone. 32 | TIME_ZONE = 'America/Chicago' 33 | 34 | # Language code for this installation. All choices can be found here: 35 | # http://www.i18nguy.com/unicode/language-identifiers.html 36 | LANGUAGE_CODE = 'en-us' 37 | 38 | SITE_ID = 1 39 | 40 | # If you set this to False, Django will make some optimizations so as not 41 | # to load the internationalization machinery. 42 | USE_I18N = True 43 | 44 | # If you set this to False, Django will not format dates, numbers and 45 | # calendars according to the current locale. 46 | USE_L10N = True 47 | 48 | # If you set this to False, Django will not use timezone-aware datetimes. 49 | USE_TZ = True 50 | 51 | # Absolute filesystem path to the directory that will hold user-uploaded files. 52 | # Example: "/home/media/media.lawrence.com/media/" 53 | MEDIA_ROOT = '' 54 | 55 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 56 | # trailing slash. 57 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 58 | MEDIA_URL = '/media/' 59 | 60 | # Absolute path to the directory static files should be collected to. 61 | # Don't put anything in this directory yourself; store your static files 62 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 63 | # Example: "/home/media/media.lawrence.com/static/" 64 | STATIC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), 'static')) 65 | 66 | # URL prefix for static files. 67 | # Example: "http://media.lawrence.com/static/" 68 | STATIC_URL = '/static/' 69 | 70 | # Additional locations of static files 71 | STATICFILES_DIRS = ( 72 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 73 | # Always use forward slashes, even on Windows. 74 | # Don't forget to use absolute paths, not relative paths. 75 | os.path.join(PROJECT_PATH, 'static'), 76 | ) 77 | 78 | # Make this unique, and don't share it with anybody. 79 | SECRET_KEY = 'zl8n#*c2)rso==w@9ygsnpy&0xpy5mkv15rxb5r+&!e&k&-@fg' 80 | 81 | TEMPLATES = [ 82 | { 83 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 84 | 'DIRS': [ 85 | os.path.join(PROJECT_PATH, 'templates'), 86 | ], 87 | 'APP_DIRS': True, 88 | 'OPTIONS': { 89 | 'context_processors': [ 90 | 'django.contrib.auth.context_processors.auth', 91 | 'django.contrib.messages.context_processors.messages', 92 | 'django.template.context_processors.debug', 93 | 'django.template.context_processors.media', 94 | 'django.template.context_processors.i18n', 95 | 'django.template.context_processors.static', 96 | 'django.template.context_processors.request', 97 | ], 98 | }, 99 | }, 100 | ] 101 | 102 | MIDDLEWARE = ( 103 | 'django.middleware.security.SecurityMiddleware', 104 | 'django.contrib.sessions.middleware.SessionMiddleware', 105 | 'django.middleware.common.CommonMiddleware', 106 | 'django.middleware.csrf.CsrfViewMiddleware', 107 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 108 | 'django.contrib.messages.middleware.MessageMiddleware', 109 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 110 | ) 111 | 112 | ROOT_URLCONF = 'example.urls' 113 | 114 | # Python dotted path to the WSGI application used by Django's runserver. 115 | WSGI_APPLICATION = 'example.wsgi.application' 116 | 117 | INSTALLED_APPS = ( 118 | 'django.contrib.auth', 119 | 'django.contrib.contenttypes', 120 | 'django.contrib.sessions', 121 | 'django.contrib.sites', 122 | 'django.contrib.messages', 123 | 'django.contrib.staticfiles', 124 | # Uncomment the next line to enable the admin: 125 | 'django.contrib.admin', 126 | # Uncomment the next line to enable admin documentation: 127 | # 'django.contrib.admindocs', 128 | 'scribbler', 129 | 'dayslog', 130 | ) 131 | 132 | # A sample logging configuration. The only tangible logging 133 | # performed by this configuration is to send an email to 134 | # the site admins on every HTTP 500 error when DEBUG=False. 135 | # See http://docs.djangoproject.com/en/dev/topics/logging for 136 | # more details on how to customize your logging configuration. 137 | LOGGING = { 138 | 'version': 1, 139 | 'disable_existing_loggers': False, 140 | 'filters': { 141 | 'require_debug_false': { 142 | '()': 'django.utils.log.RequireDebugFalse' 143 | } 144 | }, 145 | 'handlers': { 146 | 'mail_admins': { 147 | 'level': 'ERROR', 148 | 'filters': ['require_debug_false'], 149 | 'class': 'django.utils.log.AdminEmailHandler' 150 | } 151 | }, 152 | 'loggers': { 153 | 'django.request': { 154 | 'handlers': ['mail_admins'], 155 | 'level': 'ERROR', 156 | 'propagate': True, 157 | }, 158 | } 159 | } 160 | 161 | INTERNAL_IPS = ('127.0.0.1', ) 162 | -------------------------------------------------------------------------------- /example/example/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import include, path 2 | 3 | from django.contrib import admin 4 | from django.views.i18n import JavaScriptCatalog 5 | 6 | from example.views import homepage 7 | admin.autodiscover() 8 | 9 | 10 | urlpatterns = [ 11 | # Examples: 12 | # path(r'^$', 'example.views.home', name='home'), 13 | # path(r'^example/', include('example.foo.urls')), 14 | 15 | # Uncomment the admin/doc line below to enable admin documentation: 16 | # path(r'^admin/doc/', include('django.contrib.admindocs.urls')), 17 | 18 | # Uncomment the next line to enable the admin: 19 | path(r'admin/', admin.site.urls), 20 | path(r'scribble/', include('scribbler.urls')), 21 | path(r'jsi18n/', JavaScriptCatalog.as_view(packages=['scribbler']), name='jsi18n'), 22 | path(r'', homepage, name='home'), 23 | ] 24 | -------------------------------------------------------------------------------- /example/example/views.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.shortcuts import render, redirect 4 | from django.contrib.auth import login 5 | from django.contrib.auth.forms import AuthenticationForm 6 | 7 | from dayslog.models import DaysLog 8 | 9 | 10 | def homepage(request): 11 | 12 | if request.method == 'POST': 13 | form = AuthenticationForm(request=request, data=request.POST) 14 | if form.is_valid(): 15 | user = form.get_user() 16 | login(request, user) 17 | return redirect('home') 18 | else: 19 | form = AuthenticationForm(request=request) 20 | 21 | request.session.set_test_cookie() 22 | context = { 23 | 'form': form 24 | } 25 | 26 | if request.user.is_authenticated: 27 | context['days_log'], created = DaysLog.objects.get_or_create(day=datetime.date.today()) 28 | 29 | return render(request, 'home.html', context) 30 | -------------------------------------------------------------------------------- /example/example/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for example project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /example/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /example/nginx.conf: -------------------------------------------------------------------------------- 1 | location /media/ { 2 | root /home/dotcloud/data; 3 | } 4 | 5 | location /static/ { 6 | root /home/dotcloud/volatile; 7 | } 8 | -------------------------------------------------------------------------------- /example/postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python manage.py syncdb --noinput 3 | python manage.py migrate 4 | mkdir -p /home/dotcloud/data/media /home/dotcloud/volatile/static 5 | python manage.py collectstatic --noinput 6 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.25 2 | django-scribbler==1.0.0 3 | -------------------------------------------------------------------------------- /example/static/fonts/cutive-regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/static/fonts/cutive-regular-webfont.eot -------------------------------------------------------------------------------- /example/static/fonts/cutive-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/static/fonts/cutive-regular-webfont.ttf -------------------------------------------------------------------------------- /example/static/fonts/cutive-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/static/fonts/cutive-regular-webfont.woff -------------------------------------------------------------------------------- /example/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/static/img/favicon.png -------------------------------------------------------------------------------- /example/static/img/scribbler-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/static/img/scribbler-logo.png -------------------------------------------------------------------------------- /example/static/less/styles.less: -------------------------------------------------------------------------------- 1 | /* 2 | * django-scribbler 3 | * Source: https://github.com/caktus/django-scribbler 4 | * Docs: http://django-scribbler.readthedocs.org/ 5 | * 6 | * Copyright 2012-2016, Caktus Consulting Group, LLC 7 | * BSD License 8 | * 9 | */ 10 | 11 | /* Variables 12 | ======================== */ 13 | @scribblerRed: #CD0000; 14 | @black: #000; 15 | @white: #fff; 16 | @gray: #555; 17 | @grayLighter: #eee; 18 | 19 | /* Generic styles 20 | ======================== */ 21 | body { 22 | margin: 0; 23 | background: @grayLighter; 24 | &.login-home { 25 | background: @gray; 26 | } 27 | } 28 | 29 | @font-face { 30 | font-family: 'cutiveregular'; 31 | src: url('../fonts/cutive-regular-webfont.eot'); 32 | src: url('../fonts/cutive-regular-webfont.eot?#iefix') format('embedded-opentype'), 33 | url('../fonts/cutive-regular-webfont.woff') format('woff'), 34 | url('../fonts/cutive-regular-webfont.ttf') format('truetype'), 35 | url('../fonts/cutive-regular-webfont.svg#cutiveregular') format('svg'); 36 | font-weight: normal; 37 | font-style: normal; 38 | } 39 | 40 | span { font-weight: bold; } 41 | 42 | h1, h2 { 43 | font-family: 'cutiveregular', Georgia, serif; 44 | font-weight: normal; 45 | font-size: 36px; 46 | color: @black; 47 | } 48 | 49 | h1 { color: @black; } 50 | h2 { color: @scribblerRed; } 51 | 52 | p.lead { 53 | font-family: 'cutiveregular', Georgia, serif; 54 | font-weight: normal; 55 | } 56 | 57 | .content { 58 | margin-top: 100px; 59 | } 60 | 61 | #page { 62 | margin: 0 auto; 63 | max-width: 979px; 64 | position: relative; 65 | width: 80%; 66 | .scribbler-test { 67 | padding: 20px; 68 | background-color: @white; 69 | border: 1px @grayLighter solid; 70 | font-size: 15px; 71 | } 72 | } 73 | 74 | /* Login form styles 75 | ======================== */ 76 | #login-content { 77 | margin: 100px auto 20px; 78 | text-align: center; 79 | width: 100%; 80 | .modal { 81 | background-color: @white; 82 | color: @gray; 83 | border-radius: 8px; 84 | margin: 0 auto; 85 | padding: 40px 40px 10px 30px; 86 | width: 275px; 87 | } 88 | form { padding-top: 15px; } 89 | .errorlist { 90 | list-style: none; 91 | color: @white; 92 | } 93 | input[type=submit] { 94 | font-weight: bold; 95 | font-family: 'cutiveregular', Georgia, serif; 96 | } 97 | 98 | } 99 | 100 | small { 101 | text-align: center; 102 | font-family: 'cutiveregular', Georgia, serif; 103 | font-weight: normal; 104 | margin: 50px auto 20px; 105 | display: block; 106 | color: @white; 107 | font-size: 13px; 108 | span:last-child { padding-left: 15px;} 109 | } 110 | -------------------------------------------------------------------------------- /example/templates/404.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/templates/404.html -------------------------------------------------------------------------------- /example/templates/500.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/example/templates/500.html -------------------------------------------------------------------------------- /example/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | {% load scribbler_tags static %} 3 | 4 | 5 | 6 | scribbler. || Example Project 7 | 8 | 9 | 10 | 11 | 12 | {% block extra-meta %}{% endblock %} 13 | 14 | 15 | 16 | 17 | 18 |
19 | {% block content %} 20 | {% if user.is_authenticated %} 21 |
22 |

scribbler. demo

23 |

Hover over the text below and click to start editing the scribbles.

24 |
25 | {% scribble 'header' %} 26 |

Blip {% now 'Y' %} {{ STATIC_URL|upper }}

27 | {% endscribble %} 28 |

Tell us what's been happening today:

29 | {% scribble_field days_log 'happenings' %} 30 |
31 |
32 | {% else %} 33 |
34 | {{ form.non_field_errors }} 35 | 47 |
48 | USERNAME: demo PASSWORD: demo 49 | {% endif %} 50 | {% endblock %} 51 |
52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/wsgi.py: -------------------------------------------------------------------------------- 1 | "WSGI file for Dotcloud deployment." 2 | import os 3 | 4 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") 5 | 6 | from django.core.wsgi import get_wsgi_application 7 | application = get_wsgi_application() 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "django-scribbler", 3 | "version": "1.0.0", 4 | "description": " An application for managing snippets of text for a Django website.", 5 | "keywords": [], 6 | "main": "scribbler/static/scribbler/js/scribbler.js", 7 | "directories": { 8 | "doc": "docs", 9 | "example": "example" 10 | }, 11 | "scripts": { 12 | "test": "python runtests.py" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/caktus/django-scribbler.git" 17 | }, 18 | "license": "BSD-2-Clause", 19 | "bugs": { 20 | "url": "https://github.com/caktus/django-scribbler/issues" 21 | }, 22 | "dependencies": { 23 | "backbone": ">=1.2 <1.3", 24 | "codemirror": "^5.40.0", 25 | "jquery": ">=2.2 <2.3", 26 | "jshint": "^2.9.6", 27 | "less": "^2.7.3", 28 | "phantomjs-prebuilt": "^2.1.16", 29 | "underscore": ">=1.8 <1.9" 30 | }, 31 | "devDependencies": { 32 | "browserify": "^11.2.0", 33 | "browserify-compile-templates": "^0.2.0", 34 | "less": "^2.5.3", 35 | "uglify-js": "^2.5.0" 36 | }, 37 | "author": "Mark Lavin", 38 | "contributors": [ 39 | { 40 | "name": "Julia Elman" 41 | }, 42 | { 43 | "name": "Omar Estrella" 44 | }, 45 | { 46 | "name": "Karen Tracey" 47 | }, 48 | { 49 | "name": "Caleb Smith" 50 | }, 51 | { 52 | "name": "Nicolas Ippolito" 53 | }, 54 | { 55 | "name": "David Ray" 56 | }, 57 | { 58 | "name": "Yaroslav Klyuyev" 59 | }, 60 | { 61 | "name": "Ben Phillips" 62 | }, 63 | { 64 | "name": "Rebecca Conley" 65 | }, 66 | { 67 | "name": "Victor Rocha" 68 | }, 69 | { 70 | "name": "Dan Poirier", 71 | "email": "dpoirier@caktusgroup.com" 72 | } 73 | ] 74 | } 75 | -------------------------------------------------------------------------------- /runtests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import os 4 | from optparse import OptionParser 5 | 6 | import django 7 | from django.conf import settings 8 | from django.test.utils import get_runner 9 | 10 | if not settings.configured: 11 | settings.configure( 12 | DATABASES={ 13 | 'default': { 14 | 'ENGINE': 'django.db.backends.sqlite3', 15 | 'NAME': ':memory:', 16 | } 17 | }, 18 | INSTALLED_APPS=( 19 | 'django.contrib.auth', 20 | 'django.contrib.contenttypes', 21 | 'django.contrib.sessions', 22 | 'django.contrib.staticfiles', 23 | 'scribbler', 24 | ), 25 | MIDDLEWARE=( 26 | 'django.middleware.security.SecurityMiddleware', 27 | 'django.contrib.sessions.middleware.SessionMiddleware', 28 | 'django.middleware.common.CommonMiddleware', 29 | 'django.middleware.csrf.CsrfViewMiddleware', 30 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 31 | 'django.contrib.messages.middleware.MessageMiddleware', 32 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 33 | ), 34 | SECRET_KEY='super-secret', 35 | 36 | ROOT_URLCONF='scribbler.tests.urls', 37 | PASSWORD_HASHERS=( 38 | 'django.contrib.auth.hashers.MD5PasswordHasher', 39 | ), 40 | TEMPLATES=[ 41 | { 42 | 'BACKEND': 'django.template.backends.jinja2.Jinja2', 43 | 'DIRS': [os.path.join(os.path.dirname(os.path.dirname(__file__)), 'templates/jinja2')], 44 | 'APP_DIRS': True, 45 | 'OPTIONS': {'environment': 'scribbler.tests.jinja2.Environment',}, 46 | }, 47 | { 48 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 49 | 'DIRS': [os.path.join(os.path.dirname(os.path.dirname(__file__)), 'scribbler/tests') 50 | ], 51 | 'APP_DIRS': True, 52 | 'OPTIONS': { 53 | 'context_processors': [ 54 | 'django.contrib.auth.context_processors.auth', 55 | 'django.contrib.messages.context_processors.messages', 56 | 'django.template.context_processors.debug', 57 | 'django.template.context_processors.media', 58 | 'django.template.context_processors.i18n', 59 | 'django.template.context_processors.static', 60 | 'django.template.context_processors.request', 61 | ], 62 | 'debug': False, 63 | }, 64 | }, 65 | ], 66 | MIGRATION_MODULES={ 67 | # this lets us skip creating migrations for the test models. 68 | # https://docs.djangoproject.com/en/2.2/ref/settings/#migration-modules 69 | 'scribbler': None, 70 | 'dayslog': None, 71 | }, 72 | MEDIA_ROOT='', 73 | MEDIA_URL='/media/', 74 | STATIC_ROOT='', 75 | STATIC_URL='/static/', 76 | LOGIN_REDIRECT_URL='/test/' 77 | ) 78 | 79 | 80 | def runtests(*test_args, **kwargs): 81 | django.setup() 82 | if not test_args: 83 | test_args = ['scribbler', ] 84 | TestRunner = get_runner(settings) 85 | test_runner = TestRunner(verbosity=1, interactive=True, failfast=False) 86 | failures = test_runner.run_tests(test_args) 87 | sys.exit(failures) 88 | 89 | 90 | if __name__ == '__main__': 91 | parser = OptionParser() 92 | 93 | (options, args) = parser.parse_args() 94 | runtests(*args) 95 | -------------------------------------------------------------------------------- /scribbler/__init__.py: -------------------------------------------------------------------------------- 1 | "django-scribbler is an application for managing snippets of text for a Django website." 2 | 3 | 4 | __version__ = '1.2.0' 5 | 6 | 7 | default_app_config = 'scribbler.apps.ScribblerAppConfig' 8 | -------------------------------------------------------------------------------- /scribbler/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.apps import AppConfig 3 | 4 | 5 | class ScribblerAppConfig(AppConfig): 6 | name = 'scribbler' 7 | verbose_name = "Scribbler" 8 | default_auto_field = "django.db.models.AutoField" 9 | 10 | def ready(self): 11 | from django.conf import settings 12 | from django.core.exceptions import ImproperlyConfigured 13 | TEMPLATES = getattr(settings, 'TEMPLATES', None) 14 | if TEMPLATES is None: 15 | django_templates_used = True 16 | else: 17 | django_templates_used = False 18 | for config in TEMPLATES: 19 | if config.get('BACKEND', '') == 'django.template.backends.django.DjangoTemplates': 20 | django_templates_used = True 21 | break 22 | if not django_templates_used: 23 | raise ImproperlyConfigured("Django-scribbler requires 'django.template.backends.django.DjangoTemplates' to be used as templates engine") 24 | -------------------------------------------------------------------------------- /scribbler/conf.py: -------------------------------------------------------------------------------- 1 | "Default settings for django-scribbler." 2 | import hashlib 3 | 4 | from django.conf import settings 5 | 6 | 7 | DEFAULT_TIMEOUT = 12 * 60 * 60 8 | 9 | CACHE_TIMEOUT = getattr(settings, 'SCRIBBLER_CACHE_TIMEOUT', DEFAULT_TIMEOUT) 10 | 11 | CACHE_PREFIX = 'scribbler' 12 | 13 | 14 | def default_cache_key(slug, url): 15 | "Construct a cache key for a given slug/url pair." 16 | sha = hashlib.sha1('{0}#{1}'.format(url, slug).encode('utf8')) 17 | return '{0}:{1}'.format(CACHE_PREFIX, sha.hexdigest()) 18 | 19 | 20 | CACHE_KEY_FUNCTION = default_cache_key # Not currently configurable 21 | -------------------------------------------------------------------------------- /scribbler/forms.py: -------------------------------------------------------------------------------- 1 | "Create/edit forms for scribble content." 2 | from django import forms 3 | from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist 4 | from django.template import Origin, Template 5 | from django.urls import reverse 6 | from django.contrib.contenttypes.models import ContentType 7 | from django.utils.encoding import force_str 8 | 9 | from .models import Scribble 10 | 11 | 12 | class ScribbleFormMixin(object): 13 | 14 | def clean_content(self): 15 | content = self.cleaned_data.get('content', '') 16 | if content: 17 | origin = Origin(content) 18 | 19 | # Try to create a Template 20 | try: 21 | template = Template(template_string=force_str(content), origin=origin) 22 | except Exception as e: 23 | # This is an error with creating the template 24 | self.exc_info = { 25 | 'message': e.args, 26 | 'line': e.token.lineno, 27 | 'name': origin.name, 28 | } 29 | raise forms.ValidationError('Invalid Django Template') 30 | 31 | # Template has been created; try to parse 32 | try: 33 | template.compile_nodelist() 34 | except Exception as e: 35 | # This is an error with parsing 36 | # The data we pass to the views is in e.template_debug 37 | e.template_debug = template.get_exception_info(e, e.token) 38 | self.exc_info = e.template_debug 39 | raise forms.ValidationError('Parsing Error') 40 | return content 41 | 42 | 43 | class ScribbleForm(forms.ModelForm, ScribbleFormMixin): 44 | 45 | class Meta(object): 46 | model = Scribble 47 | exclude = [] 48 | widgets = { 49 | 'name': forms.HiddenInput, 50 | 'slug': forms.HiddenInput, 51 | 'url': forms.HiddenInput, 52 | } 53 | 54 | def get_data_prefix(self): 55 | return self.instance.slug 56 | 57 | def get_preview_url(self): 58 | content_type = ContentType.objects.get_for_model(Scribble) 59 | return reverse('preview-scribble', args=(content_type.pk,)) 60 | 61 | def get_save_url(self): 62 | return self.instance.get_save_url() 63 | 64 | def get_delete_url(self): 65 | return self.instance.get_delete_url() 66 | 67 | 68 | class PreviewForm(ScribbleForm): 69 | 70 | def clean(self): 71 | "Override default clean to not check for slug/url uniqueness." 72 | return self.cleaned_data 73 | 74 | 75 | class FieldScribbleForm(forms.Form, ScribbleFormMixin): 76 | content = forms.CharField(widget=forms.Textarea) 77 | 78 | def __init__(self, content_type, instance_pk, field_name, *args, **kwargs): 79 | field_value = kwargs.pop('field_value', None) 80 | if 'data' not in kwargs: 81 | kwargs['prefix'] = '{0}:{1}:{2}'.format(content_type.pk, instance_pk, field_name) 82 | super(FieldScribbleForm, self).__init__(*args, **kwargs) 83 | self.content_type = content_type 84 | self.instance_pk = instance_pk 85 | self.field_name = field_name 86 | self.fields['content'].initial = field_value 87 | try: 88 | self.fields['content'].required = not content_type.model_class()._meta.get_field(field_name).blank 89 | except FieldDoesNotExist: 90 | # Error will be caught on form validation 91 | pass 92 | self.fields[field_name] = forms.CharField(required=False) 93 | 94 | def clean(self): 95 | if not self.errors: 96 | ModelClass = self.content_type.model_class() 97 | try: 98 | current_instance = ModelClass.objects.get(pk=self.instance_pk) 99 | except ObjectDoesNotExist as e: 100 | raise forms.ValidationError(e) 101 | if not hasattr(current_instance, self.field_name): 102 | raise forms.ValidationError('{0} model has no field named {1}'.format( 103 | ModelClass.__name__, self.field_name)) 104 | setattr(current_instance, self.field_name, self.cleaned_data['content']) 105 | current_instance.full_clean() 106 | return self.cleaned_data 107 | 108 | def get_data_prefix(self): 109 | return self.prefix 110 | 111 | def get_preview_url(self): 112 | return reverse('preview-scribble', args=(self.content_type.pk,)) 113 | 114 | def get_save_url(self): 115 | args=(self.content_type.pk, self.instance_pk, self.field_name) 116 | return reverse('edit-scribble-field', args=args) 117 | 118 | def get_delete_url(self): 119 | raise NotImplementedError() 120 | 121 | def save(self): 122 | self.content_type.model_class().objects.filter(pk=self.instance_pk).update( 123 | **{self.field_name: self.cleaned_data.get('content')}) 124 | -------------------------------------------------------------------------------- /scribbler/locale/en/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: PACKAGE VERSION\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2016-01-18 16:50-0500\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: static/scribbler/js/scribbler-editor.js:97 21 | #: static/scribbler/js/scribbler-editor.js:98 22 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24306 23 | #: tests/qunit/bundle.js.c:24307 24 | msgid "Close" 25 | msgstr "" 26 | 27 | #: static/scribbler/js/scribbler-editor.js:101 28 | #: static/scribbler/js/scribbler-editor.js:102 29 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24310 30 | #: tests/qunit/bundle.js.c:24311 31 | msgid "Save" 32 | msgstr "" 33 | 34 | #: static/scribbler/js/scribbler-editor.js:104 35 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24313 36 | msgid "Save Draft" 37 | msgstr "" 38 | 39 | #: static/scribbler/js/scribbler-editor.js:105 40 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24314 41 | msgid "Save as Draft" 42 | msgstr "" 43 | 44 | #: static/scribbler/js/scribbler-editor.js:107 45 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24316 46 | msgid "Discard" 47 | msgstr "" 48 | 49 | #: static/scribbler/js/scribbler-editor.js:108 50 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24317 51 | msgid "Discard Draft" 52 | msgstr "" 53 | 54 | #: static/scribbler/js/scribbler-editor.js:117 55 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24326 56 | msgid "Press " 57 | msgstr "" 58 | 59 | #: static/scribbler/js/scribbler-editor.js:118 60 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24327 61 | msgid "F11" 62 | msgstr "" 63 | 64 | #: static/scribbler/js/scribbler-editor.js:119 65 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24328 66 | msgid " to enter/exit Fullscreen edit" 67 | msgstr "" 68 | 69 | #: static/scribbler/js/scribbler-editor.js:152 70 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24361 71 | msgid "You do not have permission to edit this content." 72 | msgstr "" 73 | 74 | #: static/scribbler/js/scribbler-editor.js:224 75 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24433 76 | msgid "Error:" 77 | msgstr "" 78 | 79 | #: static/scribbler/js/scribbler-editor.js:258 80 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24467 81 | msgid "Server response was" 82 | msgstr "" 83 | 84 | #: static/scribbler/js/scribbler-editor.js:294 85 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24503 86 | msgid "Draft saved..." 87 | msgstr "" 88 | 89 | #: static/scribbler/js/scribbler-editor.js:328 90 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24537 91 | msgid "Restored content from a draft..." 92 | msgstr "" 93 | 94 | #: static/scribbler/js/scribbler-editor.js:350 95 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24559 96 | msgid "Restored original content..." 97 | msgstr "" 98 | 99 | #: static/scribbler/js/scribbler-menu.js:39 100 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24621 101 | msgid "Toggle Menu" 102 | msgstr "" 103 | 104 | #: static/scribbler/js/scribbler-menu.js:42 105 | #: static/scribbler/js/scribbler-menu.js:43 106 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24624 107 | #: tests/qunit/bundle.js.c:24625 108 | msgid "Show all scribbles" 109 | msgstr "" 110 | -------------------------------------------------------------------------------- /scribbler/locale/fr/LC_MESSAGES/djangojs.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the PACKAGE package. 4 | # 5 | # Translators: 6 | # Dmitriy C , 2016 7 | # Mark Lavin , 2016 8 | msgid "" 9 | msgstr "" 10 | "Project-Id-Version: django-scribbler\n" 11 | "Report-Msgid-Bugs-To: \n" 12 | "POT-Creation-Date: 2016-01-18 16:50-0500\n" 13 | "PO-Revision-Date: 2016-01-18 21:56+0000\n" 14 | "Last-Translator: Mark Lavin \n" 15 | "Language-Team: French (http://www.transifex.com/mlavin/django-scribbler/language/fr/)\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | "Language: fr\n" 20 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 21 | 22 | #: static/scribbler/js/scribbler-editor.js:97 23 | #: static/scribbler/js/scribbler-editor.js:98 24 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24306 25 | #: tests/qunit/bundle.js.c:24307 26 | msgid "Close" 27 | msgstr "Fermer" 28 | 29 | #: static/scribbler/js/scribbler-editor.js:101 30 | #: static/scribbler/js/scribbler-editor.js:102 31 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24310 32 | #: tests/qunit/bundle.js.c:24311 33 | msgid "Save" 34 | msgstr "Enregistrer" 35 | 36 | #: static/scribbler/js/scribbler-editor.js:104 37 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24313 38 | msgid "Save Draft" 39 | msgstr "Enregistrer comme brouillon" 40 | 41 | #: static/scribbler/js/scribbler-editor.js:105 42 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24314 43 | msgid "Save as Draft" 44 | msgstr "Enregistrer comme brouillon" 45 | 46 | #: static/scribbler/js/scribbler-editor.js:107 47 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24316 48 | msgid "Discard" 49 | msgstr "Effacer le brouillon" 50 | 51 | #: static/scribbler/js/scribbler-editor.js:108 52 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24317 53 | msgid "Discard Draft" 54 | msgstr "Effacer le brouillon" 55 | 56 | #: static/scribbler/js/scribbler-editor.js:117 57 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24326 58 | msgid "Press " 59 | msgstr "Appuyez sur" 60 | 61 | #: static/scribbler/js/scribbler-editor.js:118 62 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24327 63 | msgid "F11" 64 | msgstr "F11" 65 | 66 | #: static/scribbler/js/scribbler-editor.js:119 67 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24328 68 | msgid " to enter/exit Fullscreen edit" 69 | msgstr "entrer/quitter en mode plein écran" 70 | 71 | #: static/scribbler/js/scribbler-editor.js:152 72 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24361 73 | msgid "You do not have permission to edit this content." 74 | msgstr "Vous n'avez pas la permission d'éditer ce contenu." 75 | 76 | #: static/scribbler/js/scribbler-editor.js:224 77 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24433 78 | msgid "Error:" 79 | msgstr "Erreur:" 80 | 81 | #: static/scribbler/js/scribbler-editor.js:258 82 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24467 83 | msgid "Server response was" 84 | msgstr "La réponse du serveur était" 85 | 86 | #: static/scribbler/js/scribbler-editor.js:294 87 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24503 88 | msgid "Draft saved..." 89 | msgstr "Brouillon sauvegardé..." 90 | 91 | #: static/scribbler/js/scribbler-editor.js:328 92 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24537 93 | msgid "Restored content from a draft..." 94 | msgstr "Contenu restauré depuis un brouillon..." 95 | 96 | #: static/scribbler/js/scribbler-editor.js:350 97 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24559 98 | msgid "Restored original content..." 99 | msgstr "Contenu original restauré..." 100 | 101 | #: static/scribbler/js/scribbler-menu.js:39 102 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24621 103 | msgid "Toggle Menu" 104 | msgstr "Afficher/Masquer le menu" 105 | 106 | #: static/scribbler/js/scribbler-menu.js:42 107 | #: static/scribbler/js/scribbler-menu.js:43 108 | #: static/scribbler/js/scribbler-min.js:16 tests/qunit/bundle.js:24624 109 | #: tests/qunit/bundle.js.c:24625 110 | msgid "Show all scribbles" 111 | msgstr "Montrer tous les scribbles" 112 | -------------------------------------------------------------------------------- /scribbler/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models, migrations 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [ 8 | ] 9 | 10 | operations = [ 11 | migrations.CreateModel( 12 | name='Scribble', 13 | fields=[ 14 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 15 | ('created_time', models.DateTimeField(auto_now_add=True)), 16 | ('modified_time', models.DateTimeField(auto_now=True)), 17 | ('name', models.CharField(default='', max_length=255, blank=True)), 18 | ('slug', models.SlugField(default='', max_length=64, blank=True)), 19 | ('url', models.CharField(default='', max_length=255, blank=True)), 20 | ('content', models.TextField(default='', blank=True)), 21 | ], 22 | ), 23 | migrations.AlterUniqueTogether( 24 | name='scribble', 25 | unique_together=set([('slug', 'url')]), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /scribbler/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/scribbler/migrations/__init__.py -------------------------------------------------------------------------------- /scribbler/models.py: -------------------------------------------------------------------------------- 1 | "Models for storing snippet content." 2 | from django.core.cache import cache 3 | from django.db import models 4 | from django.db.models.signals import post_save, post_delete, pre_save 5 | from django.dispatch import receiver 6 | from django.urls import reverse 7 | 8 | 9 | from .conf import CACHE_TIMEOUT, CACHE_KEY_FUNCTION 10 | 11 | 12 | class Scribble(models.Model): 13 | "Core model for storing snippet content." 14 | 15 | created_time = models.DateTimeField(auto_now_add=True, editable=False) 16 | modified_time = models.DateTimeField(auto_now=True, editable=False) 17 | name = models.CharField(max_length=255, blank=True, default="") 18 | slug = models.SlugField(max_length=64, blank=True, default="") 19 | url = models.CharField(max_length=255, blank=True, default="") 20 | content = models.TextField(blank=True, default="") 21 | 22 | def __str__(self): 23 | return '{0} - {1}'.format(self.slug, self.url) 24 | 25 | class Meta(object): 26 | unique_together = ('slug', 'url') 27 | 28 | def get_save_url(self): 29 | if self.pk: 30 | return reverse('edit-scribble', kwargs={'scribble_id': self.pk}) 31 | else: 32 | return reverse('create-scribble') 33 | 34 | def get_delete_url(self): 35 | return reverse('delete-scribble', kwargs={'scribble_id': self.pk}) 36 | 37 | 38 | @receiver(post_save, sender=Scribble) 39 | def update_scribble_cache(sender, instance, **kwargs): 40 | "Update scribble cache on save." 41 | if CACHE_TIMEOUT: 42 | key = CACHE_KEY_FUNCTION(slug=instance.slug, url=instance.url) 43 | cache.set(key, instance, CACHE_TIMEOUT) 44 | 45 | 46 | @receiver(post_delete, sender=Scribble) 47 | def populate_scribble_cache(sender, instance, **kwargs): 48 | "Populate cache with empty scribble cache on delete." 49 | if CACHE_TIMEOUT: 50 | key = CACHE_KEY_FUNCTION(slug=instance.slug, url=instance.url) 51 | scribble = Scribble(slug=instance.slug, url=instance.url) 52 | cache.set(key, scribble, CACHE_TIMEOUT) 53 | 54 | 55 | @receiver(pre_save, sender=Scribble) 56 | def clear_scribble_cache(sender, instance, **kwargs): 57 | "Clear cache pre-save in case slug/url has changed." 58 | if CACHE_TIMEOUT: 59 | raw = kwargs.get('raw', False) 60 | if instance.pk and not raw: 61 | # Need original slug/url from the DB 62 | original = Scribble.objects.get(pk=instance.pk) 63 | key = CACHE_KEY_FUNCTION(slug=original.slug, url=original.url) 64 | cache.delete(key) 65 | -------------------------------------------------------------------------------- /scribbler/static/scribbler/img/btn-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caktus/django-scribbler/9209d007f322edefaa1bc8ff7392d5dda91d7f6e/scribbler/static/scribbler/img/btn-close.png -------------------------------------------------------------------------------- /scribbler/static/scribbler/js/djangohint.js: -------------------------------------------------------------------------------- 1 | /*global require */ 2 | 3 | require('codemirror/addon/hint/show-hint'); 4 | 5 | var CodeMirror = require('codemirror'); 6 | 7 | function forEach(arr, f) { 8 | for (var i = 0, e = arr.length; i < e; ++i) { 9 | f(arr[i]); 10 | } 11 | } 12 | 13 | function arrayContains(arr, item) { 14 | if (!Array.prototype.indexOf) { 15 | var i = arr.length; 16 | while (i--) { 17 | if (arr[i] === item) { 18 | return true; 19 | } 20 | } 21 | return false; 22 | } 23 | return arr.indexOf(item) !== -1; 24 | } 25 | 26 | var TAG_RE = /\{\%\s?[^\s\%\}]*$/; 27 | var VARIABLE_RE = /\{\{\s?[^\s\}\}]*$/; 28 | var FILTER_RE = /\|[^\s\%\}]*$/; 29 | 30 | var django_tags = ("autoescape block comment cycle debug extends filter " + 31 | "firstof for if ifchanged ifequal ifnotequal include load now regroup " + 32 | "spaceless ssi templatetag url widthratio").split(" "); 33 | 34 | var django_filters = ("add addslashes capfirst center cut date default " + 35 | "default_if_none dictsort dictsortreversed divisibleby escape escapejs " + 36 | "filesizeformat first fix_ampersands floatformat force_escape get_digit " + 37 | "iriencode join last length length_is linebreaks linebreaksbr " + 38 | "linenumbers ljust lower make_list phone2numeric pluralize pprint " + 39 | "random removetags rjust safe slice slugify stringformat striptags time " + 40 | "timesince timeuntil title truncatewords truncatewords_html " + 41 | "unordered_list upper urlencode urlize urlizetrunc wordcount wordwrap " + 42 | "yesno").split(" "); 43 | 44 | var django_variables = []; 45 | 46 | function getCompletions(token, context, django_variables) { 47 | var found = []; 48 | var start = token.string; 49 | 50 | function maybeAdd(str) { 51 | if (str.indexOf(start) === 0 && !arrayContains(found, str)) { 52 | found.push(str); 53 | } 54 | } 55 | 56 | function gatherCompletions(token) { 57 | if (token.type === "tag") { 58 | forEach(django_tags, maybeAdd); 59 | } else if (token.type === "filter") { 60 | forEach(django_filters, maybeAdd); 61 | } else if (token.type === 'variable') { 62 | forEach(django_variables, maybeAdd); 63 | } 64 | } 65 | gatherCompletions(token); 66 | return found; 67 | } 68 | 69 | function scriptHint(editor, django_variables, getToken) { 70 | // Find the token at the cursor 71 | var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; 72 | token.type = 'unknown'; 73 | token.closing = ''; 74 | 75 | // Determine if the token is part of a tag, filter, or variable 76 | if (TAG_RE.test(token.string)) { 77 | token.type = 'tag'; 78 | token.string = TAG_RE.exec(token.string)[0]; 79 | token.closing = ' %}'; 80 | } 81 | if (VARIABLE_RE.test(token.string)) { 82 | token.type = 'variable'; 83 | token.string = VARIABLE_RE.exec(token.string)[0]; 84 | token.closing = ' }}'; 85 | } 86 | if (FILTER_RE.test(token.string)) { 87 | token.type = 'filter'; 88 | token.string = FILTER_RE.exec(token.string)[0]; 89 | token.closing = ''; 90 | } 91 | token.string = token.string.replace(/[^\w]*/g, ''); 92 | 93 | // Build list of found matches and return to autocompleter 94 | var found = getCompletions(token, [token], django_variables); 95 | return { 96 | token: token, 97 | list: found, 98 | from: {line: cur.line, ch: token.end - token.string.length}, 99 | to: {line: cur.line, ch: token.end} 100 | }; 101 | } 102 | 103 | CodeMirror.djangoHint = function (editor) { 104 | return scriptHint(editor, django_variables, function (e, cur) { 105 | return e.getTokenAt(cur); 106 | }); 107 | }; 108 | CodeMirror.update_variables = function (variables) { 109 | django_variables = variables; 110 | }; 111 | 112 | return CodeMirror; 113 | -------------------------------------------------------------------------------- /scribbler/static/scribbler/js/plugins/themes.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define(["require", "jquery", "underscore"], function (require, $, _) { 4 | var themes = [ 5 | 'default', 'ambiance', 'blackboard', 'cobalt', 'eclipse', 'elegant', 6 | 'erlang-dark', 'lesser-dark', 'monokai', 'neat', 'night', 'rubyblue', 7 | 'solarized light', 'solarized dark', 'twilight', 'vibrant-ink', 'xq-dark' 8 | ]; 9 | // Theme switcher plugin 10 | function plugin(editor, menu) { 11 | // Select input will all available themes 12 | var control = $(" 40 | 41 | 42 | 43 | 44 | 45 | 46 | USERNAME: test PASSWORD: test 47 | {% endif %} 48 | {% endblock %} 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /scribbler/tests/test_templatetags.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | "Test for template tags." 4 | from django.contrib.auth.models import Permission 5 | from django.core.cache import cache 6 | from django.template import Template, TemplateSyntaxError 7 | from django.template.context import RequestContext 8 | from django.test.client import RequestFactory 9 | from unittest import skipIf 10 | 11 | from . import DaysLog 12 | from .base import ScribblerDataTestCase 13 | from scribbler.conf import CACHE_TIMEOUT 14 | 15 | class UnicodeURLTestCase(ScribblerDataTestCase): 16 | "Test, that unicode characters in url got cached" 17 | 18 | def testUnicodeURL(self): 19 | scribble = self.create_scribble( 20 | url='/foo/čřžžýü', slug='sidebar', 21 | content='

Scribble content.

' 22 | ) 23 | self.assertEquals(scribble.url, "/foo/čřžžýü") 24 | 25 | 26 | class RenderScribbleTestCase(ScribblerDataTestCase): 27 | "Tag to render a scribble for the current page." 28 | 29 | def setUp(self): 30 | self.factory = RequestFactory() 31 | self.request = self.factory.get('/foo/') 32 | self.scribble = self.create_scribble( 33 | url='/foo/', slug='sidebar', 34 | content='

Scribble content.

' 35 | ) 36 | 37 | def tearDown(self): 38 | # Transaction will be rolled back without calling delete 39 | # so we need to clear the cache between runs 40 | cache.clear() 41 | 42 | def render_template_tag(self, slug, context=None, default='

Default.

', url=None): 43 | "Render the template tag." 44 | context = context or {} 45 | if not url: 46 | tag = '{{% load scribbler_tags %}}{{% scribble {0} %}}{1}{{% endscribble %}}' 47 | else: 48 | tag = '{{% load scribbler_tags %}}{{% scribble {0} {2} %}}{1}{{% endscribble %}}' 49 | template = Template(tag.format(slug, default, url)) 50 | context = RequestContext(self.request, context) 51 | return template.render(context) 52 | 53 | def test_basic_rendering(self): 54 | "Render a scribble by the slug." 55 | result = self.render_template_tag(slug='"sidebar"') 56 | self.assertTrue('

Scribble content.

' in result) 57 | 58 | def test_variable_slug(self): 59 | "Render a scribble by the slug as a context variable." 60 | result = self.render_template_tag(slug='foo', context={'foo': 'sidebar'}) 61 | self.assertTrue('

Scribble content.

' in result, result) 62 | 63 | def test_slug_not_found(self): 64 | "Render default if scribble not found by slug." 65 | self.scribble.slug = 'blip' 66 | self.scribble.save() 67 | result = self.render_template_tag(slug='"sidebar"') 68 | self.assertTrue('

Default.

' in result) 69 | 70 | def test_url_not_found(self): 71 | "Render default if scribble not found for the current url." 72 | self.scribble.slug = '/bar/' 73 | self.scribble.save() 74 | result = self.render_template_tag(slug='"sidebar"') 75 | self.assertTrue('

Default.

' in result) 76 | 77 | def test_default_rendering(self): 78 | "Render default if no scribbles exist." 79 | self.scribble.delete() 80 | result = self.render_template_tag(slug='"sidebar"') 81 | self.assertTrue('

Default.

' in result) 82 | 83 | def test_unicode_rendering(self): 84 | "Render with unicode defaults when no scribbles exist." 85 | # On Django>=1.9 ScribbleFormMixin.clean_content directly uses django.template.Template 86 | # and also uses force_text that may fail for non-string objects that have __str__ with 87 | # unicode output. 88 | self.scribble.delete() 89 | unicode_default = '

\u0422\u0435\u043a\u0441\u0442.

' 90 | result = self.render_template_tag(slug='"sidebar"', default=unicode_default) 91 | self.assertTrue(unicode_default in result) 92 | 93 | def test_no_slug_given(self): 94 | "Slug is required by the tag." 95 | self.assertRaises(TemplateSyntaxError, self.render_template_tag, slug='') 96 | 97 | def test_shared_scribble(self): 98 | "Render a scribble by the slug url pair." 99 | self.create_scribble( 100 | url='/shared/', slug='shared', 101 | content='

Shared scribble content.

' 102 | ) 103 | result = self.render_template_tag(slug='"shared"', url='"/shared/"') 104 | self.assertTrue('

Shared scribble content.

' in result) 105 | # switch context and render the shared scribble 106 | self.request = self.factory.get('/bar/') 107 | result = self.render_template_tag(slug='"shared"', url='"/shared/"') 108 | self.assertTrue('

Shared scribble content.

' in result) 109 | 110 | @skipIf(not CACHE_TIMEOUT, "Caching is disabled.") 111 | def test_cache_scribble_lookup(self): 112 | "DB lookups should be cached." 113 | cache.clear() 114 | with self.assertNumQueries(1): 115 | # Render twice but should be one DB lookup 116 | result = self.render_template_tag(slug='"sidebar"') 117 | self.assertTrue('

Scribble content.

' in result) 118 | result = self.render_template_tag(slug='"sidebar"') 119 | self.assertTrue('

Scribble content.

' in result) 120 | 121 | @skipIf(not CACHE_TIMEOUT, "Caching is disabled.") 122 | def test_cache_lookup_miss(self): 123 | "Scribbles not in the DB should also be cached to prevent unnecessary lookup." 124 | self.scribble.delete() 125 | cache.clear() 126 | with self.assertNumQueries(1): 127 | # Render twice but should be one DB lookup 128 | result = self.render_template_tag(slug='"sidebar"') 129 | self.assertTrue('

Default.

' in result) 130 | result = self.render_template_tag(slug='"sidebar"') 131 | self.assertTrue('

Default.

' in result) 132 | 133 | @skipIf(not CACHE_TIMEOUT, "Caching is disabled.") 134 | def test_cached_on_save(self): 135 | "Scribbles are cached on their save." 136 | cache.clear() 137 | other_scribble = self.create_scribble( 138 | url='/foo/', slug='header', 139 | content='

New content.

' 140 | ) 141 | with self.assertNumQueries(0): 142 | # Render twice but should be one DB lookup 143 | result = self.render_template_tag(slug='"header"') 144 | self.assertTrue('

New content.

' in result) 145 | 146 | def test_unauthenticated_controls(self): 147 | "Unauthenticated users will not see the scribble controls." 148 | result = self.render_template_tag(slug='"sidebar"') 149 | self.assertFalse('/', views.preview_scribble, name='preview-scribble'), 8 | path('create/', views.create_edit_scribble, name='create-scribble'), 9 | path('edit//', views.create_edit_scribble, name='edit-scribble'), 10 | path('delete//', views.delete_scribble, name='delete-scribble'), 11 | path('edit-field////', 12 | views.edit_scribble_field, name='edit-scribble-field'), 13 | ) 14 | -------------------------------------------------------------------------------- /scribbler/utils.py: -------------------------------------------------------------------------------- 1 | from collections.abc import Iterable 2 | 3 | def _flatten(iterable): 4 | """ 5 | Given an iterable with nested iterables, generate a flat iterable 6 | """ 7 | for i in iterable: 8 | if isinstance(i, Iterable) and not isinstance(i, str): 9 | for sub_i in _flatten(i): 10 | yield sub_i 11 | else: 12 | yield i 13 | 14 | 15 | def get_variables(context): 16 | """ 17 | Given a template context, return a sorted list of variable names in that 18 | context 19 | """ 20 | variables = set(context.flatten().keys()) 21 | # Don't show the rendering tree 'block' as a variable in the context 22 | try: 23 | variables.remove('block') 24 | except KeyError: 25 | pass 26 | return sorted(list(variables)) 27 | -------------------------------------------------------------------------------- /scribbler/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django import template 4 | from django.core.serializers.json import DjangoJSONEncoder 5 | from django.http import HttpResponse, HttpResponseForbidden 6 | from django.shortcuts import get_object_or_404 7 | from django.template import RequestContext 8 | from django.views.debug import ExceptionReporter 9 | from django.views.decorators.http import require_POST 10 | from django.contrib.contenttypes.models import ContentType 11 | 12 | from .forms import ScribbleForm, PreviewForm, FieldScribbleForm 13 | from .models import Scribble 14 | from .utils import get_variables 15 | 16 | 17 | def build_scribble_context(scribble): 18 | "Create context for rendering a scribble or scribble preview." 19 | context = { 20 | 'scribble': scribble, 21 | } 22 | 23 | return context 24 | 25 | 26 | @require_POST 27 | def preview_scribble(request, ct_pk): 28 | "Render scribble content or return error information." 29 | if not request.user.is_authenticated: 30 | return HttpResponseForbidden() 31 | content_type = get_object_or_404(ContentType, pk=ct_pk) 32 | change_scribble = '{0}.change_{1}'.format( 33 | content_type.app_label, content_type.model) 34 | add_scribble = '{0}.add_{1}'.format( 35 | content_type.app_label, content_type.model) 36 | can_edit = request.user.has_perm(change_scribble) 37 | can_create = request.user.has_perm(add_scribble) 38 | if not (can_edit or can_create): 39 | return HttpResponseForbidden() 40 | results = { 41 | 'valid': False, 42 | 'html': '', 43 | } 44 | form = PreviewForm(request.POST) 45 | if form.is_valid(): 46 | results['valid'] = True 47 | if hasattr(template, 'engines'): 48 | scribbler_template = template.engines['django'].from_string(form.cleaned_data.get('content', '')) 49 | else: 50 | scribbler_template = template.Template(form.cleaned_data.get('content', '')) 51 | context = build_scribble_context(form.instance) 52 | results['html'] = scribbler_template.render(context, request) 53 | results['variables'] = get_variables(RequestContext(request, context)) 54 | else: 55 | if hasattr(form, 'exc_info'): 56 | # Pre Django 1.9 57 | try: 58 | exc_type, exc_value, tb = form.exc_info 59 | reporter = ExceptionReporter(request, exc_type, exc_value, tb) 60 | reporter.get_template_exception_info() 61 | results['error'] = reporter.template_info 62 | # Django >= 1.9: get_template_info() is moved from ExceptionReporter 63 | # onto Template. We pass the data it returns from scribbler/forms.py 64 | # to here. 65 | except (ValueError, AttributeError): 66 | # ValueError is raised when we pass in all 12 the arguments, 67 | # in form.exc_info and AttributeError is raised when 68 | # ExceptionReporter.get_template_exception_info() is called. 69 | results['error'] = form.exc_info 70 | else: 71 | # Not sure what to do here 72 | results['error'] = { 73 | 'message': 'Content is not valid', 74 | 'line': '', 75 | } 76 | content = json.dumps(results, cls=DjangoJSONEncoder, ensure_ascii=False) 77 | return HttpResponse(content, content_type='application/json') 78 | 79 | 80 | @require_POST 81 | def create_edit_scribble(request, scribble_id=None): 82 | "Create a new Scribble or edit an existing one." 83 | if not request.user.is_authenticated: 84 | return HttpResponseForbidden() 85 | if scribble_id is not None: 86 | scribble = get_object_or_404(Scribble, pk=scribble_id) 87 | if not request.user.has_perm('scribbler.change_scribble'): 88 | return HttpResponseForbidden() 89 | else: 90 | scribble = Scribble() 91 | if not request.user.has_perm('scribbler.add_scribble'): 92 | return HttpResponseForbidden() 93 | form = ScribbleForm(request.POST, instance=scribble) 94 | results = { 95 | 'valid': False, 96 | } 97 | if form.is_valid(): 98 | results['valid'] = True 99 | scribble = form.save() 100 | results['url'] = scribble.get_save_url() 101 | content = json.dumps(results, cls=DjangoJSONEncoder, ensure_ascii=False) 102 | return HttpResponse(content, content_type='application/json') 103 | 104 | 105 | @require_POST 106 | def edit_scribble_field(request, ct_pk, instance_pk, field_name): 107 | if not request.user.is_authenticated: 108 | return HttpResponseForbidden() 109 | content_type = get_object_or_404(ContentType, pk=ct_pk) 110 | perm_name = '{0}.change_{1}'.format(content_type.app_label, content_type.model) 111 | if not request.user.has_perm(perm_name): 112 | return HttpResponseForbidden() 113 | form = FieldScribbleForm(content_type, instance_pk, field_name, data=request.POST) 114 | results = { 115 | 'valid': False, 116 | } 117 | if form.is_valid(): 118 | results['valid'] = True 119 | form.save() 120 | else: 121 | results['error'] = { 122 | 'message': ','.join('%s' % e for e in form.errors.values()), 123 | 'line': '', 124 | } 125 | results['url'] = form.get_save_url() 126 | content = json.dumps(results, cls=DjangoJSONEncoder, ensure_ascii=False) 127 | return HttpResponse(content, content_type='application/json') 128 | 129 | 130 | @require_POST 131 | def delete_scribble(request, scribble_id): 132 | "Delete an existing scribble." 133 | if not request.user.is_authenticated: 134 | return HttpResponseForbidden() 135 | scribble = get_object_or_404(Scribble, pk=scribble_id) 136 | if not request.user.has_perm('scribbler.delete_scribble'): 137 | return HttpResponseForbidden() 138 | scribble.delete() 139 | results = { 140 | 'valid': True, 141 | 'url': scribble.get_save_url() 142 | } 143 | content = json.dumps(results, cls=DjangoJSONEncoder, ensure_ascii=False) 144 | return HttpResponse(content, content_type='application/json') 145 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | 5 | def read_file(filename): 6 | """Read a file into a string""" 7 | path = os.path.abspath(os.path.dirname(__file__)) 8 | filepath = os.path.join(path, filename) 9 | try: 10 | return open(filepath).read() 11 | except IOError: 12 | return '' 13 | 14 | 15 | setup( 16 | name='django-scribbler', 17 | version=__import__('scribbler').__version__, 18 | author='Caktus Consulting Group', 19 | author_email='solutions@caktusgroup.com', 20 | packages=find_packages(exclude=['example']), 21 | include_package_data=True, 22 | url='https://github.com/caktus/django-scribbler', 23 | license='BSD-2-Clause', 24 | description=' '.join(__import__('scribbler').__doc__.splitlines()).strip(), 25 | classifiers=[ 26 | 'Development Status :: 5 - Production/Stable', 27 | 'Environment :: Web Environment', 28 | 'Framework :: Django', 29 | 'Framework :: Django :: 2.2', 30 | 'Framework :: Django :: 3.2', 31 | 'Framework :: Django :: 4.0', 32 | 'Intended Audience :: Developers', 33 | 'License :: OSI Approved :: BSD License', 34 | 'Operating System :: OS Independent', 35 | 'Programming Language :: Python', 36 | 'Programming Language :: Python :: 3', 37 | 'Programming Language :: Python :: 3.7', 38 | 'Programming Language :: Python :: 3.8', 39 | 'Programming Language :: Python :: 3.9', 40 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 41 | 'Topic :: Software Development :: Libraries :: Python Modules', 42 | ], 43 | long_description=read_file('README.rst'), 44 | test_suite="runtests.runtests", 45 | zip_safe=False, # because we're including media that Django needs 46 | ) 47 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Django version: Supported python versions 2 | # 3 | # 2.2: 3.7, 3.8, 3.9, 3.10 4 | # 3.2: 3.7, 3.8, 3.9, 3.10 5 | # 4.0: 3.7, 3.8, 3.9, 3.10 6 | 7 | [tox] 8 | envlist = 9 | py37-{2.2,3.2,4.0}.X, 10 | py38-{2.2,3.2,4.0}.X, 11 | py39-{2.2,3.2,4.0}.X, 12 | py310-{2.2,3.2,4.0}.X, 13 | py310-coverage, 14 | docs, 15 | qunit 16 | 17 | [gh-actions] 18 | python = 19 | 3.7: py37 20 | 3.8: py38 21 | 3.9: py39 22 | 3.10: py310 23 | 24 | [testenv] 25 | passenv = TRAVIS DISPLAY 26 | deps = 27 | 2.2.X: Django>=2.2,<3.0 28 | 3.2.X: Django>=3.2,<4.0 29 | 4.0.X: Django<4.1 30 | Jinja2 31 | selenium 32 | whitelist_externals = make 33 | commands = make fetch-static-libs build-css build-js 34 | {envpython} runtests.py 35 | 36 | [testenv:py310-coverage] 37 | passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH DISPLAY 38 | whitelist_externals = make 39 | commands = make fetch-static-libs build-css build-js 40 | coverage run runtests.py 41 | coverage report -m --fail-under 80 42 | deps = coverage>=4 43 | Django>=3.2,<4.1 44 | Jinja2 45 | selenium 46 | 47 | [testenv:docs] 48 | basepython = python3.10 49 | deps = Sphinx==4.3.0 50 | caktus-sphinx-theme==0.1.0 51 | commands = {envbindir}/sphinx-build -a -n -b html -d docs/_build/doctrees docs docs/_build/html 52 | 53 | [testenv:qunit] 54 | basepython = python3.10 55 | skip_install = true 56 | deps = 57 | whitelist_externals = make 58 | commands = make fetch-static-libs build-js test-js 59 | --------------------------------------------------------------------------------