Merge pull request #126 from mpasternak/master

Django 4.x
This commit is contained in:
Michał Pasternak 2022-08-11 07:28:21 +02:00 committed by GitHub
commit 13bacacef8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 191 additions and 105 deletions

View file

@ -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

View file

@ -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})

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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
],
),
]

View file

@ -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

View file

@ -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)

View file

@ -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):

View file

@ -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)
-----------------

View file

@ -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"],
)

View file

@ -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