diff --git a/dbtemplates/admin.py b/dbtemplates/admin.py index f6d65af..9b782d0 100644 --- a/dbtemplates/admin.py +++ b/dbtemplates/admin.py @@ -14,8 +14,7 @@ from dbtemplates.utils.template import check_template_syntax # 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: @@ -23,22 +22,22 @@ else: 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')] + 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( - super().render(name, value, attrs)) - result.append(""" + """ -""" % dict(media_prefix=settings.DBTEMPLATES_MEDIA_PREFIX, name=name)) +""" + % dict(media_prefix=settings.DBTEMPLATES_MEDIA_PREFIX, name=name) + ) return mark_safe("".join(result)) @@ -61,62 +62,79 @@ 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.") + 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.") + raise ImproperlyConfigured( + "You may use either CodeMirror or TinyMCE " + "with dbtemplates, not both. Please disable " + "one of them." + ) if settings.DBTEMPLATES_USE_TINYMCE: from tinymce.widgets import AdminTinyMCE + TemplateContentTextArea = AdminTinyMCE elif settings.DBTEMPLATES_USE_REDACTOR: from redactor.widgets import RedactorEditor + TemplateContentTextArea = RedactorEditor class TemplateAdminForm(forms.ModelForm): - """ Custom AdminForm to make the content textarea wider. """ + content = forms.CharField( - widget=TemplateContentTextArea(attrs={'rows': '24'}), - help_text=content_help_text, required=False) + 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 = ("name", "content", "sites", "creation_date", "last_changed") fields = "__all__" class TemplateAdmin(TemplateModelAdmin): form = TemplateAdminForm - readonly_fields = ['creation_date', 'last_changed'] + readonly_fields = ["creation_date", "last_changed"] fieldsets = ( - (None, { - 'fields': ('name', 'content'), - 'classes': ('monospace',), - }), - (_('Advanced'), { - 'fields': (('sites'),), - }), - (_('Date/time'), { - 'fields': (('creation_date', 'last_changed'),), - 'classes': ('collapse',), - }), + ( + None, + { + "fields": ("name", "content"), + "classes": ("monospace",), + }, + ), + ( + _("Advanced"), + { + "fields": (("sites"),), + }, + ), + ( + _("Date/time"), + { + "fields": (("creation_date", "last_changed"),), + "classes": ("collapse",), + }, + ), ) - filter_horizontal = ('sites',) - list_display = ('name', 'creation_date', 'last_changed', 'site_list') - list_filter = ('sites',) + 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'] + search_fields = ("name", "content") + actions = ["invalidate_cache", "repopulate_cache", "check_syntax"] def invalidate_cache(self, request, queryset): for template in queryset: @@ -125,10 +143,11 @@ class TemplateAdmin(TemplateModelAdmin): 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") + 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: @@ -137,37 +156,43 @@ class TemplateAdmin(TemplateModelAdmin): 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") + 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}') + 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)}) + "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}) + "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') + + site_list.short_description = _("sites") admin.site.register(Template, TemplateAdmin) diff --git a/dbtemplates/apps.py b/dbtemplates/apps.py index 9f75273..a98ba83 100644 --- a/dbtemplates/apps.py +++ b/dbtemplates/apps.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ class DBTemplatesConfig(AppConfig): - name = 'dbtemplates' - verbose_name = _('Database templates') + name = "dbtemplates" + verbose_name = _("Database templates") - default_auto_field = 'django.db.models.AutoField' + default_auto_field = "django.db.models.AutoField" diff --git a/dbtemplates/conf.py b/dbtemplates/conf.py index 010db5b..77b5d08 100644 --- a/dbtemplates/conf.py +++ b/dbtemplates/conf.py @@ -33,35 +33,45 @@ class DbTemplatesConf(AppConf): 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.") + 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.") + 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.") + 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.") + 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.") + 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 diff --git a/dbtemplates/loader.py b/dbtemplates/loader.py index 243da31..30e9dbb 100644 --- a/dbtemplates/loader.py +++ b/dbtemplates/loader.py @@ -4,8 +4,12 @@ from django.template import Origin, TemplateDoesNotExist from django.template.loaders.base import Loader as BaseLoader from dbtemplates.models import Template -from dbtemplates.utils.cache import (cache, get_cache_key, - set_and_return, get_cache_notfound_key) +from dbtemplates.utils.cache import ( + cache, + get_cache_key, + set_and_return, + get_cache_notfound_key, +) class Loader(BaseLoader): @@ -17,6 +21,7 @@ class Loader(BaseLoader): it falls back to query the database field ``name`` with the template path and ``sites`` with the current site. """ + is_usable = True def get_template_sources(self, template_name, template_dirs=None): @@ -30,11 +35,10 @@ 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}' + 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): @@ -74,15 +78,17 @@ class Loader(BaseLoader): # Not marked as not-found, move on... try: - return self._load_and_store_template(template_name, cache_key, - site, sites__in=[site.id]) + 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) + 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') + cache.set(cache_notfound_key, "1") raise TemplateDoesNotExist(template_name) diff --git a/dbtemplates/management/commands/check_template_syntax.py b/dbtemplates/management/commands/check_template_syntax.py index 3837e65..c315ad2 100644 --- a/dbtemplates/management/commands/check_template_syntax.py +++ b/dbtemplates/management/commands/check_template_syntax.py @@ -12,8 +12,9 @@ class Command(BaseCommand): for template in Template.objects.all(): valid, error = check_template_syntax(template) if not valid: - errors.append(f'{template.name}: {error}') + errors.append(f"{template.name}: {error}") if errors: raise CommandError( - 'Some templates contained errors\n%s' % '\n'.join(errors)) - self.stdout.write('OK') + "Some templates contained errors\n%s" % "\n".join(errors) + ) + self.stdout.write("OK") diff --git a/dbtemplates/management/commands/create_error_templates.py b/dbtemplates/management/commands/create_error_templates.py index 3c53d24..f7a78c9 100644 --- a/dbtemplates/management/commands/create_error_templates.py +++ b/dbtemplates/management/commands/create_error_templates.py @@ -29,29 +29,39 @@ class Command(BaseCommand): def add_arguments(self, parser): 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", + ) def handle(self, **options): - force = options.get('force') + 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") + raise CommandError( + "Please make sure to have the sites contrib " + "app installed and setup with a site object" + ) - verbosity = int(options.get('verbosity', 1)) + verbosity = int(options.get("verbosity", 1)) for error_code in (404, 500): template, created = Template.objects.get_or_create( - name=f"{error_code}.html") + name=f"{error_code}.html" + ) if created or (not created and force): - template.content = TEMPLATES.get(error_code, '') + 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) + 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) + sys.stderr.write( + "A template for %s errors already exists.\n" % error_code + ) diff --git a/dbtemplates/management/commands/sync_templates.py b/dbtemplates/management/commands/sync_templates.py index 6ce7fdc..047fedb 100644 --- a/dbtemplates/management/commands/sync_templates.py +++ b/dbtemplates/management/commands/sync_templates.py @@ -108,7 +108,9 @@ class Command(BaseCommand): "" % (name, path) ) if force or confirm.lower().startswith("y"): - t = Template.objects.create(name=name, content=path.read_text(encoding="utf-8")) + t = Template.objects.create( + name=name, content=path.read_text(encoding="utf-8") + ) t.sites.add(site) else: while True: diff --git a/dbtemplates/migrations/0001_initial.py b/dbtemplates/migrations/0001_initial.py index 7ac217f..b0e5dab 100644 --- a/dbtemplates/migrations/0001_initial.py +++ b/dbtemplates/migrations/0001_initial.py @@ -4,7 +4,6 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("sites", "0001_initial"), ] diff --git a/dbtemplates/migrations/0002_alter_template_creation_date_and_more.py b/dbtemplates/migrations/0002_alter_template_creation_date_and_more.py index 61cb561..73d4a0d 100644 --- a/dbtemplates/migrations/0002_alter_template_creation_date_and_more.py +++ b/dbtemplates/migrations/0002_alter_template_creation_date_and_more.py @@ -4,20 +4,19 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('dbtemplates', '0001_initial'), + ("dbtemplates", "0001_initial"), ] operations = [ migrations.AlterField( - model_name='template', - name='creation_date', - field=models.DateTimeField(auto_now_add=True, verbose_name='creation date'), + 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'), + model_name="template", + name="last_changed", + field=models.DateTimeField(auto_now=True, verbose_name="last changed"), ), ] diff --git a/dbtemplates/models.py b/dbtemplates/models.py index e84ecd3..62f4a1c 100644 --- a/dbtemplates/models.py +++ b/dbtemplates/models.py @@ -18,24 +18,26 @@ 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. """ - 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, verbose_name=_('sites'), - blank=True) - creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) - last_changed = models.DateTimeField(_('last changed'), auto_now=True) + + 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, 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') + on_site = CurrentSiteManager("sites") class Meta: - db_table = 'django_template' - verbose_name = _('template') - verbose_name_plural = _('templates') - ordering = ('name',) + db_table = "django_template" + verbose_name = _("template") + verbose_name_plural = _("templates") + ordering = ("name",) def __str__(self): return self.name diff --git a/dbtemplates/test_cases.py b/dbtemplates/test_cases.py index acd0cf1..0a2109d 100644 --- a/dbtemplates/test_cases.py +++ b/dbtemplates/test_cases.py @@ -16,9 +16,13 @@ from django.contrib.sites.models import Site from dbtemplates.conf import settings from dbtemplates.loader import Loader from dbtemplates.models import Template, add_default_site -from dbtemplates.utils.cache import cache, get_cache_backend, get_cache_key, set_and_return -from dbtemplates.utils.template import (get_template_source, - check_template_syntax) +from dbtemplates.utils.cache import ( + cache, + get_cache_backend, + get_cache_key, + set_and_return, +) +from dbtemplates.utils.template import get_template_source, check_template_syntax from dbtemplates.management.commands import sync_templates @@ -33,7 +37,7 @@ def handle_add_default_site(sender, setting, value, **kwargs): @contextmanager def temptemplate(name: str, cleanup: bool = True): - temp_template_dir = Path(tempfile.mkdtemp('dbtemplates')) + temp_template_dir = Path(tempfile.mkdtemp("dbtemplates")) temp_template_path = temp_template_dir / name try: yield temp_template_path @@ -44,7 +48,9 @@ def temptemplate(name: str, cleanup: bool = True): class DbTemplatesCacheTestCase(TestCase): def test_set_and_return(self): self.assertTrue(bool(cache)) - rtn = set_and_return("this_is_the_cache_key", "cache test content", "cache display name") + rtn = set_and_return( + "this_is_the_cache_key", "cache test content", "cache display name" + ) self.assertEqual(rtn, ("cache test content", "cache display name")) self.assertEqual(cache.get("this_is_the_cache_key"), "cache test content") @@ -57,13 +63,13 @@ class BaseDbTemplatesTestCase(TestCase): ) def setUp(self): self.site1, created1 = Site.objects.get_or_create( - domain="example.com", name="example.com") + 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') + 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) @@ -71,33 +77,38 @@ class DbTemplatesLoaderTestCase(BaseDbTemplatesTestCase): def test_load_and_store_template(self): from django.template.loader import _engine_list from django.core.cache import CacheKeyWarning + loader = Loader(_engine_list()[0]) with self.assertWarns(CacheKeyWarning): - rtn = loader._load_and_store_template('base.html', 'base template cache key', self.site1) - self.assertEqual(rtn, ('base', f'dbtemplates:default:base.html:example.com')) + rtn = loader._load_and_store_template( + "base.html", "base template cache key", self.site1 + ) + self.assertEqual(rtn, ("base", "dbtemplates:default:base.html:example.com")) @override_settings(DBTEMPLATES_ADD_DEFAULT_SITE=False) def test_load_templates_sites(self): t_site1 = Template.objects.create( - name='copyright.html', content='(c) example.com') + 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') + name="copyright.html", content="(c) example.org" + ) t_site2.sites.add(self.site2) - new_site = Site.objects.create( - domain="example.net", name="example.net") + new_site = Site.objects.create(domain="example.net", name="example.net") with self.settings(SITE_ID=new_site.id): Site.objects.clear_cache() - self.assertRaises(TemplateDoesNotExist, - loader.get_template, "copyright.html") + self.assertRaises( + TemplateDoesNotExist, loader.get_template, "copyright.html" + ) def test_load_templates(self): result = loader.get_template("base.html").render() - self.assertEqual(result, 'base') + self.assertEqual(result, "base") result2 = loader.get_template("sub.html").render() - self.assertEqual(result2, 'sub') + self.assertEqual(result2, "sub") def test_cache_invalidation(self): # Add t1 into the cache of site2 @@ -123,52 +134,61 @@ class DbTemplatesLoaderTestCase(BaseDbTemplatesTestCase): class DbTemplatesModelsTestCase(BaseDbTemplatesTestCase): def test_basics(self): - self.assertQuerySetEqual(self.t1.sites.all(), Site.objects.filter(id=self.site1.id)) + self.assertQuerySetEqual( + self.t1.sites.all(), Site.objects.filter(id=self.site1.id) + ) self.assertIn("base", self.t1.content) self.assertEqual(str(self.t1), self.t1.name) self.assertEqual(str(self.t2), self.t2.name) - self.assertQuerySetEqual(Template.objects.filter(sites=self.site1), - Template.objects.filter(id__in=[self.t1.id, self.t2.id])) - self.assertQuerySetEqual(self.t2.sites.all(), Site.objects.filter(id__in=[self.site1.id, self.site2.id])) + self.assertQuerySetEqual( + Template.objects.filter(sites=self.site1), + Template.objects.filter(id__in=[self.t1.id, self.t2.id]), + ) + self.assertQuerySetEqual( + self.t2.sites.all(), + Site.objects.filter(id__in=[self.site1.id, self.site2.id]), + ) def test_populate(self): - t0 = Template.objects.create(name='header.html', content='