mirror of
https://github.com/jazzband/django-dbtemplates.git
synced 2026-03-16 22:20:28 +00:00
commit
13bacacef8
13 changed files with 191 additions and 105 deletions
|
|
@ -6,8 +6,7 @@ django-dbtemplates
|
|||
:target: https://jazzband.co/
|
||||
|
||||
.. image:: https://github.com/jazzband/django-dbtemplates/workflows/Test/badge.svg
|
||||
:target: https://github.com/jazzband/django-dbtemplates/actions
|
||||
:alt: GitHub Actions
|
||||
:target: https://github.com/jazzband/django-dbtemplates/actions
|
||||
|
||||
.. image:: https://codecov.io/github/jazzband/django-dbtemplates/coverage.svg?branch=master
|
||||
:alt: Codecov
|
||||
|
|
|
|||
|
|
@ -2,19 +2,27 @@ import posixpath
|
|||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
||||
try:
|
||||
# Django 4.0
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ngettext
|
||||
except ImportError:
|
||||
# Before Django 4.0
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ungettext as ngettext
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from dbtemplates.conf import settings
|
||||
from dbtemplates.models import (Template, remove_cached_template,
|
||||
add_template_to_cache)
|
||||
from dbtemplates.models import Template, add_template_to_cache, remove_cached_template
|
||||
from dbtemplates.utils.template import check_template_syntax
|
||||
|
||||
# Check if either django-reversion-compare or django-reversion is installed and
|
||||
# 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
|
||||
from reversion_compare.admin import CompareVersionAdmin \
|
||||
as TemplateModelAdmin
|
||||
elif settings.DBTEMPLATES_USE_REVERSION:
|
||||
from reversion.admin import VersionAdmin as TemplateModelAdmin
|
||||
else:
|
||||
|
|
@ -30,7 +38,8 @@ class CodeMirrorTextArea(forms.Textarea):
|
|||
class Media:
|
||||
css = dict(screen=[posixpath.join(
|
||||
settings.DBTEMPLATES_MEDIA_PREFIX, 'css/editor.css')])
|
||||
js = [posixpath.join(settings.DBTEMPLATES_MEDIA_PREFIX, 'js/codemirror.js')]
|
||||
js = [posixpath.join(settings.DBTEMPLATES_MEDIA_PREFIX,
|
||||
'js/codemirror.js')]
|
||||
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
result = []
|
||||
|
|
@ -119,7 +128,7 @@ class TemplateAdmin(TemplateModelAdmin):
|
|||
for template in queryset:
|
||||
remove_cached_template(template)
|
||||
count = queryset.count()
|
||||
message = ungettext(
|
||||
message = ngettext(
|
||||
"Cache of one template successfully invalidated.",
|
||||
"Cache of %(count)d templates successfully invalidated.",
|
||||
count)
|
||||
|
|
@ -131,7 +140,7 @@ class TemplateAdmin(TemplateModelAdmin):
|
|||
for template in queryset:
|
||||
add_template_to_cache(template)
|
||||
count = queryset.count()
|
||||
message = ungettext(
|
||||
message = ngettext(
|
||||
"Cache successfully repopulated with one template.",
|
||||
"Cache successfully repopulated with %(count)d templates.",
|
||||
count)
|
||||
|
|
@ -147,15 +156,16 @@ class TemplateAdmin(TemplateModelAdmin):
|
|||
errors.append(f'{template.name}: {error}')
|
||||
if errors:
|
||||
count = len(errors)
|
||||
message = ungettext(
|
||||
message = ngettext(
|
||||
"Template syntax check FAILED for %(names)s.",
|
||||
"Template syntax check FAILED for %(count)d templates: %(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 = ungettext(
|
||||
message = ngettext(
|
||||
"Template syntax OK.",
|
||||
"Template syntax OK for %(count)d templates.", count)
|
||||
self.message_user(request, message % {'count': count})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
try:
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
except ImportError:
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class DBTemplatesConfig(AppConfig):
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ class DbTemplatesConf(AppConf):
|
|||
|
||||
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 "
|
||||
raise ImproperlyConfigured("Please add 'reversion_compare' to your"
|
||||
" INSTALLED_APPS setting to make "
|
||||
"use of it in dbtemplates.")
|
||||
return value
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ class Loader(BaseLoader):
|
|||
content, _ = self._load_template_source(origin.template_name)
|
||||
return content
|
||||
|
||||
def _load_and_store_template(self, template_name, cache_key, site, **params):
|
||||
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}'
|
||||
|
|
@ -73,11 +74,11 @@ class Loader(BaseLoader):
|
|||
|
||||
try:
|
||||
return self._load_and_store_template(template_name, cache_key,
|
||||
site, sites__in=[site.id])
|
||||
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)
|
||||
site, sites__isnull=True)
|
||||
except (Template.MultipleObjectsReturned, Template.DoesNotExist):
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import os
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.management.base import CommandError, BaseCommand
|
||||
from django.template.utils import get_app_template_dirs
|
||||
from django.template.loader import _engine_list
|
||||
|
||||
from dbtemplates.models import Template
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.template.loader import _engine_list
|
||||
from django.template.utils import get_app_template_dirs
|
||||
|
||||
ALWAYS_ASK, FILES_TO_DATABASE, DATABASE_TO_FILES = ('0', '1', '2')
|
||||
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')
|
||||
app_template_dirs = get_app_template_dirs("templates")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
|
@ -20,36 +20,56 @@ class Command(BaseCommand):
|
|||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"-e", "--ext",
|
||||
dest="ext", action="store", default="html",
|
||||
"-e",
|
||||
"--ext",
|
||||
dest="ext",
|
||||
action="store",
|
||||
default="html",
|
||||
help="extension of the files you want to "
|
||||
"sync with the database [default: %(default)s]")
|
||||
"sync with the database [default: %(default)s]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f", "--force",
|
||||
action="store_true", dest="force", default=False,
|
||||
help="overwrite existing database templates")
|
||||
"-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',
|
||||
"-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")
|
||||
"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,
|
||||
"-a",
|
||||
"--app-first",
|
||||
action="store_true",
|
||||
dest="app_first",
|
||||
default=False,
|
||||
help="look for templates in applications "
|
||||
"directories before project templates")
|
||||
"directories before project templates",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d", "--delete",
|
||||
action="store_true", dest="delete", default=False,
|
||||
help="Delete templates after syncing")
|
||||
"-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')
|
||||
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 = f".{extension}"
|
||||
|
|
@ -57,8 +77,10 @@ class Command(BaseCommand):
|
|||
try:
|
||||
site = Site.objects.get_current()
|
||||
except Exception:
|
||||
raise CommandError("Please make sure to have the sites contrib "
|
||||
"app installed and setup with a site object")
|
||||
raise CommandError(
|
||||
"Please make sure to have the sites contrib "
|
||||
"app installed and setup with a site object"
|
||||
)
|
||||
|
||||
if app_first:
|
||||
tpl_dirs = app_template_dirs + DIRS
|
||||
|
|
@ -68,11 +90,14 @@ class Command(BaseCommand):
|
|||
|
||||
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(str(templatedir))[1]
|
||||
if name.startswith('/'):
|
||||
if name.startswith("/"):
|
||||
name = name[1:]
|
||||
try:
|
||||
t = Template.on_site.get(name__exact=name)
|
||||
|
|
@ -81,27 +106,35 @@ class Command(BaseCommand):
|
|||
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:
|
||||
" (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:
|
||||
while True:
|
||||
if overwrite == ALWAYS_ASK:
|
||||
confirm = input(
|
||||
_i = (
|
||||
"\n%(template)s exists in the database.\n"
|
||||
"(1) Overwrite %(template)s with '%(path)s'\n"
|
||||
"(2) Overwrite '%(path)s' with %(template)s\n"
|
||||
"Type 1 or 2 or press <Enter> to skip: " %
|
||||
{'template': t.__repr__(), 'path': path})
|
||||
"(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 in (
|
||||
"",
|
||||
FILES_TO_DATABASE,
|
||||
DATABASE_TO_FILES,
|
||||
):
|
||||
if confirm == FILES_TO_DATABASE:
|
||||
with open(path, encoding='utf-8') as f:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
t.content = f.read()
|
||||
t.save()
|
||||
t.sites.add(site)
|
||||
|
|
@ -110,9 +143,10 @@ class Command(BaseCommand):
|
|||
os.remove(path)
|
||||
except OSError:
|
||||
raise CommandError(
|
||||
f"Couldn't delete {path}")
|
||||
f"Couldn't delete {path}"
|
||||
)
|
||||
elif confirm == DATABASE_TO_FILES:
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
with open(path, "w", encoding="utf-8") as f: # noqa
|
||||
f.write(t.content)
|
||||
if delete:
|
||||
t.delete()
|
||||
|
|
|
|||
|
|
@ -1,40 +1,73 @@
|
|||
import django
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sites', '0001_initial'),
|
||||
("sites", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Template',
|
||||
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)),
|
||||
('creation_date', models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name='creation date')),
|
||||
('last_changed', models.DateTimeField(
|
||||
default=django.utils.timezone.now, verbose_name='last changed')),
|
||||
('sites', models.ManyToManyField(
|
||||
to='sites.Site', verbose_name='sites', blank=True)),
|
||||
(
|
||||
"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',
|
||||
"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')),
|
||||
("objects", django.db.models.manager.Manager()),
|
||||
(
|
||||
"on_site",
|
||||
django.contrib.sites.managers.CurrentSiteManager("sites"),
|
||||
), # noqa
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,13 @@ from django.contrib.sites.models import Site
|
|||
from django.db import models
|
||||
from django.db.models import signals
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
try:
|
||||
# Django >= 4.0
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
except ImportError:
|
||||
# Django 3.2
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class DbTemplatesTestCase(TestCase):
|
|||
verbosity=0, overwrite=DATABASE_TO_FILES)
|
||||
self.assertEqual('temp test modified',
|
||||
open(temp_template_path,
|
||||
encoding='utf-8').read())
|
||||
encoding='utf-8').read())
|
||||
|
||||
call_command('sync_templates', force=True, verbosity=0,
|
||||
delete=True, overwrite=DATABASE_TO_FILES)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,16 @@
|
|||
import django
|
||||
from django.core import signals
|
||||
from django.contrib.sites.models import Site
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
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
|
||||
"""
|
||||
if django.VERSION[0] >= 3 and django.VERSION[1] >= 2:
|
||||
from django.core.cache import caches
|
||||
cache = caches.create_connection(settings.DBTEMPLATES_CACHE_BACKEND)
|
||||
else:
|
||||
from django.core.cache import _create_cache
|
||||
cache = _create_cache(settings.DBTEMPLATES_CACHE_BACKEND)
|
||||
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
|
||||
|
|
@ -28,11 +23,11 @@ cache = get_cache_backend()
|
|||
|
||||
def get_cache_key(name):
|
||||
current_site = Site.objects.get_current()
|
||||
return f'dbtemplates::{slugify(name)}::{current_site.pk}'
|
||||
return f"dbtemplates::{slugify(name)}::{current_site.pk}"
|
||||
|
||||
|
||||
def get_cache_notfound_key(name):
|
||||
return get_cache_key(name) + '::notfound'
|
||||
return get_cache_key(name) + "::notfound"
|
||||
|
||||
|
||||
def remove_notfound_key(instance):
|
||||
|
|
|
|||
|
|
@ -8,14 +8,16 @@ v4.0 (unreleased)
|
|||
|
||||
This is a backwards-incompatible release!
|
||||
|
||||
* Dropped support for Python 2.7.
|
||||
* Dropped support for Python 2.7 and Django < 3.2.
|
||||
|
||||
* Added support for Python 3.8.
|
||||
* 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)
|
||||
-----------------
|
||||
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -27,7 +27,6 @@ setup(
|
|||
"static/dbtemplates/js/*.js",
|
||||
],
|
||||
},
|
||||
python_requires=">=3.7",
|
||||
classifiers=[
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Web Environment",
|
||||
|
|
@ -43,5 +42,7 @@ setup(
|
|||
"Programming Language :: Python :: 3.10",
|
||||
"Framework :: Django",
|
||||
],
|
||||
python_requires=">=3.7",
|
||||
install_requires=["django-appconf >= 0.4"],
|
||||
)
|
||||
|
||||
|
|
|
|||
8
tox.ini
8
tox.ini
|
|
@ -4,14 +4,15 @@ usedevelop = True
|
|||
minversion = 1.8
|
||||
envlist =
|
||||
flake8
|
||||
py3{7,8,9}-dj22
|
||||
py3{7,8,9,10,11}-dj32
|
||||
py3{8,9,10,11}-dj{40,41,main}
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.7: py37
|
||||
3.8: py38
|
||||
3.9: py39
|
||||
3.10: py310
|
||||
3.10: py310, flake8
|
||||
3.11: py311
|
||||
|
||||
|
|
@ -27,13 +28,14 @@ setenv =
|
|||
DJANGO_SETTINGS_MODULE = dbtemplates.test_settings
|
||||
deps =
|
||||
-r requirements/tests.txt
|
||||
dj22: Django<2.3
|
||||
dj32: Django<3.3
|
||||
dj40: Django<4.1
|
||||
dj41: Django<4.2
|
||||
djmain: https://github.com/django/django/archive/main.tar.gz#egg=django
|
||||
|
||||
commands =
|
||||
python --version
|
||||
coverage run {envbindir}/django-admin.py test -v2 {posargs:dbtemplates}
|
||||
coverage run {envbindir}/django-admin test -v2 {posargs:dbtemplates}
|
||||
coverage report
|
||||
coverage xml
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue