mirror of
https://github.com/jazzband/django-dbtemplates.git
synced 2026-04-30 19:54:51 +00:00
Compare commits
No commits in common. "master" and "0.4.2" have entirely different histories.
80 changed files with 391 additions and 7638 deletions
|
|
@ -1,6 +0,0 @@
|
||||||
[run]
|
|
||||||
source = dbtemplates
|
|
||||||
branch = 1
|
|
||||||
|
|
||||||
[report]
|
|
||||||
omit = *tests*,*/migrations/*,test_*
|
|
||||||
40
.github/workflows/release.yml
vendored
40
.github/workflows/release.yml
vendored
|
|
@ -1,40 +0,0 @@
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
if: github.repository == 'jazzband/django-dbtemplates'
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: "3.10"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install -U pip
|
|
||||||
python -m pip install -U setuptools twine wheel
|
|
||||||
|
|
||||||
- name: Build package
|
|
||||||
run: |
|
|
||||||
python setup.py --version
|
|
||||||
python setup.py sdist --format=gztar bdist_wheel
|
|
||||||
twine check dist/*
|
|
||||||
|
|
||||||
- name: Upload packages to Jazzband
|
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
|
||||||
with:
|
|
||||||
user: jazzband
|
|
||||||
password: ${{ secrets.JAZZBAND_RELEASE_KEY }}
|
|
||||||
repository_url: https://jazzband.co/projects/django-dbtemplates/upload
|
|
||||||
48
.github/workflows/test.yml
vendored
48
.github/workflows/test.yml
vendored
|
|
@ -1,48 +0,0 @@
|
||||||
name: Test
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
max-parallel: 6
|
|
||||||
matrix:
|
|
||||||
python-version: ['3.8', '3.9', '3.10', '3.11', '3.13', '3.14']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.python-version }}
|
|
||||||
|
|
||||||
- name: Get pip cache dir
|
|
||||||
id: pip-cache
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=dir::$(pip cache dir)"
|
|
||||||
|
|
||||||
- name: Cache
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pip-cache.outputs.dir }}
|
|
||||||
key:
|
|
||||||
${{ matrix.python-version }}-v1-${{ hashFiles('**/setup.py') }}-${{ hashFiles('**/tox.ini') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ matrix.python-version }}-v1-
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
python -m pip install --upgrade tox tox-gh-actions
|
|
||||||
|
|
||||||
- name: Tox tests
|
|
||||||
run: |
|
|
||||||
tox -v
|
|
||||||
|
|
||||||
- name: Upload coverage
|
|
||||||
uses: codecov/codecov-action@v2
|
|
||||||
with:
|
|
||||||
name: Python ${{ matrix.python-version }}
|
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
|
|
@ -1,14 +0,0 @@
|
||||||
.*
|
|
||||||
!.gitignore
|
|
||||||
!.coveragerc
|
|
||||||
*.pyc
|
|
||||||
.*.swp
|
|
||||||
MANIFEST
|
|
||||||
build
|
|
||||||
dist
|
|
||||||
*.egg-info
|
|
||||||
docs/_build
|
|
||||||
.tox/
|
|
||||||
*.egg/
|
|
||||||
.coverage
|
|
||||||
coverage.xml
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
repos:
|
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
|
||||||
rev: v3.21.2
|
|
||||||
hooks:
|
|
||||||
- id: pyupgrade
|
|
||||||
args: [--py37-plus]
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
||||||
rev: v4.3.0
|
|
||||||
hooks:
|
|
||||||
- id: check-merge-conflict
|
|
||||||
- id: check-yaml
|
|
||||||
|
|
||||||
ci:
|
|
||||||
autoupdate_schedule: quarterly
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# .readthedocs.yaml
|
|
||||||
# Read the Docs configuration file
|
|
||||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
||||||
|
|
||||||
# Required
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
# Set the version of Python and other tools you might need
|
|
||||||
build:
|
|
||||||
os: ubuntu-20.04
|
|
||||||
tools:
|
|
||||||
python: "3.9"
|
|
||||||
|
|
||||||
# Build documentation in the docs/ directory with Sphinx
|
|
||||||
sphinx:
|
|
||||||
configuration: docs/conf.py
|
|
||||||
|
|
||||||
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
|
||||||
formats:
|
|
||||||
- pdf
|
|
||||||
|
|
||||||
# Optionally declare the Python requirements required to build your docs
|
|
||||||
python:
|
|
||||||
install:
|
|
||||||
- requirements: requirements/docs.txt
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
[django-dbtemplates.main]
|
|
||||||
file_filter = dbtemplates/locale/<lang>/LC_MESSAGES/django.po
|
|
||||||
source_file = dbtemplates/locale/en/LC_MESSAGES/django.po
|
|
||||||
source_lang = en
|
|
||||||
|
|
||||||
[main]
|
|
||||||
host = https://www.transifex.com
|
|
||||||
18
AUTHORS
18
AUTHORS
|
|
@ -1,18 +0,0 @@
|
||||||
Alen Mujezinovic
|
|
||||||
Alex Gaynor
|
|
||||||
Alex Kamedov
|
|
||||||
Alexander Artemenko
|
|
||||||
Arne Brodowski
|
|
||||||
David Paccoud
|
|
||||||
Diego Búrigo Zacarão
|
|
||||||
Dmitry Falk
|
|
||||||
Jannis Leidel
|
|
||||||
Jure Cuhalev
|
|
||||||
Jason Mayfield
|
|
||||||
Kevin Mooney
|
|
||||||
Mark Stahler
|
|
||||||
Matt Dorn
|
|
||||||
Oliver George
|
|
||||||
Selwin Ong
|
|
||||||
Stephan Peijnik <spe@anexia.at>, ANEXIA Internetdienstleistungs GmbH, http://www.anexia.at/
|
|
||||||
Zhang Kun
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
# Code of Conduct
|
|
||||||
|
|
||||||
As contributors and maintainers of the Jazzband projects, and in the interest of
|
|
||||||
fostering an open and welcoming community, we pledge to respect all people who
|
|
||||||
contribute through reporting issues, posting feature requests, updating documentation,
|
|
||||||
submitting pull requests or patches, and other activities.
|
|
||||||
|
|
||||||
We are committed to making participation in the Jazzband a harassment-free experience
|
|
||||||
for everyone, regardless of the level of experience, gender, gender identity and
|
|
||||||
expression, sexual orientation, disability, personal appearance, body size, race,
|
|
||||||
ethnicity, age, religion, or nationality.
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
- The use of sexualized language or imagery
|
|
||||||
- Personal attacks
|
|
||||||
- Trolling or insulting/derogatory comments
|
|
||||||
- Public or private harassment
|
|
||||||
- Publishing other's private information, such as physical or electronic addresses,
|
|
||||||
without explicit permission
|
|
||||||
- Other unethical or unprofessional conduct
|
|
||||||
|
|
||||||
The Jazzband roadies have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are not
|
|
||||||
aligned to this Code of Conduct, or to ban temporarily or permanently any contributor
|
|
||||||
for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
By adopting this Code of Conduct, the roadies commit themselves to fairly and
|
|
||||||
consistently applying these principles to every aspect of managing the jazzband
|
|
||||||
projects. Roadies who do not follow or enforce the Code of Conduct may be permanently
|
|
||||||
removed from the Jazzband roadies.
|
|
||||||
|
|
||||||
This code of conduct applies both within project spaces and in public spaces when an
|
|
||||||
individual is representing the project or its community.
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by
|
|
||||||
contacting the roadies at `roadies@jazzband.co`. All complaints will be reviewed and
|
|
||||||
investigated and will result in a response that is deemed necessary and appropriate to
|
|
||||||
the circumstances. Roadies are obligated to maintain confidentiality with regard to the
|
|
||||||
reporter of an incident.
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version
|
|
||||||
1.3.0, available at [https://contributor-covenant.org/version/1/3/0/][version]
|
|
||||||
|
|
||||||
[homepage]: https://contributor-covenant.org
|
|
||||||
[version]: https://contributor-covenant.org/version/1/3/0/
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[](https://jazzband.co/)
|
|
||||||
|
|
||||||
This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to abide by the [Contributor Code of Conduct](https://jazzband.co/docs/conduct) and follow the [guidelines](https://jazzband.co/docs/guidelines).
|
|
||||||
15
INSTALL
Normal file
15
INSTALL
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
To install it, run the following command inside this directory:
|
||||||
|
|
||||||
|
python setup.py install
|
||||||
|
|
||||||
|
Or if you'd prefer you can simply place the included ``dbtemplates``
|
||||||
|
directory somewhere on your Python path, or symlink to it from
|
||||||
|
somewhere on your Python path; this is useful if you're working from a
|
||||||
|
Subversion checkout.
|
||||||
|
|
||||||
|
Note that this application requires Python 2.3 or later, and a recent
|
||||||
|
Subversion checkout of Django. You can obtain Python from
|
||||||
|
http://www.python.org/ and Django from http://www.djangoproject.com/.
|
||||||
|
|
||||||
|
This install notice was bluntly stolen from James Bennett's registration
|
||||||
|
package, http://code.google.com/p/django-registration/
|
||||||
30
LICENSE
30
LICENSE
|
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2007-2019, Jannis Leidel and contributors
|
Copyright (c) 2007-2008, Jannis Leidel
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
@ -26,31 +26,3 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
This software includes CodeMirror released under the following license:
|
|
||||||
|
|
||||||
Copyright (c) 2007-2009 Marijn Haverbeke
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any
|
|
||||||
damages arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any
|
|
||||||
purpose, including commercial applications, and to alter it and
|
|
||||||
redistribute it freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must
|
|
||||||
not claim that you wrote the original software. If you use this
|
|
||||||
software in a product, an acknowledgment in the product
|
|
||||||
documentation would be appreciated but is not required.
|
|
||||||
|
|
||||||
2. Altered source versions must be plainly marked as such, and must
|
|
||||||
not be misrepresented as being the original software.
|
|
||||||
|
|
||||||
3. This notice may not be removed or altered from any source
|
|
||||||
distribution.
|
|
||||||
|
|
||||||
Marijn Haverbeke
|
|
||||||
marijnh at gmail
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
include LICENSE AUTHORS README.rst MANIFEST.in tox.ini .coveragerc CONTRIBUTING.md
|
include INSTALL
|
||||||
recursive-include docs *.txt
|
include LICENSE
|
||||||
|
include MANIFEST.in
|
||||||
|
include README
|
||||||
recursive-include dbtemplates/locale *
|
recursive-include dbtemplates/locale *
|
||||||
recursive-include dbtemplates/static/dbtemplates *.css *.js
|
|
||||||
|
|
|
||||||
55
README
Normal file
55
README
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
===================================
|
||||||
|
Database template loader for Django
|
||||||
|
===================================
|
||||||
|
|
||||||
|
This is a basic database template loader for Django which uses a m2m
|
||||||
|
relationship to provide a site centric template loading.
|
||||||
|
|
||||||
|
How to use it in your own Django application
|
||||||
|
============================================
|
||||||
|
|
||||||
|
0. Get the source from the subversion repository
|
||||||
|
1. Follow the instructions in the INSTALL file
|
||||||
|
2. Edit the settings.py of your Django project:
|
||||||
|
|
||||||
|
# Add ``dbtemplates`` to the ``INSTALLED_APPS`` of your django project
|
||||||
|
|
||||||
|
# Check if ``django.contrib.sites`` and ``django.contrib.admin`` are in
|
||||||
|
``INSTALLED_APPS`` and add if necessary
|
||||||
|
|
||||||
|
It should look something like this:
|
||||||
|
|
||||||
|
INSTALLED_APPS = (
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.sites',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.flatpages',
|
||||||
|
'dbtemplates',
|
||||||
|
'myapp.blog',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add ``dbtemplates.loader.load_template_source`` to the
|
||||||
|
``TEMPLATE_LOADERS`` list in the settings.py of your Django project
|
||||||
|
|
||||||
|
It should look something like this:
|
||||||
|
|
||||||
|
TEMPLATE_LOADERS = (
|
||||||
|
'django.template.loaders.filesystem.load_template_source',
|
||||||
|
'django.template.loaders.app_directories.load_template_source',
|
||||||
|
'dbtemplates.loader.load_template_source',
|
||||||
|
)
|
||||||
|
|
||||||
|
3. Sync your database via shell (hint: "./manage.py syncdb" within project dir)
|
||||||
|
4. Restart your Django server
|
||||||
|
5. Go to the admin interface and add templates by filling the ``name`` field
|
||||||
|
with filename like identifiers, for example "blog/entry_list.html"
|
||||||
|
6. Use it with ``Flatpages``, ``Generic views`` and your own custom views
|
||||||
|
|
||||||
|
What? Hm, doesn't work here. || Aaah nice, BUT...
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
Please leave your questions and messages on the designated Google Code site:
|
||||||
|
|
||||||
|
http://code.google.com/p/django-databasetemplateloader/
|
||||||
30
README.rst
30
README.rst
|
|
@ -1,30 +0,0 @@
|
||||||
django-dbtemplates
|
|
||||||
==================
|
|
||||||
|
|
||||||
.. image:: https://jazzband.co/static/img/badge.svg
|
|
||||||
:alt: Jazzband
|
|
||||||
:target: https://jazzband.co/
|
|
||||||
|
|
||||||
.. image:: https://github.com/jazzband/django-dbtemplates/workflows/Test/badge.svg
|
|
||||||
:target: https://github.com/jazzband/django-dbtemplates/actions
|
|
||||||
|
|
||||||
.. image:: https://codecov.io/github/jazzband/django-dbtemplates/coverage.svg?branch=master
|
|
||||||
:alt: Codecov
|
|
||||||
:target: https://codecov.io/github/jazzband/django-dbtemplates?branch=master
|
|
||||||
|
|
||||||
``dbtemplates`` is a Django app that consists of two parts:
|
|
||||||
|
|
||||||
1. It allows you to store templates in your database
|
|
||||||
2. It provides `template loader`_ that enables Django to load the
|
|
||||||
templates from the database
|
|
||||||
|
|
||||||
It also features optional support for versioned storage and django-admin
|
|
||||||
command, integrates with Django's caching system and the admin actions.
|
|
||||||
|
|
||||||
Please see https://django-dbtemplates.readthedocs.io/ for more details.
|
|
||||||
|
|
||||||
The source code and issue tracker can be found on Github:
|
|
||||||
|
|
||||||
https://github.com/jazzband/django-dbtemplates
|
|
||||||
|
|
||||||
.. _template loader: http://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import importlib.metadata
|
|
||||||
|
|
||||||
__version__ = importlib.metadata.version("django-dbtemplates")
|
|
||||||
|
|
@ -1,173 +1,17 @@
|
||||||
import posixpath
|
|
||||||
from django import forms
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.utils.translation import ngettext
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
from dbtemplates.conf import settings
|
from dbtemplates.models import Template
|
||||||
from dbtemplates.models import Template, add_template_to_cache, remove_cached_template
|
|
||||||
from dbtemplates.utils.template import check_template_syntax
|
|
||||||
|
|
||||||
# Check if either django-reversion-compare or django-reversion is installed and
|
class TemplateAdmin(admin.ModelAdmin):
|
||||||
# use reversion_compare's CompareVersionAdmin or reversion's VersionAdmin as
|
|
||||||
# the base admin class if yes
|
|
||||||
if settings.DBTEMPLATES_USE_REVERSION_COMPARE:
|
|
||||||
from reversion_compare.admin import CompareVersionAdmin \
|
|
||||||
as TemplateModelAdmin
|
|
||||||
elif settings.DBTEMPLATES_USE_REVERSION:
|
|
||||||
from reversion.admin import VersionAdmin as TemplateModelAdmin
|
|
||||||
else:
|
|
||||||
from django.contrib.admin import ModelAdmin as TemplateModelAdmin # noqa
|
|
||||||
|
|
||||||
|
|
||||||
class CodeMirrorTextArea(forms.Textarea):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A custom widget for the CodeMirror browser editor to be used with the
|
|
||||||
content field of the Template model.
|
|
||||||
"""
|
|
||||||
class Media:
|
|
||||||
css = dict(screen=[posixpath.join(
|
|
||||||
settings.DBTEMPLATES_MEDIA_PREFIX, 'css/editor.css')])
|
|
||||||
js = [posixpath.join(settings.DBTEMPLATES_MEDIA_PREFIX,
|
|
||||||
'js/codemirror.js')]
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, renderer=None):
|
|
||||||
result = []
|
|
||||||
result.append(
|
|
||||||
super().render(name, value, attrs))
|
|
||||||
result.append("""
|
|
||||||
<script type="text/javascript">
|
|
||||||
var editor = CodeMirror.fromTextArea('id_%(name)s', {
|
|
||||||
path: "%(media_prefix)sjs/",
|
|
||||||
parserfile: "parsedjango.js",
|
|
||||||
stylesheet: "%(media_prefix)scss/django.css",
|
|
||||||
continuousScanning: 500,
|
|
||||||
height: "40.2em",
|
|
||||||
tabMode: "shift",
|
|
||||||
indentUnit: 4,
|
|
||||||
lineNumbers: true
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
""" % dict(media_prefix=settings.DBTEMPLATES_MEDIA_PREFIX, name=name))
|
|
||||||
return mark_safe("".join(result))
|
|
||||||
|
|
||||||
|
|
||||||
if settings.DBTEMPLATES_USE_CODEMIRROR:
|
|
||||||
TemplateContentTextArea = CodeMirrorTextArea
|
|
||||||
else:
|
|
||||||
TemplateContentTextArea = forms.Textarea
|
|
||||||
|
|
||||||
if settings.DBTEMPLATES_AUTO_POPULATE_CONTENT:
|
|
||||||
content_help_text = _("Leaving this empty causes Django to look for a "
|
|
||||||
"template with the given name and populate this "
|
|
||||||
"field with its content.")
|
|
||||||
else:
|
|
||||||
content_help_text = ""
|
|
||||||
|
|
||||||
if settings.DBTEMPLATES_USE_CODEMIRROR and settings.DBTEMPLATES_USE_TINYMCE:
|
|
||||||
raise ImproperlyConfigured("You may use either CodeMirror or TinyMCE "
|
|
||||||
"with dbtemplates, not both. Please disable "
|
|
||||||
"one of them.")
|
|
||||||
|
|
||||||
if settings.DBTEMPLATES_USE_TINYMCE:
|
|
||||||
from tinymce.widgets import AdminTinyMCE
|
|
||||||
TemplateContentTextArea = AdminTinyMCE
|
|
||||||
elif settings.DBTEMPLATES_USE_REDACTOR:
|
|
||||||
from redactor.widgets import RedactorEditor
|
|
||||||
TemplateContentTextArea = RedactorEditor
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateAdminForm(forms.ModelForm):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Custom AdminForm to make the content textarea wider.
|
|
||||||
"""
|
|
||||||
content = forms.CharField(
|
|
||||||
widget=TemplateContentTextArea(attrs={'rows': '24'}),
|
|
||||||
help_text=content_help_text, required=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Template
|
|
||||||
fields = ('name', 'content', 'sites', 'creation_date', 'last_changed')
|
|
||||||
fields = "__all__"
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateAdmin(TemplateModelAdmin):
|
|
||||||
form = TemplateAdminForm
|
|
||||||
readonly_fields = ['creation_date', 'last_changed']
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {'fields': ('name', 'content', 'sites')}),
|
||||||
'fields': ('name', 'content'),
|
(_('Date information'), {
|
||||||
'classes': ('monospace',),
|
'fields': ('creation_date', 'last_changed'),
|
||||||
}),
|
'classes': ('collapse',)
|
||||||
(_('Advanced'), {
|
|
||||||
'fields': (('sites'),),
|
|
||||||
}),
|
|
||||||
(_('Date/time'), {
|
|
||||||
'fields': (('creation_date', 'last_changed'),),
|
|
||||||
'classes': ('collapse',),
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
filter_horizontal = ('sites',)
|
list_display = ('name', 'creation_date', 'last_changed')
|
||||||
list_display = ('name', 'creation_date', 'last_changed', 'site_list')
|
|
||||||
list_filter = ('sites',)
|
|
||||||
save_as = True
|
|
||||||
search_fields = ('name', 'content')
|
search_fields = ('name', 'content')
|
||||||
actions = ['invalidate_cache', 'repopulate_cache', 'check_syntax']
|
|
||||||
|
|
||||||
def invalidate_cache(self, request, queryset):
|
|
||||||
for template in queryset:
|
|
||||||
remove_cached_template(template)
|
|
||||||
count = queryset.count()
|
|
||||||
message = ngettext(
|
|
||||||
"Cache of one template successfully invalidated.",
|
|
||||||
"Cache of %(count)d templates successfully invalidated.",
|
|
||||||
count)
|
|
||||||
self.message_user(request, message % {'count': count})
|
|
||||||
invalidate_cache.short_description = _("Invalidate cache of "
|
|
||||||
"selected templates")
|
|
||||||
|
|
||||||
def repopulate_cache(self, request, queryset):
|
|
||||||
for template in queryset:
|
|
||||||
add_template_to_cache(template)
|
|
||||||
count = queryset.count()
|
|
||||||
message = ngettext(
|
|
||||||
"Cache successfully repopulated with one template.",
|
|
||||||
"Cache successfully repopulated with %(count)d templates.",
|
|
||||||
count)
|
|
||||||
self.message_user(request, message % {'count': count})
|
|
||||||
repopulate_cache.short_description = _("Repopulate cache with "
|
|
||||||
"selected templates")
|
|
||||||
|
|
||||||
def check_syntax(self, request, queryset):
|
|
||||||
errors = []
|
|
||||||
for template in queryset:
|
|
||||||
valid, error = check_template_syntax(template)
|
|
||||||
if not valid:
|
|
||||||
errors.append(f'{template.name}: {error}')
|
|
||||||
if errors:
|
|
||||||
count = len(errors)
|
|
||||||
message = ngettext(
|
|
||||||
"Template syntax check FAILED for %(names)s.",
|
|
||||||
"Template syntax check FAILED for "
|
|
||||||
"%(count)d templates: %(names)s.",
|
|
||||||
count)
|
|
||||||
self.message_user(request, message %
|
|
||||||
{'count': count, 'names': ', '.join(errors)})
|
|
||||||
else:
|
|
||||||
count = queryset.count()
|
|
||||||
message = ngettext(
|
|
||||||
"Template syntax OK.",
|
|
||||||
"Template syntax OK for %(count)d templates.", count)
|
|
||||||
self.message_user(request, message % {'count': count})
|
|
||||||
check_syntax.short_description = _("Check template syntax")
|
|
||||||
|
|
||||||
def site_list(self, template):
|
|
||||||
return ", ".join([site.name for site in template.sites.all()])
|
|
||||||
site_list.short_description = _('sites')
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Template, TemplateAdmin)
|
admin.site.register(Template, TemplateAdmin)
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class DBTemplatesConfig(AppConfig):
|
|
||||||
name = 'dbtemplates'
|
|
||||||
verbose_name = _('Database templates')
|
|
||||||
|
|
||||||
default_auto_field = 'django.db.models.AutoField'
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
import posixpath
|
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from appconf import AppConf
|
|
||||||
|
|
||||||
|
|
||||||
class DbTemplatesConf(AppConf):
|
|
||||||
USE_CODEMIRROR = False
|
|
||||||
USE_REVERSION = False
|
|
||||||
USE_REVERSION_COMPARE = False
|
|
||||||
USE_TINYMCE = False
|
|
||||||
USE_REDACTOR = False
|
|
||||||
ADD_DEFAULT_SITE = True
|
|
||||||
AUTO_POPULATE_CONTENT = True
|
|
||||||
MEDIA_PREFIX = None
|
|
||||||
CACHE_BACKEND = None
|
|
||||||
|
|
||||||
def configure_media_prefix(self, value):
|
|
||||||
if value is None:
|
|
||||||
base_url = getattr(settings, "STATIC_URL", None)
|
|
||||||
if base_url is None:
|
|
||||||
base_url = settings.MEDIA_URL
|
|
||||||
value = posixpath.join(base_url, "dbtemplates/")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def configure_cache_backend(self, value):
|
|
||||||
# If we are on Django 1.3 AND using the new CACHES setting..
|
|
||||||
if hasattr(settings, "CACHES"):
|
|
||||||
if "dbtemplates" in settings.CACHES:
|
|
||||||
return "dbtemplates"
|
|
||||||
else:
|
|
||||||
return "default"
|
|
||||||
if isinstance(value, str) and value.startswith("dbtemplates."):
|
|
||||||
raise ImproperlyConfigured("Please upgrade to one of the "
|
|
||||||
"supported backends as defined "
|
|
||||||
"in the Django docs.")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def configure_use_reversion(self, value):
|
|
||||||
if value and 'reversion' not in settings.INSTALLED_APPS:
|
|
||||||
raise ImproperlyConfigured("Please add 'reversion' to your "
|
|
||||||
"INSTALLED_APPS setting to make "
|
|
||||||
"use of it in dbtemplates.")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def configure_use_reversion_compare(self, value):
|
|
||||||
if value and 'reversion_compare' not in settings.INSTALLED_APPS:
|
|
||||||
raise ImproperlyConfigured("Please add 'reversion_compare' to your"
|
|
||||||
" INSTALLED_APPS setting to make "
|
|
||||||
"use of it in dbtemplates.")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def configure_use_tinymce(self, value):
|
|
||||||
if value and 'tinymce' not in settings.INSTALLED_APPS:
|
|
||||||
raise ImproperlyConfigured("Please add 'tinymce' to your "
|
|
||||||
"INSTALLED_APPS setting to make "
|
|
||||||
"use of it in dbtemplates.")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def configure_use_redactor(self, value):
|
|
||||||
if value and 'redactor' not in settings.INSTALLED_APPS:
|
|
||||||
raise ImproperlyConfigured("Please add 'redactor' to your "
|
|
||||||
"INSTALLED_APPS setting to make "
|
|
||||||
"use of it in dbtemplates.")
|
|
||||||
return value
|
|
||||||
|
|
@ -1,87 +1,75 @@
|
||||||
|
import os
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db.models import signals
|
||||||
|
from django.template import TemplateDoesNotExist
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
from django.db import router
|
|
||||||
from django.template import Origin, TemplateDoesNotExist
|
|
||||||
from django.template.loaders.base import Loader as BaseLoader
|
|
||||||
|
|
||||||
from dbtemplates.models import Template
|
from dbtemplates.models import Template
|
||||||
from dbtemplates.utils.cache import (cache, get_cache_key,
|
|
||||||
set_and_return, get_cache_notfound_key)
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
site = Site.objects.get_current()
|
||||||
|
except:
|
||||||
|
site = None
|
||||||
|
|
||||||
class Loader(BaseLoader):
|
try:
|
||||||
|
cache_dir = os.path.normpath(getattr(settings, 'DBTEMPLATES_CACHE_DIR', None))
|
||||||
|
if not os.path.isdir(cache_dir):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
cache_dir = None
|
||||||
|
|
||||||
|
def load_template_source(template_name, template_dirs=None):
|
||||||
"""
|
"""
|
||||||
A custom template loader to load templates from the database.
|
Tries to load the template from DBTEMPLATES_CACHE_DIR. If it does not exist
|
||||||
|
loads templates from the database by querying the database field ``name``
|
||||||
Tries to load the template from the dbtemplates cache backend specified
|
with a template path and ``sites`` with the current site,
|
||||||
by the DBTEMPLATES_CACHE_BACKEND setting. If it does not find a template
|
and tries to save the template as DBTEMPLATES_CACHE_DIR/``name`` for subsequent
|
||||||
it falls back to query the database field ``name`` with the template path
|
requests.
|
||||||
and ``sites`` with the current site.
|
If DBTEMPLATES_CACHE_DIR is not configured falls back to database-only operation.
|
||||||
"""
|
"""
|
||||||
is_usable = True
|
if site is not None:
|
||||||
|
if cache_dir is not None:
|
||||||
def get_template_sources(self, template_name, template_dirs=None):
|
filepath = os.path.join(cache_dir, template_name)
|
||||||
yield Origin(
|
|
||||||
name=template_name,
|
|
||||||
template_name=template_name,
|
|
||||||
loader=self,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_contents(self, origin):
|
|
||||||
content, _ = self._load_template_source(origin.template_name)
|
|
||||||
return content
|
|
||||||
|
|
||||||
def _load_and_store_template(self, template_name, cache_key, site,
|
|
||||||
**params):
|
|
||||||
template = Template.objects.get(name__exact=template_name, **params)
|
|
||||||
db = router.db_for_read(Template, instance=template)
|
|
||||||
display_name = f'dbtemplates:{db}:{template_name}:{site.domain}'
|
|
||||||
return set_and_return(cache_key, template.content, display_name)
|
|
||||||
|
|
||||||
def _load_template_source(self, template_name, template_dirs=None):
|
|
||||||
# The logic should work like this:
|
|
||||||
# * Try to find the template in the cache. If found, return it.
|
|
||||||
# * Now check the cache if a lookup for the given template
|
|
||||||
# has failed lately and hand over control to the next template
|
|
||||||
# loader waiting in line.
|
|
||||||
# * If this still did not fail we first try to find a site-specific
|
|
||||||
# template in the database.
|
|
||||||
# * On a failure from our last attempt we try to load the global
|
|
||||||
# template from the database.
|
|
||||||
# * If all of the above steps have failed we generate a new key
|
|
||||||
# in the cache indicating that queries failed, with the current
|
|
||||||
# timestamp.
|
|
||||||
site = Site.objects.get_current()
|
|
||||||
cache_key = get_cache_key(template_name)
|
|
||||||
if cache:
|
|
||||||
try:
|
try:
|
||||||
backend_template = cache.get(cache_key)
|
return (open(filepath).read(), filepath)
|
||||||
if backend_template:
|
except IOError:
|
||||||
return backend_template, template_name
|
try:
|
||||||
except Exception:
|
t = Template.objects.get(name__exact=template_name, sites__pk=site.id)
|
||||||
|
try:
|
||||||
|
f = open(filepath, 'w')
|
||||||
|
f.write(t.content)
|
||||||
|
f.close()
|
||||||
|
except IOError:
|
||||||
|
try:
|
||||||
|
head, tail = os.path.split(filepath)
|
||||||
|
if head and not os.path.isdir(head):
|
||||||
|
os.makedirs(head)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return (t.content, 'db:%s:%s' % (settings.DATABASE_ENGINE, template_name))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
t = Template.objects.get(name__exact=template_name, sites__pk=site.id)
|
||||||
|
return (t.content, 'db:%s:%s' % (settings.DATABASE_ENGINE, template_name))
|
||||||
|
except:
|
||||||
pass
|
pass
|
||||||
|
raise TemplateDoesNotExist, template_name
|
||||||
|
load_template_source.is_usable = True
|
||||||
|
|
||||||
# Not found in cache, move on.
|
def remove_cached_template(instance, **kwargs):
|
||||||
cache_notfound_key = get_cache_notfound_key(template_name)
|
"""
|
||||||
if cache:
|
Called via django's signals to remove cached templates, if the template in the
|
||||||
try:
|
database was changed or deleted.
|
||||||
notfound = cache.get(cache_notfound_key)
|
"""
|
||||||
if notfound:
|
if cache_dir is not None:
|
||||||
raise TemplateDoesNotExist(template_name)
|
|
||||||
except Exception:
|
|
||||||
raise TemplateDoesNotExist(template_name)
|
|
||||||
|
|
||||||
# Not marked as not-found, move on...
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return self._load_and_store_template(template_name, cache_key,
|
filepath = os.path.join(cache_dir, instance.name)
|
||||||
site, sites__in=[site.id])
|
os.remove(filepath)
|
||||||
except (Template.MultipleObjectsReturned, Template.DoesNotExist):
|
except OSError:
|
||||||
try:
|
pass
|
||||||
return self._load_and_store_template(template_name, cache_key,
|
|
||||||
site, sites__isnull=True)
|
|
||||||
except (Template.MultipleObjectsReturned, Template.DoesNotExist):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Mark as not-found in cache.
|
signals.post_save.connect(remove_cached_template, sender=Template)
|
||||||
cache.set(cache_notfound_key, '1')
|
signals.pre_delete.connect(remove_cached_template, sender=Template)
|
||||||
raise TemplateDoesNotExist(template_name)
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,108 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: django-dbtemplates\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
|
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
|
||||||
"PO-Revision-Date: 2011-08-15 11:14+0000\n"
|
|
||||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Language: da\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
|
||||||
msgid ""
|
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr ""
|
|
||||||
"Hvis du efterlader dette felt tomt, så vil Django søge efter en template med"
|
|
||||||
" det givne navn og udfylde dette felt med dets indhold."
|
|
||||||
|
|
||||||
#: admin.py:82
|
|
||||||
msgid "Advanced"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr "Dato/tid"
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural ""
|
|
||||||
"Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr "websider"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
|
||||||
msgstr "navn"
|
|
||||||
|
|
||||||
#: models.py:23
|
|
||||||
msgid "Example: 'flatpages/default.html'"
|
|
||||||
msgstr "Eksempel: 'flatpages/default.html'"
|
|
||||||
|
|
||||||
#: models.py:24
|
|
||||||
msgid "content"
|
|
||||||
msgstr "indhold"
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "creation date"
|
|
||||||
msgstr "oprettelsesdato"
|
|
||||||
|
|
||||||
#: models.py:29
|
|
||||||
msgid "last changed"
|
|
||||||
msgstr "sidst ændret"
|
|
||||||
|
|
||||||
#: models.py:37
|
|
||||||
msgid "template"
|
|
||||||
msgstr "skabelon"
|
|
||||||
|
|
||||||
#: models.py:38
|
|
||||||
msgid "templates"
|
|
||||||
msgstr "skabeloner"
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -1,110 +1,45 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
#
|
|
||||||
# Jannis Leidel <jannis@leidel.info>, 2011.
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-dbtemplates\n"
|
"Project-Id-Version: 0.4\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
"POT-Creation-Date: 2008-08-19 17:09+0200\n"
|
||||||
"PO-Revision-Date: 2011-08-15 11:14+0000\n"
|
"PO-Revision-Date: 2008-08-19 17:11+0100\n"
|
||||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Language: de\n"
|
"X-Poedit-Language: German\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
#: admin.py:9
|
||||||
msgid ""
|
msgid "Date information"
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
msgstr "Datum"
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr ""
|
|
||||||
"Wenn Sie dieses Feld leer lassen, wird Django versuchen, das Template mit "
|
|
||||||
"dem angegebenen Namen zu finden und mit dessen Inhalt das Feld zu füllen."
|
|
||||||
|
|
||||||
#: admin.py:82
|
#: models.py:14
|
||||||
msgid "Advanced"
|
|
||||||
msgstr "Erweiterte Einstellungen"
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr "Datum/Uhrzeit"
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] "Der Cache eines Templates wurde erfolgreich geleert."
|
|
||||||
msgstr[1] "Der Cache von %(count)d Templates wurde erfolgreich geleert."
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr "Cache der ausgewählten Templates leeren"
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] "Der Cache eines Templates wurde erfolgreich geleert und neu gefüllt."
|
|
||||||
msgstr[1] ""
|
|
||||||
"Der Cache von %(count)d Templates wurde erfolgreich geleert und neu gefüllt."
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr "Cache der ausgewählten Templates neu füllen"
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural ""
|
|
||||||
"Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] "Template-Syntax von %(names)s ist FEHLERHAFT."
|
|
||||||
msgstr[1] "Template-Syntax von %(count)d Templates (%(names)s) ist FEHLERHAFT."
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] "Template-Syntax ist OK."
|
|
||||||
msgstr[1] "Template-Syntax von %(count)d Templates ist OK."
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr "Template-Syntax überprüfen"
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr "Seiten"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr "Name"
|
msgstr "Name"
|
||||||
|
|
||||||
#: models.py:23
|
#: models.py:14
|
||||||
msgid "Example: 'flatpages/default.html'"
|
msgid "Example: 'flatpages/default.html'"
|
||||||
msgstr "Zum Beispiel: 'flatpages/default.html'"
|
msgstr "Zum Beispiel: 'flatpages/default.html'"
|
||||||
|
|
||||||
#: models.py:24
|
#: models.py:15
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr "Inhalt"
|
msgstr "Inhalt"
|
||||||
|
|
||||||
#: models.py:27
|
#: models.py:17
|
||||||
msgid "creation date"
|
msgid "creation date"
|
||||||
msgstr "Erstellt"
|
msgstr "Erstellt"
|
||||||
|
|
||||||
#: models.py:29
|
#: models.py:18
|
||||||
msgid "last changed"
|
msgid "last changed"
|
||||||
msgstr "Geändert"
|
msgstr "Geändert"
|
||||||
|
|
||||||
#: models.py:37
|
#: models.py:22
|
||||||
msgid "template"
|
msgid "template"
|
||||||
msgstr "Template"
|
msgstr "Template"
|
||||||
|
|
||||||
#: models.py:38
|
#: models.py:23
|
||||||
msgid "templates"
|
msgid "templates"
|
||||||
msgstr "Templates"
|
msgstr "Templates"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,104 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
|
||||||
msgid ""
|
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:82
|
|
||||||
msgid "Advanced"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural "Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:23
|
|
||||||
msgid "Example: 'flatpages/default.html'"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:24
|
|
||||||
msgid "content"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "creation date"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:29
|
|
||||||
msgid "last changed"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:37
|
|
||||||
msgid "template"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:38
|
|
||||||
msgid "templates"
|
|
||||||
msgstr ""
|
|
||||||
Binary file not shown.
|
|
@ -1,109 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
#
|
|
||||||
# Ville Säävuori <ville@syneus.fi>, 2011.
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: django-dbtemplates\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
|
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
|
||||||
"PO-Revision-Date: 2011-08-15 11:14+0000\n"
|
|
||||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Language: fi\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
|
||||||
msgid ""
|
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr ""
|
|
||||||
"Jos tämä jätetään tyhjäksi, Django etsiin annetulla nimellä olevan "
|
|
||||||
"mallipohjan ja täyttää tähän kenttään sen sisällön."
|
|
||||||
|
|
||||||
#: admin.py:82
|
|
||||||
msgid "Advanced"
|
|
||||||
msgstr "Lisäasetukset"
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr "Päiväys/aika"
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] "Yhden mallipohjan välimuisti on onnistuneesti tyhjennetty."
|
|
||||||
msgstr[1] "%(count)d mallipohjan välimusti on onnistuneesti tyhjennetty."
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr "Tyhjennä valittujen mallipohjien välimuisti."
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] "Yhden mallipohjan välimuisti on täytetty onnistuneesti."
|
|
||||||
msgstr[1] "%(count)d mallipohjan välimuisti on täytetty onnistuneesti."
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr "Täytä valittujen mallipohjien välimuisti."
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural ""
|
|
||||||
"Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr "sivustot"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
|
||||||
msgstr "nimi"
|
|
||||||
|
|
||||||
#: models.py:23
|
|
||||||
msgid "Example: 'flatpages/default.html'"
|
|
||||||
msgstr "Esimerkiksi: 'flatpages/default.html'"
|
|
||||||
|
|
||||||
#: models.py:24
|
|
||||||
msgid "content"
|
|
||||||
msgstr "sisätö"
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "creation date"
|
|
||||||
msgstr "luontipäivä"
|
|
||||||
|
|
||||||
#: models.py:29
|
|
||||||
msgid "last changed"
|
|
||||||
msgstr "viimeksi muutettu"
|
|
||||||
|
|
||||||
#: models.py:37
|
|
||||||
msgid "template"
|
|
||||||
msgstr "mallipohja"
|
|
||||||
|
|
||||||
#: models.py:38
|
|
||||||
msgid "templates"
|
|
||||||
msgstr "mallipohjat"
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -1,108 +1,45 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
#
|
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-dbtemplates\n"
|
"Project-Id-Version: 0.4\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
"POT-Creation-Date: 2008-07-29 13:19+0200\n"
|
||||||
"PO-Revision-Date: 2011-08-15 11:14+0000\n"
|
"PO-Revision-Date: 2008-08-09 21:39+0100\n"
|
||||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
"Last-Translator: Roland Frédéric <frederic.roland@creativeconvergence.be>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: Frédéric Roland <frederic.roland@creativeconvergence.be>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Language: fr\n"
|
"X-Poedit-Language: French\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
#: admin.py:9
|
||||||
msgid ""
|
msgid "Date information"
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
msgstr "Date"
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr ""
|
|
||||||
"Si vous laissez ceci vide , Django recherchera un modèle avec le nom donné "
|
|
||||||
"et remplira ce champ avec son contenu."
|
|
||||||
|
|
||||||
#: admin.py:82
|
#: models.py:14
|
||||||
msgid "Advanced"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr "Date/heure"
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, fuzzy, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] "Le cache d'un modèle a été invalidé avec succès."
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr "Invalidation du cache des modèles sélectionnés"
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, fuzzy, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] "Le cache d'un modèle a été rechargé avec succès."
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr "Rechargement du cache des modèles sélectionnés"
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural ""
|
|
||||||
"Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr "sites"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr "nom"
|
msgstr "nom"
|
||||||
|
|
||||||
#: models.py:23
|
#: models.py:14
|
||||||
msgid "Example: 'flatpages/default.html'"
|
msgid "Example: 'flatpages/default.html'"
|
||||||
msgstr "Exemple : 'flatpages/default.html'"
|
msgstr "Exemple: 'flatpages/default.html'"
|
||||||
|
|
||||||
#: models.py:24
|
#: models.py:15
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr "contenu"
|
msgstr "contenu"
|
||||||
|
|
||||||
#: models.py:27
|
#: models.py:17
|
||||||
msgid "creation date"
|
msgid "creation date"
|
||||||
msgstr "date de création"
|
msgstr "date de création"
|
||||||
|
|
||||||
#: models.py:29
|
#: models.py:18
|
||||||
msgid "last changed"
|
msgid "last changed"
|
||||||
msgstr "dernier changement"
|
msgstr "dernier changement"
|
||||||
|
|
||||||
#: models.py:37
|
#: models.py:22
|
||||||
msgid "template"
|
msgid "template"
|
||||||
msgstr "modèle"
|
msgstr "modèle"
|
||||||
|
|
||||||
#: models.py:38
|
#: models.py:23
|
||||||
msgid "templates"
|
msgid "templates"
|
||||||
msgstr "modèles"
|
msgstr "modèles"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,106 +1,50 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
# translation of PACKAGE.
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
# Copyright (C) 2008 THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# <>, 2008.
|
||||||
|
# , fuzzy
|
||||||
|
#
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: django-dbtemplates\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
"POT-Creation-Date: 2008-08-24 00:09+0200\n"
|
||||||
"PO-Revision-Date: 2011-08-15 11:14+0000\n"
|
"PO-Revision-Date: 2008-08-21 12:31+0300\n"
|
||||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
"Last-Translator: <>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Language: he\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
#: admin.py:9
|
||||||
msgid ""
|
msgid "Date information"
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
msgstr "תאריכים"
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr "אם זה ריק אז ג'נגו מחפש תבנית עם שם סיפק וממלא את השדה עם תוכנו"
|
|
||||||
|
|
||||||
#: admin.py:82
|
#: models.py:14
|
||||||
msgid "Advanced"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr "תאריך / זמן"
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural ""
|
|
||||||
"Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr "אתרים"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr "שם"
|
msgstr "שם"
|
||||||
|
|
||||||
#: models.py:23
|
#: models.py:14
|
||||||
msgid "Example: 'flatpages/default.html'"
|
msgid "Example: 'flatpages/default.html'"
|
||||||
msgstr "דוגמא: 'flatpages/default.html'"
|
msgstr "דוגמא: 'flatpages/default.html'"
|
||||||
|
|
||||||
#: models.py:24
|
#: models.py:15
|
||||||
msgid "content"
|
msgid "content"
|
||||||
msgstr "תוכן"
|
msgstr "תוכן"
|
||||||
|
|
||||||
#: models.py:27
|
#: models.py:17
|
||||||
msgid "creation date"
|
msgid "creation date"
|
||||||
msgstr "נוצר ב"
|
msgstr "נוצר ב"
|
||||||
|
|
||||||
#: models.py:29
|
#: models.py:18
|
||||||
msgid "last changed"
|
msgid "last changed"
|
||||||
msgstr "שונה ב"
|
msgstr "שונה ב"
|
||||||
|
|
||||||
#: models.py:37
|
#: models.py:22
|
||||||
msgid "template"
|
msgid "template"
|
||||||
msgstr "תבנית"
|
msgstr "תבנית"
|
||||||
|
|
||||||
#: models.py:38
|
#: models.py:23
|
||||||
msgid "templates"
|
msgid "templates"
|
||||||
msgstr "תבניות"
|
msgstr "תבניות"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,108 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: django-dbtemplates\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
|
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
|
||||||
"PO-Revision-Date: 2011-08-15 11:14+0000\n"
|
|
||||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Language: it\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
|
||||||
msgid ""
|
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr ""
|
|
||||||
"Lasciandolo vuoto, Django cercherà un template con lo stesso nome che avete "
|
|
||||||
"indicato sopra e userà il suo contenuto per riempire questo campo."
|
|
||||||
|
|
||||||
#: admin.py:82
|
|
||||||
msgid "Advanced"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural ""
|
|
||||||
"Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
|
||||||
msgstr "nome"
|
|
||||||
|
|
||||||
#: models.py:23
|
|
||||||
msgid "Example: 'flatpages/default.html'"
|
|
||||||
msgstr "Esempio: 'flatpages/default.html'"
|
|
||||||
|
|
||||||
#: models.py:24
|
|
||||||
msgid "content"
|
|
||||||
msgstr "contenuto"
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "creation date"
|
|
||||||
msgstr "data di creazione"
|
|
||||||
|
|
||||||
#: models.py:29
|
|
||||||
msgid "last changed"
|
|
||||||
msgstr "ultimo cambiamento"
|
|
||||||
|
|
||||||
#: models.py:37
|
|
||||||
msgid "template"
|
|
||||||
msgstr "template"
|
|
||||||
|
|
||||||
#: models.py:38
|
|
||||||
msgid "templates"
|
|
||||||
msgstr "template"
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -1,109 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
#
|
|
||||||
# Herson Hersonls <hersonls@gmail.com>, 2011.
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: django-dbtemplates\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
|
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
|
||||||
"PO-Revision-Date: 2011-08-15 11:14+0000\n"
|
|
||||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
|
||||||
"Language-Team: Portuguese (Brazilian) (http://www.transifex.net/projects/p/django-dbtemplates/team/pt_BR/)\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Language: pt_BR\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
|
||||||
msgid ""
|
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr ""
|
|
||||||
"Manter isto vazio faz com que o Django procure por um modelo (template) com "
|
|
||||||
"o dado nome e preencha este campo com o seu conteúdo"
|
|
||||||
|
|
||||||
#: admin.py:82
|
|
||||||
msgid "Advanced"
|
|
||||||
msgstr "Avançado"
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr "Data/hora"
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural ""
|
|
||||||
"Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
msgstr[1] ""
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr "sites"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
|
||||||
msgstr "Name"
|
|
||||||
|
|
||||||
#: models.py:23
|
|
||||||
msgid "Example: 'flatpages/default.html'"
|
|
||||||
msgstr "Exemplo: 'flatpages/default.html'"
|
|
||||||
|
|
||||||
#: models.py:24
|
|
||||||
msgid "content"
|
|
||||||
msgstr "conteúdo"
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "creation date"
|
|
||||||
msgstr "Data de criação"
|
|
||||||
|
|
||||||
#: models.py:29
|
|
||||||
msgid "last changed"
|
|
||||||
msgstr "ultima modificação"
|
|
||||||
|
|
||||||
#: models.py:37
|
|
||||||
msgid "template"
|
|
||||||
msgstr "modelo"
|
|
||||||
|
|
||||||
#: models.py:38
|
|
||||||
msgid "templates"
|
|
||||||
msgstr "modelos"
|
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
|
|
@ -1,106 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2013-07-30 14:03+0600\n"
|
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
|
||||||
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
|
||||||
|
|
||||||
#: admin.py:57
|
|
||||||
msgid ""
|
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr "Если вы оставите это поле незаполненным, Django будет искать шаблон с введённым именем и заполнит поле его содержимым."
|
|
||||||
|
|
||||||
#: admin.py:92
|
|
||||||
msgid "Advanced"
|
|
||||||
msgstr "Дополнительно"
|
|
||||||
|
|
||||||
#: admin.py:95
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr "Дата/время"
|
|
||||||
|
|
||||||
#: admin.py:112
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] "Кэш для шаблона успешно очищен."
|
|
||||||
msgstr[1] "Кэш для шаблонов (%(count)d шт.) успешно очищен."
|
|
||||||
|
|
||||||
#: admin.py:116
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr "Очистить кэш для выделенных шаблонов"
|
|
||||||
|
|
||||||
#: admin.py:124
|
|
||||||
#, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] "Кэш для шаблона успешно заполнен."
|
|
||||||
msgstr[1] "Кэш для шаблонов (%(count)d шт.) успешно заполнен."
|
|
||||||
|
|
||||||
#: admin.py:128
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr "Заполнить кэш для выделенных шаблонов"
|
|
||||||
|
|
||||||
#: admin.py:140
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural "Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] "Неверный синтаксис у шаблона %(names)s."
|
|
||||||
msgstr[1] "Неверный синтаксис у следующих шаблонов: %(names)s."
|
|
||||||
|
|
||||||
#: admin.py:148
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] "Синтаксис шаблона корректен."
|
|
||||||
msgstr[1] "Синтаксис шаблонов корректен."
|
|
||||||
|
|
||||||
#: admin.py:151
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr "Проверить синтаксис шаблона"
|
|
||||||
|
|
||||||
#: admin.py:155 models.py:29
|
|
||||||
msgid "sites"
|
|
||||||
msgstr "сайты"
|
|
||||||
|
|
||||||
#: models.py:26
|
|
||||||
msgid "name"
|
|
||||||
msgstr "название"
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "Example: 'flatpages/default.html'"
|
|
||||||
msgstr "Например: 'flatpages/default.html'"
|
|
||||||
|
|
||||||
#: models.py:28
|
|
||||||
msgid "content"
|
|
||||||
msgstr "содержимое"
|
|
||||||
|
|
||||||
#: models.py:31
|
|
||||||
msgid "creation date"
|
|
||||||
msgstr "дата создания"
|
|
||||||
|
|
||||||
#: models.py:33
|
|
||||||
msgid "last changed"
|
|
||||||
msgstr "последнее изменение"
|
|
||||||
|
|
||||||
#: models.py:41
|
|
||||||
msgid "template"
|
|
||||||
msgstr "шаблон"
|
|
||||||
|
|
||||||
#: models.py:42
|
|
||||||
msgid "templates"
|
|
||||||
msgstr "шаблоны"
|
|
||||||
Binary file not shown.
|
|
@ -1,102 +0,0 @@
|
||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: django-dbtemplates\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
|
|
||||||
"POT-Creation-Date: 2011-08-15 13:13+0200\n"
|
|
||||||
"PO-Revision-Date: 2011-08-15 11:14+0000\n"
|
|
||||||
"Last-Translator: Jannis <jannis@leidel.info>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Language: zh_CN\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0\n"
|
|
||||||
|
|
||||||
#: admin.py:56
|
|
||||||
msgid ""
|
|
||||||
"Leaving this empty causes Django to look for a template with the given name "
|
|
||||||
"and populate this field with its content."
|
|
||||||
msgstr "此项目留空将使系统用指定的名称寻找模板并应用到该项目。"
|
|
||||||
|
|
||||||
#: admin.py:82
|
|
||||||
msgid "Advanced"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:85
|
|
||||||
msgid "Date/time"
|
|
||||||
msgstr "日期/时间"
|
|
||||||
|
|
||||||
#: admin.py:102
|
|
||||||
#, fuzzy, python-format
|
|
||||||
msgid "Cache of one template successfully invalidated."
|
|
||||||
msgid_plural "Cache of %(count)d templates successfully invalidated."
|
|
||||||
msgstr[0] "该模板的缓存已经成功撤销。"
|
|
||||||
|
|
||||||
#: admin.py:106
|
|
||||||
msgid "Invalidate cache of selected templates"
|
|
||||||
msgstr "撤销选中模板的缓存"
|
|
||||||
|
|
||||||
#: admin.py:114
|
|
||||||
#, fuzzy, python-format
|
|
||||||
msgid "Cache successfully repopulated with one template."
|
|
||||||
msgid_plural "Cache successfully repopulated with %(count)d templates."
|
|
||||||
msgstr[0] "该模板的缓存已经成功启用。"
|
|
||||||
|
|
||||||
#: admin.py:118
|
|
||||||
msgid "Repopulate cache with selected templates"
|
|
||||||
msgstr "重新启用选中模板的缓存"
|
|
||||||
|
|
||||||
#: admin.py:130
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax check FAILED for %(names)s."
|
|
||||||
msgid_plural ""
|
|
||||||
"Template syntax check FAILED for %(count)d templates: %(names)s."
|
|
||||||
msgstr[0] ""
|
|
||||||
|
|
||||||
#: admin.py:138
|
|
||||||
#, python-format
|
|
||||||
msgid "Template syntax OK."
|
|
||||||
msgid_plural "Template syntax OK for %(count)d templates."
|
|
||||||
msgstr[0] ""
|
|
||||||
|
|
||||||
#: admin.py:141
|
|
||||||
msgid "Check template syntax"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:145 models.py:25
|
|
||||||
msgid "sites"
|
|
||||||
msgstr "站点"
|
|
||||||
|
|
||||||
#: models.py:22
|
|
||||||
msgid "name"
|
|
||||||
msgstr "名称"
|
|
||||||
|
|
||||||
#: models.py:23
|
|
||||||
msgid "Example: 'flatpages/default.html'"
|
|
||||||
msgstr "例如: 'flatpages/default.html'"
|
|
||||||
|
|
||||||
#: models.py:24
|
|
||||||
msgid "content"
|
|
||||||
msgstr "内容"
|
|
||||||
|
|
||||||
#: models.py:27
|
|
||||||
msgid "creation date"
|
|
||||||
msgstr "创建日期"
|
|
||||||
|
|
||||||
#: models.py:29
|
|
||||||
msgid "last changed"
|
|
||||||
msgstr "最新变更"
|
|
||||||
|
|
||||||
#: models.py:37
|
|
||||||
msgid "template"
|
|
||||||
msgstr "模板"
|
|
||||||
|
|
||||||
#: models.py:38
|
|
||||||
msgid "templates"
|
|
||||||
msgstr "模板"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
from django.db.models import signals
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
|
|
||||||
|
from dbtemplates.models import Template
|
||||||
|
from dbtemplates import models as template_app
|
||||||
|
|
||||||
|
def create_default_templates(app, created_models, verbosity, **kwargs):
|
||||||
|
"""Creates the default database template objects."""
|
||||||
|
try:
|
||||||
|
site = Site.objects.get_current()
|
||||||
|
except Site.DoesNotExist:
|
||||||
|
site = None
|
||||||
|
|
||||||
|
if site is not None:
|
||||||
|
if Template in created_models:
|
||||||
|
if verbosity >= 2:
|
||||||
|
print "Creating default database templates for error 404 and 500"
|
||||||
|
|
||||||
|
template404, created404 = Template.objects.get_or_create(
|
||||||
|
name="404.html")
|
||||||
|
if created404:
|
||||||
|
template404.content="""
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>{% trans 'Page not found' %}</h2>
|
||||||
|
<p>{% trans "We're sorry, but the requested page could not be found." %}</p>
|
||||||
|
{% endblock %}
|
||||||
|
"""
|
||||||
|
template404.save()
|
||||||
|
template404.sites.add(site)
|
||||||
|
|
||||||
|
template500, created500 = Template.objects.get_or_create(
|
||||||
|
name="500.html")
|
||||||
|
if created500:
|
||||||
|
template500.content="""
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% trans 'Server Error <em>(500)</em>' %}</h1>
|
||||||
|
<p>{% trans "There's been an error." %}</p>
|
||||||
|
{% endblock %}
|
||||||
|
"""
|
||||||
|
template500.save()
|
||||||
|
template500.sites.add(site)
|
||||||
|
|
||||||
|
signals.post_syncdb.connect(create_default_templates, sender=template_app)
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
from django.core.management.base import CommandError, BaseCommand
|
|
||||||
|
|
||||||
from dbtemplates.models import Template
|
|
||||||
from dbtemplates.utils.template import check_template_syntax
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Ensures templates stored in the database don't have syntax errors."
|
|
||||||
|
|
||||||
def handle(self, **options):
|
|
||||||
errors = []
|
|
||||||
for template in Template.objects.all():
|
|
||||||
valid, error = check_template_syntax(template)
|
|
||||||
if not valid:
|
|
||||||
errors.append(f'{template.name}: {error}')
|
|
||||||
if errors:
|
|
||||||
raise CommandError(
|
|
||||||
'Some templates contained errors\n%s' % '\n'.join(errors))
|
|
||||||
self.stdout.write('OK')
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import sys
|
|
||||||
from django.core.management.base import CommandError, BaseCommand
|
|
||||||
from django.contrib.sites.models import Site
|
|
||||||
|
|
||||||
from dbtemplates.models import Template
|
|
||||||
|
|
||||||
TEMPLATES = {
|
|
||||||
404: """
|
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block content %}
|
|
||||||
<h2>{% trans 'Page not found' %}</h2>
|
|
||||||
<p>{% trans "We're sorry, but the requested page could not be found." %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
""",
|
|
||||||
500: """
|
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% block content %}
|
|
||||||
<h1>{% trans 'Server Error <em>(500)</em>' %}</h1>
|
|
||||||
<p>{% trans "There's been an error." %}</p>
|
|
||||||
{% endblock %}
|
|
||||||
""",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Creates the default error templates as database template objects."
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument(
|
|
||||||
"-f", "--force", action="store_true", dest="force",
|
|
||||||
default=False, help="overwrite existing database templates")
|
|
||||||
|
|
||||||
def handle(self, **options):
|
|
||||||
force = options.get('force')
|
|
||||||
try:
|
|
||||||
site = Site.objects.get_current()
|
|
||||||
except Site.DoesNotExist:
|
|
||||||
raise CommandError("Please make sure to have the sites contrib "
|
|
||||||
"app installed and setup with a site object")
|
|
||||||
|
|
||||||
verbosity = int(options.get('verbosity', 1))
|
|
||||||
for error_code in (404, 500):
|
|
||||||
template, created = Template.objects.get_or_create(
|
|
||||||
name=f"{error_code}.html")
|
|
||||||
if created or (not created and force):
|
|
||||||
template.content = TEMPLATES.get(error_code, '')
|
|
||||||
template.save()
|
|
||||||
template.sites.add(site)
|
|
||||||
if verbosity >= 1:
|
|
||||||
sys.stdout.write("Created database template "
|
|
||||||
"for %s errors.\n" % error_code)
|
|
||||||
else:
|
|
||||||
if verbosity >= 1:
|
|
||||||
sys.stderr.write("A template for %s errors "
|
|
||||||
"already exists.\n" % error_code)
|
|
||||||
|
|
@ -1,174 +1,79 @@
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
from optparse import make_option
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core.management.base import CommandError, NoArgsCommand
|
||||||
|
from django.template.loaders.app_directories import app_template_dirs
|
||||||
|
|
||||||
from dbtemplates.models import Template
|
from dbtemplates.models import Template
|
||||||
from django.contrib.sites.models import Site
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
|
||||||
from django.template.loader import _engine_list
|
|
||||||
from django.template.utils import get_app_template_dirs
|
|
||||||
|
|
||||||
ALWAYS_ASK, FILES_TO_DATABASE, DATABASE_TO_FILES = ("0", "1", "2")
|
class Command(NoArgsCommand):
|
||||||
|
|
||||||
DIRS = []
|
|
||||||
for engine in _engine_list():
|
|
||||||
DIRS.extend(engine.dirs)
|
|
||||||
DIRS = tuple(DIRS)
|
|
||||||
app_template_dirs = get_app_template_dirs("templates")
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Syncs file system templates with the database bidirectionally."
|
help = "Syncs file system templates with the database bidirectionally."
|
||||||
|
option_list = NoArgsCommand.option_list + (
|
||||||
def add_arguments(self, parser):
|
make_option("-e", "--ext", dest="ext", action="store", default="html",
|
||||||
parser.add_argument(
|
help="extension of the files you want to sync with the database "
|
||||||
"-e",
|
"[default: %default]"),
|
||||||
"--ext",
|
make_option("-f", "--force", action="store_true", dest="force",
|
||||||
dest="ext",
|
default=False, help="overwrite existing database templates")
|
||||||
action="store",
|
)
|
||||||
default="html",
|
def handle_noargs(self, **options):
|
||||||
help="extension of the files you want to "
|
extension = options.get('ext')
|
||||||
"sync with the database [default: %(default)s]",
|
force = options.get('force')
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-f",
|
|
||||||
"--force",
|
|
||||||
action="store_true",
|
|
||||||
dest="force",
|
|
||||||
default=False,
|
|
||||||
help="overwrite existing database templates",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-o",
|
|
||||||
"--overwrite",
|
|
||||||
action="store",
|
|
||||||
dest="overwrite",
|
|
||||||
default="0",
|
|
||||||
help="'0' - ask always, '1' - overwrite database "
|
|
||||||
"templates from template files, '2' - overwrite "
|
|
||||||
"template files from database templates",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-a",
|
|
||||||
"--app-first",
|
|
||||||
action="store_true",
|
|
||||||
dest="app_first",
|
|
||||||
default=False,
|
|
||||||
help="look for templates in applications "
|
|
||||||
"directories before project templates",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-d",
|
|
||||||
"--delete",
|
|
||||||
action="store_true",
|
|
||||||
dest="delete",
|
|
||||||
default=False,
|
|
||||||
help="Delete templates after syncing",
|
|
||||||
)
|
|
||||||
group = parser.add_mutually_exclusive_group()
|
|
||||||
group.add_argument(
|
|
||||||
"-y",
|
|
||||||
"--yes",
|
|
||||||
action="store_true",
|
|
||||||
dest="auto_answer",
|
|
||||||
default=None,
|
|
||||||
help="Answer yes to all template creation questions."
|
|
||||||
)
|
|
||||||
group.add_argument(
|
|
||||||
"-n",
|
|
||||||
"--no",
|
|
||||||
action="store_false",
|
|
||||||
dest="auto_answer",
|
|
||||||
default=None,
|
|
||||||
help="Answer no to all template creation questions."
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, **options):
|
|
||||||
extension = options.get("ext")
|
|
||||||
force = options.get("force")
|
|
||||||
overwrite = options.get("overwrite")
|
|
||||||
app_first = options.get("app_first")
|
|
||||||
delete = options.get("delete")
|
|
||||||
auto_answer = options.get('auto_answer')
|
|
||||||
|
|
||||||
if not extension.startswith("."):
|
if not extension.startswith("."):
|
||||||
extension = f".{extension}"
|
extension = ".%s" % extension
|
||||||
|
|
||||||
try:
|
try:
|
||||||
site = Site.objects.get_current()
|
site = Site.objects.get_current()
|
||||||
except Exception:
|
except:
|
||||||
raise CommandError(
|
site = None
|
||||||
"Please make sure to have the sites contrib "
|
|
||||||
"app installed and setup with a site object"
|
|
||||||
)
|
|
||||||
|
|
||||||
if app_first:
|
if site is None:
|
||||||
tpl_dirs = app_template_dirs + DIRS
|
raise CommandError("Please make sure to have the sites contrib "
|
||||||
else:
|
"app installed and setup with a site object")
|
||||||
tpl_dirs = DIRS + app_template_dirs
|
|
||||||
templatedirs = [str(d) for d in tpl_dirs if os.path.isdir(d)]
|
if not type(settings.TEMPLATE_DIRS) in (tuple, list):
|
||||||
|
raise CommandError("Please make sure settings.TEMPLATE_DIRS is a "
|
||||||
|
"list or tuple.")
|
||||||
|
|
||||||
|
templatedirs = [d for d in
|
||||||
|
settings.TEMPLATE_DIRS + app_template_dirs if os.path.isdir(d)]
|
||||||
|
|
||||||
for templatedir in templatedirs:
|
for templatedir in templatedirs:
|
||||||
for dirpath, subdirs, filenames in os.walk(templatedir):
|
for dirpath, subdirs, filenames in os.walk(templatedir):
|
||||||
for f in [
|
for f in [f for f in filenames if f.endswith(extension)
|
||||||
f
|
and not f.startswith(".")]:
|
||||||
for f in filenames
|
|
||||||
if f.endswith(extension) and not f.startswith(".")
|
|
||||||
]:
|
|
||||||
path = os.path.join(dirpath, f)
|
path = os.path.join(dirpath, f)
|
||||||
name = path.split(str(templatedir))[1]
|
name = path.split(templatedir)[1][1:]
|
||||||
if name.startswith("/"):
|
|
||||||
name = name[1:]
|
|
||||||
try:
|
try:
|
||||||
t = Template.on_site.get(name__exact=name)
|
t = Template.objects.get(name__exact=name)
|
||||||
except Template.DoesNotExist:
|
except Template.DoesNotExist:
|
||||||
if not force:
|
confirm = raw_input(
|
||||||
if auto_answer is not None:
|
"\nA '%s' template doesn't exist in the database.\n"
|
||||||
confirm = "y" if auto_answer else "n"
|
"Create it with '%s'?"
|
||||||
else:
|
" (y/[n]): """ % (name, path))
|
||||||
confirm = input(
|
if confirm.lower().startswith('y'):
|
||||||
"\nA '%s' template doesn't exist in the "
|
t = Template(name=name,
|
||||||
"database.\nCreate it with '%s'?"
|
content=open(path, "r").read())
|
||||||
" (y/[n]): "
|
|
||||||
"" % (name, path)
|
|
||||||
)
|
|
||||||
if force or confirm.lower().startswith("y"):
|
|
||||||
with open(path, encoding="utf-8") as f:
|
|
||||||
t = Template(name=name, content=f.read())
|
|
||||||
t.save()
|
t.save()
|
||||||
t.sites.add(site)
|
t.sites.add(site)
|
||||||
else:
|
else:
|
||||||
while True:
|
while 1:
|
||||||
if overwrite == ALWAYS_ASK:
|
confirm = raw_input(
|
||||||
_i = (
|
"\n%s exists in the database.\n"
|
||||||
"\n%(template)s exists in the database.\n"
|
"(1) Overwrite %s with '%s'\n"
|
||||||
"(1) Overwrite %(template)s with '%(path)s'\n" # noqa
|
"(2) Overwrite '%s' with %s\n"
|
||||||
"(2) Overwrite '%(path)s' with %(template)s\n" # noqa
|
"Type 1 or 2 or press <Enter> to skip: "
|
||||||
"Type 1 or 2 or press <Enter> to skip: "
|
% (t.__repr__(),
|
||||||
% {"template": t.__repr__(), "path": path}
|
t.__repr__(), path,
|
||||||
)
|
path, t.__repr__()))
|
||||||
|
if confirm == '' or confirm in ('1', '2'):
|
||||||
confirm = input(_i)
|
if confirm == '1':
|
||||||
else:
|
t.content = open(path, 'r').read()
|
||||||
confirm = overwrite
|
t.save()
|
||||||
if confirm in (
|
t.sites.add(site)
|
||||||
"",
|
elif confirm == '2':
|
||||||
FILES_TO_DATABASE,
|
open(path, 'w').write(t.content)
|
||||||
DATABASE_TO_FILES,
|
|
||||||
):
|
|
||||||
if confirm == FILES_TO_DATABASE:
|
|
||||||
with open(path, encoding="utf-8") as f:
|
|
||||||
t.content = f.read()
|
|
||||||
t.save()
|
|
||||||
t.sites.add(site)
|
|
||||||
if delete:
|
|
||||||
try:
|
|
||||||
os.remove(path)
|
|
||||||
except OSError:
|
|
||||||
raise CommandError(
|
|
||||||
f"Couldn't delete {path}"
|
|
||||||
)
|
|
||||||
elif confirm == DATABASE_TO_FILES:
|
|
||||||
with open(path, "w", encoding="utf-8") as f: # noqa
|
|
||||||
f.write(t.content)
|
|
||||||
if delete:
|
|
||||||
t.delete()
|
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
import django
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("sites", "0001_initial"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="Template",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.AutoField(
|
|
||||||
verbose_name="ID",
|
|
||||||
serialize=False,
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"name",
|
|
||||||
models.CharField(
|
|
||||||
help_text="Example: 'flatpages/default.html'",
|
|
||||||
max_length=100,
|
|
||||||
verbose_name="name",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"content",
|
|
||||||
models.TextField(verbose_name="content", blank=True),
|
|
||||||
), # noqa
|
|
||||||
(
|
|
||||||
"creation_date",
|
|
||||||
models.DateTimeField(
|
|
||||||
default=django.utils.timezone.now,
|
|
||||||
verbose_name="creation date", # noqa
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"last_changed",
|
|
||||||
models.DateTimeField(
|
|
||||||
default=django.utils.timezone.now,
|
|
||||||
verbose_name="last changed", # noqa
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"sites",
|
|
||||||
models.ManyToManyField(
|
|
||||||
to="sites.Site", verbose_name="sites", blank=True
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"ordering": ("name",),
|
|
||||||
"db_table": "django_template",
|
|
||||||
"verbose_name": "template",
|
|
||||||
"verbose_name_plural": "templates",
|
|
||||||
},
|
|
||||||
bases=(models.Model,),
|
|
||||||
managers=[
|
|
||||||
("objects", django.db.models.manager.Manager()),
|
|
||||||
(
|
|
||||||
"on_site",
|
|
||||||
django.contrib.sites.managers.CurrentSiteManager("sites"),
|
|
||||||
), # noqa
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
# Generated by Django 5.1 on 2025-05-26 19:59
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('dbtemplates', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='template',
|
|
||||||
name='creation_date',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, verbose_name='creation date'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='template',
|
|
||||||
name='last_changed',
|
|
||||||
field=models.DateTimeField(auto_now=True, verbose_name='last changed'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
# Generated by Django 4.2.23 on 2025-07-28 13:34
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("dbtemplates", "0002_alter_template_creation_date_and_more"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name="template",
|
|
||||||
name="id",
|
|
||||||
field=models.BigAutoField(
|
|
||||||
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
@ -1,35 +1,21 @@
|
||||||
from dbtemplates.conf import settings
|
# -*- coding: utf-8 -*-
|
||||||
from dbtemplates.utils.cache import (
|
from datetime import datetime
|
||||||
add_template_to_cache,
|
|
||||||
remove_cached_template,
|
|
||||||
)
|
|
||||||
from dbtemplates.utils.template import get_template_source
|
|
||||||
|
|
||||||
from django.contrib.sites.managers import CurrentSiteManager
|
|
||||||
from django.contrib.sites.models import Site
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import signals
|
from django.template import loader, Context
|
||||||
from django.template import TemplateDoesNotExist
|
from django.core import validators
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class Template(models.Model):
|
class Template(models.Model):
|
||||||
"""
|
"""
|
||||||
Defines a template model for use with the database template loader.
|
Defines a template model for use with the database template loader.
|
||||||
The field ``name`` is the equivalent to the filename of a static template.
|
The field ``name`` is the equivalent to the filename of a static template.
|
||||||
"""
|
"""
|
||||||
id = models.BigAutoField(primary_key=True, verbose_name=_('ID'),
|
name = models.CharField(_('name'), unique=True, max_length=100, help_text=_("Example: 'flatpages/default.html'"))
|
||||||
serialize=False, auto_created=True)
|
content = models.TextField(_('content'))
|
||||||
name = models.CharField(_('name'), max_length=100,
|
sites = models.ManyToManyField(Site)
|
||||||
help_text=_("Example: 'flatpages/default.html'"))
|
creation_date = models.DateTimeField(_('creation date'), default=datetime.now)
|
||||||
content = models.TextField(_('content'), blank=True)
|
last_changed = models.DateTimeField(_('last changed'), default=datetime.now)
|
||||||
sites = models.ManyToManyField(Site, verbose_name=_('sites'),
|
|
||||||
blank=True)
|
|
||||||
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True)
|
|
||||||
last_changed = models.DateTimeField(_('last changed'), auto_now=True)
|
|
||||||
|
|
||||||
objects = models.Manager()
|
|
||||||
on_site = CurrentSiteManager('sites')
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'django_template'
|
db_table = 'django_template'
|
||||||
|
|
@ -37,44 +23,36 @@ class Template(models.Model):
|
||||||
verbose_name_plural = _('templates')
|
verbose_name_plural = _('templates')
|
||||||
ordering = ('name',)
|
ordering = ('name',)
|
||||||
|
|
||||||
def __str__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def populate(self, name=None):
|
def save(self):
|
||||||
"""
|
self.last_changed = datetime.now()
|
||||||
Tries to find a template with the same name and populates
|
super(Template, self).save()
|
||||||
the content field if found.
|
|
||||||
"""
|
|
||||||
if name is None:
|
|
||||||
name = self.name
|
|
||||||
try:
|
|
||||||
source = get_template_source(name)
|
|
||||||
if source:
|
|
||||||
self.content = source
|
|
||||||
except TemplateDoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
# If content is empty look for a template with the given name and
|
|
||||||
# populate the template instance with its content.
|
|
||||||
if settings.DBTEMPLATES_AUTO_POPULATE_CONTENT and not self.content:
|
|
||||||
self.populate()
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def add_default_site(instance, **kwargs):
|
__test__ = {'API_TESTS':"""
|
||||||
"""
|
>>> test_site = Site.objects.get(pk=1)
|
||||||
Called via Django's signals to cache the templates, if the template
|
>>> test_site
|
||||||
in the database was added or changed, only if
|
<Site: example.com>
|
||||||
DBTEMPLATES_ADD_DEFAULT_SITE setting is set.
|
>>> t1 = Template(name='base.html', content="<html><head></head><body>{% block content %}Welcome at {{ title }}{% endblock %}</body></html>")
|
||||||
"""
|
>>> t1.save()
|
||||||
if not settings.DBTEMPLATES_ADD_DEFAULT_SITE:
|
>>> t1.sites.add(test_site)
|
||||||
return
|
>>> t1
|
||||||
current_site = Site.objects.get_current()
|
<Template: base.html>
|
||||||
if current_site not in instance.sites.all():
|
>>> t2 = Template(name='sub.html', content='{% extends "base.html" %}{% block content %}This is {{ title }}{% endblock %}')
|
||||||
instance.sites.add(current_site)
|
>>> t2.save()
|
||||||
|
>>> t2.sites.add(test_site)
|
||||||
|
>>> t2
|
||||||
signals.post_save.connect(add_default_site, sender=Template)
|
<Template: sub.html>
|
||||||
signals.post_save.connect(add_template_to_cache, sender=Template)
|
>>> Template.objects.filter(sites=test_site)
|
||||||
signals.pre_delete.connect(remove_cached_template, sender=Template)
|
[<Template: 404.html>, <Template: 500.html>, <Template: base.html>, <Template: sub.html>]
|
||||||
|
>>> t2.sites.all()
|
||||||
|
[<Site: example.com>]
|
||||||
|
>>> from dbtemplates.loader import load_template_source
|
||||||
|
>>> loader.template_source_loaders = [load_template_source]
|
||||||
|
>>> loader.get_template("base.html").render(Context({'title':'MainPage'}))
|
||||||
|
u'<html><head></head><body>Welcome at MainPage</body></html>'
|
||||||
|
>>> loader.get_template("sub.html").render(Context({'title':'SubPage'}))
|
||||||
|
u'<html><head></head><body>This is SubPage</body></html>'
|
||||||
|
"""}
|
||||||
|
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
html {
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editbox {
|
|
||||||
margin: .4em;
|
|
||||||
padding: 0;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 10pt;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editbox p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.django {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.django-quote {
|
|
||||||
color: #281;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.django-tag-name {
|
|
||||||
color: #000;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-tagname {
|
|
||||||
color: #A0B;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-attribute {
|
|
||||||
color: #281;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-punctuation {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-attname {
|
|
||||||
color: #00F;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-comment {
|
|
||||||
color: #A70;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-cdata {
|
|
||||||
color: #48A;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-processing {
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-entity {
|
|
||||||
color: #A22;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-error {
|
|
||||||
color: #F00 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.xml-text {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
.dbtemplates-template div.content {
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-right: 0.6em;
|
|
||||||
}
|
|
||||||
.dbtemplates-template #id_content {
|
|
||||||
height: 40.2em;
|
|
||||||
width: 85%;
|
|
||||||
}
|
|
||||||
.CodeMirror-line-numbers {
|
|
||||||
width: 2.2em;
|
|
||||||
text-align: right;
|
|
||||||
margin: .4em;
|
|
||||||
margin-left: 6em;
|
|
||||||
padding: 0;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 10pt;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,496 +0,0 @@
|
||||||
/* CodeMirror main module
|
|
||||||
*
|
|
||||||
* Implements the CodeMirror constructor and prototype, which take care
|
|
||||||
* of initializing the editor frame, and providing the outside interface.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// The CodeMirrorConfig object is used to specify a default
|
|
||||||
// configuration. If you specify such an object before loading this
|
|
||||||
// file, the values you put into it will override the defaults given
|
|
||||||
// below. You can also assign to it after loading.
|
|
||||||
var CodeMirrorConfig = window.CodeMirrorConfig || {};
|
|
||||||
|
|
||||||
var CodeMirror = (function(){
|
|
||||||
function setDefaults(object, defaults) {
|
|
||||||
for (var option in defaults) {
|
|
||||||
if (!object.hasOwnProperty(option))
|
|
||||||
object[option] = defaults[option];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function forEach(array, action) {
|
|
||||||
for (var i = 0; i < array.length; i++)
|
|
||||||
action(array[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// These default options can be overridden by passing a set of
|
|
||||||
// options to a specific CodeMirror constructor. See manual.html for
|
|
||||||
// their meaning.
|
|
||||||
setDefaults(CodeMirrorConfig, {
|
|
||||||
stylesheet: [],
|
|
||||||
path: "",
|
|
||||||
parserfile: [],
|
|
||||||
basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"],
|
|
||||||
iframeClass: null,
|
|
||||||
passDelay: 200,
|
|
||||||
passTime: 50,
|
|
||||||
lineNumberDelay: 200,
|
|
||||||
lineNumberTime: 50,
|
|
||||||
continuousScanning: false,
|
|
||||||
saveFunction: null,
|
|
||||||
onChange: null,
|
|
||||||
undoDepth: 50,
|
|
||||||
undoDelay: 800,
|
|
||||||
disableSpellcheck: true,
|
|
||||||
textWrapping: true,
|
|
||||||
readOnly: false,
|
|
||||||
width: "",
|
|
||||||
height: "300px",
|
|
||||||
autoMatchParens: false,
|
|
||||||
parserConfig: null,
|
|
||||||
tabMode: "indent", // or "spaces", "default", "shift"
|
|
||||||
reindentOnLoad: false,
|
|
||||||
activeTokens: null,
|
|
||||||
cursorActivity: null,
|
|
||||||
lineNumbers: false,
|
|
||||||
indentUnit: 2,
|
|
||||||
domain: null
|
|
||||||
});
|
|
||||||
|
|
||||||
function addLineNumberDiv(container) {
|
|
||||||
var nums = document.createElement("DIV"),
|
|
||||||
scroller = document.createElement("DIV");
|
|
||||||
nums.style.position = "absolute";
|
|
||||||
nums.style.height = "100%";
|
|
||||||
if (nums.style.setExpression) {
|
|
||||||
try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");}
|
|
||||||
catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions
|
|
||||||
}
|
|
||||||
nums.style.top = "0px";
|
|
||||||
nums.style.overflow = "hidden";
|
|
||||||
container.appendChild(nums);
|
|
||||||
scroller.className = "CodeMirror-line-numbers";
|
|
||||||
nums.appendChild(scroller);
|
|
||||||
scroller.innerHTML = "<div>1</div>";
|
|
||||||
return nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
function frameHTML(options) {
|
|
||||||
if (typeof options.parserfile == "string")
|
|
||||||
options.parserfile = [options.parserfile];
|
|
||||||
if (typeof options.stylesheet == "string")
|
|
||||||
options.stylesheet = [options.stylesheet];
|
|
||||||
|
|
||||||
var html = ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html><head>"];
|
|
||||||
// Hack to work around a bunch of IE8-specific problems.
|
|
||||||
html.push("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=EmulateIE7\"/>");
|
|
||||||
forEach(options.stylesheet, function(file) {
|
|
||||||
html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + "\"/>");
|
|
||||||
});
|
|
||||||
forEach(options.basefiles.concat(options.parserfile), function(file) {
|
|
||||||
if (!/^https?:/.test(file)) file = options.path + file;
|
|
||||||
html.push("<script type=\"text/javascript\" src=\"" + file + "\"><" + "/script>");
|
|
||||||
});
|
|
||||||
html.push("</head><body style=\"border-width: 0;\" class=\"editbox\" spellcheck=\"" +
|
|
||||||
(options.disableSpellcheck ? "false" : "true") + "\"></body></html>");
|
|
||||||
return html.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
|
|
||||||
|
|
||||||
function CodeMirror(place, options) {
|
|
||||||
// Use passed options, if any, to override defaults.
|
|
||||||
this.options = options = options || {};
|
|
||||||
setDefaults(options, CodeMirrorConfig);
|
|
||||||
|
|
||||||
// Backward compatibility for deprecated options.
|
|
||||||
if (options.dumbTabs) options.tabMode = "spaces";
|
|
||||||
else if (options.normalTab) options.tabMode = "default";
|
|
||||||
|
|
||||||
var frame = this.frame = document.createElement("IFRAME");
|
|
||||||
if (options.iframeClass) frame.className = options.iframeClass;
|
|
||||||
frame.frameBorder = 0;
|
|
||||||
frame.style.border = "0";
|
|
||||||
frame.style.width = '100%';
|
|
||||||
frame.style.height = '100%';
|
|
||||||
// display: block occasionally suppresses some Firefox bugs, so we
|
|
||||||
// always add it, redundant as it sounds.
|
|
||||||
frame.style.display = "block";
|
|
||||||
|
|
||||||
var div = this.wrapping = document.createElement("DIV");
|
|
||||||
div.style.position = "relative";
|
|
||||||
div.className = "CodeMirror-wrapping";
|
|
||||||
div.style.width = options.width;
|
|
||||||
div.style.height = options.height;
|
|
||||||
// This is used by Editor.reroutePasteEvent
|
|
||||||
var teHack = this.textareaHack = document.createElement("TEXTAREA");
|
|
||||||
div.appendChild(teHack);
|
|
||||||
teHack.style.position = "absolute";
|
|
||||||
teHack.style.left = "-10000px";
|
|
||||||
teHack.style.width = "10px";
|
|
||||||
|
|
||||||
// Link back to this object, so that the editor can fetch options
|
|
||||||
// and add a reference to itself.
|
|
||||||
frame.CodeMirror = this;
|
|
||||||
if (options.domain && internetExplorer) {
|
|
||||||
this.html = frameHTML(options);
|
|
||||||
frame.src = "javascript:(function(){document.open();" +
|
|
||||||
(options.domain ? "document.domain=\"" + options.domain + "\";" : "") +
|
|
||||||
"document.write(window.frameElement.CodeMirror.html);document.close();})()";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
frame.src = "javascript:false";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (place.appendChild) place.appendChild(div);
|
|
||||||
else place(div);
|
|
||||||
div.appendChild(frame);
|
|
||||||
if (options.lineNumbers) this.lineNumbers = addLineNumberDiv(div);
|
|
||||||
|
|
||||||
this.win = frame.contentWindow;
|
|
||||||
if (!options.domain || !internetExplorer) {
|
|
||||||
this.win.document.open();
|
|
||||||
this.win.document.write(frameHTML(options));
|
|
||||||
this.win.document.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CodeMirror.prototype = {
|
|
||||||
init: function() {
|
|
||||||
if (this.options.initCallback) this.options.initCallback(this);
|
|
||||||
if (this.options.lineNumbers) this.activateLineNumbers();
|
|
||||||
if (this.options.reindentOnLoad) this.reindent();
|
|
||||||
},
|
|
||||||
|
|
||||||
getCode: function() {return this.editor.getCode();},
|
|
||||||
setCode: function(code) {this.editor.importCode(code);},
|
|
||||||
selection: function() {this.focusIfIE(); return this.editor.selectedText();},
|
|
||||||
reindent: function() {this.editor.reindent();},
|
|
||||||
reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);},
|
|
||||||
|
|
||||||
focusIfIE: function() {
|
|
||||||
// in IE, a lot of selection-related functionality only works when the frame is focused
|
|
||||||
if (this.win.select.ie_selection) this.focus();
|
|
||||||
},
|
|
||||||
focus: function() {
|
|
||||||
this.win.focus();
|
|
||||||
if (this.editor.selectionSnapshot) // IE hack
|
|
||||||
this.win.select.setBookmark(this.win.document.body, this.editor.selectionSnapshot);
|
|
||||||
},
|
|
||||||
replaceSelection: function(text) {
|
|
||||||
this.focus();
|
|
||||||
this.editor.replaceSelection(text);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
replaceChars: function(text, start, end) {
|
|
||||||
this.editor.replaceChars(text, start, end);
|
|
||||||
},
|
|
||||||
getSearchCursor: function(string, fromCursor, caseFold) {
|
|
||||||
return this.editor.getSearchCursor(string, fromCursor, caseFold);
|
|
||||||
},
|
|
||||||
|
|
||||||
undo: function() {this.editor.history.undo();},
|
|
||||||
redo: function() {this.editor.history.redo();},
|
|
||||||
historySize: function() {return this.editor.history.historySize();},
|
|
||||||
clearHistory: function() {this.editor.history.clear();},
|
|
||||||
|
|
||||||
grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);},
|
|
||||||
ungrabKeys: function() {this.editor.ungrabKeys();},
|
|
||||||
|
|
||||||
setParser: function(name, parserConfig) {this.editor.setParser(name, parserConfig);},
|
|
||||||
setSpellcheck: function(on) {this.win.document.body.spellcheck = on;},
|
|
||||||
setStylesheet: function(names) {
|
|
||||||
if (typeof names === "string") names = [names];
|
|
||||||
var activeStylesheets = {};
|
|
||||||
var matchedNames = {};
|
|
||||||
var links = this.win.document.getElementsByTagName("link");
|
|
||||||
// Create hashes of active stylesheets and matched names.
|
|
||||||
// This is O(n^2) but n is expected to be very small.
|
|
||||||
for (var x = 0, link; link = links[x]; x++) {
|
|
||||||
if (link.rel.indexOf("stylesheet") !== -1) {
|
|
||||||
for (var y = 0; y < names.length; y++) {
|
|
||||||
var name = names[y];
|
|
||||||
if (link.href.substring(link.href.length - name.length) === name) {
|
|
||||||
activeStylesheets[link.href] = true;
|
|
||||||
matchedNames[name] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Activate the selected stylesheets and disable the rest.
|
|
||||||
for (var x = 0, link; link = links[x]; x++) {
|
|
||||||
if (link.rel.indexOf("stylesheet") !== -1) {
|
|
||||||
link.disabled = !(link.href in activeStylesheets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Create any new stylesheets.
|
|
||||||
for (var y = 0; y < names.length; y++) {
|
|
||||||
var name = names[y];
|
|
||||||
if (!(name in matchedNames)) {
|
|
||||||
var link = this.win.document.createElement("link");
|
|
||||||
link.rel = "stylesheet";
|
|
||||||
link.type = "text/css";
|
|
||||||
link.href = name;
|
|
||||||
this.win.document.getElementsByTagName('head')[0].appendChild(link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setTextWrapping: function(on) {
|
|
||||||
if (on == this.options.textWrapping) return;
|
|
||||||
this.win.document.body.style.whiteSpace = on ? "" : "nowrap";
|
|
||||||
this.options.textWrapping = on;
|
|
||||||
if (this.lineNumbers) {
|
|
||||||
this.setLineNumbers(false);
|
|
||||||
this.setLineNumbers(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setIndentUnit: function(unit) {this.win.indentUnit = unit;},
|
|
||||||
setUndoDepth: function(depth) {this.editor.history.maxDepth = depth;},
|
|
||||||
setTabMode: function(mode) {this.options.tabMode = mode;},
|
|
||||||
setLineNumbers: function(on) {
|
|
||||||
if (on && !this.lineNumbers) {
|
|
||||||
this.lineNumbers = addLineNumberDiv(this.wrapping);
|
|
||||||
this.activateLineNumbers();
|
|
||||||
}
|
|
||||||
else if (!on && this.lineNumbers) {
|
|
||||||
this.wrapping.removeChild(this.lineNumbers);
|
|
||||||
this.wrapping.style.marginLeft = "";
|
|
||||||
this.lineNumbers = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);},
|
|
||||||
firstLine: function() {return this.editor.firstLine();},
|
|
||||||
lastLine: function() {return this.editor.lastLine();},
|
|
||||||
nextLine: function(line) {return this.editor.nextLine(line);},
|
|
||||||
prevLine: function(line) {return this.editor.prevLine(line);},
|
|
||||||
lineContent: function(line) {return this.editor.lineContent(line);},
|
|
||||||
setLineContent: function(line, content) {this.editor.setLineContent(line, content);},
|
|
||||||
removeLine: function(line){this.editor.removeLine(line);},
|
|
||||||
insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);},
|
|
||||||
selectLines: function(startLine, startOffset, endLine, endOffset) {
|
|
||||||
this.win.focus();
|
|
||||||
this.editor.selectLines(startLine, startOffset, endLine, endOffset);
|
|
||||||
},
|
|
||||||
nthLine: function(n) {
|
|
||||||
var line = this.firstLine();
|
|
||||||
for (; n > 1 && line !== false; n--)
|
|
||||||
line = this.nextLine(line);
|
|
||||||
return line;
|
|
||||||
},
|
|
||||||
lineNumber: function(line) {
|
|
||||||
var num = 0;
|
|
||||||
while (line !== false) {
|
|
||||||
num++;
|
|
||||||
line = this.prevLine(line);
|
|
||||||
}
|
|
||||||
return num;
|
|
||||||
},
|
|
||||||
jumpToLine: function(line) {
|
|
||||||
if (typeof line == "number") line = this.nthLine(line);
|
|
||||||
this.selectLines(line, 0);
|
|
||||||
this.win.focus();
|
|
||||||
},
|
|
||||||
currentLine: function() { // Deprecated, but still there for backward compatibility
|
|
||||||
return this.lineNumber(this.cursorLine());
|
|
||||||
},
|
|
||||||
cursorLine: function() {
|
|
||||||
return this.cursorPosition().line;
|
|
||||||
},
|
|
||||||
cursorCoords: function(start) {return this.editor.cursorCoords(start);},
|
|
||||||
|
|
||||||
activateLineNumbers: function() {
|
|
||||||
var frame = this.frame, win = frame.contentWindow, doc = win.document, body = doc.body,
|
|
||||||
nums = this.lineNumbers, scroller = nums.firstChild, self = this;
|
|
||||||
var barWidth = null;
|
|
||||||
|
|
||||||
function sizeBar() {
|
|
||||||
if (frame.offsetWidth == 0) return;
|
|
||||||
for (var root = frame; root.parentNode; root = root.parentNode);
|
|
||||||
if (!nums.parentNode || root != document || !win.Editor) {
|
|
||||||
// Clear event handlers (their nodes might already be collected, so try/catch)
|
|
||||||
try{clear();}catch(e){}
|
|
||||||
clearInterval(sizeInterval);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nums.offsetWidth != barWidth) {
|
|
||||||
barWidth = nums.offsetWidth;
|
|
||||||
nums.style.left = "-" + (frame.parentNode.style.marginLeft = barWidth + "px");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function doScroll() {
|
|
||||||
nums.scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0;
|
|
||||||
}
|
|
||||||
// Cleanup function, registered by nonWrapping and wrapping.
|
|
||||||
var clear = function(){};
|
|
||||||
sizeBar();
|
|
||||||
var sizeInterval = setInterval(sizeBar, 500);
|
|
||||||
|
|
||||||
function ensureEnoughLineNumbers(fill) {
|
|
||||||
var lineHeight = scroller.firstChild.offsetHeight;
|
|
||||||
if (lineHeight == 0) return;
|
|
||||||
var targetHeight = 50 + Math.max(body.offsetHeight, Math.max(frame.offsetHeight, body.scrollHeight || 0)),
|
|
||||||
lastNumber = Math.ceil(targetHeight / lineHeight);
|
|
||||||
for (var i = scroller.childNodes.length; i <= lastNumber; i++) {
|
|
||||||
var div = document.createElement("DIV");
|
|
||||||
div.appendChild(document.createTextNode(fill ? String(i + 1) : "\u00a0"));
|
|
||||||
scroller.appendChild(div);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nonWrapping() {
|
|
||||||
function update() {
|
|
||||||
ensureEnoughLineNumbers(true);
|
|
||||||
doScroll();
|
|
||||||
}
|
|
||||||
self.updateNumbers = update;
|
|
||||||
var onScroll = win.addEventHandler(win, "scroll", doScroll, true),
|
|
||||||
onResize = win.addEventHandler(win, "resize", update, true);
|
|
||||||
clear = function(){
|
|
||||||
onScroll(); onResize();
|
|
||||||
if (self.updateNumbers == update) self.updateNumbers = null;
|
|
||||||
};
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function wrapping() {
|
|
||||||
var node, lineNum, next, pos, changes = [], styleNums = self.options.styleNumbers;
|
|
||||||
|
|
||||||
function setNum(n, node) {
|
|
||||||
// Does not typically happen (but can, if you mess with the
|
|
||||||
// document during the numbering)
|
|
||||||
if (!lineNum) lineNum = scroller.appendChild(document.createElement("DIV"));
|
|
||||||
if (styleNums) styleNums(lineNum, node, n);
|
|
||||||
// Changes are accumulated, so that the document layout
|
|
||||||
// doesn't have to be recomputed during the pass
|
|
||||||
changes.push(lineNum); changes.push(n);
|
|
||||||
pos = lineNum.offsetHeight + lineNum.offsetTop;
|
|
||||||
lineNum = lineNum.nextSibling;
|
|
||||||
}
|
|
||||||
function commitChanges() {
|
|
||||||
for (var i = 0; i < changes.length; i += 2)
|
|
||||||
changes[i].innerHTML = changes[i + 1];
|
|
||||||
changes = [];
|
|
||||||
}
|
|
||||||
function work() {
|
|
||||||
if (!scroller.parentNode || scroller.parentNode != self.lineNumbers) return;
|
|
||||||
|
|
||||||
var endTime = new Date().getTime() + self.options.lineNumberTime;
|
|
||||||
while (node) {
|
|
||||||
setNum(next++, node.previousSibling);
|
|
||||||
for (; node && !win.isBR(node); node = node.nextSibling) {
|
|
||||||
var bott = node.offsetTop + node.offsetHeight;
|
|
||||||
while (scroller.offsetHeight && bott - 3 > pos) setNum(" ");
|
|
||||||
}
|
|
||||||
if (node) node = node.nextSibling;
|
|
||||||
if (new Date().getTime() > endTime) {
|
|
||||||
commitChanges();
|
|
||||||
pending = setTimeout(work, self.options.lineNumberDelay);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
commitChanges();
|
|
||||||
doScroll();
|
|
||||||
}
|
|
||||||
function start() {
|
|
||||||
doScroll();
|
|
||||||
ensureEnoughLineNumbers(false);
|
|
||||||
node = body.firstChild;
|
|
||||||
lineNum = scroller.firstChild;
|
|
||||||
pos = 0;
|
|
||||||
next = 1;
|
|
||||||
work();
|
|
||||||
}
|
|
||||||
|
|
||||||
start();
|
|
||||||
var pending = null;
|
|
||||||
function update() {
|
|
||||||
if (pending) clearTimeout(pending);
|
|
||||||
if (self.editor.allClean()) start();
|
|
||||||
else pending = setTimeout(update, 200);
|
|
||||||
}
|
|
||||||
self.updateNumbers = update;
|
|
||||||
var onScroll = win.addEventHandler(win, "scroll", doScroll, true),
|
|
||||||
onResize = win.addEventHandler(win, "resize", update, true);
|
|
||||||
clear = function(){
|
|
||||||
if (pending) clearTimeout(pending);
|
|
||||||
if (self.updateNumbers == update) self.updateNumbers = null;
|
|
||||||
onScroll();
|
|
||||||
onResize();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
(this.options.textWrapping || this.options.styleNumbers ? wrapping : nonWrapping)();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}};
|
|
||||||
|
|
||||||
CodeMirror.replace = function(element) {
|
|
||||||
if (typeof element == "string")
|
|
||||||
element = document.getElementById(element);
|
|
||||||
return function(newElement) {
|
|
||||||
element.parentNode.replaceChild(newElement, element);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.fromTextArea = function(area, options) {
|
|
||||||
if (typeof area == "string")
|
|
||||||
area = document.getElementById(area);
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
if (area.style.width && options.width == null)
|
|
||||||
options.width = area.style.width;
|
|
||||||
if (area.style.height && options.height == null)
|
|
||||||
options.height = area.style.height;
|
|
||||||
if (options.content == null) options.content = area.value;
|
|
||||||
|
|
||||||
if (area.form) {
|
|
||||||
function updateField() {
|
|
||||||
area.value = mirror.getCode();
|
|
||||||
}
|
|
||||||
if (typeof area.form.addEventListener == "function")
|
|
||||||
area.form.addEventListener("submit", updateField, false);
|
|
||||||
else
|
|
||||||
area.form.attachEvent("onsubmit", updateField);
|
|
||||||
var realSubmit = area.form.submit;
|
|
||||||
function wrapSubmit() {
|
|
||||||
updateField();
|
|
||||||
// Can't use realSubmit.apply because IE6 is too stupid
|
|
||||||
area.form.submit = realSubmit;
|
|
||||||
area.form.submit();
|
|
||||||
area.form.submit = wrapSubmit;
|
|
||||||
}
|
|
||||||
area.form.submit = wrapSubmit;
|
|
||||||
}
|
|
||||||
|
|
||||||
function insert(frame) {
|
|
||||||
if (area.nextSibling)
|
|
||||||
area.parentNode.insertBefore(frame, area.nextSibling);
|
|
||||||
else
|
|
||||||
area.parentNode.appendChild(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
area.style.display = "none";
|
|
||||||
var mirror = new CodeMirror(insert, options);
|
|
||||||
return mirror;
|
|
||||||
};
|
|
||||||
|
|
||||||
CodeMirror.isProbablySupported = function() {
|
|
||||||
// This is rather awful, but can be useful.
|
|
||||||
var match;
|
|
||||||
if (window.opera)
|
|
||||||
return Number(window.opera.version()) >= 9.52;
|
|
||||||
else if (/Apple Computers, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./)))
|
|
||||||
return Number(match[1]) >= 3;
|
|
||||||
else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/)))
|
|
||||||
return Number(match[1]) >= 6;
|
|
||||||
else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i))
|
|
||||||
return Number(match[1]) >= 20050901;
|
|
||||||
else if (match = navigator.userAgent.match(/AppleWebKit\/(\d+)/))
|
|
||||||
return Number(match[1]) >= 525;
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return CodeMirror;
|
|
||||||
})();
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,68 +0,0 @@
|
||||||
// Minimal framing needed to use CodeMirror-style parsers to highlight
|
|
||||||
// code. Load this along with tokenize.js, stringstream.js, and your
|
|
||||||
// parser. Then call highlightText, passing a string as the first
|
|
||||||
// argument, and as the second argument either a callback function
|
|
||||||
// that will be called with an array of SPAN nodes for every line in
|
|
||||||
// the code, or a DOM node to which to append these spans, and
|
|
||||||
// optionally (not needed if you only loaded one parser) a parser
|
|
||||||
// object.
|
|
||||||
|
|
||||||
// Stuff from util.js that the parsers are using.
|
|
||||||
var StopIteration = {toString: function() {return "StopIteration"}};
|
|
||||||
|
|
||||||
var Editor = {};
|
|
||||||
var indentUnit = 2;
|
|
||||||
|
|
||||||
(function(){
|
|
||||||
function normaliseString(string) {
|
|
||||||
var tab = "";
|
|
||||||
for (var i = 0; i < indentUnit; i++) tab += " ";
|
|
||||||
|
|
||||||
string = string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n");
|
|
||||||
var pos = 0, parts = [], lines = string.split("\n");
|
|
||||||
for (var line = 0; line < lines.length; line++) {
|
|
||||||
if (line != 0) parts.push("\n");
|
|
||||||
parts.push(lines[line]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
next: function() {
|
|
||||||
if (pos < parts.length) return parts[pos++];
|
|
||||||
else throw StopIteration;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
window.highlightText = function(string, callback, parser) {
|
|
||||||
parser = (parser || Editor.Parser).make(stringStream(normaliseString(string)));
|
|
||||||
var line = [];
|
|
||||||
if (callback.nodeType == 1) {
|
|
||||||
var node = callback;
|
|
||||||
callback = function(line) {
|
|
||||||
for (var i = 0; i < line.length; i++)
|
|
||||||
node.appendChild(line[i]);
|
|
||||||
node.appendChild(document.createElement("BR"));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
var token = parser.next();
|
|
||||||
if (token.value == "\n") {
|
|
||||||
callback(line);
|
|
||||||
line = [];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var span = document.createElement("SPAN");
|
|
||||||
span.className = token.style;
|
|
||||||
span.appendChild(document.createTextNode(token.value));
|
|
||||||
line.push(span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
if (e != StopIteration) throw e;
|
|
||||||
}
|
|
||||||
if (line.length) callback(line);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
/* Demonstration of embedding CodeMirror in a bigger application. The
|
|
||||||
* interface defined here is a mess of prompts and confirms, and
|
|
||||||
* should probably not be used in a real project.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function MirrorFrame(place, options) {
|
|
||||||
this.home = document.createElement("DIV");
|
|
||||||
if (place.appendChild)
|
|
||||||
place.appendChild(this.home);
|
|
||||||
else
|
|
||||||
place(this.home);
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
function makeButton(name, action) {
|
|
||||||
var button = document.createElement("INPUT");
|
|
||||||
button.type = "button";
|
|
||||||
button.value = name;
|
|
||||||
self.home.appendChild(button);
|
|
||||||
button.onclick = function(){self[action].call(self);};
|
|
||||||
}
|
|
||||||
|
|
||||||
makeButton("Search", "search");
|
|
||||||
makeButton("Replace", "replace");
|
|
||||||
makeButton("Current line", "line");
|
|
||||||
makeButton("Jump to line", "jump");
|
|
||||||
makeButton("Insert constructor", "macro");
|
|
||||||
makeButton("Indent all", "reindent");
|
|
||||||
|
|
||||||
this.mirror = new CodeMirror(this.home, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
MirrorFrame.prototype = {
|
|
||||||
search: function() {
|
|
||||||
var text = prompt("Enter search term:", "");
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
var first = true;
|
|
||||||
do {
|
|
||||||
var cursor = this.mirror.getSearchCursor(text, first);
|
|
||||||
first = false;
|
|
||||||
while (cursor.findNext()) {
|
|
||||||
cursor.select();
|
|
||||||
if (!confirm("Search again?"))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} while (confirm("End of document reached. Start over?"));
|
|
||||||
},
|
|
||||||
|
|
||||||
replace: function() {
|
|
||||||
// This is a replace-all, but it is possible to implement a
|
|
||||||
// prompting replace.
|
|
||||||
var from = prompt("Enter search string:", ""), to;
|
|
||||||
if (from) to = prompt("What should it be replaced with?", "");
|
|
||||||
if (to == null) return;
|
|
||||||
|
|
||||||
var cursor = this.mirror.getSearchCursor(from, false);
|
|
||||||
while (cursor.findNext())
|
|
||||||
cursor.replace(to);
|
|
||||||
},
|
|
||||||
|
|
||||||
jump: function() {
|
|
||||||
var line = prompt("Jump to line:", "");
|
|
||||||
if (line && !isNaN(Number(line)))
|
|
||||||
this.mirror.jumpToLine(Number(line));
|
|
||||||
},
|
|
||||||
|
|
||||||
line: function() {
|
|
||||||
alert("The cursor is currently at line " + this.mirror.currentLine());
|
|
||||||
this.mirror.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
macro: function() {
|
|
||||||
var name = prompt("Name your constructor:", "");
|
|
||||||
if (name)
|
|
||||||
this.mirror.replaceSelection("function " + name + "() {\n \n}\n\n" + name + ".prototype = {\n \n};\n");
|
|
||||||
},
|
|
||||||
|
|
||||||
reindent: function() {
|
|
||||||
this.mirror.reindent();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,354 +0,0 @@
|
||||||
/* This file defines an XML/Django parser, with a few kludges to make it
|
|
||||||
* useable for HTML. autoSelfClosers defines a set of tag names that
|
|
||||||
* are expected to not have a closing tag, and doNotIndent specifies
|
|
||||||
* the tags inside of which no indentation should happen (see Config
|
|
||||||
* object). These can be disabled by passing the editor an object like
|
|
||||||
* {useHTMLKludges: false} as parserConfig option.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var XMLParser = Editor.Parser = (function() {
|
|
||||||
var Kludges = {
|
|
||||||
autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
|
|
||||||
"meta": true, "col": true, "frame": true, "base": true, "area": true},
|
|
||||||
doNotIndent: {"pre": true, "!cdata": true}
|
|
||||||
};
|
|
||||||
var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}};
|
|
||||||
var UseKludges = Kludges;
|
|
||||||
var alignCDATA = false;
|
|
||||||
|
|
||||||
// Simple stateful tokenizer for XML documents. Returns a
|
|
||||||
// MochiKit-style iterator, with a state property that contains a
|
|
||||||
// function encapsulating the current state. See tokenize.js.
|
|
||||||
var tokenizeXML = (function() {
|
|
||||||
function inText(source, setState) {
|
|
||||||
var ch = source.next();
|
|
||||||
|
|
||||||
if (ch == "{" && source.equals("{")){
|
|
||||||
setState(inDjango("}}", false));
|
|
||||||
return "django";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == "{" && source.equals("%")){
|
|
||||||
setState(inDjango("%}", true));
|
|
||||||
return "django";
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (ch == "<") {
|
|
||||||
if (source.equals("!")) {
|
|
||||||
source.next();
|
|
||||||
if (source.equals("[")) {
|
|
||||||
if (source.lookAhead("[CDATA[", true)) {
|
|
||||||
setState(inBlock("xml-cdata", "]]>"));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "xml-text";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (source.lookAhead("--", true)) {
|
|
||||||
setState(inBlock("xml-comment", "-->"));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "xml-text";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (source.equals("?")) {
|
|
||||||
source.next();
|
|
||||||
source.nextWhileMatches(/[\w\._\-]/);
|
|
||||||
setState(inBlock("xml-processing", "?>"));
|
|
||||||
return "xml-processing";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (source.equals("/")) source.next();
|
|
||||||
setState(inTag);
|
|
||||||
return "xml-punctuation";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ch == "&") {
|
|
||||||
while (!source.endOfLine()) {
|
|
||||||
if (source.next() == ";")
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return "xml-entity";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
source.nextWhileMatches(/[^&<\n{]/);
|
|
||||||
return "xml-text";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function inTag(source, setState) {
|
|
||||||
var ch = source.next();
|
|
||||||
if (ch == ">") {
|
|
||||||
setState(inText);
|
|
||||||
return "xml-punctuation";
|
|
||||||
}
|
|
||||||
else if (/[?\/]/.test(ch) && source.equals(">")) {
|
|
||||||
source.next();
|
|
||||||
setState(inText);
|
|
||||||
return "xml-punctuation";
|
|
||||||
}
|
|
||||||
else if (ch == "=") {
|
|
||||||
return "xml-punctuation";
|
|
||||||
}
|
|
||||||
else if (/[\'\"]/.test(ch)) {
|
|
||||||
setState(inAttribute(ch));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/);
|
|
||||||
return "xml-name";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function inAttribute(quote) {
|
|
||||||
return function(source, setState) {
|
|
||||||
while (!source.endOfLine()) {
|
|
||||||
if (source.next() == quote) {
|
|
||||||
setState(inTag);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "xml-attribute";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function inBlock(style, terminator) {
|
|
||||||
return function(source, setState) {
|
|
||||||
while (!source.endOfLine()) {
|
|
||||||
if (source.lookAhead(terminator, true)) {
|
|
||||||
setState(inText);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
source.next();
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function inDjangoQuote(quote, terminator){
|
|
||||||
return function(source, setState) {
|
|
||||||
source.next();
|
|
||||||
while (!source.endOfLine()) {
|
|
||||||
if (source.next() == quote) {
|
|
||||||
setState(inDjango(terminator, false));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "django-quote";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function inDjangoTagName(terminator){
|
|
||||||
return function(source, setState){
|
|
||||||
while (!source.endOfLine()) {
|
|
||||||
if (source.equals(' ')) {
|
|
||||||
setState(inDjango(terminator, false));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
source.next();
|
|
||||||
}
|
|
||||||
return "django-tag-name";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function inDjango(terminator, open){
|
|
||||||
return function(source, setState){
|
|
||||||
if (open){
|
|
||||||
source.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
while(!source.endOfLine()){
|
|
||||||
ch = source.next();
|
|
||||||
|
|
||||||
if (open && !source.equals(' ')){
|
|
||||||
setState(inDjangoTagName(terminator));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (ch == "'" || source.equals("'")){
|
|
||||||
setState(inDjangoQuote("'", terminator));
|
|
||||||
return "django-quote";
|
|
||||||
}
|
|
||||||
else if (ch == '"' || source.equals('"')){
|
|
||||||
setState(inDjangoQuote('"', terminator));
|
|
||||||
return "django-quote";
|
|
||||||
}
|
|
||||||
else if (ch == terminator[0] && source.equals(terminator[1])){
|
|
||||||
source.next();
|
|
||||||
setState(inText);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "django";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return function(source, startState) {
|
|
||||||
return tokenizer(source, startState || inText);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
// The parser. The structure of this function largely follows that of
|
|
||||||
// parseJavaScript in parsejavascript.js (there is actually a bit more
|
|
||||||
// shared code than I'd like), but it is quite a bit simpler.
|
|
||||||
function parseXML(source) {
|
|
||||||
var tokens = tokenizeXML(source), token;
|
|
||||||
var cc = [base];
|
|
||||||
var tokenNr = 0, indented = 0;
|
|
||||||
var currentTag = null, context = null;
|
|
||||||
var consume;
|
|
||||||
|
|
||||||
function push(fs) {
|
|
||||||
for (var i = fs.length - 1; i >= 0; i--)
|
|
||||||
cc.push(fs[i]);
|
|
||||||
}
|
|
||||||
function cont() {
|
|
||||||
push(arguments);
|
|
||||||
consume = true;
|
|
||||||
}
|
|
||||||
function pass() {
|
|
||||||
push(arguments);
|
|
||||||
consume = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function markErr() {
|
|
||||||
token.style += " xml-error";
|
|
||||||
}
|
|
||||||
function expect(text) {
|
|
||||||
return function(style, content) {
|
|
||||||
if (content == text) cont();
|
|
||||||
else {markErr(); cont(arguments.callee);}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function pushContext(tagname, startOfLine) {
|
|
||||||
var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
|
|
||||||
context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
|
|
||||||
}
|
|
||||||
function popContext() {
|
|
||||||
context = context.prev;
|
|
||||||
}
|
|
||||||
function computeIndentation(baseContext) {
|
|
||||||
return function(nextChars, current) {
|
|
||||||
var context = baseContext;
|
|
||||||
if (context && context.noIndent)
|
|
||||||
return current;
|
|
||||||
if (alignCDATA && /<!\[CDATA\[/.test(nextChars))
|
|
||||||
return 0;
|
|
||||||
if (context && /^<\//.test(nextChars))
|
|
||||||
context = context.prev;
|
|
||||||
while (context && !context.startOfLine)
|
|
||||||
context = context.prev;
|
|
||||||
if (context)
|
|
||||||
return context.indent + indentUnit;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function base() {
|
|
||||||
return pass(element, base);
|
|
||||||
}
|
|
||||||
var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true, "django": true, "django-quote": true, "django-tag-name": true };
|
|
||||||
function element(style, content) {
|
|
||||||
if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
|
|
||||||
else if (content == "</") cont(closetagname, expect(">"));
|
|
||||||
else if (style == "xml-cdata") {
|
|
||||||
if (!context || context.name != "!cdata") pushContext("!cdata");
|
|
||||||
if (/\]\]>$/.test(content)) popContext();
|
|
||||||
cont();
|
|
||||||
}
|
|
||||||
else if (harmlessTokens.hasOwnProperty(style)) cont();
|
|
||||||
else {markErr(); cont();}
|
|
||||||
}
|
|
||||||
function tagname(style, content) {
|
|
||||||
if (style == "xml-name") {
|
|
||||||
currentTag = content.toLowerCase();
|
|
||||||
token.style = "xml-tagname";
|
|
||||||
cont();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
currentTag = null;
|
|
||||||
pass();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function closetagname(style, content) {
|
|
||||||
if (style == "xml-name") {
|
|
||||||
token.style = "xml-tagname";
|
|
||||||
if (context && content.toLowerCase() == context.name) popContext();
|
|
||||||
else markErr();
|
|
||||||
}
|
|
||||||
cont();
|
|
||||||
}
|
|
||||||
function endtag(startOfLine) {
|
|
||||||
return function(style, content) {
|
|
||||||
if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
|
|
||||||
else if (content == ">") {pushContext(currentTag, startOfLine); cont();}
|
|
||||||
else {markErr(); cont(arguments.callee);}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function attributes(style) {
|
|
||||||
if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);}
|
|
||||||
else pass();
|
|
||||||
}
|
|
||||||
function attribute(style, content) {
|
|
||||||
if (content == "=") cont(value);
|
|
||||||
else if (content == ">" || content == "/>") pass(endtag);
|
|
||||||
else pass();
|
|
||||||
}
|
|
||||||
function value(style) {
|
|
||||||
if (style == "xml-attribute") cont(value);
|
|
||||||
else pass();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
indentation: function() {return indented;},
|
|
||||||
|
|
||||||
next: function(){
|
|
||||||
token = tokens.next();
|
|
||||||
if (token.style == "whitespace" && tokenNr == 0)
|
|
||||||
indented = token.value.length;
|
|
||||||
else
|
|
||||||
tokenNr++;
|
|
||||||
if (token.content == "\n") {
|
|
||||||
indented = tokenNr = 0;
|
|
||||||
token.indentation = computeIndentation(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.style == "whitespace" || token.type == "xml-comment")
|
|
||||||
return token;
|
|
||||||
|
|
||||||
while(true){
|
|
||||||
consume = false;
|
|
||||||
cc.pop()(token.style, token.content);
|
|
||||||
if (consume) return token;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
copy: function(){
|
|
||||||
var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
|
|
||||||
var parser = this;
|
|
||||||
|
|
||||||
return function(input){
|
|
||||||
cc = _cc.concat([]);
|
|
||||||
tokenNr = indented = 0;
|
|
||||||
context = _context;
|
|
||||||
tokens = tokenizeXML(input, _tokenState);
|
|
||||||
return parser;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
make: parseXML,
|
|
||||||
electricChars: "/",
|
|
||||||
configure: function(config) {
|
|
||||||
if (config.useHTMLKludges != null)
|
|
||||||
UseKludges = config.useHTMLKludges ? Kludges : NoKludges;
|
|
||||||
if (config.alignCDATA)
|
|
||||||
alignCDATA = config.alignCDATA;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
@ -1,643 +0,0 @@
|
||||||
/* Functionality for finding, storing, and restoring selections
|
|
||||||
*
|
|
||||||
* This does not provide a generic API, just the minimal functionality
|
|
||||||
* required by the CodeMirror system.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Namespace object.
|
|
||||||
var select = {};
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
select.ie_selection = document.selection && document.selection.createRangeCollection;
|
|
||||||
|
|
||||||
// Find the 'top-level' (defined as 'a direct child of the node
|
|
||||||
// passed as the top argument') node that the given node is
|
|
||||||
// contained in. Return null if the given node is not inside the top
|
|
||||||
// node.
|
|
||||||
function topLevelNodeAt(node, top) {
|
|
||||||
while (node && node.parentNode != top)
|
|
||||||
node = node.parentNode;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the top-level node that contains the node before this one.
|
|
||||||
function topLevelNodeBefore(node, top) {
|
|
||||||
while (!node.previousSibling && node.parentNode != top)
|
|
||||||
node = node.parentNode;
|
|
||||||
return topLevelNodeAt(node.previousSibling, top);
|
|
||||||
}
|
|
||||||
|
|
||||||
var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
|
|
||||||
|
|
||||||
select.scrollToNode = function(element) {
|
|
||||||
if (!element) return;
|
|
||||||
var doc = element.ownerDocument, body = doc.body,
|
|
||||||
win = (doc.defaultView || doc.parentWindow),
|
|
||||||
html = doc.documentElement,
|
|
||||||
atEnd = !element.nextSibling || !element.nextSibling.nextSibling
|
|
||||||
|| !element.nextSibling.nextSibling.nextSibling;
|
|
||||||
// In Opera (and recent Webkit versions), BR elements *always*
|
|
||||||
// have a offsetTop property of zero.
|
|
||||||
var compensateHack = 0;
|
|
||||||
while (element && !element.offsetTop) {
|
|
||||||
compensateHack++;
|
|
||||||
element = element.previousSibling;
|
|
||||||
}
|
|
||||||
// atEnd is another kludge for these browsers -- if the cursor is
|
|
||||||
// at the end of the document, and the node doesn't have an
|
|
||||||
// offset, just scroll to the end.
|
|
||||||
if (compensateHack == 0) atEnd = false;
|
|
||||||
|
|
||||||
// WebKit has a bad habit of (sometimes) happily returning bogus
|
|
||||||
// offsets when the document has just been changed. This seems to
|
|
||||||
// always be 5/5, so we don't use those.
|
|
||||||
if (webkit && element && element.offsetTop == 5 && element.offsetLeft == 5)
|
|
||||||
return
|
|
||||||
|
|
||||||
var y = compensateHack * (element ? element.offsetHeight : 0), x = 0, pos = element;
|
|
||||||
while (pos && pos.offsetParent) {
|
|
||||||
y += pos.offsetTop;
|
|
||||||
// Don't count X offset for <br> nodes
|
|
||||||
if (!isBR(pos))
|
|
||||||
x += pos.offsetLeft;
|
|
||||||
pos = pos.offsetParent;
|
|
||||||
}
|
|
||||||
|
|
||||||
var scroll_x = body.scrollLeft || html.scrollLeft || 0,
|
|
||||||
scroll_y = body.scrollTop || html.scrollTop || 0,
|
|
||||||
screen_x = x - scroll_x, screen_y = y - scroll_y, scroll = false;
|
|
||||||
|
|
||||||
if (screen_x < 0 || screen_x > (win.innerWidth || html.clientWidth || 0)) {
|
|
||||||
scroll_x = x;
|
|
||||||
scroll = true;
|
|
||||||
}
|
|
||||||
if (screen_y < 0 || atEnd || screen_y > (win.innerHeight || html.clientHeight || 0) - 50) {
|
|
||||||
scroll_y = atEnd ? 1e6 : y;
|
|
||||||
scroll = true;
|
|
||||||
}
|
|
||||||
if (scroll) win.scrollTo(scroll_x, scroll_y);
|
|
||||||
};
|
|
||||||
|
|
||||||
select.scrollToCursor = function(container) {
|
|
||||||
select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Used to prevent restoring a selection when we do not need to.
|
|
||||||
var currentSelection = null;
|
|
||||||
|
|
||||||
select.snapshotChanged = function() {
|
|
||||||
if (currentSelection) currentSelection.changed = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// This is called by the code in editor.js whenever it is replacing
|
|
||||||
// a text node. The function sees whether the given oldNode is part
|
|
||||||
// of the current selection, and updates this selection if it is.
|
|
||||||
// Because nodes are often only partially replaced, the length of
|
|
||||||
// the part that gets replaced has to be taken into account -- the
|
|
||||||
// selection might stay in the oldNode if the newNode is smaller
|
|
||||||
// than the selection's offset. The offset argument is needed in
|
|
||||||
// case the selection does move to the new object, and the given
|
|
||||||
// length is not the whole length of the new node (part of it might
|
|
||||||
// have been used to replace another node).
|
|
||||||
select.snapshotReplaceNode = function(from, to, length, offset) {
|
|
||||||
if (!currentSelection) return;
|
|
||||||
|
|
||||||
function replace(point) {
|
|
||||||
if (from == point.node) {
|
|
||||||
currentSelection.changed = true;
|
|
||||||
if (length && point.offset > length) {
|
|
||||||
point.offset -= length;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
point.node = to;
|
|
||||||
point.offset += (offset || 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
replace(currentSelection.start);
|
|
||||||
replace(currentSelection.end);
|
|
||||||
};
|
|
||||||
|
|
||||||
select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
|
|
||||||
if (!currentSelection) return;
|
|
||||||
|
|
||||||
function move(point) {
|
|
||||||
if (from == point.node && (!ifAtStart || point.offset == 0)) {
|
|
||||||
currentSelection.changed = true;
|
|
||||||
point.node = to;
|
|
||||||
if (relative) point.offset = Math.max(0, point.offset + distance);
|
|
||||||
else point.offset = distance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
move(currentSelection.start);
|
|
||||||
move(currentSelection.end);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Most functions are defined in two ways, one for the IE selection
|
|
||||||
// model, one for the W3C one.
|
|
||||||
if (select.ie_selection) {
|
|
||||||
function selectionNode(win, start) {
|
|
||||||
var range = win.document.selection.createRange();
|
|
||||||
range.collapse(start);
|
|
||||||
|
|
||||||
function nodeAfter(node) {
|
|
||||||
var found = null;
|
|
||||||
while (!found && node) {
|
|
||||||
found = node.nextSibling;
|
|
||||||
node = node.parentNode;
|
|
||||||
}
|
|
||||||
return nodeAtStartOf(found);
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeAtStartOf(node) {
|
|
||||||
while (node && node.firstChild) node = node.firstChild;
|
|
||||||
return {node: node, offset: 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
var containing = range.parentElement();
|
|
||||||
if (!isAncestor(win.document.body, containing)) return null;
|
|
||||||
if (!containing.firstChild) return nodeAtStartOf(containing);
|
|
||||||
|
|
||||||
var working = range.duplicate();
|
|
||||||
working.moveToElementText(containing);
|
|
||||||
working.collapse(true);
|
|
||||||
for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
|
|
||||||
if (cur.nodeType == 3) {
|
|
||||||
var size = cur.nodeValue.length;
|
|
||||||
working.move("character", size);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
working.moveToElementText(cur);
|
|
||||||
working.collapse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var dir = range.compareEndPoints("StartToStart", working);
|
|
||||||
if (dir == 0) return nodeAfter(cur);
|
|
||||||
if (dir == 1) continue;
|
|
||||||
if (cur.nodeType != 3) return nodeAtStartOf(cur);
|
|
||||||
|
|
||||||
working.setEndPoint("StartToEnd", range);
|
|
||||||
return {node: cur, offset: size - working.text.length};
|
|
||||||
}
|
|
||||||
return nodeAfter(containing);
|
|
||||||
}
|
|
||||||
|
|
||||||
select.markSelection = function(win) {
|
|
||||||
currentSelection = null;
|
|
||||||
var sel = win.document.selection;
|
|
||||||
if (!sel) return;
|
|
||||||
var start = selectionNode(win, true),
|
|
||||||
end = selectionNode(win, false);
|
|
||||||
if (!start || !end) return;
|
|
||||||
currentSelection = {start: start, end: end, window: win, changed: false};
|
|
||||||
};
|
|
||||||
|
|
||||||
select.selectMarked = function() {
|
|
||||||
if (!currentSelection || !currentSelection.changed) return;
|
|
||||||
var win = currentSelection.window, doc = win.document;
|
|
||||||
|
|
||||||
function makeRange(point) {
|
|
||||||
var range = doc.body.createTextRange(),
|
|
||||||
node = point.node;
|
|
||||||
if (!node) {
|
|
||||||
range.moveToElementText(currentSelection.window.document.body);
|
|
||||||
range.collapse(false);
|
|
||||||
}
|
|
||||||
else if (node.nodeType == 3) {
|
|
||||||
range.moveToElementText(node.parentNode);
|
|
||||||
var offset = point.offset;
|
|
||||||
while (node.previousSibling) {
|
|
||||||
node = node.previousSibling;
|
|
||||||
offset += (node.innerText || "").length;
|
|
||||||
}
|
|
||||||
range.move("character", offset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
range.moveToElementText(node);
|
|
||||||
range.collapse(true);
|
|
||||||
}
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
|
|
||||||
var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
|
|
||||||
start.setEndPoint("StartToEnd", end);
|
|
||||||
start.select();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the top-level node that one end of the cursor is inside or
|
|
||||||
// after. Note that this returns false for 'no cursor', and null
|
|
||||||
// for 'start of document'.
|
|
||||||
select.selectionTopNode = function(container, start) {
|
|
||||||
var selection = container.ownerDocument.selection;
|
|
||||||
if (!selection) return false;
|
|
||||||
|
|
||||||
var range = selection.createRange(), range2 = range.duplicate();
|
|
||||||
range.collapse(start);
|
|
||||||
var around = range.parentElement();
|
|
||||||
if (around && isAncestor(container, around)) {
|
|
||||||
// Only use this node if the selection is not at its start.
|
|
||||||
range2.moveToElementText(around);
|
|
||||||
if (range.compareEndPoints("StartToStart", range2) == 1)
|
|
||||||
return topLevelNodeAt(around, container);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move the start of a range to the start of a node,
|
|
||||||
// compensating for the fact that you can't call
|
|
||||||
// moveToElementText with text nodes.
|
|
||||||
function moveToNodeStart(range, node) {
|
|
||||||
if (node.nodeType == 3) {
|
|
||||||
var count = 0, cur = node.previousSibling;
|
|
||||||
while (cur && cur.nodeType == 3) {
|
|
||||||
count += cur.nodeValue.length;
|
|
||||||
cur = cur.previousSibling;
|
|
||||||
}
|
|
||||||
if (cur) {
|
|
||||||
try{range.moveToElementText(cur);}
|
|
||||||
catch(e){return false;}
|
|
||||||
range.collapse(false);
|
|
||||||
}
|
|
||||||
else range.moveToElementText(node.parentNode);
|
|
||||||
if (count) range.move("character", count);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
try{range.moveToElementText(node);}
|
|
||||||
catch(e){return false;}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a binary search through the container object, comparing
|
|
||||||
// the start of each node to the selection
|
|
||||||
var start = 0, end = container.childNodes.length - 1;
|
|
||||||
while (start < end) {
|
|
||||||
var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle];
|
|
||||||
if (!node) return false; // Don't ask. IE6 manages this sometimes.
|
|
||||||
if (!moveToNodeStart(range2, node)) return false;
|
|
||||||
if (range.compareEndPoints("StartToStart", range2) == 1)
|
|
||||||
start = middle;
|
|
||||||
else
|
|
||||||
end = middle - 1;
|
|
||||||
}
|
|
||||||
return container.childNodes[start] || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Place the cursor after this.start. This is only useful when
|
|
||||||
// manually moving the cursor instead of restoring it to its old
|
|
||||||
// position.
|
|
||||||
select.focusAfterNode = function(node, container) {
|
|
||||||
var range = container.ownerDocument.body.createTextRange();
|
|
||||||
range.moveToElementText(node || container);
|
|
||||||
range.collapse(!node);
|
|
||||||
range.select();
|
|
||||||
};
|
|
||||||
|
|
||||||
select.somethingSelected = function(win) {
|
|
||||||
var sel = win.document.selection;
|
|
||||||
return sel && (sel.createRange().text != "");
|
|
||||||
};
|
|
||||||
|
|
||||||
function insertAtCursor(window, html) {
|
|
||||||
var selection = window.document.selection;
|
|
||||||
if (selection) {
|
|
||||||
var range = selection.createRange();
|
|
||||||
range.pasteHTML(html);
|
|
||||||
range.collapse(false);
|
|
||||||
range.select();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to normalize the effect of the enter key, since browsers
|
|
||||||
// do widely different things when pressing enter in designMode.
|
|
||||||
select.insertNewlineAtCursor = function(window) {
|
|
||||||
insertAtCursor(window, "<br>");
|
|
||||||
};
|
|
||||||
|
|
||||||
select.insertTabAtCursor = function(window) {
|
|
||||||
insertAtCursor(window, fourSpaces);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the BR node at the start of the line on which the cursor
|
|
||||||
// currently is, and the offset into the line. Returns null as
|
|
||||||
// node if cursor is on first line.
|
|
||||||
select.cursorPos = function(container, start) {
|
|
||||||
var selection = container.ownerDocument.selection;
|
|
||||||
if (!selection) return null;
|
|
||||||
|
|
||||||
var topNode = select.selectionTopNode(container, start);
|
|
||||||
while (topNode && !isBR(topNode))
|
|
||||||
topNode = topNode.previousSibling;
|
|
||||||
|
|
||||||
var range = selection.createRange(), range2 = range.duplicate();
|
|
||||||
range.collapse(start);
|
|
||||||
if (topNode) {
|
|
||||||
range2.moveToElementText(topNode);
|
|
||||||
range2.collapse(false);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// When nothing is selected, we can get all kinds of funky errors here.
|
|
||||||
try { range2.moveToElementText(container); }
|
|
||||||
catch (e) { return null; }
|
|
||||||
range2.collapse(true);
|
|
||||||
}
|
|
||||||
range.setEndPoint("StartToStart", range2);
|
|
||||||
|
|
||||||
return {node: topNode, offset: range.text.length};
|
|
||||||
};
|
|
||||||
|
|
||||||
select.setCursorPos = function(container, from, to) {
|
|
||||||
function rangeAt(pos) {
|
|
||||||
var range = container.ownerDocument.body.createTextRange();
|
|
||||||
if (!pos.node) {
|
|
||||||
range.moveToElementText(container);
|
|
||||||
range.collapse(true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
range.moveToElementText(pos.node);
|
|
||||||
range.collapse(false);
|
|
||||||
}
|
|
||||||
range.move("character", pos.offset);
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
|
|
||||||
var range = rangeAt(from);
|
|
||||||
if (to && to != from)
|
|
||||||
range.setEndPoint("EndToEnd", rangeAt(to));
|
|
||||||
range.select();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some hacks for storing and re-storing the selection when the editor loses and regains focus.
|
|
||||||
select.getBookmark = function (container) {
|
|
||||||
var from = select.cursorPos(container, true), to = select.cursorPos(container, false);
|
|
||||||
if (from && to) return {from: from, to: to};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Restore a stored selection.
|
|
||||||
select.setBookmark = function(container, mark) {
|
|
||||||
if (!mark) return;
|
|
||||||
select.setCursorPos(container, mark.from, mark.to);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// W3C model
|
|
||||||
else {
|
|
||||||
// Store start and end nodes, and offsets within these, and refer
|
|
||||||
// back to the selection object from those nodes, so that this
|
|
||||||
// object can be updated when the nodes are replaced before the
|
|
||||||
// selection is restored.
|
|
||||||
select.markSelection = function (win) {
|
|
||||||
var selection = win.getSelection();
|
|
||||||
if (!selection || selection.rangeCount == 0)
|
|
||||||
return (currentSelection = null);
|
|
||||||
var range = selection.getRangeAt(0);
|
|
||||||
|
|
||||||
currentSelection = {
|
|
||||||
start: {node: range.startContainer, offset: range.startOffset},
|
|
||||||
end: {node: range.endContainer, offset: range.endOffset},
|
|
||||||
window: win,
|
|
||||||
changed: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// We want the nodes right at the cursor, not one of their
|
|
||||||
// ancestors with a suitable offset. This goes down the DOM tree
|
|
||||||
// until a 'leaf' is reached (or is it *up* the DOM tree?).
|
|
||||||
function normalize(point){
|
|
||||||
while (point.node.nodeType != 3 && !isBR(point.node)) {
|
|
||||||
var newNode = point.node.childNodes[point.offset] || point.node.nextSibling;
|
|
||||||
point.offset = 0;
|
|
||||||
while (!newNode && point.node.parentNode) {
|
|
||||||
point.node = point.node.parentNode;
|
|
||||||
newNode = point.node.nextSibling;
|
|
||||||
}
|
|
||||||
point.node = newNode;
|
|
||||||
if (!newNode)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize(currentSelection.start);
|
|
||||||
normalize(currentSelection.end);
|
|
||||||
};
|
|
||||||
|
|
||||||
select.selectMarked = function () {
|
|
||||||
var cs = currentSelection;
|
|
||||||
// on webkit-based browsers, it is apparently possible that the
|
|
||||||
// selection gets reset even when a node that is not one of the
|
|
||||||
// endpoints get messed with. the most common situation where
|
|
||||||
// this occurs is when a selection is deleted or overwitten. we
|
|
||||||
// check for that here.
|
|
||||||
function focusIssue() {
|
|
||||||
return cs.start.node == cs.end.node && cs.start.offset == 0 && cs.end.offset == 0;
|
|
||||||
}
|
|
||||||
if (!cs || !(cs.changed || (webkit && focusIssue()))) return;
|
|
||||||
var win = cs.window, range = win.document.createRange();
|
|
||||||
|
|
||||||
function setPoint(point, which) {
|
|
||||||
if (point.node) {
|
|
||||||
// Some magic to generalize the setting of the start and end
|
|
||||||
// of a range.
|
|
||||||
if (point.offset == 0)
|
|
||||||
range["set" + which + "Before"](point.node);
|
|
||||||
else
|
|
||||||
range["set" + which](point.node, point.offset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
range.setStartAfter(win.document.body.lastChild || win.document.body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPoint(cs.end, "End");
|
|
||||||
setPoint(cs.start, "Start");
|
|
||||||
selectRange(range, win);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper for selecting a range object.
|
|
||||||
function selectRange(range, window) {
|
|
||||||
var selection = window.getSelection();
|
|
||||||
selection.removeAllRanges();
|
|
||||||
selection.addRange(range);
|
|
||||||
}
|
|
||||||
function selectionRange(window) {
|
|
||||||
var selection = window.getSelection();
|
|
||||||
if (!selection || selection.rangeCount == 0)
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return selection.getRangeAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finding the top-level node at the cursor in the W3C is, as you
|
|
||||||
// can see, quite an involved process.
|
|
||||||
select.selectionTopNode = function(container, start) {
|
|
||||||
var range = selectionRange(container.ownerDocument.defaultView);
|
|
||||||
if (!range) return false;
|
|
||||||
|
|
||||||
var node = start ? range.startContainer : range.endContainer;
|
|
||||||
var offset = start ? range.startOffset : range.endOffset;
|
|
||||||
// Work around (yet another) bug in Opera's selection model.
|
|
||||||
if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
|
|
||||||
container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset]))
|
|
||||||
offset--;
|
|
||||||
|
|
||||||
// For text nodes, we look at the node itself if the cursor is
|
|
||||||
// inside, or at the node before it if the cursor is at the
|
|
||||||
// start.
|
|
||||||
if (node.nodeType == 3){
|
|
||||||
if (offset > 0)
|
|
||||||
return topLevelNodeAt(node, container);
|
|
||||||
else
|
|
||||||
return topLevelNodeBefore(node, container);
|
|
||||||
}
|
|
||||||
// Occasionally, browsers will return the HTML node as
|
|
||||||
// selection. If the offset is 0, we take the start of the frame
|
|
||||||
// ('after null'), otherwise, we take the last node.
|
|
||||||
else if (node.nodeName.toUpperCase() == "HTML") {
|
|
||||||
return (offset == 1 ? null : container.lastChild);
|
|
||||||
}
|
|
||||||
// If the given node is our 'container', we just look up the
|
|
||||||
// correct node by using the offset.
|
|
||||||
else if (node == container) {
|
|
||||||
return (offset == 0) ? null : node.childNodes[offset - 1];
|
|
||||||
}
|
|
||||||
// In any other case, we have a regular node. If the cursor is
|
|
||||||
// at the end of the node, we use the node itself, if it is at
|
|
||||||
// the start, we use the node before it, and in any other
|
|
||||||
// case, we look up the child before the cursor and use that.
|
|
||||||
else {
|
|
||||||
if (offset == node.childNodes.length)
|
|
||||||
return topLevelNodeAt(node, container);
|
|
||||||
else if (offset == 0)
|
|
||||||
return topLevelNodeBefore(node, container);
|
|
||||||
else
|
|
||||||
return topLevelNodeAt(node.childNodes[offset - 1], container);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
select.focusAfterNode = function(node, container) {
|
|
||||||
var win = container.ownerDocument.defaultView,
|
|
||||||
range = win.document.createRange();
|
|
||||||
range.setStartBefore(container.firstChild || container);
|
|
||||||
// In Opera, setting the end of a range at the end of a line
|
|
||||||
// (before a BR) will cause the cursor to appear on the next
|
|
||||||
// line, so we set the end inside of the start node when
|
|
||||||
// possible.
|
|
||||||
if (node && !node.firstChild)
|
|
||||||
range.setEndAfter(node);
|
|
||||||
else if (node)
|
|
||||||
range.setEnd(node, node.childNodes.length);
|
|
||||||
else
|
|
||||||
range.setEndBefore(container.firstChild || container);
|
|
||||||
range.collapse(false);
|
|
||||||
selectRange(range, win);
|
|
||||||
};
|
|
||||||
|
|
||||||
select.somethingSelected = function(win) {
|
|
||||||
var range = selectionRange(win);
|
|
||||||
return range && !range.collapsed;
|
|
||||||
};
|
|
||||||
|
|
||||||
function insertNodeAtCursor(window, node) {
|
|
||||||
var range = selectionRange(window);
|
|
||||||
if (!range) return;
|
|
||||||
|
|
||||||
range.deleteContents();
|
|
||||||
range.insertNode(node);
|
|
||||||
webkitLastLineHack(window.document.body);
|
|
||||||
|
|
||||||
// work around weirdness where Opera will magically insert a new
|
|
||||||
// BR node when a BR node inside a span is moved around. makes
|
|
||||||
// sure the BR ends up outside of spans.
|
|
||||||
if (window.opera && isBR(node) && isSpan(node.parentNode)) {
|
|
||||||
var next = node.nextSibling, p = node.parentNode, outer = p.parentNode;
|
|
||||||
outer.insertBefore(node, p.nextSibling);
|
|
||||||
var textAfter = "";
|
|
||||||
for (; next && next.nodeType == 3; next = next.nextSibling) {
|
|
||||||
textAfter += next.nodeValue;
|
|
||||||
removeElement(next);
|
|
||||||
}
|
|
||||||
outer.insertBefore(makePartSpan(textAfter, window.document), node.nextSibling);
|
|
||||||
}
|
|
||||||
range = window.document.createRange();
|
|
||||||
range.selectNode(node);
|
|
||||||
range.collapse(false);
|
|
||||||
selectRange(range, window);
|
|
||||||
}
|
|
||||||
|
|
||||||
select.insertNewlineAtCursor = function(window) {
|
|
||||||
insertNodeAtCursor(window, window.document.createElement("BR"));
|
|
||||||
};
|
|
||||||
|
|
||||||
select.insertTabAtCursor = function(window) {
|
|
||||||
insertNodeAtCursor(window, window.document.createTextNode(fourSpaces));
|
|
||||||
};
|
|
||||||
|
|
||||||
select.cursorPos = function(container, start) {
|
|
||||||
var range = selectionRange(window);
|
|
||||||
if (!range) return;
|
|
||||||
|
|
||||||
var topNode = select.selectionTopNode(container, start);
|
|
||||||
while (topNode && !isBR(topNode))
|
|
||||||
topNode = topNode.previousSibling;
|
|
||||||
|
|
||||||
range = range.cloneRange();
|
|
||||||
range.collapse(start);
|
|
||||||
if (topNode)
|
|
||||||
range.setStartAfter(topNode);
|
|
||||||
else
|
|
||||||
range.setStartBefore(container);
|
|
||||||
|
|
||||||
var text = range.toString();
|
|
||||||
// Don't count characters introduced by webkitLastLineHack (see editor.js)
|
|
||||||
if (webkit) text = text.replace(/\u200b/g, "");
|
|
||||||
return {node: topNode, offset: text.length};
|
|
||||||
};
|
|
||||||
|
|
||||||
select.setCursorPos = function(container, from, to) {
|
|
||||||
var win = container.ownerDocument.defaultView,
|
|
||||||
range = win.document.createRange();
|
|
||||||
|
|
||||||
function setPoint(node, offset, side) {
|
|
||||||
if (offset == 0 && node && !node.nextSibling) {
|
|
||||||
range["set" + side + "After"](node);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node)
|
|
||||||
node = container.firstChild;
|
|
||||||
else
|
|
||||||
node = node.nextSibling;
|
|
||||||
|
|
||||||
if (!node) return;
|
|
||||||
|
|
||||||
if (offset == 0) {
|
|
||||||
range["set" + side + "Before"](node);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var backlog = []
|
|
||||||
function decompose(node) {
|
|
||||||
if (node.nodeType == 3)
|
|
||||||
backlog.push(node);
|
|
||||||
else
|
|
||||||
forEach(node.childNodes, decompose);
|
|
||||||
}
|
|
||||||
while (true) {
|
|
||||||
while (node && !backlog.length) {
|
|
||||||
decompose(node);
|
|
||||||
node = node.nextSibling;
|
|
||||||
}
|
|
||||||
var cur = backlog.shift();
|
|
||||||
if (!cur) return false;
|
|
||||||
|
|
||||||
var length = cur.nodeValue.length;
|
|
||||||
if (length >= offset) {
|
|
||||||
range["set" + side](cur, offset);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
offset -= length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
to = to || from;
|
|
||||||
if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
|
|
||||||
selectRange(range, win);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
/* String streams are the things fed to parsers (which can feed them
|
|
||||||
* to a tokenizer if they want). They provide peek and next methods
|
|
||||||
* for looking at the current character (next 'consumes' this
|
|
||||||
* character, peek does not), and a get method for retrieving all the
|
|
||||||
* text that was consumed since the last time get was called.
|
|
||||||
*
|
|
||||||
* An easy mistake to make is to let a StopIteration exception finish
|
|
||||||
* the token stream while there are still characters pending in the
|
|
||||||
* string stream (hitting the end of the buffer while parsing a
|
|
||||||
* token). To make it easier to detect such errors, the stringstreams
|
|
||||||
* throw an exception when this happens.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Make a stringstream stream out of an iterator that returns strings.
|
|
||||||
// This is applied to the result of traverseDOM (see codemirror.js),
|
|
||||||
// and the resulting stream is fed to the parser.
|
|
||||||
var stringStream = function(source){
|
|
||||||
// String that's currently being iterated over.
|
|
||||||
var current = "";
|
|
||||||
// Position in that string.
|
|
||||||
var pos = 0;
|
|
||||||
// Accumulator for strings that have been iterated over but not
|
|
||||||
// get()-ed yet.
|
|
||||||
var accum = "";
|
|
||||||
// Make sure there are more characters ready, or throw
|
|
||||||
// StopIteration.
|
|
||||||
function ensureChars() {
|
|
||||||
while (pos == current.length) {
|
|
||||||
accum += current;
|
|
||||||
current = ""; // In case source.next() throws
|
|
||||||
pos = 0;
|
|
||||||
try {current = source.next();}
|
|
||||||
catch (e) {
|
|
||||||
if (e != StopIteration) throw e;
|
|
||||||
else return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Return the next character in the stream.
|
|
||||||
peek: function() {
|
|
||||||
if (!ensureChars()) return null;
|
|
||||||
return current.charAt(pos);
|
|
||||||
},
|
|
||||||
// Get the next character, throw StopIteration if at end, check
|
|
||||||
// for unused content.
|
|
||||||
next: function() {
|
|
||||||
if (!ensureChars()) {
|
|
||||||
if (accum.length > 0)
|
|
||||||
throw "End of stringstream reached without emptying buffer ('" + accum + "').";
|
|
||||||
else
|
|
||||||
throw StopIteration;
|
|
||||||
}
|
|
||||||
return current.charAt(pos++);
|
|
||||||
},
|
|
||||||
// Return the characters iterated over since the last call to
|
|
||||||
// .get().
|
|
||||||
get: function() {
|
|
||||||
var temp = accum;
|
|
||||||
accum = "";
|
|
||||||
if (pos > 0){
|
|
||||||
temp += current.slice(0, pos);
|
|
||||||
current = current.slice(pos);
|
|
||||||
pos = 0;
|
|
||||||
}
|
|
||||||
return temp;
|
|
||||||
},
|
|
||||||
// Push a string back into the stream.
|
|
||||||
push: function(str) {
|
|
||||||
current = current.slice(0, pos) + str + current.slice(pos);
|
|
||||||
},
|
|
||||||
lookAhead: function(str, consume, skipSpaces, caseInsensitive) {
|
|
||||||
function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
|
|
||||||
str = cased(str);
|
|
||||||
var found = false;
|
|
||||||
|
|
||||||
var _accum = accum, _pos = pos;
|
|
||||||
if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
var end = pos + str.length, left = current.length - pos;
|
|
||||||
if (end <= current.length) {
|
|
||||||
found = str == cased(current.slice(pos, end));
|
|
||||||
pos = end;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (str.slice(0, left) == cased(current.slice(pos))) {
|
|
||||||
accum += current; current = "";
|
|
||||||
try {current = source.next();}
|
|
||||||
catch (e) {break;}
|
|
||||||
pos = 0;
|
|
||||||
str = str.slice(left);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(found && consume)) {
|
|
||||||
current = accum.slice(_accum.length) + current;
|
|
||||||
pos = _pos;
|
|
||||||
accum = _accum;
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Utils built on top of the above
|
|
||||||
more: function() {
|
|
||||||
return this.peek() !== null;
|
|
||||||
},
|
|
||||||
applies: function(test) {
|
|
||||||
var next = this.peek();
|
|
||||||
return (next !== null && test(next));
|
|
||||||
},
|
|
||||||
nextWhile: function(test) {
|
|
||||||
var next;
|
|
||||||
while ((next = this.peek()) !== null && test(next))
|
|
||||||
this.next();
|
|
||||||
},
|
|
||||||
matches: function(re) {
|
|
||||||
var next = this.peek();
|
|
||||||
return (next !== null && re.test(next));
|
|
||||||
},
|
|
||||||
nextWhileMatches: function(re) {
|
|
||||||
var next;
|
|
||||||
while ((next = this.peek()) !== null && re.test(next))
|
|
||||||
this.next();
|
|
||||||
},
|
|
||||||
equals: function(ch) {
|
|
||||||
return ch === this.peek();
|
|
||||||
},
|
|
||||||
endOfLine: function() {
|
|
||||||
var next = this.peek();
|
|
||||||
return next == null || next == "\n";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
// A framework for simple tokenizers. Takes care of newlines and
|
|
||||||
// white-space, and of getting the text from the source stream into
|
|
||||||
// the token object. A state is a function of two arguments -- a
|
|
||||||
// string stream and a setState function. The second can be used to
|
|
||||||
// change the tokenizer's state, and can be ignored for stateless
|
|
||||||
// tokenizers. This function should advance the stream over a token
|
|
||||||
// and return a string or object containing information about the next
|
|
||||||
// token, or null to pass and have the (new) state be called to finish
|
|
||||||
// the token. When a string is given, it is wrapped in a {style, type}
|
|
||||||
// object. In the resulting object, the characters consumed are stored
|
|
||||||
// under the content property. Any whitespace following them is also
|
|
||||||
// automatically consumed, and added to the value property. (Thus,
|
|
||||||
// content is the actual meaningful part of the token, while value
|
|
||||||
// contains all the text it spans.)
|
|
||||||
|
|
||||||
function tokenizer(source, state) {
|
|
||||||
// Newlines are always a separate token.
|
|
||||||
function isWhiteSpace(ch) {
|
|
||||||
// The messy regexp is because IE's regexp matcher is of the
|
|
||||||
// opinion that non-breaking spaces are no whitespace.
|
|
||||||
return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tokenizer = {
|
|
||||||
state: state,
|
|
||||||
|
|
||||||
take: function(type) {
|
|
||||||
if (typeof(type) == "string")
|
|
||||||
type = {style: type, type: type};
|
|
||||||
|
|
||||||
type.content = (type.content || "") + source.get();
|
|
||||||
if (!/\n$/.test(type.content))
|
|
||||||
source.nextWhile(isWhiteSpace);
|
|
||||||
type.value = type.content + source.get();
|
|
||||||
return type;
|
|
||||||
},
|
|
||||||
|
|
||||||
next: function () {
|
|
||||||
if (!source.more()) throw StopIteration;
|
|
||||||
|
|
||||||
var type;
|
|
||||||
if (source.equals("\n")) {
|
|
||||||
source.next();
|
|
||||||
return this.take("whitespace");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source.applies(isWhiteSpace))
|
|
||||||
type = "whitespace";
|
|
||||||
else
|
|
||||||
while (!type)
|
|
||||||
type = this.state(source, function(s) {tokenizer.state = s;});
|
|
||||||
|
|
||||||
return this.take(type);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return tokenizer;
|
|
||||||
}
|
|
||||||
|
|
@ -1,410 +0,0 @@
|
||||||
/**
|
|
||||||
* Storage and control for undo information within a CodeMirror
|
|
||||||
* editor. 'Why on earth is such a complicated mess required for
|
|
||||||
* that?', I hear you ask. The goal, in implementing this, was to make
|
|
||||||
* the complexity of storing and reverting undo information depend
|
|
||||||
* only on the size of the edited or restored content, not on the size
|
|
||||||
* of the whole document. This makes it necessary to use a kind of
|
|
||||||
* 'diff' system, which, when applied to a DOM tree, causes some
|
|
||||||
* complexity and hackery.
|
|
||||||
*
|
|
||||||
* In short, the editor 'touches' BR elements as it parses them, and
|
|
||||||
* the UndoHistory stores these. When nothing is touched in commitDelay
|
|
||||||
* milliseconds, the changes are committed: It goes over all touched
|
|
||||||
* nodes, throws out the ones that did not change since last commit or
|
|
||||||
* are no longer in the document, and assembles the rest into zero or
|
|
||||||
* more 'chains' -- arrays of adjacent lines. Links back to these
|
|
||||||
* chains are added to the BR nodes, while the chain that previously
|
|
||||||
* spanned these nodes is added to the undo history. Undoing a change
|
|
||||||
* means taking such a chain off the undo history, restoring its
|
|
||||||
* content (text is saved per line) and linking it back into the
|
|
||||||
* document.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// A history object needs to know about the DOM container holding the
|
|
||||||
// document, the maximum amount of undo levels it should store, the
|
|
||||||
// delay (of no input) after which it commits a set of changes, and,
|
|
||||||
// unfortunately, the 'parent' window -- a window that is not in
|
|
||||||
// designMode, and on which setTimeout works in every browser.
|
|
||||||
function UndoHistory(container, maxDepth, commitDelay, editor) {
|
|
||||||
this.container = container;
|
|
||||||
this.maxDepth = maxDepth; this.commitDelay = commitDelay;
|
|
||||||
this.editor = editor; this.parent = editor.parent;
|
|
||||||
// This line object represents the initial, empty editor.
|
|
||||||
var initial = {text: "", from: null, to: null};
|
|
||||||
// As the borders between lines are represented by BR elements, the
|
|
||||||
// start of the first line and the end of the last one are
|
|
||||||
// represented by null. Since you can not store any properties
|
|
||||||
// (links to line objects) in null, these properties are used in
|
|
||||||
// those cases.
|
|
||||||
this.first = initial; this.last = initial;
|
|
||||||
// Similarly, a 'historyTouched' property is added to the BR in
|
|
||||||
// front of lines that have already been touched, and 'firstTouched'
|
|
||||||
// is used for the first line.
|
|
||||||
this.firstTouched = false;
|
|
||||||
// History is the set of committed changes, touched is the set of
|
|
||||||
// nodes touched since the last commit.
|
|
||||||
this.history = []; this.redoHistory = []; this.touched = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
UndoHistory.prototype = {
|
|
||||||
// Schedule a commit (if no other touches come in for commitDelay
|
|
||||||
// milliseconds).
|
|
||||||
scheduleCommit: function() {
|
|
||||||
var self = this;
|
|
||||||
this.parent.clearTimeout(this.commitTimeout);
|
|
||||||
this.commitTimeout = this.parent.setTimeout(function(){self.tryCommit();}, this.commitDelay);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Mark a node as touched. Null is a valid argument.
|
|
||||||
touch: function(node) {
|
|
||||||
this.setTouched(node);
|
|
||||||
this.scheduleCommit();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Undo the last change.
|
|
||||||
undo: function() {
|
|
||||||
// Make sure pending changes have been committed.
|
|
||||||
this.commit();
|
|
||||||
|
|
||||||
if (this.history.length) {
|
|
||||||
// Take the top diff from the history, apply it, and store its
|
|
||||||
// shadow in the redo history.
|
|
||||||
var item = this.history.pop();
|
|
||||||
this.redoHistory.push(this.updateTo(item, "applyChain"));
|
|
||||||
this.notifyEnvironment();
|
|
||||||
return this.chainNode(item);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Redo the last undone change.
|
|
||||||
redo: function() {
|
|
||||||
this.commit();
|
|
||||||
if (this.redoHistory.length) {
|
|
||||||
// The inverse of undo, basically.
|
|
||||||
var item = this.redoHistory.pop();
|
|
||||||
this.addUndoLevel(this.updateTo(item, "applyChain"));
|
|
||||||
this.notifyEnvironment();
|
|
||||||
return this.chainNode(item);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
clear: function() {
|
|
||||||
this.history = [];
|
|
||||||
this.redoHistory = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
// Ask for the size of the un/redo histories.
|
|
||||||
historySize: function() {
|
|
||||||
return {undo: this.history.length, redo: this.redoHistory.length};
|
|
||||||
},
|
|
||||||
|
|
||||||
// Push a changeset into the document.
|
|
||||||
push: function(from, to, lines) {
|
|
||||||
var chain = [];
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
var end = (i == lines.length - 1) ? to : this.container.ownerDocument.createElement("BR");
|
|
||||||
chain.push({from: from, to: end, text: cleanText(lines[i])});
|
|
||||||
from = end;
|
|
||||||
}
|
|
||||||
this.pushChains([chain], from == null && to == null);
|
|
||||||
this.notifyEnvironment();
|
|
||||||
},
|
|
||||||
|
|
||||||
pushChains: function(chains, doNotHighlight) {
|
|
||||||
this.commit(doNotHighlight);
|
|
||||||
this.addUndoLevel(this.updateTo(chains, "applyChain"));
|
|
||||||
this.redoHistory = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
// Retrieve a DOM node from a chain (for scrolling to it after undo/redo).
|
|
||||||
chainNode: function(chains) {
|
|
||||||
for (var i = 0; i < chains.length; i++) {
|
|
||||||
var start = chains[i][0], node = start && (start.from || start.to);
|
|
||||||
if (node) return node;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Clear the undo history, make the current document the start
|
|
||||||
// position.
|
|
||||||
reset: function() {
|
|
||||||
this.history = []; this.redoHistory = [];
|
|
||||||
},
|
|
||||||
|
|
||||||
textAfter: function(br) {
|
|
||||||
return this.after(br).text;
|
|
||||||
},
|
|
||||||
|
|
||||||
nodeAfter: function(br) {
|
|
||||||
return this.after(br).to;
|
|
||||||
},
|
|
||||||
|
|
||||||
nodeBefore: function(br) {
|
|
||||||
return this.before(br).from;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Commit unless there are pending dirty nodes.
|
|
||||||
tryCommit: function() {
|
|
||||||
if (!window.UndoHistory) return; // Stop when frame has been unloaded
|
|
||||||
if (this.editor.highlightDirty()) this.commit(true);
|
|
||||||
else this.scheduleCommit();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Check whether the touched nodes hold any changes, if so, commit
|
|
||||||
// them.
|
|
||||||
commit: function(doNotHighlight) {
|
|
||||||
this.parent.clearTimeout(this.commitTimeout);
|
|
||||||
// Make sure there are no pending dirty nodes.
|
|
||||||
if (!doNotHighlight) this.editor.highlightDirty(true);
|
|
||||||
// Build set of chains.
|
|
||||||
var chains = this.touchedChains(), self = this;
|
|
||||||
|
|
||||||
if (chains.length) {
|
|
||||||
this.addUndoLevel(this.updateTo(chains, "linkChain"));
|
|
||||||
this.redoHistory = [];
|
|
||||||
this.notifyEnvironment();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// [ end of public interface ]
|
|
||||||
|
|
||||||
// Update the document with a given set of chains, return its
|
|
||||||
// shadow. updateFunc should be "applyChain" or "linkChain". In the
|
|
||||||
// second case, the chains are taken to correspond the the current
|
|
||||||
// document, and only the state of the line data is updated. In the
|
|
||||||
// first case, the content of the chains is also pushed iinto the
|
|
||||||
// document.
|
|
||||||
updateTo: function(chains, updateFunc) {
|
|
||||||
var shadows = [], dirty = [];
|
|
||||||
for (var i = 0; i < chains.length; i++) {
|
|
||||||
shadows.push(this.shadowChain(chains[i]));
|
|
||||||
dirty.push(this[updateFunc](chains[i]));
|
|
||||||
}
|
|
||||||
if (updateFunc == "applyChain")
|
|
||||||
this.notifyDirty(dirty);
|
|
||||||
return shadows;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Notify the editor that some nodes have changed.
|
|
||||||
notifyDirty: function(nodes) {
|
|
||||||
forEach(nodes, method(this.editor, "addDirtyNode"))
|
|
||||||
this.editor.scheduleHighlight();
|
|
||||||
},
|
|
||||||
|
|
||||||
notifyEnvironment: function() {
|
|
||||||
if (this.onChange) this.onChange();
|
|
||||||
// Used by the line-wrapping line-numbering code.
|
|
||||||
if (window.frameElement && window.frameElement.CodeMirror.updateNumbers)
|
|
||||||
window.frameElement.CodeMirror.updateNumbers();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Link a chain into the DOM nodes (or the first/last links for null
|
|
||||||
// nodes).
|
|
||||||
linkChain: function(chain) {
|
|
||||||
for (var i = 0; i < chain.length; i++) {
|
|
||||||
var line = chain[i];
|
|
||||||
if (line.from) line.from.historyAfter = line;
|
|
||||||
else this.first = line;
|
|
||||||
if (line.to) line.to.historyBefore = line;
|
|
||||||
else this.last = line;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get the line object after/before a given node.
|
|
||||||
after: function(node) {
|
|
||||||
return node ? node.historyAfter : this.first;
|
|
||||||
},
|
|
||||||
before: function(node) {
|
|
||||||
return node ? node.historyBefore : this.last;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Mark a node as touched if it has not already been marked.
|
|
||||||
setTouched: function(node) {
|
|
||||||
if (node) {
|
|
||||||
if (!node.historyTouched) {
|
|
||||||
this.touched.push(node);
|
|
||||||
node.historyTouched = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.firstTouched = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Store a new set of undo info, throw away info if there is more of
|
|
||||||
// it than allowed.
|
|
||||||
addUndoLevel: function(diffs) {
|
|
||||||
this.history.push(diffs);
|
|
||||||
if (this.history.length > this.maxDepth)
|
|
||||||
this.history.shift();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Build chains from a set of touched nodes.
|
|
||||||
touchedChains: function() {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// The temp system is a crummy hack to speed up determining
|
|
||||||
// whether a (currently touched) node has a line object associated
|
|
||||||
// with it. nullTemp is used to store the object for the first
|
|
||||||
// line, other nodes get it stored in their historyTemp property.
|
|
||||||
var nullTemp = null;
|
|
||||||
function temp(node) {return node ? node.historyTemp : nullTemp;}
|
|
||||||
function setTemp(node, line) {
|
|
||||||
if (node) node.historyTemp = line;
|
|
||||||
else nullTemp = line;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLine(node) {
|
|
||||||
var text = [];
|
|
||||||
for (var cur = node ? node.nextSibling : self.container.firstChild;
|
|
||||||
cur && !isBR(cur); cur = cur.nextSibling)
|
|
||||||
if (cur.currentText) text.push(cur.currentText);
|
|
||||||
return {from: node, to: cur, text: cleanText(text.join(""))};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out unchanged lines and nodes that are no longer in the
|
|
||||||
// document. Build up line objects for remaining nodes.
|
|
||||||
var lines = [];
|
|
||||||
if (self.firstTouched) self.touched.push(null);
|
|
||||||
forEach(self.touched, function(node) {
|
|
||||||
if (node && node.parentNode != self.container) return;
|
|
||||||
|
|
||||||
if (node) node.historyTouched = false;
|
|
||||||
else self.firstTouched = false;
|
|
||||||
|
|
||||||
var line = buildLine(node), shadow = self.after(node);
|
|
||||||
if (!shadow || shadow.text != line.text || shadow.to != line.to) {
|
|
||||||
lines.push(line);
|
|
||||||
setTemp(node, line);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the BR element after/before the given node.
|
|
||||||
function nextBR(node, dir) {
|
|
||||||
var link = dir + "Sibling", search = node[link];
|
|
||||||
while (search && !isBR(search))
|
|
||||||
search = search[link];
|
|
||||||
return search;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble line objects into chains by scanning the DOM tree
|
|
||||||
// around them.
|
|
||||||
var chains = []; self.touched = [];
|
|
||||||
forEach(lines, function(line) {
|
|
||||||
// Note that this makes the loop skip line objects that have
|
|
||||||
// been pulled into chains by lines before them.
|
|
||||||
if (!temp(line.from)) return;
|
|
||||||
|
|
||||||
var chain = [], curNode = line.from, safe = true;
|
|
||||||
// Put any line objects (referred to by temp info) before this
|
|
||||||
// one on the front of the array.
|
|
||||||
while (true) {
|
|
||||||
var curLine = temp(curNode);
|
|
||||||
if (!curLine) {
|
|
||||||
if (safe) break;
|
|
||||||
else curLine = buildLine(curNode);
|
|
||||||
}
|
|
||||||
chain.unshift(curLine);
|
|
||||||
setTemp(curNode, null);
|
|
||||||
if (!curNode) break;
|
|
||||||
safe = self.after(curNode);
|
|
||||||
curNode = nextBR(curNode, "previous");
|
|
||||||
}
|
|
||||||
curNode = line.to; safe = self.before(line.from);
|
|
||||||
// Add lines after this one at end of array.
|
|
||||||
while (true) {
|
|
||||||
if (!curNode) break;
|
|
||||||
var curLine = temp(curNode);
|
|
||||||
if (!curLine) {
|
|
||||||
if (safe) break;
|
|
||||||
else curLine = buildLine(curNode);
|
|
||||||
}
|
|
||||||
chain.push(curLine);
|
|
||||||
setTemp(curNode, null);
|
|
||||||
safe = self.before(curNode);
|
|
||||||
curNode = nextBR(curNode, "next");
|
|
||||||
}
|
|
||||||
chains.push(chain);
|
|
||||||
});
|
|
||||||
|
|
||||||
return chains;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Find the 'shadow' of a given chain by following the links in the
|
|
||||||
// DOM nodes at its start and end.
|
|
||||||
shadowChain: function(chain) {
|
|
||||||
var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to;
|
|
||||||
while (true) {
|
|
||||||
shadows.push(next);
|
|
||||||
var nextNode = next.to;
|
|
||||||
if (!nextNode || nextNode == end)
|
|
||||||
break;
|
|
||||||
else
|
|
||||||
next = nextNode.historyAfter || this.before(end);
|
|
||||||
// (The this.before(end) is a hack -- FF sometimes removes
|
|
||||||
// properties from BR nodes, in which case the best we can hope
|
|
||||||
// for is to not break.)
|
|
||||||
}
|
|
||||||
return shadows;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Update the DOM tree to contain the lines specified in a given
|
|
||||||
// chain, link this chain into the DOM nodes.
|
|
||||||
applyChain: function(chain) {
|
|
||||||
// Some attempt is made to prevent the cursor from jumping
|
|
||||||
// randomly when an undo or redo happens. It still behaves a bit
|
|
||||||
// strange sometimes.
|
|
||||||
var cursor = select.cursorPos(this.container, false), self = this;
|
|
||||||
|
|
||||||
// Remove all nodes in the DOM tree between from and to (null for
|
|
||||||
// start/end of container).
|
|
||||||
function removeRange(from, to) {
|
|
||||||
var pos = from ? from.nextSibling : self.container.firstChild;
|
|
||||||
while (pos != to) {
|
|
||||||
var temp = pos.nextSibling;
|
|
||||||
removeElement(pos);
|
|
||||||
pos = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var start = chain[0].from, end = chain[chain.length - 1].to;
|
|
||||||
// Clear the space where this change has to be made.
|
|
||||||
removeRange(start, end);
|
|
||||||
|
|
||||||
// Insert the content specified by the chain into the DOM tree.
|
|
||||||
for (var i = 0; i < chain.length; i++) {
|
|
||||||
var line = chain[i];
|
|
||||||
// The start and end of the space are already correct, but BR
|
|
||||||
// tags inside it have to be put back.
|
|
||||||
if (i > 0)
|
|
||||||
self.container.insertBefore(line.from, end);
|
|
||||||
|
|
||||||
// Add the text.
|
|
||||||
var node = makePartSpan(fixSpaces(line.text), this.container.ownerDocument);
|
|
||||||
self.container.insertBefore(node, end);
|
|
||||||
// See if the cursor was on this line. Put it back, adjusting
|
|
||||||
// for changed line length, if it was.
|
|
||||||
if (cursor && cursor.node == line.from) {
|
|
||||||
var cursordiff = 0;
|
|
||||||
var prev = this.after(line.from);
|
|
||||||
if (prev && i == chain.length - 1) {
|
|
||||||
// Only adjust if the cursor is after the unchanged part of
|
|
||||||
// the line.
|
|
||||||
for (var match = 0; match < cursor.offset &&
|
|
||||||
line.text.charAt(match) == prev.text.charAt(match); match++);
|
|
||||||
if (cursor.offset > match)
|
|
||||||
cursordiff = line.text.length - prev.text.length;
|
|
||||||
}
|
|
||||||
select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)});
|
|
||||||
}
|
|
||||||
// Cursor was in removed line, this is last new line.
|
|
||||||
else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) {
|
|
||||||
select.setCursorPos(this.container, {node: line.from, offset: line.text.length});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anchor the chain in the DOM tree.
|
|
||||||
this.linkChain(chain);
|
|
||||||
return start;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
/* A few useful utility functions. */
|
|
||||||
|
|
||||||
// Capture a method on an object.
|
|
||||||
function method(obj, name) {
|
|
||||||
return function() {obj[name].apply(obj, arguments);};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The value used to signal the end of a sequence in iterators.
|
|
||||||
var StopIteration = {toString: function() {return "StopIteration"}};
|
|
||||||
|
|
||||||
// Apply a function to each element in a sequence.
|
|
||||||
function forEach(iter, f) {
|
|
||||||
if (iter.next) {
|
|
||||||
try {while (true) f(iter.next());}
|
|
||||||
catch (e) {if (e != StopIteration) throw e;}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (var i = 0; i < iter.length; i++)
|
|
||||||
f(iter[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map a function over a sequence, producing an array of results.
|
|
||||||
function map(iter, f) {
|
|
||||||
var accum = [];
|
|
||||||
forEach(iter, function(val) {accum.push(f(val));});
|
|
||||||
return accum;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a predicate function that tests a string againsts a given
|
|
||||||
// regular expression. No longer used but might be used by 3rd party
|
|
||||||
// parsers.
|
|
||||||
function matcher(regexp){
|
|
||||||
return function(value){return regexp.test(value);};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test whether a DOM node has a certain CSS class. Much faster than
|
|
||||||
// the MochiKit equivalent, for some reason.
|
|
||||||
function hasClass(element, className){
|
|
||||||
var classes = element.className;
|
|
||||||
return classes && new RegExp("(^| )" + className + "($| )").test(classes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert a DOM node after another node.
|
|
||||||
function insertAfter(newNode, oldNode) {
|
|
||||||
var parent = oldNode.parentNode;
|
|
||||||
parent.insertBefore(newNode, oldNode.nextSibling);
|
|
||||||
return newNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeElement(node) {
|
|
||||||
if (node.parentNode)
|
|
||||||
node.parentNode.removeChild(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearElement(node) {
|
|
||||||
while (node.firstChild)
|
|
||||||
node.removeChild(node.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether a node is contained in another one.
|
|
||||||
function isAncestor(node, child) {
|
|
||||||
while (child = child.parentNode) {
|
|
||||||
if (node == child)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The non-breaking space character.
|
|
||||||
var nbsp = "\u00a0";
|
|
||||||
var matching = {"{": "}", "[": "]", "(": ")",
|
|
||||||
"}": "{", "]": "[", ")": "("};
|
|
||||||
|
|
||||||
// Standardize a few unportable event properties.
|
|
||||||
function normalizeEvent(event) {
|
|
||||||
if (!event.stopPropagation) {
|
|
||||||
event.stopPropagation = function() {this.cancelBubble = true;};
|
|
||||||
event.preventDefault = function() {this.returnValue = false;};
|
|
||||||
}
|
|
||||||
if (!event.stop) {
|
|
||||||
event.stop = function() {
|
|
||||||
this.stopPropagation();
|
|
||||||
this.preventDefault();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.type == "keypress") {
|
|
||||||
event.code = (event.charCode == null) ? event.keyCode : event.charCode;
|
|
||||||
event.character = String.fromCharCode(event.code);
|
|
||||||
}
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Portably register event handlers.
|
|
||||||
function addEventHandler(node, type, handler, removeFunc) {
|
|
||||||
function wrapHandler(event) {
|
|
||||||
handler(normalizeEvent(event || window.event));
|
|
||||||
}
|
|
||||||
if (typeof node.addEventListener == "function") {
|
|
||||||
node.addEventListener(type, wrapHandler, false);
|
|
||||||
if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
node.attachEvent("on" + type, wrapHandler);
|
|
||||||
if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeText(node) {
|
|
||||||
return node.textContent || node.innerText || node.nodeValue || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeTop(node) {
|
|
||||||
var top = 0;
|
|
||||||
while (node.offsetParent) {
|
|
||||||
top += node.offsetTop;
|
|
||||||
node = node.offsetParent;
|
|
||||||
}
|
|
||||||
return top;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isBR(node) {
|
|
||||||
var nn = node.nodeName;
|
|
||||||
return nn == "BR" || nn == "br";
|
|
||||||
}
|
|
||||||
function isSpan(node) {
|
|
||||||
var nn = node.nodeName;
|
|
||||||
return nn == "SPAN" || nn == "span";
|
|
||||||
}
|
|
||||||
17
dbtemplates/sync_templates.py
Normal file
17
dbtemplates/sync_templates.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
"""This is just for backwards compatiblity"""
|
||||||
|
from optparse import OptionParser
|
||||||
|
from django.core.management import call_command
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = OptionParser()
|
||||||
|
parser.add_option("-e", "--ext", dest="ext", action="store", type="string",
|
||||||
|
help="file extension of the files you want to sync [default: %default]",
|
||||||
|
default="html")
|
||||||
|
parser.add_option("-f", "--force", action="store_true", dest="force",
|
||||||
|
default=False, help="overwrite existing database templates")
|
||||||
|
opts, args = parser.parse_args()
|
||||||
|
|
||||||
|
call_command('sync_templates', **{'ext': opts.ext, 'force': opts.force})
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -1,175 +0,0 @@
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import tempfile
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from django.conf import settings as django_settings
|
|
||||||
from django.core.cache.backends.base import BaseCache
|
|
||||||
from django.core.management import call_command
|
|
||||||
from django.template import loader, TemplateDoesNotExist
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
from django.contrib.sites.models import Site
|
|
||||||
|
|
||||||
from dbtemplates.conf import settings
|
|
||||||
from dbtemplates.models import Template
|
|
||||||
from dbtemplates.utils.cache import get_cache_backend, get_cache_key
|
|
||||||
from dbtemplates.utils.template import (get_template_source,
|
|
||||||
check_template_syntax)
|
|
||||||
from dbtemplates.management.commands.sync_templates import (FILES_TO_DATABASE,
|
|
||||||
DATABASE_TO_FILES)
|
|
||||||
|
|
||||||
|
|
||||||
class DbTemplatesTestCase(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.old_TEMPLATES = settings.TEMPLATES
|
|
||||||
if 'dbtemplates.loader.Loader' not in settings.TEMPLATES:
|
|
||||||
loader.template_source_loaders = None
|
|
||||||
settings.TEMPLATES = list(settings.TEMPLATES) + [
|
|
||||||
'dbtemplates.loader.Loader'
|
|
||||||
]
|
|
||||||
|
|
||||||
self.site1, created1 = Site.objects.get_or_create(
|
|
||||||
domain="example.com", name="example.com")
|
|
||||||
self.site2, created2 = Site.objects.get_or_create(
|
|
||||||
domain="example.org", name="example.org")
|
|
||||||
self.t1, _ = Template.objects.get_or_create(
|
|
||||||
name='base.html', content='base')
|
|
||||||
self.t2, _ = Template.objects.get_or_create(
|
|
||||||
name='sub.html', content='sub')
|
|
||||||
self.t2.sites.add(self.site2)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
loader.template_source_loaders = None
|
|
||||||
settings.TEMPLATES = self.old_TEMPLATES
|
|
||||||
|
|
||||||
def test_basics(self):
|
|
||||||
self.assertEqual(list(self.t1.sites.all()), [self.site1])
|
|
||||||
self.assertTrue("base" in self.t1.content)
|
|
||||||
self.assertEqual(list(Template.objects.filter(sites=self.site1)),
|
|
||||||
[self.t1, self.t2])
|
|
||||||
self.assertEqual(list(self.t2.sites.all()), [self.site1, self.site2])
|
|
||||||
|
|
||||||
def test_empty_sites(self):
|
|
||||||
old_add_default_site = settings.DBTEMPLATES_ADD_DEFAULT_SITE
|
|
||||||
try:
|
|
||||||
settings.DBTEMPLATES_ADD_DEFAULT_SITE = False
|
|
||||||
self.t3 = Template.objects.create(
|
|
||||||
name='footer.html', content='footer')
|
|
||||||
self.assertEqual(list(self.t3.sites.all()), [])
|
|
||||||
finally:
|
|
||||||
settings.DBTEMPLATES_ADD_DEFAULT_SITE = old_add_default_site
|
|
||||||
|
|
||||||
def test_load_templates_sites(self):
|
|
||||||
old_add_default_site = settings.DBTEMPLATES_ADD_DEFAULT_SITE
|
|
||||||
old_site_id = django_settings.SITE_ID
|
|
||||||
try:
|
|
||||||
settings.DBTEMPLATES_ADD_DEFAULT_SITE = False
|
|
||||||
t_site1 = Template.objects.create(
|
|
||||||
name='copyright.html', content='(c) example.com')
|
|
||||||
t_site1.sites.add(self.site1)
|
|
||||||
t_site2 = Template.objects.create(
|
|
||||||
name='copyright.html', content='(c) example.org')
|
|
||||||
t_site2.sites.add(self.site2)
|
|
||||||
|
|
||||||
django_settings.SITE_ID = Site.objects.create(
|
|
||||||
domain="example.net", name="example.net").id
|
|
||||||
Site.objects.clear_cache()
|
|
||||||
|
|
||||||
self.assertRaises(TemplateDoesNotExist,
|
|
||||||
loader.get_template, "copyright.html")
|
|
||||||
finally:
|
|
||||||
django_settings.SITE_ID = old_site_id
|
|
||||||
settings.DBTEMPLATES_ADD_DEFAULT_SITE = old_add_default_site
|
|
||||||
|
|
||||||
def test_load_templates(self):
|
|
||||||
result = loader.get_template("base.html").render()
|
|
||||||
self.assertEqual(result, 'base')
|
|
||||||
result2 = loader.get_template("sub.html").render()
|
|
||||||
self.assertEqual(result2, 'sub')
|
|
||||||
|
|
||||||
def test_error_templates_creation(self):
|
|
||||||
call_command('create_error_templates', force=True, verbosity=0)
|
|
||||||
self.assertEqual(list(Template.objects.filter(sites=self.site1)),
|
|
||||||
list(Template.objects.filter()))
|
|
||||||
self.assertTrue(Template.objects.filter(name='404.html').exists())
|
|
||||||
|
|
||||||
def test_automatic_sync(self):
|
|
||||||
admin_base_template = get_template_source('admin/base.html')
|
|
||||||
template = Template.objects.create(name='admin/base.html')
|
|
||||||
self.assertEqual(admin_base_template, template.content)
|
|
||||||
|
|
||||||
def test_sync_templates(self):
|
|
||||||
old_template_dirs = settings.TEMPLATES[0].get('DIRS', [])
|
|
||||||
temp_template_dir = tempfile.mkdtemp('dbtemplates')
|
|
||||||
temp_template_path = os.path.join(temp_template_dir, 'temp_test.html')
|
|
||||||
temp_template = open(temp_template_path, 'w', encoding='utf-8')
|
|
||||||
try:
|
|
||||||
temp_template.write('temp test')
|
|
||||||
settings.TEMPLATES[0]['DIRS'] = (temp_template_dir,)
|
|
||||||
# these works well if is not settings patched at runtime
|
|
||||||
# for supporting django < 1.7 tests we must patch dirs in runtime
|
|
||||||
from dbtemplates.management.commands import sync_templates
|
|
||||||
sync_templates.DIRS = settings.TEMPLATES[0]['DIRS']
|
|
||||||
|
|
||||||
self.assertFalse(
|
|
||||||
Template.objects.filter(name='temp_test.html').exists())
|
|
||||||
call_command('sync_templates', force=True,
|
|
||||||
verbosity=0, overwrite=FILES_TO_DATABASE)
|
|
||||||
self.assertTrue(
|
|
||||||
Template.objects.filter(name='temp_test.html').exists())
|
|
||||||
|
|
||||||
t = Template.objects.get(name='temp_test.html')
|
|
||||||
t.content = 'temp test modified'
|
|
||||||
t.save()
|
|
||||||
call_command('sync_templates', force=True,
|
|
||||||
verbosity=0, overwrite=DATABASE_TO_FILES)
|
|
||||||
self.assertEqual('temp test modified',
|
|
||||||
open(temp_template_path,
|
|
||||||
encoding='utf-8').read())
|
|
||||||
|
|
||||||
call_command('sync_templates', force=True, verbosity=0,
|
|
||||||
delete=True, overwrite=DATABASE_TO_FILES)
|
|
||||||
self.assertTrue(os.path.exists(temp_template_path))
|
|
||||||
self.assertFalse(
|
|
||||||
Template.objects.filter(name='temp_test.html').exists())
|
|
||||||
finally:
|
|
||||||
temp_template.close()
|
|
||||||
settings.TEMPLATES[0]['DIRS'] = old_template_dirs
|
|
||||||
shutil.rmtree(temp_template_dir)
|
|
||||||
|
|
||||||
def test_get_cache(self):
|
|
||||||
self.assertTrue(isinstance(get_cache_backend(), BaseCache))
|
|
||||||
|
|
||||||
def test_check_template_syntax(self):
|
|
||||||
bad_template, _ = Template.objects.get_or_create(
|
|
||||||
name='bad.html', content='{% if foo %}Bar')
|
|
||||||
good_template, _ = Template.objects.get_or_create(
|
|
||||||
name='good.html', content='{% if foo %}Bar{% endif %}')
|
|
||||||
self.assertFalse(check_template_syntax(bad_template)[0])
|
|
||||||
self.assertTrue(check_template_syntax(good_template)[0])
|
|
||||||
|
|
||||||
def test_get_cache_name(self):
|
|
||||||
self.assertEqual(get_cache_key('name with spaces'),
|
|
||||||
'dbtemplates::name-with-spaces::1')
|
|
||||||
|
|
||||||
def test_cache_invalidation(self):
|
|
||||||
# Add t1 into the cache of site2
|
|
||||||
self.t1.sites.add(self.site2)
|
|
||||||
with mock.patch('django.contrib.sites.models.SiteManager.get_current',
|
|
||||||
return_value=self.site2):
|
|
||||||
result = loader.get_template("base.html").render()
|
|
||||||
self.assertEqual(result, 'base')
|
|
||||||
|
|
||||||
# Update content
|
|
||||||
self.t1.content = 'new content'
|
|
||||||
self.t1.save()
|
|
||||||
result = loader.get_template("base.html").render()
|
|
||||||
self.assertEqual(result, 'new content')
|
|
||||||
|
|
||||||
# Cache invalidation should work across sites.
|
|
||||||
# Site2 should see the new content.
|
|
||||||
with mock.patch('django.contrib.sites.models.SiteManager.get_current',
|
|
||||||
return_value=self.site2):
|
|
||||||
result = loader.get_template("base.html").render()
|
|
||||||
self.assertEqual(result, 'new content')
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
DBTEMPLATES_CACHE_BACKEND = 'dummy://'
|
|
||||||
|
|
||||||
DATABASE_ENGINE = 'sqlite3'
|
|
||||||
# SQLite does not support removing unique constraints (see #28)
|
|
||||||
SOUTH_TESTS_MIGRATE = False
|
|
||||||
|
|
||||||
SITE_ID = 1
|
|
||||||
|
|
||||||
SECRET_KEY = 'something-something'
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.sqlite3',
|
|
||||||
'NAME': ':memory:',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sites',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'dbtemplates',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = (
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
)
|
|
||||||
|
|
||||||
TEMPLATE_LOADERS = (
|
|
||||||
'django.template.loaders.filesystem.Loader',
|
|
||||||
'django.template.loaders.app_directories.Loader',
|
|
||||||
'dbtemplates.loader.Loader',
|
|
||||||
)
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'OPTIONS': {
|
|
||||||
'loaders': TEMPLATE_LOADERS,
|
|
||||||
'context_processors': [
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
from dbtemplates.conf import settings
|
|
||||||
from django.contrib.sites.models import Site
|
|
||||||
from django.core import signals
|
|
||||||
from django.template.defaultfilters import slugify
|
|
||||||
|
|
||||||
|
|
||||||
def get_cache_backend():
|
|
||||||
"""
|
|
||||||
Compatibilty wrapper for getting Django's cache backend instance
|
|
||||||
"""
|
|
||||||
from django.core.cache import caches
|
|
||||||
cache = caches.create_connection(settings.DBTEMPLATES_CACHE_BACKEND)
|
|
||||||
|
|
||||||
# Some caches -- python-memcached in particular -- need to do a cleanup at
|
|
||||||
# the end of a request cycle. If not implemented in a particular backend
|
|
||||||
# cache.close is a no-op
|
|
||||||
signals.request_finished.connect(cache.close)
|
|
||||||
return cache
|
|
||||||
|
|
||||||
|
|
||||||
cache = get_cache_backend()
|
|
||||||
|
|
||||||
|
|
||||||
def get_cache_key(name, site=None):
|
|
||||||
if site is None:
|
|
||||||
site = Site.objects.get_current()
|
|
||||||
return f"dbtemplates::{slugify(name)}::{site.pk}"
|
|
||||||
|
|
||||||
|
|
||||||
def get_cache_notfound_key(name):
|
|
||||||
return get_cache_key(name) + "::notfound"
|
|
||||||
|
|
||||||
|
|
||||||
def remove_notfound_key(instance):
|
|
||||||
# Remove notfound key as soon as we save the template.
|
|
||||||
cache.delete(get_cache_notfound_key(instance.name))
|
|
||||||
|
|
||||||
|
|
||||||
def set_and_return(cache_key, content, display_name):
|
|
||||||
# Save in cache backend explicitly if manually deleted or invalidated
|
|
||||||
if cache:
|
|
||||||
cache.set(cache_key, content)
|
|
||||||
return (content, display_name)
|
|
||||||
|
|
||||||
|
|
||||||
def add_template_to_cache(instance, **kwargs):
|
|
||||||
"""
|
|
||||||
Called via Django's signals to cache the templates, if the template
|
|
||||||
in the database was added or changed.
|
|
||||||
"""
|
|
||||||
remove_cached_template(instance)
|
|
||||||
remove_notfound_key(instance)
|
|
||||||
cache.set(get_cache_key(instance.name), instance.content)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_cached_template(instance, **kwargs):
|
|
||||||
"""
|
|
||||||
Called via Django's signals to remove cached templates, if the template
|
|
||||||
in the database was changed or deleted.
|
|
||||||
"""
|
|
||||||
for site in instance.sites.all():
|
|
||||||
cache.delete(get_cache_key(instance.name, site=site))
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
from django.template import (Template, TemplateDoesNotExist,
|
|
||||||
TemplateSyntaxError)
|
|
||||||
|
|
||||||
|
|
||||||
def get_loaders():
|
|
||||||
from django.template.loader import _engine_list
|
|
||||||
loaders = []
|
|
||||||
for engine in _engine_list():
|
|
||||||
loaders.extend(engine.engine.template_loaders)
|
|
||||||
return loaders
|
|
||||||
|
|
||||||
|
|
||||||
def get_template_source(name):
|
|
||||||
source = None
|
|
||||||
for loader in get_loaders():
|
|
||||||
if loader.__module__.startswith('dbtemplates.'):
|
|
||||||
# Don't give a damn about dbtemplates' own loader.
|
|
||||||
continue
|
|
||||||
for origin in loader.get_template_sources(name):
|
|
||||||
try:
|
|
||||||
source = loader.get_contents(origin)
|
|
||||||
except (NotImplementedError, TemplateDoesNotExist):
|
|
||||||
continue
|
|
||||||
if source:
|
|
||||||
return source
|
|
||||||
|
|
||||||
|
|
||||||
def check_template_syntax(template):
|
|
||||||
try:
|
|
||||||
Template(template.content)
|
|
||||||
except TemplateSyntaxError as e:
|
|
||||||
return (False, e)
|
|
||||||
return (True, None)
|
|
||||||
130
docs/Makefile
130
docs/Makefile
|
|
@ -1,130 +0,0 @@
|
||||||
# Makefile for Sphinx documentation
|
|
||||||
#
|
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = sphinx-build
|
|
||||||
PAPER =
|
|
||||||
BUILDDIR = _build
|
|
||||||
|
|
||||||
# Internal variables.
|
|
||||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
|
||||||
PAPEROPT_letter = -D latex_paper_size=letter
|
|
||||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
|
|
||||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
|
||||||
@echo " html to make standalone HTML files"
|
|
||||||
@echo " dirhtml to make HTML files named index.html in directories"
|
|
||||||
@echo " singlehtml to make a single large HTML file"
|
|
||||||
@echo " pickle to make pickle files"
|
|
||||||
@echo " json to make JSON files"
|
|
||||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
|
||||||
@echo " qthelp to make HTML files and a qthelp project"
|
|
||||||
@echo " devhelp to make HTML files and a Devhelp project"
|
|
||||||
@echo " epub to make an epub"
|
|
||||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
|
||||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
|
||||||
@echo " text to make text files"
|
|
||||||
@echo " man to make manual pages"
|
|
||||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
|
||||||
@echo " linkcheck to check all external links for integrity"
|
|
||||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
-rm -rf $(BUILDDIR)/*
|
|
||||||
|
|
||||||
html:
|
|
||||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
|
||||||
|
|
||||||
dirhtml:
|
|
||||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
|
||||||
|
|
||||||
singlehtml:
|
|
||||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
|
||||||
|
|
||||||
pickle:
|
|
||||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the pickle files."
|
|
||||||
|
|
||||||
json:
|
|
||||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can process the JSON files."
|
|
||||||
|
|
||||||
htmlhelp:
|
|
||||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
|
||||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
|
||||||
|
|
||||||
qthelp:
|
|
||||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
|
||||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
|
||||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/asd.qhcp"
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/asd.qhc"
|
|
||||||
|
|
||||||
devhelp:
|
|
||||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
|
||||||
@echo
|
|
||||||
@echo "Build finished."
|
|
||||||
@echo "To view the help file:"
|
|
||||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/asd"
|
|
||||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/asd"
|
|
||||||
@echo "# devhelp"
|
|
||||||
|
|
||||||
epub:
|
|
||||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
|
||||||
|
|
||||||
latex:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo
|
|
||||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
|
||||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
|
||||||
"(use \`make latexpdf' here to do that automatically)."
|
|
||||||
|
|
||||||
latexpdf:
|
|
||||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
|
||||||
@echo "Running LaTeX files through pdflatex..."
|
|
||||||
make -C $(BUILDDIR)/latex all-pdf
|
|
||||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
|
||||||
|
|
||||||
text:
|
|
||||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
|
||||||
|
|
||||||
man:
|
|
||||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
|
||||||
|
|
||||||
changes:
|
|
||||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
|
||||||
@echo
|
|
||||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
|
||||||
|
|
||||||
linkcheck:
|
|
||||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
|
||||||
|
|
||||||
doctest:
|
|
||||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
|
||||||
@echo "Testing of doctests in the sources finished, look at the " \
|
|
||||||
"results in $(BUILDDIR)/doctest/output.txt."
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
=================
|
|
||||||
Advanced features
|
|
||||||
=================
|
|
||||||
|
|
||||||
.. _caching:
|
|
||||||
|
|
||||||
Caching
|
|
||||||
=======
|
|
||||||
|
|
||||||
``dbtemplates`` uses Django's default caching infrastructure for caching, and
|
|
||||||
operates automatically when creating, updating or deleting templates in
|
|
||||||
the database.
|
|
||||||
|
|
||||||
To enable one of them you need to specify a setting called
|
|
||||||
``DBTEMPLATES_CACHE_BACKEND`` to one of the valid values Django's
|
|
||||||
``CACHE_BACKEND`` can be set to. E.g.::
|
|
||||||
|
|
||||||
DBTEMPLATES_CACHE_BACKEND = 'memcached://127.0.0.1:11211/'
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Starting in version 1.0 ``dbtemplates`` allows you also to set the new
|
|
||||||
dict-based ``CACHES`` setting, which was introduced in Django 1.3.
|
|
||||||
|
|
||||||
All you have to do is to provide a new entry in the ``CACHES`` dict
|
|
||||||
named ``'dbtemplates'``, e.g.::
|
|
||||||
|
|
||||||
CACHES = {
|
|
||||||
'dbtemplates': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
|
||||||
'LOCATION': '127.0.0.1:11211',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Please see the `cache documentation`_ if you want to know more about it.
|
|
||||||
|
|
||||||
.. _cache documentation: http://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache
|
|
||||||
|
|
||||||
.. _versioned:
|
|
||||||
|
|
||||||
Versioned storage
|
|
||||||
=================
|
|
||||||
|
|
||||||
``dbtemplates`` comes prepared to use the third party Django app
|
|
||||||
`django-reversion`_, that once installed besides ``dbtemplates`` allows you
|
|
||||||
to jump back to old versions of your templates. It automatically saves every
|
|
||||||
state when you save the template in your database and provides an easy to use
|
|
||||||
interface.
|
|
||||||
|
|
||||||
Please refer to `django-reversion's documentation`_ for more information
|
|
||||||
about how it works.
|
|
||||||
|
|
||||||
.. hint::
|
|
||||||
Just visit the "History" section of each template instance and browse its history.
|
|
||||||
|
|
||||||
Short installation howto
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
1. Get the source from the `django-reversion`_ project site and put it
|
|
||||||
somewhere on your `PYTHONPATH`.
|
|
||||||
2. Add ``reversion`` to the ``INSTALLED_APPS`` setting of your Django project
|
|
||||||
3. Sync your database with ``python manage.py syncdb``
|
|
||||||
4. Set ``DBTEMPLATES_USE_REVERSION`` setting to ``True``
|
|
||||||
|
|
||||||
History compare view
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
You can also use ``dbtemplates`` together with `django-reversion-compare`_ which
|
|
||||||
provides a history compare view to compare two versions of a model which is under
|
|
||||||
reversion.
|
|
||||||
|
|
||||||
.. _django-reversion: https://github.com/etianen/django-reversion
|
|
||||||
.. _django-reversion's documentation: https://django-reversion.readthedocs.io/en/latest/
|
|
||||||
.. _django-reversion-compare: https://github.com/jedie/django-reversion-compare
|
|
||||||
|
|
||||||
|
|
||||||
.. _commands:
|
|
||||||
|
|
||||||
Management commands
|
|
||||||
===================
|
|
||||||
|
|
||||||
``dbtemplates`` comes with two `Django management commands`_ to be used with
|
|
||||||
``django-admin.py`` or ``manage.py``:
|
|
||||||
|
|
||||||
* ``sync_templates``
|
|
||||||
|
|
||||||
Enables you to sync your already existing file systems templates with the
|
|
||||||
database. It will guide you through the whole process.
|
|
||||||
|
|
||||||
* ``create_error_templates``
|
|
||||||
|
|
||||||
Tries to add the two templates ``404.html`` and ``500.html`` that are used
|
|
||||||
by Django when a error occurs.
|
|
||||||
|
|
||||||
* ``check_template_syntax``
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
|
|
||||||
Checks the saved templates whether they are valid Django templates.
|
|
||||||
|
|
||||||
.. _Django management commands: http://docs.djangoproject.com/en/dev/ref/django-admin/
|
|
||||||
|
|
||||||
.. _admin_actions:
|
|
||||||
|
|
||||||
Admin actions
|
|
||||||
=============
|
|
||||||
|
|
||||||
``dbtemplates`` provides two `admin actions`_ to be used with Django>=1.1.
|
|
||||||
|
|
||||||
* ``invalidate_cache``
|
|
||||||
|
|
||||||
Invalidates the cache of the selected templates by calling the appropriate
|
|
||||||
cache backend methods.
|
|
||||||
|
|
||||||
* ``repopulate_cache``
|
|
||||||
|
|
||||||
Repopulates the cache with selected templates by invalidating it first and
|
|
||||||
filling then after that.
|
|
||||||
|
|
||||||
* ``check_syntax``
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
|
|
||||||
Checks the selected tempaltes for syntax errors.
|
|
||||||
|
|
||||||
.. _admin actions: http://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/
|
|
||||||
|
|
@ -1,428 +0,0 @@
|
||||||
Changelog
|
|
||||||
=========
|
|
||||||
|
|
||||||
v5.0 (unreleased)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This is a backwards-incompatible release!
|
|
||||||
|
|
||||||
* Dropped support for Python 3.7 and Django < 4.2.
|
|
||||||
|
|
||||||
* Added support for Python 3.11, 3.12, 3.13, 3.14.
|
|
||||||
|
|
||||||
* Django 5.x and 6.0 support
|
|
||||||
|
|
||||||
v4.0 (2022-09-3)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This is a backwards-incompatible release!
|
|
||||||
|
|
||||||
* Dropped support for Python 2.7 and Django < 3.2.
|
|
||||||
|
|
||||||
* Added support for Python 3.8, 3.9, 3.10.
|
|
||||||
|
|
||||||
* Moved test runner to GitHub Actions:
|
|
||||||
|
|
||||||
http://github.com/jazzband/django-dbtemplates/actions
|
|
||||||
|
|
||||||
* Django 4.x support
|
|
||||||
|
|
||||||
v3.0 (2019-01-27)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This is a backwards-incompatible release!
|
|
||||||
|
|
||||||
* Dropped support for Django < 1.11.
|
|
||||||
|
|
||||||
* Added support for Django 2.0 and 2.1.
|
|
||||||
|
|
||||||
* Added support for Python 3.7.
|
|
||||||
|
|
||||||
* Recompiled Russian locale.
|
|
||||||
|
|
||||||
* Fixed byte string in migration file that caused the migration
|
|
||||||
system to falsely think that there are new changes.
|
|
||||||
|
|
||||||
* Fixed string representation of template model, e.g. to improve
|
|
||||||
readability in choice fields.
|
|
||||||
|
|
||||||
v2.0 (2016-09-29)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
This is a backwards-incompatible release!
|
|
||||||
|
|
||||||
* Moved maintenance to the `Jazzband <https://jazzband.co/>`_
|
|
||||||
|
|
||||||
* Dropped support for Python 2.6
|
|
||||||
|
|
||||||
* Added support for Python 3.4 and 3.5
|
|
||||||
|
|
||||||
* Dropped support for Django < 1.8
|
|
||||||
|
|
||||||
* Removed South migrations. Please use Django's native migration system instead
|
|
||||||
|
|
||||||
* Removed the example project since it's out-of-date quickly
|
|
||||||
|
|
||||||
v1.3.2 (2015-06-15)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* support for Django 1.8 (not full, but usable)
|
|
||||||
* support for RedactorJS
|
|
||||||
|
|
||||||
thanks for contrib - @eculver, @kmooney, @volksman
|
|
||||||
|
|
||||||
v1.3.1 (2012-05-23)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Minor release to move away from nose again and use own
|
|
||||||
`django-discover-runner`_.
|
|
||||||
|
|
||||||
.. _`django-discover-runner`: http://pypi.python.org/pypi/django-discover-runner
|
|
||||||
|
|
||||||
v1.3 (2012-05-07)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
* Dropped support for Django < 1.3 **backwards incompatible**
|
|
||||||
|
|
||||||
* Dropped using versiontools in favor of home made solution.
|
|
||||||
|
|
||||||
* Added optional support for TinyMCE editor instead of the CodeMirror
|
|
||||||
editor (just enable ``DBTEMPLATES_USE_TINYMCE``).
|
|
||||||
|
|
||||||
* Fixed compatibility to Django 1.4's handling of the ``DATABASES``
|
|
||||||
setting. Should also respect database routers now.
|
|
||||||
|
|
||||||
* Fixed an issue of the cache key generation in combination with
|
|
||||||
memcache's inability to stomach spaces.
|
|
||||||
|
|
||||||
* Moved test runner to use nose_ and a hosted CI project at Travis_:
|
|
||||||
http://travis-ci.org/jazzband/django-dbtemplates
|
|
||||||
|
|
||||||
.. _nose: https://nose.readthedocs.io/
|
|
||||||
.. _Travis: http://travis-ci.org
|
|
||||||
|
|
||||||
v1.2.1 (2011-09-07)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Fixed a wrong use of the non-lazy localization tools.
|
|
||||||
|
|
||||||
* Fixed bugs in the documentation.
|
|
||||||
|
|
||||||
* Make use of django-appconf and versiontools.
|
|
||||||
|
|
||||||
v1.2 (2011-08-15)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
* Refactored the template loader to be even more cache effective.
|
|
||||||
|
|
||||||
* Added ``check_template_syntax`` management command and admin action
|
|
||||||
to make sure the saved templates are valid Django templates.
|
|
||||||
|
|
||||||
v1.1.1 (2011-07-08)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Fixed bug in cache loading (again).
|
|
||||||
|
|
||||||
* Fixed bugs in the documentation.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Since ``dbtemplates`` removed support for Django lower than 1.2 you
|
|
||||||
have to use the template loader class in the ``TEMPLATE_LOADERS``
|
|
||||||
(``'dbtemplates.loader.Loader'``) and **not** the previosly included
|
|
||||||
function that ended with ``load_template_source``.
|
|
||||||
|
|
||||||
v1.1 (2011-07-06)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
* **BACKWARDS-INCOMPATIBLE** Requires Django 1.2 or higher.
|
|
||||||
For previous Django versions use an older versions of ``dbtemplates``,
|
|
||||||
e.g.::
|
|
||||||
|
|
||||||
$ pip install "django-dbtemplates<1.1"
|
|
||||||
|
|
||||||
* Added South migrations.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
If you are using South in your Django project, you can easily enable
|
|
||||||
dbtemplates' migrations, *faking* the first migration by using the
|
|
||||||
``--fake`` option of South's ``migrate`` management command::
|
|
||||||
|
|
||||||
$ manage.py migrate --fake 0001 dbtemplates
|
|
||||||
|
|
||||||
Then run the rest of the migrations::
|
|
||||||
|
|
||||||
$ manage.py migrate dbtemplates
|
|
||||||
|
|
||||||
* Removed uniqueness on the ``name`` field of the ``Template`` model. This is
|
|
||||||
needed because there isn't a ``unique_together`` for M2M fields in Django
|
|
||||||
such as the ``sites`` field in the ``Template`` model.
|
|
||||||
|
|
||||||
* Made the ``sites`` field optional to support a way to apply a template to
|
|
||||||
all sites.
|
|
||||||
|
|
||||||
* Added ``--delete`` option to ``sync_templates`` managment command to delete
|
|
||||||
the file or database entry after syncing (depending on used ``--overwrite``
|
|
||||||
mode).
|
|
||||||
|
|
||||||
* Updated translations.
|
|
||||||
|
|
||||||
* Fixed issue with incorrectly splitting paths in ``sync_templates``.
|
|
||||||
|
|
||||||
* Extended tests.
|
|
||||||
|
|
||||||
* Fixed issue with cache settings handling.
|
|
||||||
|
|
||||||
v1.0.1 (2011-04-14)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Minor bugfixes with regard to the new cache handling.
|
|
||||||
|
|
||||||
v1.0 (2011-04-11)
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
This is the first stable release of django-dbtemplates which comes with a
|
|
||||||
series of backwards incompatible changes.
|
|
||||||
|
|
||||||
* Removed own caching mechanism in favor of Django based caching mechanism.
|
|
||||||
The ``DBTEMPLATES_CACHE_BACKEND`` is expected to be a valid cache backend
|
|
||||||
URI, just like Django's own ``CACHE_BACKEND`` setting. In Django >= 1.3
|
|
||||||
an ``'dbtemplates'`` entry in the ``CACHES`` setting is also considered
|
|
||||||
valid.
|
|
||||||
|
|
||||||
* Added tox configuration to test ``dbtemplates`` on Python 2.5, 2.6 and 2.7
|
|
||||||
with Django 1.1.X, 1.2.X and 1.3.X.
|
|
||||||
|
|
||||||
* Added Transifex configuration.
|
|
||||||
|
|
||||||
* Use ``STATIC_URL`` setting instead of ``MEDIA_URL`` for the media prefix.
|
|
||||||
Also moved files from media/* to static/* to follow convention introduced
|
|
||||||
in Django 1.3.
|
|
||||||
|
|
||||||
* Use ReadTheDocs for documentation hosting.
|
|
||||||
|
|
||||||
v0.8.0 (2010-11-07)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Added Finnish translation (by jholster)
|
|
||||||
|
|
||||||
* Added --overwrite and --app-first options to sync_templates command (by Alex Kamedov).
|
|
||||||
|
|
||||||
v0.7.4 (2010-09-23)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Fixed tests.
|
|
||||||
|
|
||||||
v0.7.3 (2010-09-21)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Added ``DBTEMPLATES_AUTO_POPULATE_CONTENT`` setting to be able to disable
|
|
||||||
to auto-populating of template content.
|
|
||||||
|
|
||||||
* Fixed cosmetic issue in admin with collapsable fields.
|
|
||||||
|
|
||||||
v0.7.2 (2010-09-04)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Moved to Github again. Sigh.
|
|
||||||
|
|
||||||
v0.7.1 (2010-07-07)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Fixed problem with the CodeMirror textarea, which wasn't completely
|
|
||||||
disabled before.
|
|
||||||
|
|
||||||
* Fixed problem with the ``DBTEMPLATES_MEDIA_PREFIX`` setting, which defaults
|
|
||||||
now to ``os.path.join(settings.MEDIA_ROOT, 'dbtemplates')`` now.
|
|
||||||
|
|
||||||
In other words, if you don't specify a ``DBTEMPLATES_MEDIA_PREFIX`` setting
|
|
||||||
and have the CodeMirror textarea enabled, dbtemplates will look in a
|
|
||||||
subdirectory of your site's ``MEDIA_ROOT`` for the CodeMirror media files.
|
|
||||||
|
|
||||||
v0.7.0 (2010-06-24)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Added CodeMirror_-based syntax highlighting textarea, based on the amaxing
|
|
||||||
work_ by `Nic Pottier`_. Set the ``DBTEMPLATES_USE_CODEMIRROR`` setting
|
|
||||||
to ``True`` to enable it.
|
|
||||||
|
|
||||||
* Make use of the full width in plain textarea mode.
|
|
||||||
|
|
||||||
* Added Chinese translation
|
|
||||||
|
|
||||||
* Added support for Django 1.2
|
|
||||||
|
|
||||||
* Updated French translation
|
|
||||||
|
|
||||||
* Added ``DBTEMPLATES_USE_REVERSION`` setting to be able to explicitely enable
|
|
||||||
reversion support. (Default: ``False``)
|
|
||||||
|
|
||||||
.. _CodeMirror: http://marijn.haverbeke.nl/codemirror/
|
|
||||||
.. _work: https://gist.github.com/368758/86bcafe53c438e2e2a0e3442c3b30f2c6011fbba
|
|
||||||
.. _`Nic Pottier`: http://github.com/nicpottier
|
|
||||||
|
|
||||||
v0.6.1 (2009-10-19)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Fixed issue with default site of a template, added ability to disable
|
|
||||||
default site (``DBTEMPLATES_ADD_DEFAULT_SITE``).
|
|
||||||
|
|
||||||
v0.6.0 (2009-10-09)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
* Updated and added locales (Danish, Brazilian Portuguese)
|
|
||||||
|
|
||||||
* Fixes an ambiguity problem with the cache invalidation
|
|
||||||
|
|
||||||
* Added ``invalidate_cache`` and ``repopulate_cache`` admin actions
|
|
||||||
|
|
||||||
* Added Sphinx documentation
|
|
||||||
|
|
||||||
v0.5.7
|
|
||||||
------
|
|
||||||
|
|
||||||
* Updates to the docs
|
|
||||||
|
|
||||||
* switch back to Bitbucket
|
|
||||||
|
|
||||||
* fixed tests
|
|
||||||
|
|
||||||
* Added Italian translation
|
|
||||||
|
|
||||||
* list of sites the template is used on
|
|
||||||
|
|
||||||
* fixed bug in ``create_error_template`` command.
|
|
||||||
|
|
||||||
v0.5.4
|
|
||||||
------
|
|
||||||
|
|
||||||
* Made loader and cache backends site-aware.
|
|
||||||
|
|
||||||
* The filesystem cache backend now saves the files under
|
|
||||||
``<dir>/<site_domain>/<file_name>``.
|
|
||||||
|
|
||||||
* The Django cache backend the Site id in the cache key
|
|
||||||
|
|
||||||
* Template is now saved explicitly to backend if not existent in cache
|
|
||||||
(e.g. if deleted manually or invalidated).
|
|
||||||
|
|
||||||
v0.5.3
|
|
||||||
------
|
|
||||||
|
|
||||||
* Removed automatic creation of 404.html and 50v0.html templates and added a
|
|
||||||
new management command for those cases called ``create_error_templates``
|
|
||||||
|
|
||||||
* Also reverted move to Bitbucket
|
|
||||||
|
|
||||||
v0.5.2
|
|
||||||
------
|
|
||||||
|
|
||||||
* Fixed a problem with ``django.contrib.sites`` when its table hasn't been
|
|
||||||
populated yet on initialization of dbtemplates. Thanks for the report,
|
|
||||||
Kevin Fricovsky
|
|
||||||
|
|
||||||
* Added an example Django project and docs for it
|
|
||||||
|
|
||||||
v0.5.1
|
|
||||||
------
|
|
||||||
|
|
||||||
* Removed unneeded code that registered the model with reversion.
|
|
||||||
|
|
||||||
* Updated docs a bit.
|
|
||||||
|
|
||||||
* Moved codebase to Bitbucket.
|
|
||||||
|
|
||||||
* Removed legacy ``sync_templates.py`` script, use ``django-admin.py
|
|
||||||
sync_templates`` from now on.
|
|
||||||
|
|
||||||
v0.5.0
|
|
||||||
------
|
|
||||||
|
|
||||||
* Added support for `django-reversion`_
|
|
||||||
|
|
||||||
* added feature that populates the content field automatically when left
|
|
||||||
empty by using Django's other template loaders
|
|
||||||
|
|
||||||
* added caching backend system with two default backends:
|
|
||||||
|
|
||||||
* ``FileSystemBackend``
|
|
||||||
* ``DjangoCacheBackend``
|
|
||||||
|
|
||||||
More about it in the `blog post`_ and in the docs.
|
|
||||||
|
|
||||||
.. _django-reversion: http://code.google.com/p/django-reversion/
|
|
||||||
.. _blog post: http://jannisleidel.com/2008/11/updates-to-django-dbtemplates-and-half-assed-promise/
|
|
||||||
|
|
||||||
v0.4.7
|
|
||||||
------
|
|
||||||
|
|
||||||
* Minor bugfix
|
|
||||||
|
|
||||||
v0.4.6
|
|
||||||
------
|
|
||||||
|
|
||||||
* Minor doc change and PyPI support
|
|
||||||
|
|
||||||
v0.4.5
|
|
||||||
------
|
|
||||||
|
|
||||||
* fixed the --force option of the sync_templates command
|
|
||||||
|
|
||||||
v0.4.4
|
|
||||||
------
|
|
||||||
|
|
||||||
* fixed error in custom model save() after changes in Django `r8670`_.
|
|
||||||
|
|
||||||
.. _r8670: http://code.djangoproject.com/changeset/8670
|
|
||||||
|
|
||||||
v0.4.3
|
|
||||||
------
|
|
||||||
|
|
||||||
* removed oldforms code
|
|
||||||
|
|
||||||
v0.4.2
|
|
||||||
------
|
|
||||||
|
|
||||||
* added Hebrew translation (by mkriheli)
|
|
||||||
|
|
||||||
v0.4.1
|
|
||||||
------
|
|
||||||
|
|
||||||
* added French (by Roland Frederic) and German locale
|
|
||||||
|
|
||||||
v0.4.0
|
|
||||||
------
|
|
||||||
|
|
||||||
* adds better support for newforms-admin
|
|
||||||
|
|
||||||
* don't forget to load the dbtemplates.admin, e.g. by using
|
|
||||||
django.contrib.admin.autodiscover() in you urls.py
|
|
||||||
|
|
||||||
v0.3.1
|
|
||||||
------
|
|
||||||
|
|
||||||
* adds a new management command *sync_templates* for bidirectional syncing
|
|
||||||
between filesystem and database (backwards-compatible) and
|
|
||||||
FilesystemCaching (thanks, Arne Brodowski!)
|
|
||||||
|
|
||||||
v0.2.5
|
|
||||||
------
|
|
||||||
|
|
||||||
* adds support for newforms-admin
|
|
||||||
|
|
||||||
Support
|
|
||||||
=======
|
|
||||||
|
|
||||||
Please leave your questions and messages on the designated site:
|
|
||||||
|
|
||||||
http://github.com/jazzband/django-dbtemplates/issues/
|
|
||||||
198
docs/conf.py
198
docs/conf.py
|
|
@ -1,198 +0,0 @@
|
||||||
#
|
|
||||||
# django-dbtemplates documentation build configuration file, created by
|
|
||||||
# sphinx-quickstart on Fri Oct 9 14:52:11 2009.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
import sys, os
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
sys.path.append(os.path.abspath('.'))
|
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage']
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.txt'
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
#source_encoding = 'utf-8'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = 'django-dbtemplates'
|
|
||||||
copyright = '2007-2019, Jannis Leidel and contributors'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
try:
|
|
||||||
from dbtemplates import __version__
|
|
||||||
# The short X.Y version.
|
|
||||||
version = '.'.join(__version__.split('.')[:2])
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = __version__
|
|
||||||
except ImportError:
|
|
||||||
version = release = 'dev'
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
#language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
#today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
#today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of documents that shouldn't be included in the build.
|
|
||||||
#unused_docs = []
|
|
||||||
|
|
||||||
# List of directories, relative to source directory, that shouldn't be searched
|
|
||||||
# for source files.
|
|
||||||
exclude_trees = ['_build']
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
|
||||||
#default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
#add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
#add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
#show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
#modindex_common_prefix = []
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ---------------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
|
||||||
html_theme = 'default'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
#html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
# html_theme_path = ['_theme']
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
html_title = "django-dbtemplates documentation"
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
html_short_title = "django-dbtemplates"
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
#html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
#html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
# html_static_path = ['_static']
|
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
#html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
#html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
#html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
#html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#html_use_modindex = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
#html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
#html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
#html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
#html_use_opensearch = ''
|
|
||||||
|
|
||||||
# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
#html_file_suffix = ''
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'django-dbtemplatesdoc'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
|
||||||
|
|
||||||
# The paper size ('letter' or 'a4').
|
|
||||||
#latex_paper_size = 'letter'
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
#latex_font_size = '10pt'
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
|
||||||
latex_documents = [
|
|
||||||
('index', 'django-dbtemplates.tex', 'django-dbtemplates Documentation',
|
|
||||||
'Jannis Leidel and contributors', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
#latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
#latex_use_parts = False
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
#latex_preamble = ''
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
#latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
#latex_use_modindex = True
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
django-dbtemplates
|
|
||||||
==================
|
|
||||||
|
|
||||||
``dbtemplates`` is a Django app that consists of two parts:
|
|
||||||
|
|
||||||
1. It allows you to store templates in your database
|
|
||||||
2. It provides `template loader`_ that enables Django to load the
|
|
||||||
templates from the database
|
|
||||||
|
|
||||||
It also features optional support for :ref:`versioned storage <versioned>`
|
|
||||||
and :ref:`django-admin command <commands>`, integrates with Django's
|
|
||||||
:ref:`caching system <caching>` and the :ref:`admin actions <admin_actions>`.
|
|
||||||
|
|
||||||
Please see https://django-dbtemplates.readthedocs.io/ for more details.
|
|
||||||
|
|
||||||
The source code and issue tracker can be found on Github: https://github.com/jazzband/django-dbtemplates
|
|
||||||
|
|
||||||
.. _template loader: http://docs.djangoproject.com/en/dev/ref/templates/api/#loading-templates
|
|
||||||
|
|
||||||
Contents:
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
overview
|
|
||||||
advanced
|
|
||||||
settings
|
|
||||||
changelog
|
|
||||||
170
docs/make.bat
170
docs/make.bat
|
|
@ -1,170 +0,0 @@
|
||||||
@ECHO OFF
|
|
||||||
|
|
||||||
REM Command file for Sphinx documentation
|
|
||||||
|
|
||||||
if "%SPHINXBUILD%" == "" (
|
|
||||||
set SPHINXBUILD=sphinx-build
|
|
||||||
)
|
|
||||||
set BUILDDIR=_build
|
|
||||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
|
||||||
if NOT "%PAPER%" == "" (
|
|
||||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "" goto help
|
|
||||||
|
|
||||||
if "%1" == "help" (
|
|
||||||
:help
|
|
||||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
|
||||||
echo. html to make standalone HTML files
|
|
||||||
echo. dirhtml to make HTML files named index.html in directories
|
|
||||||
echo. singlehtml to make a single large HTML file
|
|
||||||
echo. pickle to make pickle files
|
|
||||||
echo. json to make JSON files
|
|
||||||
echo. htmlhelp to make HTML files and a HTML help project
|
|
||||||
echo. qthelp to make HTML files and a qthelp project
|
|
||||||
echo. devhelp to make HTML files and a Devhelp project
|
|
||||||
echo. epub to make an epub
|
|
||||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
|
||||||
echo. text to make text files
|
|
||||||
echo. man to make manual pages
|
|
||||||
echo. changes to make an overview over all changed/added/deprecated items
|
|
||||||
echo. linkcheck to check all external links for integrity
|
|
||||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "clean" (
|
|
||||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
|
||||||
del /q /s %BUILDDIR%\*
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "html" (
|
|
||||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "dirhtml" (
|
|
||||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "singlehtml" (
|
|
||||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "pickle" (
|
|
||||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can process the pickle files.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "json" (
|
|
||||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can process the JSON files.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "htmlhelp" (
|
|
||||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
|
||||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "qthelp" (
|
|
||||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
|
||||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
|
||||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\asd.qhcp
|
|
||||||
echo.To view the help file:
|
|
||||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\asd.ghc
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "devhelp" (
|
|
||||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "epub" (
|
|
||||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "latex" (
|
|
||||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "text" (
|
|
||||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "man" (
|
|
||||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "changes" (
|
|
||||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.The overview file is in %BUILDDIR%/changes.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "linkcheck" (
|
|
||||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Link check complete; look for any errors in the above output ^
|
|
||||||
or in %BUILDDIR%/linkcheck/output.txt.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%1" == "doctest" (
|
|
||||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
|
||||||
if errorlevel 1 exit /b 1
|
|
||||||
echo.
|
|
||||||
echo.Testing of doctests in the sources finished, look at the ^
|
|
||||||
results in %BUILDDIR%/doctest/output.txt.
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
:end
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
Setup
|
|
||||||
=====
|
|
||||||
|
|
||||||
1. Get the source from the `Git repository`_ or install it from the
|
|
||||||
Python Package Index by running ``pip install django-dbtemplates``.
|
|
||||||
2. Edit the settings.py of your Django site:
|
|
||||||
|
|
||||||
* Add ``dbtemplates`` to the ``INSTALLED_APPS`` setting
|
|
||||||
|
|
||||||
Check if ``django.contrib.sites`` and ``django.contrib.admin`` are in
|
|
||||||
``INSTALLED_APPS`` and add if necessary.
|
|
||||||
|
|
||||||
It should look something like this::
|
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.sites',
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.flatpages',
|
|
||||||
# ..
|
|
||||||
'dbtemplates',
|
|
||||||
)
|
|
||||||
|
|
||||||
* Add ``dbtemplates.loader.Loader`` to the ``TEMPLATES.OPTIONS.loaders`` list
|
|
||||||
in the settings.py of your Django project.
|
|
||||||
|
|
||||||
It should look something like this::
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [ # your template dirs here
|
|
||||||
],
|
|
||||||
'APP_DIRS': False,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.i18n',
|
|
||||||
'django.template.context_processors.media',
|
|
||||||
'django.template.context_processors.static',
|
|
||||||
'django.template.context_processors.tz',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
],
|
|
||||||
'loaders': [
|
|
||||||
'django.template.loaders.filesystem.Loader',
|
|
||||||
'django.template.loaders.app_directories.Loader',
|
|
||||||
'dbtemplates.loader.Loader',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
The order of ``TEMPLATES.OPTIONS.loaders`` is important. In the former
|
|
||||||
example, templates from the database will be used as a fallback (ie. when
|
|
||||||
the template does not exists in other locations). If you want the template
|
|
||||||
from the database to be used to override templates in other locations,
|
|
||||||
put ``dbtemplates.loader.Loader`` at the beginning of ``loaders``.
|
|
||||||
|
|
||||||
3. Sync your database ``python manage.py migrate``
|
|
||||||
4. Restart your Django server
|
|
||||||
|
|
||||||
.. _Git repository: https://github.com/jazzband/django-dbtemplates/
|
|
||||||
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
Creating database templates is pretty simple: Just open the admin interface
|
|
||||||
of your Django-based site in your browser and click on "Templates" in the
|
|
||||||
"Database templates" section.
|
|
||||||
|
|
||||||
There you only need to fill in the ``name`` field with the identifier, Django
|
|
||||||
is supposed to use while searching for templates, e.g.
|
|
||||||
``blog/entry_list.html``. The ``content`` field should be filled with the
|
|
||||||
content of your template.
|
|
||||||
|
|
||||||
Optionally, by leaving the ``content`` field empty you are able to tell
|
|
||||||
``dbtemplates`` to look for a template with the ``name`` by using Django's
|
|
||||||
other template loaders. For example, if you have a template called
|
|
||||||
``blog/entry_list.html`` on your file system and want to save the templates
|
|
||||||
contents in the database, you just need to leave the content field empty to
|
|
||||||
automatically populate it. That's especially useful if you don't want to
|
|
||||||
copy and paste its content manually to the textarea.
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
Settings
|
|
||||||
========
|
|
||||||
|
|
||||||
``DBTEMPLATES_ADD_DEFAULT_SITE``
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
``dbtemplates`` adds the current site (``settings.SITE_ID``) to the database
|
|
||||||
template when it is created by default. You can disable this feature by
|
|
||||||
setting ``DBTEMPLATES_ADD_DEFAULT_SITE`` to ``False``.
|
|
||||||
|
|
||||||
``DBTEMPLATES_AUTO_POPULATE_CONTENT``
|
|
||||||
-------------------------------------
|
|
||||||
|
|
||||||
``dbtemplates`` auto-populates the content of a newly created template with
|
|
||||||
the content of a template with the same name the other template loader.
|
|
||||||
To disable this feature set ``DBTEMPLATES_AUTO_POPULATE_CONTENT`` to
|
|
||||||
``False``.
|
|
||||||
|
|
||||||
``DBTEMPLATES_CACHE_BACKEND``
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
The dotted Python path to the cache backend class. See
|
|
||||||
:ref:`Caching <caching>` for details.
|
|
||||||
|
|
||||||
``DBTEMPLATES_USE_CODEMIRROR``
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
A boolean, if enabled triggers the use of the CodeMirror based editor.
|
|
||||||
Set to ``False`` by default.
|
|
||||||
|
|
||||||
``DBTEMPLATES_USE_TINYMCE``
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
.. versionadded:: 1.3
|
|
||||||
|
|
||||||
A boolean, if enabled triggers the use of the TinyMCE based editor.
|
|
||||||
Set to ``False`` by default.
|
|
||||||
|
|
||||||
``DBTEMPLATES_USE_REVERSION``
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
A boolean, if enabled triggers the use of ``django-reversion``.
|
|
||||||
|
|
||||||
``DBTEMPLATES_USE_REVERSION_COMPARE``
|
|
||||||
-----------------------------
|
|
||||||
|
|
||||||
A boolean, if enabled triggers the use of ``django-reversion-compare``.
|
|
||||||
|
|
||||||
``DBTEMPLATES_MEDIA_PREFIX``
|
|
||||||
----------------------------
|
|
||||||
|
|
||||||
The URL prefix for ``dbtemplates``' media -- CSS and JavaScript used by
|
|
||||||
the CodeMirror based editor. Make sure to use a trailing
|
|
||||||
slash, and to have this be different from the ``STATIC_URL`` setting
|
|
||||||
(since the same URL cannot be mapped onto two different sets of
|
|
||||||
files).
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
Starting in version 1.0, ``dbtemplates`` uses the ``STATIC_URL`` setting,
|
|
||||||
originally introduced by the django-staticfiles_ app. The app has since
|
|
||||||
been added to Django itself and isn't needed if you use Django 1.3 or
|
|
||||||
higher. Please refer to the `contrib docs`_ in that case.
|
|
||||||
|
|
||||||
.. _django-staticfiles: http://pypi.python.org/pypi/django-staticfiles
|
|
||||||
.. _contrib docs: http://docs.djangoproject.com/en/dev/ref/staticfiles/
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
[build-system]
|
|
||||||
requires = [
|
|
||||||
"setuptools>=61.2",
|
|
||||||
"setuptools_scm",
|
|
||||||
]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
|
|
||||||
[project]
|
|
||||||
name = "django-dbtemplates"
|
|
||||||
authors = [{name = "Jannis Leidel", email = "jannis@leidel.info"}]
|
|
||||||
description = "Template loader for templates stored in the database"
|
|
||||||
readme = "README.rst"
|
|
||||||
classifiers = [
|
|
||||||
"Development Status :: 5 - Production/Stable",
|
|
||||||
"Environment :: Web Environment",
|
|
||||||
"Intended Audience :: Developers",
|
|
||||||
"License :: OSI Approved :: BSD License",
|
|
||||||
"Operating System :: OS Independent",
|
|
||||||
"Programming Language :: Python",
|
|
||||||
"Programming Language :: Python :: 3",
|
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
|
||||||
"Programming Language :: Python :: 3.11",
|
|
||||||
"Programming Language :: Python :: 3.12",
|
|
||||||
"Programming Language :: Python :: 3.13",
|
|
||||||
"Programming Language :: Python :: 3.14",
|
|
||||||
"Framework :: Django",
|
|
||||||
]
|
|
||||||
requires-python = ">=3.8"
|
|
||||||
dependencies = ["django-appconf >= 0.4"]
|
|
||||||
dynamic = ["version"]
|
|
||||||
|
|
||||||
[project.urls]
|
|
||||||
Documentation = "https://django-dbtemplates.readthedocs.io/"
|
|
||||||
Changelog = "https://django-dbtemplates.readthedocs.io/en/latest/changelog.html"
|
|
||||||
Source = "https://github.com/jazzband/django-dbtemplates"
|
|
||||||
|
|
||||||
[tool.setuptools]
|
|
||||||
zip-safe = false
|
|
||||||
include-package-data = false
|
|
||||||
|
|
||||||
[tool.setuptools.packages]
|
|
||||||
find = {namespaces = false}
|
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
|
||||||
dbtemplates = [
|
|
||||||
"locale/*/LC_MESSAGES/*",
|
|
||||||
"static/dbtemplates/css/*.css",
|
|
||||||
"static/dbtemplates/js/*.js",
|
|
||||||
]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
django
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
flake8
|
|
||||||
coverage
|
|
||||||
26
setup.py
Normal file
26
setup.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
from distutils.core import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='dbtemplates',
|
||||||
|
version='0.4.2',
|
||||||
|
description='Template loader for database stored templates',
|
||||||
|
author='Jannis Leidel',
|
||||||
|
author_email='jannis@leidel.info',
|
||||||
|
url='http://code.google.com/p/django-databasetemplateloader/',
|
||||||
|
scripts=['dbtemplates/sync_templates.py',],
|
||||||
|
packages=[
|
||||||
|
'dbtemplates',
|
||||||
|
'dbtemplates.management',
|
||||||
|
'dbtemplates.management.commands'
|
||||||
|
],
|
||||||
|
package_dir={'dbtemplates': 'dbtemplates'},
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Environment :: Web Environment',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: BSD License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Topic :: Utilities'
|
||||||
|
]
|
||||||
|
)
|
||||||
62
tox.ini
62
tox.ini
|
|
@ -1,62 +0,0 @@
|
||||||
[tox]
|
|
||||||
minversion = 4.0
|
|
||||||
envlist =
|
|
||||||
flake8
|
|
||||||
py3{8,9,10,11,12}-dj42
|
|
||||||
py3{10,11,12,13,14}-dj52
|
|
||||||
py3{12,13,14}-dj60
|
|
||||||
py3{12,13,14}-djmain
|
|
||||||
coverage
|
|
||||||
|
|
||||||
[gh-actions]
|
|
||||||
python =
|
|
||||||
3.8: py38
|
|
||||||
3.9: py39
|
|
||||||
3.10: py310
|
|
||||||
3.10: py310, flake8
|
|
||||||
3.11: py311
|
|
||||||
3.12: py312
|
|
||||||
3.13: py313
|
|
||||||
3.14: py314
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
skipsdist = true
|
|
||||||
package = editable
|
|
||||||
basepython =
|
|
||||||
py38: python3.8
|
|
||||||
py39: python3.9
|
|
||||||
py310: python3.10
|
|
||||||
py311: python3.11
|
|
||||||
py312: python3.12
|
|
||||||
py313: python3.13
|
|
||||||
py314: python3.14
|
|
||||||
setenv =
|
|
||||||
DJANGO_SETTINGS_MODULE = dbtemplates.test_settings
|
|
||||||
deps =
|
|
||||||
-r requirements/tests.txt
|
|
||||||
dj42: Django>=4.2,<4.3
|
|
||||||
dj52: Django>=5.2,<5.3
|
|
||||||
dj60: Django>=6.0,<6.1
|
|
||||||
djmain: https://github.com/django/django/archive/main.tar.gz#egg=django
|
|
||||||
|
|
||||||
commands =
|
|
||||||
python --version
|
|
||||||
python -c "import django ; print(django.VERSION)"
|
|
||||||
coverage run --branch --parallel-mode {envbindir}/django-admin test -v2 {posargs:dbtemplates}
|
|
||||||
|
|
||||||
[testenv:coverage]
|
|
||||||
basepython = python3.10
|
|
||||||
deps = coverage
|
|
||||||
commands =
|
|
||||||
coverage combine
|
|
||||||
coverage report
|
|
||||||
coverage xml
|
|
||||||
|
|
||||||
[testenv:flake8]
|
|
||||||
basepython = python3.10
|
|
||||||
commands = flake8 dbtemplates
|
|
||||||
deps = flake8
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
exclude=.tox
|
|
||||||
ignore=E501,E127,E128,E124
|
|
||||||
Loading…
Reference in a new issue