Compare commits

..

487 commits

Author SHA1 Message Date
Thomas Güttler
10e7de2eda
Fix cache invalidation (#131) 2025-06-17 16:38:05 -07:00
Viktor Kálmán
930ce5c65c
add project urls (#156) 2025-06-09 11:47:43 -07:00
blag
8e284b54d8
Revert "Split out an AbstractTemplate model for easier reuse" (#154)
This reverts commit 46be8fc748.
2025-05-29 23:31:49 +02:00
blag
05f1ee1193
Split out an AbstractTemplate model for easier reuse (#150) 2025-05-26 22:09:25 +02:00
blag
303bd0cabe
Add missing migration for creation_date and last_changed changes (#151) 2025-05-26 22:08:21 +02:00
Łukasz Chojnacki
64d112cc4f
Add explicit id field to avoid creating migration with DEFAULT_AUTO_FIELD set to BigAutoField (#142)
Co-authored-by: blag <blag@users.noreply.github.com>
2025-05-26 20:13:03 +02:00
blag
7f1c6701c1
Properly add Django 5.2 and tweak coverage collection (#148) 2025-05-26 20:12:55 +02:00
blag
873c90b777
Convert setup.py and setup.cfg to pyproject.toml (#149) 2025-05-26 20:12:37 +02:00
Thomas Güttler
e64a457281
There is no INSTALL file. (#128)
removed "Follow the instructions in the INSTALL file", since there is no INSTALL file.

Co-authored-by: blag <blag@users.noreply.github.com>
2025-05-26 20:00:16 +02:00
blag
a7f4e0bbe8
Make creation_date and last_updated fields readonly in admin (#144) 2025-05-26 19:59:50 +02:00
blag
218b28b7aa
Let Django handle creation_date and last_changed (#145) 2025-05-26 19:59:18 +02:00
blag
602717af95
Add default_auto_field to AppConfig (#146) 2025-05-26 19:53:42 +02:00
Jannis Leidel
8769e29057
Use correct release branch of pypa/publish action. (#138) 2025-05-20 21:40:09 +02:00
Viktor Kálmán
ac740e06f3
Support for Python 3.12 and up (#143)
* support for Python 3.12 and up

* removed unused deprecated ugettext imports

* fix django main being Python 3.12+

* missed some copypaste
2025-02-16 21:08:16 +01:00
Jannis Leidel
233a401e75
Fix docs rendering (#127)
* Create .readthedocs.yaml

* Add docs requirements
2022-08-29 12:09:54 +02:00
Michał Pasternak
13bacacef8
Merge pull request #126 from mpasternak/master
Django 4.x
2022-08-11 07:28:21 +02:00
Michał Pasternak
fa72b2771b Flake8 2022-08-10 22:32:23 +02:00
Michał Pasternak
fac9f6a807 Mention dropping Django below 3.2 2022-08-10 22:29:46 +02:00
Michał Pasternak
f53244ce54 Merge branch 'master' of github.com:mpasternak/django-dbtemplates-iplweb 2022-08-10 22:29:10 +02:00
Michał Pasternak
6ca53981d3 Test all the supported versions 2022-08-10 22:28:36 +02:00
Michał Pasternak
45e216b8ac Remove compatibility wrapper 2022-08-10 22:28:21 +02:00
Michał Pasternak
5a363dbe34
Update setup.py
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2022-08-10 10:47:46 +02:00
Michał Pasternak
2c8dc82721
Update tox.ini
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2022-08-09 17:50:42 +02:00
Michał Pasternak
98bc9921b0 Remove trailing whitespace 2022-08-09 17:41:44 +02:00
Michał Pasternak
b6b0bf48ba Django 4.0 i18n fixes 2022-08-09 17:38:47 +02:00
Michał Pasternak
36fbb80fc0 Update to Django 4.0 i18n style 2022-08-09 17:38:39 +02:00
Michał Pasternak
e08a8d3950 Document changes in 4.0 (unreleased) 2022-08-09 17:35:13 +02:00
Michał Pasternak
6dd23d2325 Proper links to CI badge 2022-08-09 17:34:29 +02:00
Michał Pasternak
a68caedc2d Revert unintentional typo 2022-08-09 17:34:14 +02:00
Michał Pasternak
2b5034951f Remove unsupported Django versions 2022-08-09 17:34:06 +02:00
Michał Pasternak
b138cafcfa Passing flake8 2022-08-08 00:49:40 +02:00
Michał Pasternak
f98fb8ca24 Min. supported Python version 2022-08-08 00:40:17 +02:00
Michał Pasternak
2e3f009426
Merge branch 'master' into master 2022-08-08 00:36:05 +02:00
Michał Pasternak
8e8b76fc0a
Merge pull request #125 from jazzband/add-3.10
Add support for Python 3.10
2022-08-08 00:25:12 +02:00
Jazzband Bot
49dd3be520
Jazzband: Created local 'CODE_OF_CONDUCT.md' from remote 'CODE_OF_CONDUCT.md' (#120) 2022-08-05 14:12:18 +02:00
Hugo van Kemenade
74ee8a2fe3 Add pre-commit config 2022-06-15 16:02:33 +03:00
Michał Pasternak
1a20742b71 Fix flake8 target on GitHub 2022-06-15 14:58:13 +02:00
Hugo van Kemenade
9455b36281 Upgrade to f-strings with 'flynt .' 2022-06-15 15:54:51 +03:00
Hugo van Kemenade
165ffd0b15 Test Python 3.11 beta 2022-06-15 15:54:51 +03:00
Hugo van Kemenade
a3054c7724 Fix Flake8 2022-06-15 15:47:52 +03:00
Hugo van Kemenade
e5e4e55d22 Upgrade Python syntax with pyupgrade --py37-plus 2022-06-15 15:47:52 +03:00
Hugo van Kemenade
9a27f4938c Drop support for EOL Python 3.6 2022-06-15 15:47:52 +03:00
Michał Pasternak
013057425f Min. Python version 2022-06-15 14:43:43 +02:00
Michał Pasternak
7ffc0fa33f Don't build unversal wheel 2022-06-15 14:43:32 +02:00
Michał Pasternak
de4babcad4 Describe changes 2022-06-15 14:39:44 +02:00
Hugo van Kemenade
6b18f19ffd Add support for Python 3.10 2022-06-15 15:39:30 +03:00
Michał Pasternak
5281b74ea7 Don't change basepython for flake8 2022-06-15 14:31:46 +02:00
Michał Pasternak
9dbdb95227 Rename for PyPI release 2022-06-15 14:29:06 +02:00
Michał Pasternak
e1e11c42cb Django 4.x fixes 2022-06-15 14:25:32 +02:00
Michał Pasternak
9f664ea43c Django 4.x fix 2022-06-15 14:22:48 +02:00
Michał Pasternak
e477561810 Proper image path 2022-06-15 14:22:42 +02:00
Michał Pasternak
2d622ee28c Python 3.10 2022-06-15 14:22:32 +02:00
Michał Pasternak
8901551893 Merge branch 'master' of github.com:mpasternak/django-dbtemplates-iplweb 2022-06-15 14:22:06 +02:00
Michał Pasternak
9e4a6f7c78 Django 4.x 2022-06-15 14:20:06 +02:00
Michał Pasternak
9486f0c78b
Merge pull request #118 from wamberg/cache-compat-#116
Django 3.2 compatibility
2022-06-15 12:17:42 +02:00
Nikolaus Schlemm
376f33916f focus on currently supported releases of python and django 2022-02-08 09:20:18 -05:00
Bill Amberg
8cd8a17bc1 Use strings explicitly for template directories 2022-02-08 09:15:46 -05:00
Bill Amberg
f45cd228f9 Get cache for Django>=3.2 2022-02-08 09:15:46 -05:00
Bill Amberg
24b5f469a2 Add failing test environment for Django==3.2 2022-02-08 09:15:33 -05:00
Giovanni B
2b747bc4af
Fixes #113 (#115)
* Fixes #113

* Update tox.ini

Co-authored-by: Jannis Leidel <jannis@leidel.info>

Co-authored-by: Jannis Leidel <jannis@leidel.info>
2021-10-21 17:35:56 +02:00
Jannis Leidel
ac86ca5ec9
Rename Django's dev branch to main. (#114)
More information: https://groups.google.com/g/django-developers/c/tctDuKUGosc/
Refs: https://github.com/django/django/pull/14048
2021-03-09 13:34:27 +01:00
Jannis Leidel
012fe061a8
Migrate to GitHub Actions. (#112)
* Add GitHub Actions test workflow.

* Update version map.

* Fix flake8

* Write coverage file.

* Fix typo.

* Add release workflow.

* Removed Travis cruft and updated other files.

* Update trove classifiers.

* Black setup.

* Remove unneeded twine check.

* Extend changelog.

* Fix six issue.

* Update tox.ini

Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>

* Add 3.9.

* Add base python

Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
2020-12-09 09:55:35 +01:00
Kaustubh Bhalerao
68ac54a72b
Django 3.0.4 (#107)
Co-authored-by: Kaustubh Bhalerao <bhalerao@soildiagnostics.com>
2020-06-15 14:17:18 +07:00
Nikolaus Schlemm
7ea4655b57 integrate with django-reversion-compare to offer a history compare view in admin 2020-06-15 13:08:47 +07:00
John Vandenberg
1d53b25cc2
Merge pull request #102 from nschlemm/nschlemm-patch-help_text 2020-05-06 00:31:10 +07:00
John Vandenberg
81ba1364f8
Merge pull request #103 from nschlemm/update-tox 2020-05-05 20:47:13 +07:00
John Vandenberg
ae97ad7e24
Merge pull request #100 from nschlemm/nschlemm-patch-raw_input 2020-05-05 20:36:54 +07:00
John Vandenberg
5c6c35bf3d
Merge pull request #98 from nschlemm/patch-2 2020-05-05 20:35:41 +07:00
Nikolaus Schlemm
50f02a50bc added django 2.2, dropped django 2.0 and python 3.4
keeping in sync with https://www.djangoproject.com/download/#supported-versions
2019-06-19 13:37:44 +02:00
Nikolaus Schlemm
230157640c let's keep flake8 silent until we can throw this backwards-compatibility hack out completely 2019-06-19 12:50:02 +02:00
Nikolaus Schlemm
f443f29047
Fixes #101 by expanding the help text default value 2019-06-19 12:34:03 +02:00
Nikolaus Schlemm
79d1d3986f
Fixes #99 by replacing raw_input with input
added (propably obsolete) backwards compatability as django already threw out six
2019-06-19 12:15:59 +02:00
Nikolaus Schlemm
73a33361d3
Fixes #82 TypeError: can only concatenate list (not "tuple") to list 2019-01-28 02:07:52 +01:00
Jannis Leidel
a04933e5ce
Fix changelog date. 2019-01-27 22:54:59 +01:00
Jannis Leidel
78ca3f48dd
Bump version to 3.0. 2019-01-27 22:26:41 +01:00
Jannis Leidel
727746add1
12 years! 2019-01-27 22:26:28 +01:00
Jannis Leidel
07d62d8d26
Updated changelog. 2019-01-27 22:25:42 +01:00
Jannis Leidel
4ece48c7b2
Remove Python 3.4 from Django 2.1 tests since it’s not supported. 2019-01-27 22:24:20 +01:00
Jannis Leidel
966b68ee5c
Added some Python versions to trove classifiers. 2019-01-27 22:23:54 +01:00
Jannis Leidel
039f6f419f
Build universal wheel files. 2019-01-27 22:08:06 +01:00
Jannis Leidel
24c8d2005a
Use twine instead of readme_renderer for readme check. 2019-01-27 22:07:58 +01:00
Jannis Leidel
df358a53dd
Merge pull request #88 from m-vdb/mvdb/fix-migration
Fix bytes string migration (python3)
2019-01-27 22:02:31 +01:00
Jannis Leidel
f5b9e36fc7
Merge pull request #89 from m-vdb/mvdb/fix-str-representation
Fix Template string representation for python3
2019-01-27 21:58:28 +01:00
Jannis Leidel
16afa49831
Use django.utils.six instead of six directly. 2019-01-27 21:54:57 +01:00
Jannis Leidel
cef1ec49c0
Merge pull request #97 from nschlemm/patch-1
Fix flake8 W504 line break after binary operator
2019-01-27 21:46:24 +01:00
Jannis Leidel
745e64f3e0
Re-enable project releases via Jazzband.co. Fix #95. 2019-01-27 21:43:29 +01:00
Nikolaus Schlemm
28bde8c32d
Fix flake8 W504 line break after binary operator
and thereby hopefully allow for closing https://github.com/jazzband/django-dbtemplates/issues/95 ?
2019-01-26 01:18:32 +01:00
Waldecir Santos
16a80d4635
fix PyPi upload 2018-10-01 20:14:42 +01:00
Waldecir Santos
6522a39250
added server tag to deploy 2018-10-01 19:45:48 +01:00
Waldecir Santos
149a16e308
bump version 2018-10-01 18:53:22 +01:00
Waldecir Santos
92e0e1ca0c
Merge pull request #87 from eprikazc/master
Add support for Django2.0, drop 1.8
2018-10-01 18:52:35 +01:00
Eugene Prikazchikov
c2009596ef Add workaround to make Python3.7 in Travis work properly 2018-10-01 11:20:33 +03:00
Eugene Prikazchikov
9cce7cd03b Update tox.ini to support Python3.7 2018-10-01 10:59:47 +03:00
Eugene Prikazchikov
34e34b7259 Add renderer parameter to CodeMirrorTextArea.render
According to django2.1
2018-09-13 23:37:41 +03:00
m-vdb
db5e4ec4b6 fix Template string representation for python3 2018-02-12 20:12:38 +01:00
m-vdb
3ba01f425c remove byte string from migration file 2018-02-12 20:02:23 +01:00
Jannis Leidel
77d8fc4e33
Merge pull request #83 from paulgueltekin/master
fixed method call with named argument
2017-12-09 23:49:08 +01:00
Jannis Leidel
3939947a0e
Merge pull request #86 from kammato/russian_locale_fix
Recompiled russian locale correctly
2017-12-09 23:46:11 +01:00
Eugene Prikazchikov
1c80410a5b Update get_template_source utility function to use up-to-date template API 2017-12-09 21:38:42 +03:00
Eugene Prikazchikov
18afb50582 Remove django 1.8 from tox.ini 2017-12-09 20:43:44 +03:00
Eugene Prikazchikov
e3318658a5 Update Loader class to up-to-date template loader API
load_template_sources is deprecated in Django 1.9 ad removed in Django2.0
see https://docs.djangoproject.com/en/1.9/releases/1.9/#template-loader-apis-have-changed
2017-12-09 20:40:57 +03:00
Jannis Leidel
3ee69a58ce
Drop testing pypy. 2017-11-22 15:47:11 +01:00
Jannis Leidel
9a84b1af86
Fix test matrix. 2017-11-22 15:43:19 +01:00
Jannis Leidel
bc206765fa
Appease flake8. 2017-11-22 11:01:02 +01:00
Khamid Tomov
8176a137dd Recompiled russian locale correctly 2017-05-18 18:00:03 +03:00
Paul Gueltekin
1c9467fa37 fixed method call with named argument 2017-01-17 16:51:06 +01:00
Jannis Leidel
49fc6c47ff Fix condition for auto-release 2016-09-20 12:55:46 +02:00
Jannis Leidel
d3221c3acd Update docs 2016-09-20 12:44:29 +02:00
Jannis Leidel
70fb31c190 Add Django appconfig for overriding the name in the admin 2016-09-20 12:41:44 +02:00
Jannis Leidel
6fbde482af Some packaging fixes as usual 2016-09-20 12:32:59 +02:00
Matthias K
62ba054698 Merge pull request #81 from jazzband/python3
Port to Python 3 and get rid of legacy code
2016-09-20 12:28:16 +02:00
Jannis Leidel
e4acf6e754 Use saner test assertion 2016-09-20 12:02:03 +02:00
Jannis Leidel
3d5d70db41 More Py3 fixes 2016-09-20 11:46:13 +02:00
Jannis Leidel
8c1e33de72 Use io instead of codecs 2016-09-20 11:34:28 +02:00
Jannis Leidel
44c040523a Remove last version switch 2016-09-20 11:34:15 +02:00
Jannis Leidel
39c8420511 Fix a codec issue in Py3 2016-09-20 11:20:34 +02:00
Jannis Leidel
7454e109ea Update docs 2016-09-20 10:57:49 +02:00
Jannis Leidel
d7ecd93cce Port to Python 3 and removed legacy code 2016-09-20 10:34:59 +02:00
Jannis Leidel
1e2a4f0dff Remove old example project 2016-09-20 10:33:24 +02:00
Jannis Leidel
f0cfdfa9ce Use better version checks, fix #78 2016-09-20 10:33:24 +02:00
Matthias K
279fba2635 Do not install flake8>=3 as this project still seems to support Python 2.6 2016-09-20 10:14:23 +02:00
Matthias K
cd2a06ea51 Merge pull request #76 from adamchainz/readthedocs.io
Convert readthedocs links for their .org -> .io migration for hosted projects
2016-09-20 10:12:53 +02:00
Matthias K
892bc8b0aa Merge pull request #74 from thedrow/patch-1
Added django 1.9 to the build
2016-09-20 10:11:48 +02:00
Adam Chainz
2427759736 Convert readthedocs links for their .org -> .io migration for hosted projects
As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’:

> Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard.

Test Plan: Manually visited all the links I’ve modified.
2016-06-11 10:53:29 +01:00
Omer Katz
f1fb4577cc Exclude migrations. 2016-04-01 15:42:47 +03:00
Omer Katz
9c297392dc Added django 1.9 to the build
Also, added caching and bumped the latest django 1.8.x version.
2016-04-01 15:38:12 +03:00
Michael Kutý
7619f9fb71 Merge pull request #72 from jazzband/hotfix/django_import
Use native importlib istead of Django version.
2016-01-07 16:48:43 +01:00
Michael Kutý
d1b83e1145 Install importlib backport in python 2.6. 2016-01-03 00:10:42 +01:00
Michael Kutý
ca8841ce9c Use native importlib. 2016-01-03 00:01:45 +01:00
Jannis Leidel
a3334a05ec Moved to Jazzband. 2015-12-17 10:44:42 +01:00
Jannis Leidel
483db6616a Merge remote-tracking branch 'origin/develop-1.4' 2015-12-17 10:38:40 +01:00
Jannis Leidel
0b4cec538e Merge branch 'develop' 2015-12-17 10:38:29 +01:00
Michael Kutý
6815ce6c6c Merge pull request #69 from glader/develop-1.4
Fix localization
2015-10-16 14:10:34 +02:00
Mikhail Polykovskij
2c7afb6a1a Fix localization 2015-10-16 17:05:42 +05:00
Michael Kutý
676a410517 Merge pull request #68 from michaelkuty/develop
fix get cache deprecated
2015-09-17 21:18:21 +02:00
Michael Kutý
9648555473 fix RemovedInDjango19Warning: 'get_cache' is deprecated in favor of 'caches'. 2015-09-17 21:15:10 +02:00
Michael Kutý
b6912fc339 Merge pull request #67 from philippeowagner/develop-1.4
Docs update for  ``TEMPLATES.OPTIONS.loaders`` config. Fixes #65
2015-09-17 12:23:01 +02:00
Philippe O. Wagner
308d03ba98 Docs update for `TEMPLATES.OPTIONS.loaders` config.
Works for Django 1.8+
2015-09-17 12:12:20 +02:00
Michael Kutý
b9b2ac5885 Merge pull request #66 from barseghyanartur/develop
python 3 fix
2015-09-17 11:28:54 +02:00
Michael Kutý
db74e84604 Merge pull request #1 from jezdez/develop-1.4
Develop 1.4
2015-08-25 10:49:50 +02:00
Michael Kutý
35a57bbcb1 drop support for django < 1.3.7 2015-08-19 14:56:00 +02:00
Michael Kutý
c8a048352e Fix some Python 3 issues - thanks @vikingco 2015-08-19 11:42:39 +02:00
Artur Barseghyan
bb9199b0be python 3 fix 2015-08-18 16:29:14 +02:00
Michael Kutý
fbc2891c41 Merge pull request #64 from barseghyanartur/develop
fix setup for python 3
2015-08-18 15:59:42 +02:00
Artur Barseghyan
59c65c56ab fix setup for python 3 2015-08-18 14:15:26 +02:00
Michael Kutý
6dd19d3a17 Merge pull request #62 from michaelkuty/develop
use managers for django 1.8+
2015-07-23 15:43:58 +02:00
Michael Kutý
c55e2220b3 use managers for django 1.8+ 2015-07-23 15:42:07 +02:00
Michael Kutý
70da2e6a48 use migrations generated from django 1.7 2015-07-15 00:08:45 +02:00
Michael Kutý
04c2d12dbd added django migrations and south moved to own directory 2015-07-14 23:30:53 +02:00
Michael Kutý
42590af744 fixed (fields.W340) null has no effect on ManyToManyField and import sorting 2015-07-06 22:16:43 +02:00
Michael Kutý
464425b236 fix settings runtime patching which cause fails because if we set correct template dirs is not there test dirs 2015-06-16 00:07:27 +02:00
Michael Kutý
926a3215eb cleanup yaml 2015-06-15 23:19:22 +02:00
Michael Kutý
acdf9dc8e5 properly exclude python 2.6 from new djangos 2015-06-15 23:15:27 +02:00
Michael Kutý
201077e11b properly collect template dirs for all available template engines in dj 1.8 and prettify readme 2015-06-15 23:10:41 +02:00
Michael Kutý
99ed8b2ce3 fix app dirs on django 1.8 2015-06-15 22:44:44 +02:00
Michael Kutý
edbb5e1ef0 exclude python 2.6 from Dj 1.7 and 1.8 where was dropped support for this version 2015-06-15 22:39:17 +02:00
Michael Kutý
51b62d9a62 omg fix pep8 passing 2015-06-15 21:17:18 +02:00
Michael Kutý
c9569a81db update readme and added notice about compatibility, bump version and changelog 2015-06-15 21:03:09 +02:00
Michael Kutý
b47af2e1ff added middleware classes 2015-06-15 20:36:24 +02:00
Michael Kutý
16fe97575c pep8 cleanup and fix test runner for new django 2015-06-15 20:20:02 +02:00
Michael Kutý
0e97716488 ModelForm now requires either fields or exclude - mentoined in #52 thanks @volksman 2015-06-15 16:54:21 +02:00
Michael Kutý
b03803010d Merge pull request #59 from eculver/develop
Support for using redactorjs for editing templates.
2015-06-15 16:48:43 +02:00
Michael Kutý
078d556390 fix flake8 2015-06-15 16:44:34 +02:00
Michael Kutý
658aca88dc bump version 2015-06-15 16:35:15 +02:00
Michael Kutý
984f83c0a8 update dj versions 2015-04-28 11:19:19 +02:00
Michael Kutý
685c77e718 proper solution 2015-04-28 11:16:06 +02:00
Michael Kutý
15c4a5930b fix loader for django 1.8 2015-04-28 01:03:45 +02:00
Michael Kutý
50a877a7d5 fix name 2015-04-28 01:00:24 +02:00
Michael Kutý
c2d49e1d71 hot-fixed populating template djagno 1.8+ 2015-04-27 23:40:42 +02:00
Kevin Mooney
5d3a392e4c Deleted utils.py 2015-04-03 16:37:59 +00:00
Jannis Leidel
b371cc6518 Merge pull request #53 from kangfend/fix-modelform
Include fields to class Meta
2014-10-29 13:26:19 +01:00
Sutrisno Efendi
4352fb639e Include fields to class Meta 2014-09-23 15:19:16 +07:00
Jannis Leidel
1372f43992 Merge pull request #45 from jnns/develop
Fix link to django-reversion doc.
2013-11-06 05:52:56 -08:00
Jannis
bfd7186db0 Fix URL to django-reversion doc.
Fix URL of django-reversion's documentation. Apparently, django-reversion uses Readthedocs instead of GitHub's wiki now.
2013-11-06 14:35:34 +01:00
Jannis Leidel
91d5e137f1 Merge pull request #42 from szelga/feature_rulocale
add Russian locale
2013-07-30 01:59:23 -07:00
Wasil W Sergejczyk
12ffd84380 add Russian locale 2013-07-30 14:35:07 +06:00
Jannis Leidel
f668370eee Drop support for Python 2.5. 2013-04-04 11:41:38 +02:00
Jannis Leidel
b051161aea Test on Django 1.5.X, too. 2013-04-04 11:34:18 +02:00
Jannis Leidel
4b024de965 Minor cosmetic changes to please flake8. 2013-04-04 11:33:00 +02:00
Evan Culver
2fc79eda7a Support for using redactorjs for editing templates. 2012-09-05 16:47:34 -04:00
Jannis Leidel
d2f595ce82 Moved to django-discover-runner. 2012-05-23 23:50:32 +02:00
Jannis Leidel
f7378df802 Use __isnull for querying a non-site specific template. Refs #33. 2012-05-19 21:40:47 +02:00
Jannis Leidel
1c520b0fad Re-enabled 2.5 now that flake8 works on it again. 2012-05-19 19:24:17 +02:00
Jannis Leidel
c36d649e4d Disabled Python 2.5 again as flake8 doesn't support it at the moment :( 2012-05-19 13:27:09 +02:00
Jannis Leidel
6d356db2ef Trying 2.5 2012-05-19 13:10:03 +02:00
Jannis Leidel
0672fa42bf Set env vars before installing and use crate. 2012-05-19 13:08:02 +02:00
Jannis Leidel
7802fbe328 Merge branch 'release/1.3' into develop 2012-05-07 23:53:19 +02:00
Jannis Leidel
327384645c Merge branch 'release/1.3' 2012-05-07 23:53:05 +02:00
Jannis Leidel
8844180d6c Bumped version to 1.3. 2012-05-07 23:52:59 +02:00
Jannis Leidel
25d9283758 Use version from __version__. 2012-05-07 23:52:50 +02:00
Jannis Leidel
ca9d2fc468 Removed old hg tags. 2012-05-07 23:50:01 +02:00
Jannis Leidel
c59de762f9 Correct version. 2012-05-07 23:48:21 +02:00
Jannis Leidel
ee29bbe46e Updated settings docs. 2012-05-07 23:48:07 +02:00
Jannis Leidel
4c3c459e82 Updated changelog. 2012-05-07 23:47:30 +02:00
Jannis Leidel
60215fe947 Updated AUTHORS file. 2012-05-07 23:47:15 +02:00
Jannis Leidel
a1cec65e29 Removed INSTALL file from manifest template. 2012-05-07 14:15:52 +02:00
Jannis Leidel
e7b6b8ce00 Ignore long lines, stupid. 2012-05-07 14:12:07 +02:00
Jannis Leidel
d018f826da Forgotten dirname for flake8. 2012-05-07 14:10:12 +02:00
Jannis Leidel
07fe3cc7ee Fixed typo. 2012-05-07 14:08:08 +02:00
Jannis Leidel
842e08cf2e Switched to using nose and django-nose for easy test running. 2012-05-07 14:06:54 +02:00
Jannis Leidel
2e430d5370 Minor code cleanup of the migrations. 2012-05-07 10:57:36 +02:00
Jannis Leidel
2f27327beb Handle timezone on 1.4 correctly. 2012-05-07 10:57:23 +02:00
Jannis Leidel
f1e680aa31 Added link to Travic build. 2012-05-07 10:54:26 +02:00
Jannis Leidel
0e43258b5b Added DATABASES setting to please Django 1.4. 2012-05-07 10:35:31 +02:00
Jannis Leidel
d926d6c934 Merge pull request #32 from selwin/develop
Fixed an issue where ``get_cache_key`` may produce invalid memcached keys
2012-05-07 01:32:40 -07:00
Jannis Leidel
f3fc408385 Get the database name from the routers and dropped support for 1.2.X. 2012-05-07 10:31:24 +02:00
Jannis Leidel
20aebf894d Limit the tests to dbtemplates. 2012-05-07 09:48:01 +02:00
Jannis Leidel
d3ccb0c42a Stop using a context manager when we don't need it really. 2012-05-07 09:04:31 +02:00
Jannis Leidel
222c7947d2 Added auth app to list of installed apps during tests. 2012-05-07 09:04:05 +02:00
Jannis Leidel
bea453548f Renamed test requirements file to correct name. 2012-05-07 08:58:36 +02:00
Jannis Leidel
abc2fe5cd2 Added a few more test requirements. 2012-05-07 08:57:45 +02:00
Jannis Leidel
87c5c46999 Further fiddling with the travis config. 2012-05-07 08:55:27 +02:00
Jannis Leidel
0ff18227f9 Hrm, setting the variable in the env block? 2012-05-07 08:43:48 +02:00
Jannis Leidel
20a5d9fc66 Forgot to set the DJANGO_SETTINGS_MODULE var. 2012-05-07 08:41:02 +02:00
Jannis Leidel
3c67628a63 Removed dependency on versiontools. 2012-05-07 08:36:01 +02:00
Jannis Leidel
45700c7e1c Moved CI to Travis. 2012-05-07 08:35:36 +02:00
Selwin Ong
bb4e7ce36f Fixed an issue where `get_cache_key` may produce invalid memcached
keys.
2012-05-06 20:52:17 +07:00
Jannis Leidel
33c1197ad1 Merge pull request #30 from bmihelac/fix-27-MultipleObjectsReturned
Fix 27 multiple objects returned
2012-03-30 06:31:55 -07:00
Jannis Leidel
45407e1624 Merge pull request #29 from bmihelac/fix-28-test-warnings
Fix for #28
2012-03-30 06:31:23 -07:00
Bojan Mihelac
3aa3132255 Fix for #28 2012-03-30 15:25:45 +02:00
Bojan Mihelac
29f08281d7 Fix #27.
dbtemplates tries to find template from database which is associated
with current site, and if that fails then template from database which
is not associated with any site.
2012-03-30 15:14:21 +02:00
Bojan Mihelac
f503013b0f Add failing tests for #27.
Raises MultipleObjectsReturned with sites
2012-03-30 15:11:57 +02:00
Jannis Leidel
832b000c49 Merge pull request #21 from markstahler/develop
django-tinymce support
2012-03-19 14:18:49 -07:00
Jannis Leidel
311875c68b Merge pull request #25 from gandalfar/test_no_dbloader
fix for tests when dbtemplates template loader is not in main settings
2012-03-19 14:17:59 -07:00
Jure Cuhalev
0cfd927938 fix for tests when dbtemplates template loader is not in main settings.py 2012-02-25 22:47:05 +01:00
Jannis Leidel
ad7b258308 Force an encoding when writing the content to the file. 2012-02-24 23:35:51 +01:00
Mark Stahler
68d4342df4 removed urls.py which is not required if django-tinymce is installed
properly. fixed new bug relating to codemirror introduced by tinymce
2012-01-18 08:33:13 -05:00
Mark Stahler
edaf6ea258 respect column widths 2012-01-17 22:11:08 -05:00
Mark Stahler
e866e52074 fixed tinyMce not appearing in admin area 2012-01-17 21:58:47 -05:00
Mark Stahler
02aaad635a Added TinyMCE support via django-tinymce 2012-01-17 21:22:22 -05:00
Jannis Leidel
b13ffbb248 Merge pull request #19 from bmihelac/patch-2
Fix typo in docs - ``dbtemplates.loader.Loader`` should be first in ``TEMPLATE_LOADERS``.
2012-01-09 10:11:18 -08:00
bmihelac
3e18bc9784 Update docs/overview.txt 2012-01-09 13:12:19 +01:00
bmihelac
cb3fe58a6e Fix typo in docs - `dbtemplates.loader.Loader should be first in TEMPLATE_LOADERS`. 2012-01-09 12:56:09 +01:00
Jannis Leidel
6a6229648e Merge pull request #18 from bmihelac/patch-1
Fix typo in docs.
2012-01-09 03:46:42 -08:00
bmihelac
5a6053fef0 Fix typo in docs. 2012-01-09 12:37:26 +01:00
Jannis Leidel
1c10468529 Fixed ignore files. 2012-01-09 00:01:31 +01:00
Jannis Leidel
4ae9392e08 Use django-jenkins for tests and prepare it for ci.enn.io. 2012-01-09 00:01:17 +01:00
Jannis Leidel
9cc07437ee Updated year. 2012-01-09 00:00:10 +01:00
Jannis Leidel
65684c1243 Use latest versiontools. 2012-01-08 23:59:52 +01:00
Jannis Leidel
fcca3742e0 Minor cleanup in docs. 2012-01-08 23:59:34 +01:00
Jannis Leidel
cc94071fc4 Merge branch 'release/1.2.1' into develop 2011-09-07 12:08:42 +02:00
Jannis Leidel
357954a83c Merge branch 'release/1.2.1' 2011-09-07 12:08:37 +02:00
Jannis Leidel
41842de12d Bumped version to 1.2.1. 2011-09-07 12:05:09 +02:00
Jannis Leidel
b6d5bfa226 Use better testrunner. 2011-09-06 15:00:30 +02:00
Jannis Leidel
1224ab4005 Added versiontools requirement to setup.py. 2011-09-06 12:31:56 +02:00
Jannis Leidel
d5be3e42d5 Use django-appconf. 2011-09-06 12:31:28 +02:00
Jannis Leidel
fcc9045829 Fixed changelog and added bugfix to it. 2011-09-05 11:02:55 +02:00
Jannis Leidel
e3c53f6b17 Merge pull request #16 from dfalk/develop
Use ugettext_lazy instead of gettext_lazy for verbose name.
2011-08-25 15:49:44 -07:00
dfalk
9e81e53099 using ugettext 2011-08-26 01:28:49 +04:00
Jannis Leidel
f53536fdcd Merge branch 'release/1.2' into develop 2011-08-15 13:24:56 +02:00
Jannis Leidel
4fb0219dd4 Merge branch 'release/1.2' 2011-08-15 13:24:51 +02:00
Jannis Leidel
e12c6e8ece Updated translations. 2011-08-15 13:23:48 +02:00
Jannis Leidel
362af384a6 Updated base translation files. 2011-08-15 13:13:37 +02:00
Jannis Leidel
f6ea762ed4 Bumped to 1.2 and extended Trove classifiers. 2011-08-15 13:12:47 +02:00
Jannis Leidel
bfcf68fe63 Bundle authors list. 2011-08-15 13:09:29 +02:00
Jannis Leidel
6c08984118 Updated docs. 2011-08-15 13:08:46 +02:00
Jannis Leidel
e55e52156f Slight corrections to loader. 2011-08-15 13:08:38 +02:00
Jannis Leidel
52528648ba Fixed tests. 2011-08-15 13:05:34 +02:00
Jannis Leidel
ede4013fad Renamed admin action to be less ambiguous. 2011-08-15 13:05:26 +02:00
Jannis Leidel
09ece4a5e3 Some minor additions to the "performance speedup". Extends the list of authors, too. 2011-08-15 12:53:29 +02:00
Stephan Peijnik
49eb36775b Typo fix.
Signed-off-by: Stephan Peijnik <spe@anexia.at>
2011-08-15 12:48:16 +02:00
Stephan Peijnik
7e93da6a94 Removed license headers, reverted LICENSE file and added a CONTRIBUTORS file. 2011-08-15 12:48:10 +02:00
Stephan Peijnik
2d46b418da Use the cache to remember which templates were not found in the database.
Also reverse the logic of the database lookup. First check if there is
site-specific template in the database and only if that fails
try to load the global template.
This should cut down the number of database queries that need to be executed,
especially in a loop that includes a given template that does not exist
in the database.

Signed-off-by: Stephan Peijnik <spe@anexia.at>
2011-08-15 12:48:02 +02:00
Matt Dorn
6c25da5301 Added template syntax checker admin actions. 2011-08-15 12:42:27 +02:00
Jannis Leidel
29cedc5271 Merge branch 'release/1.1.1' into develop 2011-07-08 15:01:22 +02:00
Jannis Leidel
ecbca49616 Merge branch 'release/1.1.1' 2011-07-08 15:01:18 +02:00
Jannis Leidel
88f6c54c83 Bumped version up a bit. 2011-07-08 15:00:28 +02:00
Jannis Leidel
7449de7483 Extended docs about using the Loader class instead of the previously available function. Fixes #11. 2011-07-08 14:56:24 +02:00
Jannis Leidel
692a77725d Added links to the repo on Github. Fixes #10. 2011-07-08 14:53:11 +02:00
Jannis Leidel
0379c8cf39 Fixed cache loading. Fixes #13. 2011-07-08 14:46:41 +02:00
Jannis Leidel
75bf2f3c3b Merge branch 'release/1.1' into develop 2011-07-06 21:28:46 +02:00
Jannis Leidel
3add998584 Merge branch 'release/1.1' 2011-07-06 21:28:40 +02:00
Jannis Leidel
99188bb92c Bumped version and copyright year. 2011-07-06 21:25:17 +02:00
Jannis Leidel
d5de8390ac Updated translations. 2011-07-06 21:23:41 +02:00
Jannis Leidel
e5cb4fd2d7 Fixed Transifex configuration. 2011-07-06 21:19:21 +02:00
Jannis Leidel
a3cc44c069 Removed trailing whitespace. 2011-07-06 21:16:16 +02:00
Jannis Leidel
fd82a6b1ab Merge branch 'develop' of github.com:jezdez/django-dbtemplates into develop 2011-07-06 21:15:22 +02:00
Jannis Leidel
c2a21533fd Prepared changelog for release. 2011-07-06 21:15:08 +02:00
Jannis Leidel
19801a4592 Removed unneeded requirement from example project. 2011-07-06 21:07:03 +02:00
Jannis Leidel
ed665e2cab Merge pull request #9 from flashingpumpkin/develop
Fixes template names being cut off when using sync_templates:
2011-07-06 10:13:16 -07:00
Alen Mujezinovic
8c5e1b95b3 Fixes template names being cut off when using sync_templates:
http://cl.ly/0R092W1H3n3m19120j1x
2011-07-06 18:11:05 +01:00
kmooney
c588e51e83 Change to skip template loaders whose load method is not implemented. This happens with the Django cached template loader, in particular. 2011-07-06 17:21:31 +02:00
olivergeorge
fb1fdcbd84 Extended sync_templates command to make exporting changes out to file easier. 2011-07-06 17:00:44 +02:00
Jannis Leidel
7473b54bb1 Added tests for sync_templates command. 2011-07-06 15:40:56 +02:00
Jannis Leidel
05b109ec5a A bunch of flake8 fixees. 2011-07-01 15:50:04 +02:00
Jannis Leidel
e3986a3e08 Extended changelog. 2011-07-01 15:45:09 +02:00
Jannis Leidel
2804cd527f Refactored loader to be class based only. 2011-07-01 15:44:23 +02:00
Jannis Leidel
a40204def7 Moved setttings over to use AppSettings class. 2011-07-01 15:44:09 +02:00
Jannis Leidel
ca032e70cf Split utils module. 2011-07-01 15:41:24 +02:00
Jannis Leidel
2c814889ed 1.1 isn't supported anymore. 2011-07-01 14:27:32 +02:00
kmooney
9843cb8190 Merge branch 'master' into develop 2011-06-30 12:37:39 -05:00
kmooney
431813c9b1 Change to skip template loaders whose load method is not implemented. This happens with the Django cached template loader, in particular. 2011-06-30 12:26:50 -05:00
Jannis Leidel
6caa401c7c Use current example project. 2011-06-15 16:20:40 +02:00
Jannis Leidel
519ad3763a Fixed import. 2011-06-15 16:20:30 +02:00
Jason Mayfield
825e3b95ff allow blank sites
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2011-06-15 16:17:47 +02:00
Jason Mayfield
1415896b3d allow save as for templates to easily copy templates for another site
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2011-06-15 16:16:29 +02:00
Jason Mayfield
dd15deab60 filter horizontal the sites
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2011-06-15 16:16:21 +02:00
Jason Mayfield
587b66232c typo 2011-06-15 16:16:09 +02:00
Jason Mayfield
e694b3dfb8 remove name uniqueness, with migration
Signed-off-by: Jannis Leidel <jannis@leidel.info>
2011-06-15 16:15:31 +02:00
Jannis Leidel
4507b02ed5 Relaxed the cache setting handling a bit. Fixes #7. 2011-06-15 15:59:29 +02:00
Jannis Leidel
8049569be6 Forgot to update changelog. 2011-04-14 13:57:30 +02:00
Jannis Leidel
c530146dd5 Merge branch 'hotfix/cache-fixes' into develop 2011-04-14 13:46:30 +02:00
Jannis Leidel
b71032fa13 Merge branch 'hotfix/cache-fixes' 2011-04-14 13:46:22 +02:00
Jannis Leidel
1dc6580b67 Bumped to 1.0.1. 2011-04-14 13:46:15 +02:00
Jannis Leidel
ae6818f745 Fixed cache setting handling. Fixes issue #4. 2011-04-14 13:45:22 +02:00
Jannis Leidel
e11c2ee71d Only check for old-style caches if the value is a string (e.g. locmem://). 2011-04-14 13:45:16 +02:00
Jannis Leidel
8c28bcf52d Use the class based loader in the docs. 2011-04-14 13:45:06 +02:00
Jannis Leidel
cdcae5d640 Merge branch 'master' of github.com:jezdez/django-dbtemplates 2011-04-11 23:54:24 +02:00
Jannis Leidel
dc2b89ec1d Merge branch 'release/1.0' into develop 2011-04-11 23:35:24 +02:00
Jannis Leidel
9a3d4cf7ee Merge branch 'release/1.0' 2011-04-11 23:35:18 +02:00
Jannis Leidel
39f16ef8fc Also bumped Sphinx config version and changelog. 2011-04-11 23:35:04 +02:00
Jannis Leidel
5dd6bf991d Added PEP386 helper code. 2011-04-11 23:33:47 +02:00
Jannis Leidel
baa451ea22 Updated code to pass more Pyflakes. 2011-04-11 23:16:21 +02:00
Jannis Leidel
5c8d2eefde Fall back to MEDIA_URL actually, to make sure we don't hit an AttributeError with the default value of None. 2011-04-11 22:38:17 +02:00
Jannis Leidel
c11afa37ad Removed unneeded import. 2011-04-11 19:31:08 +02:00
Jannis Leidel
41f0f46145 Updated changelog and added a note about the tested Django versions. Fixes #3. 2011-04-11 19:31:01 +02:00
Jannis Leidel
a11006849d Fixed ImportError. 2011-04-11 19:29:49 +02:00
Jannis Leidel
d6a0af9aa7 Split docs in multiple files and updated use ReadTheDocs. 2011-04-11 19:21:51 +02:00
Jannis Leidel
8c603399c2 Added DATABASES setting to example project to make sure we can run on modern Django. 2011-04-11 19:21:11 +02:00
Jannis Leidel
7c4e6642bd Added make files. 2011-04-11 19:20:23 +02:00
Jannis Leidel
75e41a90a1 Use default theme. 2011-04-11 19:20:11 +02:00
Jannis Leidel
fc7dc8b1c9 Updated example URL conf. 2011-04-11 19:19:57 +02:00
Jannis Leidel
5f69f71584 BACKWARDS INCOMPATIBLE CHANGE. Use STATIC_URL by default. 2011-04-11 19:19:39 +02:00
Jannis Leidel
5190186f3b BACKWARDS INCOMPATIBLE CHANGE. Refactored caching to just use Django's caching system, instead of its own. 2011-04-11 19:19:00 +02:00
Jannis Leidel
1cff31f515 Updated setup.py and manifest template to use new static file location. 2011-04-11 18:02:04 +02:00
Jannis Leidel
0bc5469460 Added tox config and test runner. 2011-04-11 18:01:13 +02:00
Jannis Leidel
04f6372d68 Moved media/* to static/* for new staticfiles compatibility. 2011-04-11 18:00:48 +02:00
Jannis Leidel
4052790d51 Added new Transifex config. 2011-01-31 11:15:26 +01:00
Jannis Leidel
256d957c4a Updated PO file a bit. 2011-01-31 11:15:15 +01:00
Jannis Leidel
139121098a Updated a few gettext metadata items. 2011-01-31 11:12:21 +01:00
Jannis Leidel
ac3ae862bd Merge branch 'release/0.8.0' into develop 2010-11-07 00:26:15 +01:00
Jannis Leidel
ad72990c2a Merge branch 'release/0.8.0' 2010-11-07 00:26:06 +01:00
Jannis Leidel
153a6f1368 Bumped to 0.8.0. 2010-11-07 00:25:57 +01:00
Jannis Leidel
18cf9fcda9 Added news to docs. 2010-11-07 00:22:23 +01:00
Jannis Leidel
2d1c45376b Updated sync_templates management command slightly. 2010-11-07 00:22:13 +01:00
Jannis Leidel
5f0471e67f Merge branch 'develop' of github.com:jezdez/django-dbtemplates into develop 2010-11-07 00:03:29 +01:00
Jannis Leidel
c87af1888d Updated English base translation. 2010-11-07 00:02:50 +01:00
Jannis Leidel
89e82f07dd Updated German translation. 2010-11-07 00:02:10 +01:00
Jannis Leidel
696099d2e3 Added Finnish translation, thanks Jaakko Holster. 2010-11-07 00:01:05 +01:00
Alex Kamedov
d095052518 add --app_first option to sync_templates management command to process templates from app directories before templates from settings.TEMPLATE_DIRS 2010-11-07 06:57:17 +08:00
Alex Kamedov
91bb9e9095 add --overwrite option to sync_templates command 2010-11-07 06:57:08 +08:00
Jannis Leidel
ff13527988 Merge branch 'master' of github.com:jezdez/django-dbtemplates into develop 2010-11-06 23:48:56 +01:00
Jannis Leidel
8aeaf6f2e6 Merge branch 'release/0.7.4' into develop 2010-09-23 04:04:17 +02:00
Jannis Leidel
cdb7787022 Merge branch 'release/0.7.4' 2010-09-23 04:04:11 +02:00
Jannis Leidel
b0e64ffd4c Bumped to 0.7.4. 2010-09-23 04:01:49 +02:00
Jannis Leidel
b91c23e15e Fixed issue 12 -- Make the test not break other tests. 2010-09-23 03:33:19 +02:00
Jannis Leidel
2fe90cd55b Merge branch 'release/0.7.3' into develop 2010-09-21 14:41:10 +02:00
Jannis Leidel
edd645f212 Merge branch 'release/0.7.3' 2010-09-21 14:41:03 +02:00
Jannis Leidel
f0f3b53bf1 More detail regarding the new setting. 2010-09-21 14:40:53 +02:00
Jannis Leidel
46ee615c3e Removed stray print statement. 2010-09-21 14:40:41 +02:00
Jannis Leidel
4629a998d8 Bumped version. 2010-09-21 14:40:34 +02:00
Jannis Leidel
dc2874ccc1 Added news for latest release. 2010-09-21 14:25:29 +02:00
Jannis Leidel
975e0a4087 Added DBTEMPLATES_AUTO_POPULATE_CONTENT setting to be able to disable to auto populating of template content. 2010-09-21 14:25:14 +02:00
Jannis Leidel
e4cdd4a1c4 Don't collapse the advanced fields, cause sites is required. 2010-09-21 14:23:25 +02:00
Jannis Leidel
cab1f12cb8 Nicer settings in admin module. 2010-09-21 14:23:04 +02:00
Jannis Leidel
be19bea513 Extended gitignore. 2010-09-21 14:21:38 +02:00
Jannis Leidel
6d680a5100 Make sure of the correct backend when loading an initial version of a template. Possible fix for issue #8. 2010-09-21 14:21:30 +02:00
Jannis Leidel
fcafe7fb70 Prepare for a Github move only release. 2010-09-04 14:21:31 +02:00
Alexander Artemenko
947bb8901f .gitignore
Signed-off-by: Jannis Leidel <jannis@leidel.info>
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : c8f055185964ffe1c80a7e7ac717b141c69145cc
2010-09-04 14:14:05 +02:00
Jannis Leidel
86fc7f1ce0 Added additional upload sphinx section to setup config file. 2010-08-09 11:29:23 +02:00
Jannis Leidel
72abb2f6f1 Bumped version to 0.7.2a1 2010-07-07 12:42:30 +02:00
Jannis Leidel
a46fe74a9a Added tag 0.7.1 for changeset 74c22fa8c4a6 2010-07-07 12:41:25 +02:00
Jannis Leidel
d1249489f4 Prepare for release. 2010-07-07 12:40:53 +02:00
Jannis Leidel
ba3fef8a02 Added news for last commits. 2010-07-07 12:40:12 +02:00
Jannis Leidel
2237fcd00f Made DBTEMPLATES_MEDIA_PREFIX setting default to <MEDIA_ROOT>/dbtemplates instead of None. 2010-07-07 12:39:51 +02:00
Jannis Leidel
9953ac8069 Fixed disabling the CodeMirror textarea. 2010-07-07 12:38:54 +02:00
Jannis Leidel
4feebfca0b Fixed setting name in example project. 2010-07-07 12:37:37 +02:00
Jannis Leidel
19e03526a0 Bumped to 0.7.1a1. 2010-06-24 16:25:57 +02:00
Jannis Leidel
1046580cf7 Added tag 0.7.0 for changeset 34a051192862 2010-06-24 16:25:19 +02:00
Jannis Leidel
fa6f51a6fe Removed alpha flag. 2010-06-24 16:25:12 +02:00
Jannis Leidel
4a91d91cce Fixed silly versioning kerfuffle. 2010-06-24 16:24:36 +02:00
Jannis Leidel
9a60675e0b Updated docs a little more. 2010-06-24 16:23:07 +02:00
Jannis Leidel
ddd9d9b4fc Updated packaging files to not include template file anymore. 2010-06-24 16:22:47 +02:00
Jannis Leidel
fdd7c8c66c Updated docs for upcoming 0.7.0 release. 2010-06-24 16:15:33 +02:00
Jannis Leidel
07b4d1ac06 Moved sites selection to collapsed fieldset by default. 2010-06-24 16:15:06 +02:00
Jannis Leidel
b06b60f3e2 Added optional CodeMirror-based syntax highlighing editor. 2010-06-24 15:56:41 +02:00
Jannis Leidel
cbc2043cd2 Added staticfiles as a requirement of the example app. 2010-06-24 14:23:17 +02:00
Jannis Leidel
dd538aad81 Added test for issue #8. 2010-06-24 11:59:55 +02:00
Jannis Leidel
2df8e9658a Bumped version to 0.7.0a1. 2010-06-24 11:50:11 +02:00
Jannis Leidel
59d3eb6c67 Converted doctests to unittests, yay! 2010-06-24 11:48:08 +02:00
Jannis Leidel
a0797ec6f9 Don't print to stdout in when creating default error templates, if verbosity = 0. 2010-06-24 11:47:41 +02:00
Jannis Leidel
46d1f8b21b Added class based template loader for Django >= 1.2. 2010-06-24 11:46:51 +02:00
Jannis Leidel
6536b73a16 Cleaned up a little. 2010-06-24 11:43:41 +02:00
Jannis Leidel
982e6f2de7 Moved settings to own module. 2010-06-24 10:22:35 +02:00
Jannis Leidel
fbbf265b55 Bumped version up a little. 2010-03-18 19:20:33 +01:00
Jannis Leidel
70afccbb8f Updated packaging a little.
--HG--
rename : README => README.rst
2010-03-18 18:50:06 +01:00
timesong
e6cff5aa3f Add Chinese translation 2010-01-05 17:52:59 +08:00
Jannis Leidel
eb5d698d1e Tweaking the stylesheet overrides a little 2009-11-24 21:13:27 +01:00
Jannis Leidel
a610e90b7a Fixes issue 4 -- The textarea of the content field now uses the full width in the admin 2009-11-24 21:03:34 +01:00
David Paccoud
0ae0890e4e l10n: Updates to French (fr) translation
Transmitted-via: Transifex (www.transifex.net)
2009-10-21 18:02:44 +00:00
Jannis Leidel
78cbb86130 Removed code-block again since PyPI barfs 2009-10-19 03:17:56 +02:00
Jannis Leidel
2beda0697d Living in the future 2009-10-19 02:50:46 +02:00
Jannis Leidel
b18f6bf8ad Added tag 0.6.1 for changeset 0ac352fec2c2 2009-10-19 02:42:40 +02:00
Jannis Leidel
3c8d5dfe5b Bumped to 0.6.1 2009-10-19 02:42:37 +02:00
Jannis Leidel
df617acc90 Added documentation for the new feature. 2009-10-19 02:33:03 +02:00
Jannis Leidel
e8c459d016 Updated tests for the new feature and fixed them. 2009-10-19 02:32:43 +02:00
Jannis Leidel
d1902401e7 Fixes issue 3 - Templates don't default to a site. Added ability to turn this off by setting DBTEMPLATES_ADD_DEFAULT_SITE to False. 2009-10-19 02:30:52 +02:00
Jannis Leidel
7f0e111262 Added nature theme and updated documentation to use it 2009-10-09 15:44:48 +02:00
Jannis Leidel
a04c535a49 Bumped version up to 0.6.1dev and fixed minor problem with doc index 2009-10-09 15:36:47 +02:00
Jannis Leidel
aac0b2ef63 Moved changelog to docs, yay 2009-10-09 15:29:15 +02:00
Jannis Leidel
beb2a26f2a Merge 2009-10-09 15:07:26 +02:00
Jannis Leidel
39738e5704 Added tag 0.5.8 for changeset f8f7eaf275c5 2009-10-09 15:04:33 +02:00
Jannis Leidel
a8e0a72087 Added tag 0.6.0 for changeset 4b36382cdfd7 2009-10-09 15:02:22 +02:00
Jannis Leidel
a2547a90be Getting ready for 0.6.0 2009-10-09 15:01:57 +02:00
Jannis Leidel
5ccb175589 Added Sphinx configuration and updated the docs with latest changes 2009-10-09 15:01:26 +02:00
Jannis Leidel
bd214ad336 Compiled translations 2009-10-09 14:12:48 +02:00
Jannis Leidel
62caa94e14 Updated locales after latest changes 2009-10-09 14:03:26 +02:00
Jannis Leidel
085b7bab84 Added example project db to hgignore 2009-10-09 14:02:39 +02:00
Jannis Leidel
62b371d1ee Added admin actions to invalidate and repopulate template cache 2009-10-09 14:01:57 +02:00
Jannis Leidel
e266593ac4 Enabled egg template loader in case someone is trying to test with django-reversion which might get zipped when installed with easy_install 2009-10-09 13:47:08 +02:00
Jannis Leidel
991367c3c8 Compiled danish and pt_BR locales 2009-09-20 16:55:51 +02:00
Jannis Leidel
73c756ed47 Updated locales again 2009-09-20 16:55:11 +02:00
Jannis Leidel
5cc3dc62a6 Updated locales and added empty en locale 2009-09-20 16:54:05 +02:00
Jannis Leidel
4222c43190 Merge. 2009-09-20 16:53:51 +02:00
Jannis Leidel
88999da27d Updated locales and added empty en locale 2009-09-20 16:51:38 +02:00
Diego Búrigo Zacarão
5f198ee276 l10n: Added Brazilian Portuguese translation
Transmitted-via: Transifex (www.transifex.net)
2009-09-10 18:46:38 +00:00
Jannis Leidel
2129bb1361 l10n: Added Danish locale, thanks illio. Fixed issue #2.
Transmitted-via: Transifex (www.transifex.net)
2009-09-04 15:15:23 +00:00
Alex
abf6d70a1f l10n: Updates to Hebrew (he) translation
Transmitted-via: Transifex (www.transifex.net)
2009-08-25 17:43:09 +00:00
Jannis Leidel
ac0932fb59 l10n: Updates to German (de) translation
Transmitted-via: Transifex (www.transifex.net)
2009-08-25 09:11:14 +00:00
Jannis Leidel
74c30f1eec Fixes an ambiguity problem with the cache invalidation 2009-08-19 20:55:07 +02:00
Jannis Leidel
5585c5320f Bumped version up to 0.5.9 and added dev flag 2009-08-19 20:51:26 +02:00
Jannis Leidel
3da20729b5 Exlucde example app from installation, bumped to 0.5.8 2009-05-26 19:54:27 +02:00
Jannis Leidel
090f7aa7e8 Updated docs with correct link to issue tracker 2009-05-20 14:57:31 +02:00
Jannis Leidel
23f9b18f10 Fixed rST error in README 2009-05-20 14:23:59 +02:00
Jannis Leidel
8f9cc14443 Added marker for Bitbucket's README parser 2009-05-20 14:22:46 +02:00
Jannis Leidel
efd78a045c Added tag 0.5.7 for changeset dff01be9c8af 2009-05-20 14:18:58 +02:00
Jannis Leidel
ef07913543 Added tag 0.5.6 for changeset ade167225d06 2009-05-20 14:18:15 +02:00
Jannis Leidel
1b58a1b0e7 Added tag 0.5.5 for changeset 4109e0db4340 2009-05-20 14:17:42 +02:00
Jannis Leidel
0e5cf25859 Added tag 0.5.4 for changeset 5965315c03c1 2009-05-20 14:17:26 +02:00
Jannis Leidel
3e062ae747 Added tag 0.5.3 for changeset 6967bbbee378 2009-05-20 14:17:09 +02:00
Jannis Leidel
c4722ffb87 Added tag 0.5.2 for changeset 67a86cf9f7c8 2009-05-20 14:16:47 +02:00
Jannis Leidel
98d7697b12 Added tag 0.5.1 for changeset bf3db2fe192d 2009-05-20 14:16:15 +02:00
Jannis Leidel
1ebbfc2988 Added tag 0.5.0 for changeset a3be97628ed8 2009-05-20 14:16:00 +02:00
Jannis Leidel
449eb1adb8 Added tag 0.4.7 for changeset 9dc2a0e48494 2009-05-20 14:15:39 +02:00
Jannis Leidel
3a445f983b Added tag 0.4.6 for changeset 9a30f34bc5b0 2009-05-20 14:15:23 +02:00
Jannis Leidel
bc856f0a02 Added tag 0.4.5 for changeset ea4d636f3459 2009-05-20 14:15:11 +02:00
Jannis Leidel
2c62fa8fda Added tag 0.4.4 for changeset 5b2e4f7fc267 2009-05-20 14:14:44 +02:00
Jannis Leidel
07797d87ce Added tag 0.4.3 for changeset 447247c1ce1f 2009-05-20 14:14:16 +02:00
Jannis Leidel
70bffb646d Added tag 0.4.2 for changeset a4bd56a7c2ea 2009-05-20 14:13:46 +02:00
Jannis Leidel
d22c5e6d50 Added tag 0.4.1 for changeset d35a41ea96d3 2009-05-20 14:13:22 +02:00
Jannis Leidel
daa5472df5 Added tag 0.4.0 for changeset 50c69325d375 2009-05-20 14:13:00 +02:00
Jannis Leidel
8b7d8f646e Added tag 0.3.1 for changeset 97da228cc698 2009-05-20 14:12:34 +02:00
Jannis Leidel
3aea6eb30c Added tag 0.3.0 for changeset 1b426859f05b 2009-05-20 14:12:12 +02:00
Jannis Leidel
fe33b0e7cd Added tag 0.2.5 for changeset bd537cd8beba 2009-05-20 14:10:52 +02:00
Jannis Leidel
394c69c4cc Changed docs and setup.py to use new location at Bitbucket. Added hgignore, remove .gitignore 2009-05-20 14:08:59 +02:00
Jannis Leidel
6a7c5da99e Fixed a rST warning with implicite target name
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 5899ac70c9ff2ae1c4931ca1fd6a4bf9c6c31a34
2009-04-11 11:18:01 +02:00
Jannis Leidel
a5d8d3bd92 Updated gitignore file
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 304e6a6051e10dfff9da4f8e4b62b573bd06588a
2009-04-11 11:14:22 +02:00
Jannis Leidel
fc8b4e148a Bumped to version 0.5.6
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : ed792bd5fbc13aaf9eb307144c65c5f36cddcbd0
2009-04-11 11:13:47 +02:00
Jannis Leidel
c2c7f1da62 Moved doctests to own file, updated to actually work :)
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 0fc4c5c27f0b59f985a37668ea80f545fcfcb36f
2009-04-11 11:13:33 +02:00
Jannis Leidel
1d78802096 Fixed a stupid bug in the create_error_template command.
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 49a0d0c50120e909e222dac77eae7a9e2256178b
2009-04-11 11:12:32 +02:00
Jannis Leidel
c7335e4a5e Minor updates to install instructions, license, manifest template and setup.py.
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : adf33b8f9e976f92d0a82d753b265e693dca947c
2009-04-11 11:12:11 +02:00
Alexander Artemenko
4b60060d0e .gitignore
Signed-off-by: Jannis Leidel <jannis@leidel.info>
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : c8f055185964ffe1c80a7e7ac717b141c69145cc
2009-04-11 17:06:31 +08:00
Jannis Leidel
5e7e9e6b6c Merge branch 'master' of git@github.com:jezdez/django-dbtemplates
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 139d3e8df32971663243e435ee5aa6aed5f53a75
2009-03-01 01:05:19 +01:00
Jannis Leidel
078985d629 Changed setup.py to use setuptools because it has a with zip_safe attribute which prevents creating zipped installations.
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 5e5b1e2202d8e4b2354e52273e468a829f173508
2009-03-01 01:04:19 +01:00
Jannis Leidel
25cb0a3bfb Added Italian translation (thanks marcoberi) and updated other locales with latest string additions.
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 1a243ff6fe2587ee309b8aa00154fc5b6bb93a8f
2009-03-01 01:04:10 +01:00
Jannis Leidel
be573da5da Merge commit 'origin/master'
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 799df762173f0b11058e7067d4bcb16ce3f6759c
2009-01-21 11:46:07 +01:00
leidel
50bc53488b Removed superfluous imports
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@93 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 8fb9234f876ad589d244a6d082b7c40e4bf5e8c1
2009-01-21 10:45:39 +00:00
leidel
92ec88352a Fix typo
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@92 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 8e5b8d2297bd82cdecd757019fdbe573dbae23a6
2009-01-21 10:43:47 +00:00
Jannis Leidel
7faed66308 Fix typo
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 5ccecda72884dee4999910e4f61a591ba575a0a9
2009-01-21 11:42:22 +01:00
Jannis Leidel
d24b09c037 Merge commit 'origin/master'
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : f950b5f40f71f849d4a2300f7c943de940368f24
2009-01-21 11:36:44 +01:00
leidel
640720a341 Look in INSTALLED_APPS to find out if django-reversion is installed
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@91 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 2dc4215694abff348b90ba7e63b39db86d74057d
2009-01-21 10:29:26 +00:00
leidel
e569b18386 Added a list to the admin list view that contains all site names that the template is used on.
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@89 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 33a11e588fcee4fd9bdf6fa34065b68aced13c60
2009-01-03 11:55:22 +00:00
leidel
b7011dce7e 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). Bumped version to 0.5.4.
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@88 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 564571948adb06cdda915b665aa61d00b3118ed0
2008-12-26 17:39:48 +00:00
Jannis Leidel
b9c6f2a4cb Merge branch 'master' of git@github.com:jezdez/django-dbtemplates
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : f70c0e30f1ab94eb2ef356d2b02bfa2808f4cc2d
2008-11-30 01:55:15 +01:00
leidel
7fafcbd72b Small change to fix download from Github.
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@87 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : b0159e9f5cb18d49f224d9d5649614492f80ac49
2008-11-30 00:54:35 +00:00
leidel
bdb6a93931 Removed hg cruft
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@86 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 2da1fa8dc738b40607eaa3a556a16947feea8cc6
2008-11-28 13:16:54 +00:00
Jannis Leidel
351809557b Removed hg cruft
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : b2579d27046e389ff5f9aa0d39c16bb25ecabcb0
2008-11-28 14:15:56 +01:00
Jannis Leidel
7e3a9f5ece Merge branch 'master' of git@github.com:jezdez/django-dbtemplates
Conflicts:
	docs/overview.txt

committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : e3e5cfa40c5c3ea7c4308048fbe0effaa81cd145
2008-11-28 14:13:18 +01:00
leidel
6491d781f2 Changed wrong link in docs
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@85 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : d08a9fdb8a00b1b3f305cb6286fdd43507bc821d
2008-11-28 13:11:40 +00:00
leidel
c9277e9c84 Removed automatic creation of 404.html and 500.html templates and added a new management command for those cases called "create_error_templates". Also made reverted move to Bitbucket.
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@84 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : b03959a7322a1acd040dad721d80d7bbd680f16d
2008-11-28 13:06:34 +00:00
Jannis Leidel
85539c107e Removed automatic creation of 404.html and 500.html templates and added a new management command for those cases called "create_error_templates". Also made reverted move to Bitbucket.
committer: Jannis Leidel <jannis@leidel.info>

--HG--
extra : convert_revision : 8ecefaad1d1348b5937fdd969473b4dfb8c542bd
2008-11-28 14:05:44 +01:00
leidel
84dc3e0bcc Added hooks for __version__
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@83 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 5c361908f79ffc41f9d88db684370b4a2e194028
2008-11-21 11:26:14 +00:00
leidel
2f37c244b0 Small change in the description
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@82 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 5c5cc003dad58a241d8c762bd3e3ac16b581ce3d
2008-11-21 11:25:55 +00:00
leidel
cc3006c05a Added tag v0.5.2 for changeset c2ddc104f0da
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@81 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 4e972692a0eaa3e839fea7e836710bfe65b4bd39
2008-11-21 11:25:38 +00:00
leidel
6810ac0647 tag update
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@80 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : e88916d629b7f1c7dc6aaaee0413888c38a6fe07
2008-11-16 02:07:58 +00:00
leidel
19794cb5cc Added some documentation for the example project, support to start it directly and small touches on the docs
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@79 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 541571d5a5f1a13dc5f65b26fef6a4e93201ee90
2008-11-16 02:03:22 +00:00
leidel
e786c1bca3 Some for annoyance for hgignore
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@78 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 83ee20c52c7569da664bec596c7a3609df4c73f2
2008-11-16 02:03:04 +00:00
leidel
fff8405c69 Updated MANIFEST and bumped minor version to 0.5.2
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@77 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : d6a0d6246b4187ae39b4b0504be20964d46e13b9
2008-11-16 02:02:48 +00:00
leidel
631a2f871a Fixed problem when django.contrib.sites' table isn't populated yet on initialization of dbtemplates. Thanks for the report, Kevin Fricovsky
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@76 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 42df3a04d82bc0957159d20fc8d666f420ab24a1
2008-11-16 02:02:30 +00:00
leidel
feb4aac05c Changed a translation string and updated locales
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@75 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : a6fcbc158c0be2148c2e4ef154410ced6737d048
2008-11-16 02:02:10 +00:00
leidel
38d74ab66a Added simple example project for testing
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@74 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 9ae2a2dff6369f3dfb415743f594349e21a24488
2008-11-16 02:01:44 +00:00
leidel
39b730391d Added hgignore file
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@73 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 99161a20e39320df2ac811ee0f6327ce7f7d5730
2008-11-16 02:01:18 +00:00
leidel
a15520ecee Added tag v0.5.1 for changeset d4d6fd6ef24e
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@70 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 6d06bc9a24c282b60b1a0ccde9584b2c62dcdece
2008-11-09 13:56:58 +00:00
leidel
dd4367ee99 D'oh, stupid bug in docs
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@69 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : eb0185ddf1f7e60823e4df39457e7102f873d77a
2008-11-09 13:56:40 +00:00
leidel
96e085e711 Added tag v0.5.1 for changeset d28c311da014
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@68 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 4177004af4be64c433477ee43a76e79db78f310b
2008-11-09 13:56:23 +00:00
leidel
16a75eb97e Use docs/overview.txt for PyPI and only have small things in README
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@67 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : b17aa26289871ff1eea693d117c3527e3e315efa
2008-11-09 13:56:05 +00:00
leidel
e4e23120c9 Added tag v0.5.1 for changeset 374b15b01724
git-svn-id: https://django-dbtemplates.googlecode.com/svn/trunk@66 cfb8ba98-e953-0410-9cff-959ffddf5974

committer: leidel <leidel@cfb8ba98-e953-0410-9cff-959ffddf5974>

--HG--
extra : convert_revision : 13e1b3291d00e2f9355c72cad99d437dd34e6db4
2008-11-09 13:55:43 +00:00
78 changed files with 7494 additions and 609 deletions

6
.coveragerc Normal file
View file

@ -0,0 +1,6 @@
[run]
source = dbtemplates
branch = 1
[report]
omit = *tests*,*/migrations/*,test_*

40
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,40 @@
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 Normal file
View file

@ -0,0 +1,48 @@
name: Test
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 5
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.13']
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 Normal file
View file

@ -0,0 +1,14 @@
.*
!.gitignore
!.coveragerc
*.pyc
.*.swp
MANIFEST
build
dist
*.egg-info
docs/_build
.tox/
*.egg/
.coverage
coverage.xml

15
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,15 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.34.0
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

25
.readthedocs.yaml Normal file
View file

@ -0,0 +1,25 @@
# .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

7
.tx/config Normal file
View file

@ -0,0 +1,7 @@
[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 Normal file
View file

@ -0,0 +1,18 @@
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

46
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,46 @@
# 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/

3
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,3 @@
[![Jazzband](https://jazzband.co/static/img/jazzband.svg)](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
View file

@ -1,15 +0,0 @@
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://www.bitbucket.org/ubernostrum/django-registration/

30
LICENSE
View file

@ -1,4 +1,4 @@
Copyright (c) 2007-2008, Jannis Leidel
Copyright (c) 2007-2019, Jannis Leidel and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
@ -26,3 +26,31 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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

View file

@ -1,5 +1,4 @@
include INSTALL
include LICENSE
include MANIFEST.in
include README.rst
include LICENSE AUTHORS README.rst MANIFEST.in tox.ini .coveragerc CONTRIBUTING.md
recursive-include docs *.txt
recursive-include dbtemplates/locale *
recursive-include dbtemplates/static/dbtemplates *.css *.js

View file

@ -1,55 +1,30 @@
===================================
Database template loader for Django
===================================
django-dbtemplates
==================
This is a basic database template loader for Django which uses a m2m
relationship to provide a site centric template loading.
.. image:: https://jazzband.co/static/img/badge.svg
:alt: Jazzband
:target: https://jazzband.co/
How to use it in your own Django application
============================================
.. image:: https://github.com/jazzband/django-dbtemplates/workflows/Test/badge.svg
:target: https://github.com/jazzband/django-dbtemplates/actions
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:
.. image:: https://codecov.io/github/jazzband/django-dbtemplates/coverage.svg?branch=master
:alt: Codecov
:target: https://codecov.io/github/jazzband/django-dbtemplates?branch=master
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',
)
``dbtemplates`` is a Django app that consists of two parts:
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
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
Support
=======
It also features optional support for versioned storage and django-admin
command, integrates with Django's caching system and the admin actions.
Please leave your questions and messages on the designated site:
Please see https://django-dbtemplates.readthedocs.io/ for more details.
http://www.bitbucket.org/jezdez/django-dbtemplates/issues/
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

View file

@ -0,0 +1,3 @@
import importlib.metadata
__version__ = importlib.metadata.version("django-dbtemplates")

View file

@ -1,46 +1,173 @@
import posixpath
from django import forms
from django.contrib import admin
from django.db.models import get_app
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from django.utils.safestring import mark_safe
from dbtemplates.models import Template
from dbtemplates.conf import settings
from dbtemplates.models import Template, add_template_to_cache, remove_cached_template
from dbtemplates.utils.template import check_template_syntax
# Check if django-reversion is installed and use reversions' VersionAdmin
# as the base admin class if yes
try:
get_app('reversion')
# Check if either django-reversion-compare or django-reversion is installed and
# 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
except ImproperlyConfigured:
from django.contrib.admin import ModelAdmin 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=forms.Textarea({'cols': '80', 'rows': '24'}),
help_text=_("Leaving this empty causes Django to look for a template "
"with the given name and populate this field with its content."),
required=False)
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 = (
(None, {
'fields': ('name', 'content', 'sites'),
'fields': ('name', 'content'),
'classes': ('monospace',),
}),
(_('Date information'), {
(_('Advanced'), {
'fields': (('sites'),),
}),
(_('Date/time'), {
'fields': (('creation_date', 'last_changed'),),
'classes': ('collapse',),
}),
)
list_display = ('name', 'creation_date', 'last_changed')
filter_horizontal = ('sites',)
list_display = ('name', 'creation_date', 'last_changed', 'site_list')
list_filter = ('sites',)
save_as = True
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)

9
dbtemplates/apps.py Normal file
View file

@ -0,0 +1,9 @@
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'

View file

@ -1,97 +0,0 @@
import os
from django.conf import settings
from django.core.cache import cache
from django.contrib.sites.models import Site
from django.template import TemplateDoesNotExist
from django.core.exceptions import ImproperlyConfigured
from django.utils.encoding import smart_unicode, force_unicode
class BaseCacheBackend(object):
"""
Base class for custom cache backend of dbtemplates to be used while
subclassing.
Set DBTEMPLATES_CACHE_BACKEND setting to the Python path to that subclass.
"""
def __init__(self):
self.site = Site.objects.get_current()
def load(self, name):
"""
Loads a template from the cache with the given name.
"""
raise NotImplemented
def save(self, name, content):
"""
Saves the given template content with the given name in the cache.
"""
raise NotImplemented
def remove(self, name):
"""
Removes the template with the given name from the cache.
"""
raise NotImplemented
class DjangoCacheBackend(BaseCacheBackend):
"""
A cache backend that uses Django's cache mechanism.
"""
def _cache_key(self, name):
return 'dbtemplates::%s' % name
def load(self, name):
cache_key = self._cache_key(name)
return cache.get(cache_key)
def save(self, name, content):
cache_key = self._cache_key(name)
cache.set(cache_key, content)
def remove(self, name):
cache_key = self._cache_key(name)
cache.delete(cache_key)
class FileSystemBackend(BaseCacheBackend):
"""
A cache backend that uses simple files to hold the template cache.
"""
def __init__(self):
try:
self.cache_dir = getattr(settings, 'DBTEMPLATES_CACHE_DIR', None)
self.cache_dir = os.path.normpath(self.cache_dir)
if not os.path.isdir(self.cache_dir):
pass
except:
raise ImproperlyConfigured('You\'re using the dbtemplates\' file system cache backend without having set the DBTEMPLATES_CACHE_DIR setting to a valid value. Make sure the directory exists and is writeable for the user your Django instance is running with.')
super(FileSystemBackend, self).__init__()
def _filepath(self, name):
return os.path.join(self.cache_dir, name)
def load(self, name):
try:
filepath = self._filepath(name)
return open(filepath).read().decode('utf-8')
except:
return None
def save(self, name, content, retry=False):
try:
filepath = self._filepath(name)
dirname = os.path.dirname(filepath)
if not os.path.exists(dirname):
os.makedirs(dirname)
cache_file = open(filepath, 'w')
cache_file.write(force_unicode(content).encode('utf-8'))
cache_file.close()
except Exception:
raise
def remove(self, name):
try:
filepath = self._filepath(name)
os.remove(filepath)
except:
pass

67
dbtemplates/conf.py Normal file
View file

@ -0,0 +1,67 @@
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

View file

@ -1,30 +1,87 @@
import os
from django.conf import settings
from django.template import TemplateDoesNotExist
from django.core.exceptions import ImproperlyConfigured
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, backend
from dbtemplates.models import Template
from dbtemplates.utils.cache import (cache, get_cache_key,
set_and_return, get_cache_notfound_key)
def load_template_source(template_name, template_dirs=None):
class Loader(BaseLoader):
"""
A custom template loader to load templates from the database.
Tries to load the template from the dbtemplates cache backend specified
by the DBTEMPLATES_CACHE_BACKEND setting. If it does not find a template
it falls back to query the database field ``name`` with the template path
and ``sites`` with the current site.
"""
display_name = 'db:%s:%s' % (settings.DATABASE_ENGINE, template_name)
if backend:
is_usable = True
def get_template_sources(self, template_name, template_dirs=None):
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:
backend_template = cache.get(cache_key)
if backend_template:
return backend_template, template_name
except Exception:
pass
# Not found in cache, move on.
cache_notfound_key = get_cache_notfound_key(template_name)
if cache:
try:
notfound = cache.get(cache_notfound_key)
if notfound:
raise TemplateDoesNotExist(template_name)
except Exception:
raise TemplateDoesNotExist(template_name)
# Not marked as not-found, move on...
try:
backend_template = backend.load(template_name)
if backend_template is not None:
return backend_template, template_name
except:
pass
try:
template = Template.objects.get(name__exact=template_name,
sites__pk=settings.SITE_ID)
return (template.content, display_name)
except:
pass
raise TemplateDoesNotExist, template_name
load_template_source.is_usable = True
return self._load_and_store_template(template_name, cache_key,
site, sites__in=[site.id])
except (Template.MultipleObjectsReturned, Template.DoesNotExist):
try:
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.
cache.set(cache_notfound_key, '1')
raise TemplateDoesNotExist(template_name)

Binary file not shown.

View file

@ -0,0 +1,108 @@
# 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"

View file

@ -1,17 +1,23 @@
# 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 ""
msgstr ""
"Project-Id-Version: 0.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2008-11-01 23:45+0100\n"
"PO-Revision-Date: 2008-08-19 17:11+0100\n"
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
"Language-Team: Jannis Leidel <jannis@leidel.info>\n"
"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"
"X-Poedit-Language: German\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: admin.py:23
#: 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."
@ -19,34 +25,86 @@ 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:37
msgid "Date information"
msgstr "Datum"
#: admin.py:82
msgid "Advanced"
msgstr "Erweiterte Einstellungen"
#: models.py:17
#: 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"
msgstr "Name"
#: models.py:17
#: models.py:23
msgid "Example: 'flatpages/default.html'"
msgstr "Zum Beispiel: 'flatpages/default.html'"
#: models.py:18
#: models.py:24
msgid "content"
msgstr "Inhalt"
#: models.py:20
#: models.py:27
msgid "creation date"
msgstr "Erstellt"
#: models.py:21
#: models.py:29
msgid "last changed"
msgstr "Geändert"
#: models.py:25
#: models.py:37
msgid "template"
msgstr "Template"
#: models.py:26
#: models.py:38
msgid "templates"
msgstr "Templates"

Binary file not shown.

View file

@ -0,0 +1,104 @@
# 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.

View file

@ -0,0 +1,109 @@
# 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"

View file

@ -1,45 +1,108 @@
# 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: 0.4\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2008-07-29 13:19+0200\n"
"PO-Revision-Date: 2008-08-09 21:39+0100\n"
"Last-Translator: Roland Frédéric <frederic.roland@creativeconvergence.be>\n"
"Language-Team: Frédéric Roland <frederic.roland@creativeconvergence.be>\n"
"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"
"X-Poedit-Language: French\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
#: admin.py:9
msgid "Date information"
msgstr "Date"
#: 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 ""
"Si vous laissez ceci vide , Django recherchera un modèle avec le nom donné "
"et remplira ce champ avec son contenu."
#: models.py:14
#: admin.py:82
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"
msgstr "nom"
#: models.py:14
#: models.py:23
msgid "Example: 'flatpages/default.html'"
msgstr "Exemple: 'flatpages/default.html'"
msgstr "Exemple : 'flatpages/default.html'"
#: models.py:15
#: models.py:24
msgid "content"
msgstr "contenu"
#: models.py:17
#: models.py:27
msgid "creation date"
msgstr "date de création"
#: models.py:18
#: models.py:29
msgid "last changed"
msgstr "dernier changement"
#: models.py:22
#: models.py:37
msgid "template"
msgstr "modèle"
#: models.py:23
#: models.py:38
msgid "templates"
msgstr "modèles"

View file

@ -1,56 +1,106 @@
# translation of PACKAGE.
# Copyright (C) 2008 THE PACKAGE'S COPYRIGHT HOLDER
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# <>, 2008.
# , fuzzy
#
#
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2008-11-01 23:47+0100\n"
"PO-Revision-Date: 2008-08-21 12:31+0300\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"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: he\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: admin.py:23
#: 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:37
msgid "Date information"
msgstr "תאריכים"
#: admin.py:82
msgid "Advanced"
msgstr ""
#: models.py:17
#: 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:17
#: models.py:23
msgid "Example: 'flatpages/default.html'"
msgstr "דוגמא: 'flatpages/default.html'"
#: models.py:18
#: models.py:24
msgid "content"
msgstr "תוכן"
#: models.py:20
#: models.py:27
msgid "creation date"
msgstr "נוצר ב"
#: models.py:21
#: models.py:29
msgid "last changed"
msgstr "שונה ב"
#: models.py:25
#: models.py:37
msgid "template"
msgstr "תבנית"
#: models.py:26
#: models.py:38
msgid "templates"
msgstr "תבניות"

Binary file not shown.

View file

@ -0,0 +1,108 @@
# 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.

View file

@ -0,0 +1,109 @@
# 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.

View file

@ -0,0 +1,106 @@
# 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.

View file

@ -0,0 +1,102 @@
# 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 "模板"

View file

@ -1,47 +0,0 @@
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)

View file

@ -0,0 +1,19 @@
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')

View file

@ -0,0 +1,57 @@
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)

View file

@ -1,80 +1,153 @@
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 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
class Command(NoArgsCommand):
ALWAYS_ASK, FILES_TO_DATABASE, DATABASE_TO_FILES = ("0", "1", "2")
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."
option_list = NoArgsCommand.option_list + (
make_option("-e", "--ext", dest="ext", action="store", default="html",
help="extension of the files you want to sync with the database "
"[default: %default]"),
make_option("-f", "--force", action="store_true", dest="force",
default=False, help="overwrite existing database templates")
)
def handle_noargs(self, **options):
extension = options.get('ext')
force = options.get('force')
def add_arguments(self, parser):
parser.add_argument(
"-e",
"--ext",
dest="ext",
action="store",
default="html",
help="extension of the files you want to "
"sync with the database [default: %(default)s]",
)
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",
)
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")
if not extension.startswith("."):
extension = ".%s" % extension
extension = f".{extension}"
try:
site = Site.objects.get_current()
except:
site = None
except Exception:
raise CommandError(
"Please make sure to have the sites contrib "
"app installed and setup with a site object"
)
if site is None:
raise CommandError("Please make sure to have the sites contrib "
"app installed and setup with a site object")
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)]
if app_first:
tpl_dirs = app_template_dirs + DIRS
else:
tpl_dirs = DIRS + app_template_dirs
templatedirs = [str(d) for d in tpl_dirs if os.path.isdir(d)]
for templatedir in templatedirs:
for dirpath, subdirs, filenames in os.walk(templatedir):
for f in [f for f in filenames if f.endswith(extension)
and not f.startswith(".")]:
for f in [
f
for f in filenames
if f.endswith(extension) and not f.startswith(".")
]:
path = os.path.join(dirpath, f)
name = path.split(templatedir)[1][1:]
name = path.split(str(templatedir))[1]
if name.startswith("/"):
name = name[1:]
try:
t = Template.objects.get(name__exact=name)
t = Template.on_site.get(name__exact=name)
except Template.DoesNotExist:
if force == False:
confirm = raw_input(
"\nA '%s' template doesn't exist in the database.\n"
"Create it with '%s'?"
" (y/[n]): """ % (name, path))
if confirm.lower().startswith('y') or force:
t = Template(name=name,
content=open(path, "r").read())
if not force:
confirm = input(
"\nA '%s' template doesn't exist in the "
"database.\nCreate it with '%s'?"
" (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.sites.add(site)
else:
while 1:
confirm = raw_input(
"\n%s exists in the database.\n"
"(1) Overwrite %s with '%s'\n"
"(2) Overwrite '%s' with %s\n"
"Type 1 or 2 or press <Enter> to skip: "
% (t.__repr__(),
t.__repr__(), path,
path, t.__repr__()))
if confirm == '' or confirm in ('1', '2'):
if confirm == '1':
t.content = open(path, 'r').read()
t.save()
t.sites.add(site)
elif confirm == '2':
open(path, 'w').write(t.content)
while True:
if overwrite == ALWAYS_ASK:
_i = (
"\n%(template)s exists in the database.\n"
"(1) Overwrite %(template)s with '%(path)s'\n" # noqa
"(2) Overwrite '%(path)s' with %(template)s\n" # noqa
"Type 1 or 2 or press <Enter> to skip: "
% {"template": t.__repr__(), "path": path}
)
confirm = input(_i)
else:
confirm = overwrite
if confirm in (
"",
FILES_TO_DATABASE,
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

View file

@ -0,0 +1,73 @@
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
],
),
]

View file

@ -0,0 +1,23 @@
# 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'),
),
]

View file

View file

@ -1,24 +1,35 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from django.db import models
from django.conf import settings
from django.db.models import signals
from dbtemplates.conf import settings
from dbtemplates.utils.cache import (
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.utils.translation import gettext_lazy as _
from django.db import models
from django.db.models import signals
from django.template import TemplateDoesNotExist
from django.template.loader import find_template_source
from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import gettext_lazy as _
class Template(models.Model):
"""
Defines a template model for use with the database template loader.
The field ``name`` is the equivalent to the filename of a static template.
"""
name = models.CharField(_('name'), unique=True, max_length=100, help_text=_("Example: 'flatpages/default.html'"))
id = models.AutoField(primary_key=True, verbose_name=_('ID'),
serialize=False, auto_created=True)
name = models.CharField(_('name'), max_length=100,
help_text=_("Example: 'flatpages/default.html'"))
content = models.TextField(_('content'), blank=True)
sites = models.ManyToManyField(Site, default=[settings.SITE_ID])
creation_date = models.DateTimeField(_('creation date'), default=datetime.now)
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:
db_table = 'django_template'
@ -26,82 +37,44 @@ class Template(models.Model):
verbose_name_plural = _('templates')
ordering = ('name',)
def __unicode__(self):
def __str__(self):
return self.name
def populate(self, name=None):
"""
Tries to find a template with the same name and populates
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):
self.last_changed = datetime.now()
# If content is empty look for a template with the given name and
# populate the template instance with its content.
if not self.content:
try:
source, origin = find_template_source(self.name)
if source:
self.content = source
except TemplateDoesNotExist:
pass
super(Template, self).save(*args, **kwargs)
if settings.DBTEMPLATES_AUTO_POPULATE_CONTENT and not self.content:
self.populate()
super().save(*args, **kwargs)
def get_cache_backend():
path = getattr(settings, 'DBTEMPLATES_CACHE_BACKEND', False)
if path:
i = path.rfind('.')
module, attr = path[:i], path[i+1:]
try:
mod = __import__(module, {}, {}, [attr])
except ImportError, e:
raise ImproperlyConfigured, 'Error importing dbtemplates cache backend %s: "%s"' % (module, e)
try:
cls = getattr(mod, attr)
except AttributeError:
raise ImproperlyConfigured, 'Module "%s" does not define a "%s" cache backend' % (module, attr)
return cls()
return False
backend = get_cache_backend()
def add_template_to_cache(instance, **kwargs):
def add_default_site(instance, **kwargs):
"""
Called via Django's signals to cache the templates, if the template
in the database was added or changed.
in the database was added or changed, only if
DBTEMPLATES_ADD_DEFAULT_SITE setting is set.
"""
backend.save(instance.name, instance.content)
if not settings.DBTEMPLATES_ADD_DEFAULT_SITE:
return
current_site = Site.objects.get_current()
if current_site not in instance.sites.all():
instance.sites.add(current_site)
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.
"""
backend.remove(instance.name)
if backend:
signals.post_save.connect(remove_cached_template, sender=Template)
signals.post_save.connect(add_template_to_cache, sender=Template)
signals.pre_delete.connect(remove_cached_template, sender=Template)
__test__ = {'API_TESTS':"""
>>> from django.template import loader, Context
>>> test_site = Site.objects.get(pk=1)
>>> test_site
<Site: example.com>
>>> t1 = Template(name='base.html', content="<html><head></head><body>{% block content %}Welcome at {{ title }}{% endblock %}</body></html>")
>>> t1.save()
>>> t1.sites.add(test_site)
>>> t1
<Template: base.html>
>>> t2 = Template(name='sub.html', content='{% extends "base.html" %}{% block content %}This is {{ title }}{% endblock %}')
>>> t2.save()
>>> t2.sites.add(test_site)
>>> t2
<Template: sub.html>
>>> Template.objects.filter(sites=test_site)
[<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>'
"""}
signals.post_save.connect(add_default_site, sender=Template)
signals.post_save.connect(add_template_to_cache, sender=Template)
signals.pre_delete.connect(remove_cached_template, sender=Template)

View file

@ -0,0 +1,68 @@
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;
}

View file

@ -0,0 +1,19 @@
.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;
}

View file

@ -0,0 +1,496 @@
/* 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("&nbsp;");
}
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

View file

@ -0,0 +1,68 @@
// 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);
}
})();

View file

@ -0,0 +1,81 @@
/* 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();
}
};

View file

@ -0,0 +1,354 @@
/* 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;
}
};
})();

View file

@ -0,0 +1,643 @@
/* 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);
};
}
})();

View file

@ -0,0 +1,140 @@
/* 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";
}
};
};

View file

@ -0,0 +1,57 @@
// 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;
}

View file

@ -0,0 +1,410 @@
/**
* 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;
}
};

View file

@ -0,0 +1,130 @@
/* 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";
}

175
dbtemplates/test_cases.py Normal file
View file

@ -0,0 +1,175 @@
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')

View file

@ -0,0 +1,51 @@
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',
]
}
},
]

View file

View file

@ -0,0 +1,62 @@
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))

View file

@ -0,0 +1,33 @@
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 Normal file
View file

@ -0,0 +1,130 @@
# 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."

125
docs/advanced.txt Normal file
View file

@ -0,0 +1,125 @@
=================
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/

428
docs/changelog.txt Normal file
View file

@ -0,0 +1,428 @@
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.
* Django 5.x 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 Normal file
View file

@ -0,0 +1,198 @@
#
# 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

View file

@ -1,16 +1,28 @@
==================
django-dbtemplates
==================
``dbtemplates`` is a Django app that comes with to parts: It allows you to
create templates that are saved in your database, and it provides a so called
`template loader`_, a function that enables Django to find the templates you
created in the database.
``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.txt
overview
advanced
settings
changelog

170
docs/make.bat Normal file
View file

@ -0,0 +1,170 @@
@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

View file

@ -1,23 +1,9 @@
===================================
Database template loader for Django
===================================
``dbtemplates`` is a Django app that comes with to parts: It allows you to
create templates that are saved in your database, and it provides a so called
`template loader`_, a function that enables Django to find the templates you
created in the database.
It also includes a extensible caching mechanism and supports version control
of the templates saved in the database.
.. _template loader: http://docs.djangoproject.com/en/dev/ref/templates/api/#loading-templates
Setup
=====
1. Get the source from the subversion repository
2. Follow the instructions in the INSTALL file
3. Edit the settings.py of your Django site:
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
@ -37,26 +23,54 @@ Setup
'dbtemplates',
)
* Add ``dbtemplates.loader.load_template_source`` to the
``TEMPLATE_LOADERS`` list in the settings.py of your Django project
* Add ``dbtemplates.loader.Loader`` to the ``TEMPLATES.OPTIONS.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',
)
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',
],
},
},
]
4. Sync your database ``python manage.py syncdb``
5. Restart your Django server
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
"Dbtemplates" section.
"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.
@ -70,106 +84,3 @@ other template loaders. For example, if you have a template called
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.
Caching
=======
Using the default caching
-------------------------
Dbtemplates comes with different backends for caching that are automatically
created, updated and deleted when templates are saved in the database by
using Django's signal framework.
To enable one of them you need to specify a setting called
``DBTEMPLATES_CACHE_BACKEND`` to one of the following values:
* ``dbtemplates.cache.FileSystemBackend`` -- File system caching
The ``FileSystemBackend`` is a simple way to store the templates you have
in the database on the filesystem. That's especially useful if you don't
use a full caching framework like Django is providing.
To use this backend you need additionally create a setting
``DBTEMPLATES_CACHE_DIR`` that contains the full file system path to the
directory where ``dbtemplates`` should create the cache files in.
* ``dbtemplates.cache.DjangoCacheBackend`` -- Django cache
The ``DjangoCacheBackend`` is a thin wrapper around Django's caching
framework that enables you to use advanced caching solutions like
memcached or database caching. 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
Writing your own caching backends
---------------------------------
Writing your own cache backends is perfectly easy since ``dbtemplates``
includes a easy-to-use base class in ``dbtemplates.cache.BaseCacheBackend``.
Just subclass that base backend somewhere in your own code and provide the
follwing three reuqired methods:
* ``load``
Loads a template from the cache with the given name and returns its
contents. Return None if nothing found.
Arguments:
* ``name`` - name of the template
* ``save``
Saves the passed template contents with the passed name in the cache.
Arguments:
* ``name`` - name of the template
* ``content`` - contents of the template
* ``remove``
Removes the template with the passed name from the cache.
Arguments:
* ``name`` - name of the template
Please see also the `source of the default backends`_ to see how it works.
.. _source of the default backends: http://www.bitbucket.org/jezdez/django-dbtemplates/src/tip/dbtemplates/cache.py
Versionizing your templates
===========================
``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. ``dbtemplates`` automatically recognizes if
``django-reversion`` is installed and works out of the box. 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``
.. _django-reversion: http://code.google.com/p/django-reversion/
.. _django-reversion's documentation: http://code.google.com/p/django-reversion/wiki/GettingStarted
Support
=======
Please leave your questions and messages on the designated Google Code site:
http://code.google.com/p/django-dbtemplates/

65
docs/settings.txt Normal file
View file

@ -0,0 +1,65 @@
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/

51
pyproject.toml Normal file
View file

@ -0,0 +1,51 @@
[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",
"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
requirements/docs.txt Normal file
View file

@ -0,0 +1 @@
django

2
requirements/tests.txt Normal file
View file

@ -0,0 +1,2 @@
flake8
coverage

View file

@ -1,27 +0,0 @@
from distutils.core import setup
setup(
name='django-dbtemplates',
version='0.5.1',
description='Template loader for database stored templates',
long_description=open('README.rst').read(),
author='Jannis Leidel',
author_email='jannis@leidel.info',
url='http://www.bitbucket.org/jezdez/django-dbtemplates/wiki/',
download_url='http://www.bitbucket.org/jezdez/django-dbtemplates/get/v0.5.1.gz',
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',
'Framework :: Django',
]
)

61
tox.ini Normal file
View file

@ -0,0 +1,61 @@
[tox]
minversion = 4.0
envlist =
flake8
py3{8,9,10,11,12}-dj42
py3{10,11,12}-dj{50}
py3{10,11,12,13}-dj{51,52}
py3{12,13}-dj{main}
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
[testenv]
skipsdist = true
package = editable
basepython =
py38: python3.8
py39: python3.9
py310: python3.10
py311: python3.11
py312: python3.12
py313: python3.13
setenv =
DJANGO_SETTINGS_MODULE = dbtemplates.test_settings
deps =
-r requirements/tests.txt
dj42: Django>=4.2,<4.3
dj50: Django>=5.0,<5.1
dj51: Django>=5.1,<5.2
dj52: Django>=5.2,<5.3
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