Compare commits

..

1 commit

Author SHA1 Message Date
Michał Pasternak
efc75968e9 4.0 release date 2022-08-29 03:36:35 +02:00
21 changed files with 121 additions and 241 deletions

View file

@ -3,4 +3,4 @@ source = dbtemplates
branch = 1 branch = 1
[report] [report]
omit = *tests*,*/migrations/*,test_* omit = *tests*,*migrations*

View file

@ -33,7 +33,7 @@ jobs:
- name: Upload packages to Jazzband - name: Upload packages to Jazzband
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1 uses: pypa/gh-action-pypi-publish@master
with: with:
user: jazzband user: jazzband
password: ${{ secrets.JAZZBAND_RELEASE_KEY }} password: ${{ secrets.JAZZBAND_RELEASE_KEY }}

View file

@ -7,9 +7,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 6 max-parallel: 5
matrix: matrix:
python-version: ['3.8', '3.9', '3.10', '3.11', '3.13', '3.14'] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11-dev']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View file

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.21.2 rev: v2.34.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py37-plus] args: [--py37-plus]

View file

@ -1,25 +0,0 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
formats:
- pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: requirements/docs.txt

View file

@ -1,3 +1,10 @@
import importlib.metadata from pkg_resources import get_distribution, DistributionNotFound
__version__ = importlib.metadata.version("django-dbtemplates") try:
__version__ = get_distribution("django-dbtemplates").version
except DistributionNotFound:
# package is not installed
__version__ = None
default_app_config = 'dbtemplates.apps.DBTemplatesConfig'

View file

@ -2,8 +2,15 @@ import posixpath
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.translation import gettext_lazy as _ try:
from django.utils.translation import ngettext # 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 django.utils.safestring import mark_safe
from dbtemplates.conf import settings from dbtemplates.conf import settings
@ -97,7 +104,6 @@ class TemplateAdminForm(forms.ModelForm):
class TemplateAdmin(TemplateModelAdmin): class TemplateAdmin(TemplateModelAdmin):
form = TemplateAdminForm form = TemplateAdminForm
readonly_fields = ['creation_date', 'last_changed']
fieldsets = ( fieldsets = (
(None, { (None, {
'fields': ('name', 'content'), 'fields': ('name', 'content'),

View file

@ -1,9 +1,10 @@
from django.apps import AppConfig from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _ try:
from django.utils.translation import ugettext_lazy as _
except ImportError:
from django.utils.translation import gettext_lazy as _
class DBTemplatesConfig(AppConfig): class DBTemplatesConfig(AppConfig):
name = 'dbtemplates' name = 'dbtemplates'
verbose_name = _('Database templates') verbose_name = _('Database templates')
default_auto_field = 'django.db.models.AutoField'

View file

@ -63,23 +63,6 @@ class Command(BaseCommand):
default=False, default=False,
help="Delete templates after syncing", help="Delete templates after syncing",
) )
group = parser.add_mutually_exclusive_group()
group.add_argument(
"-y",
"--yes",
action="store_true",
dest="auto_answer",
default=None,
help="Answer yes to all template creation questions."
)
group.add_argument(
"-n",
"--no",
action="store_false",
dest="auto_answer",
default=None,
help="Answer no to all template creation questions."
)
def handle(self, **options): def handle(self, **options):
extension = options.get("ext") extension = options.get("ext")
@ -87,7 +70,6 @@ class Command(BaseCommand):
overwrite = options.get("overwrite") overwrite = options.get("overwrite")
app_first = options.get("app_first") app_first = options.get("app_first")
delete = options.get("delete") delete = options.get("delete")
auto_answer = options.get('auto_answer')
if not extension.startswith("."): if not extension.startswith("."):
extension = f".{extension}" extension = f".{extension}"
@ -121,15 +103,12 @@ class Command(BaseCommand):
t = Template.on_site.get(name__exact=name) t = Template.on_site.get(name__exact=name)
except Template.DoesNotExist: except Template.DoesNotExist:
if not force: if not force:
if auto_answer is not None: confirm = input(
confirm = "y" if auto_answer else "n" "\nA '%s' template doesn't exist in the "
else: "database.\nCreate it with '%s'?"
confirm = input( " (y/[n]): "
"\nA '%s' template doesn't exist in the " "" % (name, path)
"database.\nCreate it with '%s'?" )
" (y/[n]): "
"" % (name, path)
)
if force or confirm.lower().startswith("y"): if force or confirm.lower().startswith("y"):
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
t = Template(name=name, content=f.read()) t = Template(name=name, content=f.read())

View file

@ -1,23 +0,0 @@
# Generated by Django 5.1 on 2025-05-26 19:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dbtemplates', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='template',
name='creation_date',
field=models.DateTimeField(auto_now_add=True, verbose_name='creation date'),
),
migrations.AlterField(
model_name='template',
name='last_changed',
field=models.DateTimeField(auto_now=True, verbose_name='last changed'),
),
]

View file

@ -1,20 +0,0 @@
# Generated by Django 4.2.23 on 2025-07-28 13:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("dbtemplates", "0002_alter_template_creation_date_and_more"),
]
operations = [
migrations.AlterField(
model_name="template",
name="id",
field=models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
]

View file

@ -1,16 +1,20 @@
from dbtemplates.conf import settings from dbtemplates.conf import settings
from dbtemplates.utils.cache import ( from dbtemplates.utils.cache import (add_template_to_cache,
add_template_to_cache, remove_cached_template)
remove_cached_template,
)
from dbtemplates.utils.template import get_template_source from dbtemplates.utils.template import get_template_source
from django.contrib.sites.managers import CurrentSiteManager from django.contrib.sites.managers import CurrentSiteManager
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.db import models from django.db import models
from django.db.models import signals from django.db.models import signals
from django.template import TemplateDoesNotExist from django.template import TemplateDoesNotExist
from django.utils.translation import gettext_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
class Template(models.Model): class Template(models.Model):
@ -18,15 +22,15 @@ class Template(models.Model):
Defines a template model for use with the database template loader. Defines a template model for use with the database template loader.
The field ``name`` is the equivalent to the filename of a static template. The field ``name`` is the equivalent to the filename of a static template.
""" """
id = models.BigAutoField(primary_key=True, verbose_name=_('ID'),
serialize=False, auto_created=True)
name = models.CharField(_('name'), max_length=100, name = models.CharField(_('name'), max_length=100,
help_text=_("Example: 'flatpages/default.html'")) help_text=_("Example: 'flatpages/default.html'"))
content = models.TextField(_('content'), blank=True) content = models.TextField(_('content'), blank=True)
sites = models.ManyToManyField(Site, verbose_name=_('sites'), sites = models.ManyToManyField(Site, verbose_name=_('sites'),
blank=True) blank=True)
creation_date = models.DateTimeField(_('creation date'), auto_now_add=True) creation_date = models.DateTimeField(_('creation date'),
last_changed = models.DateTimeField(_('last changed'), auto_now=True) default=now)
last_changed = models.DateTimeField(_('last changed'),
default=now)
objects = models.Manager() objects = models.Manager()
on_site = CurrentSiteManager('sites') on_site = CurrentSiteManager('sites')
@ -55,6 +59,7 @@ class Template(models.Model):
pass pass
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.last_changed = now()
# If content is empty look for a template with the given name and # If content is empty look for a template with the given name and
# populate the template instance with its content. # populate the template instance with its content.
if settings.DBTEMPLATES_AUTO_POPULATE_CONTENT and not self.content: if settings.DBTEMPLATES_AUTO_POPULATE_CONTENT and not self.content:

View file

@ -1,7 +1,6 @@
import os import os
import shutil import shutil
import tempfile import tempfile
from unittest import mock
from django.conf import settings as django_settings from django.conf import settings as django_settings
from django.core.cache.backends.base import BaseCache from django.core.cache.backends.base import BaseCache
@ -152,24 +151,3 @@ class DbTemplatesTestCase(TestCase):
def test_get_cache_name(self): def test_get_cache_name(self):
self.assertEqual(get_cache_key('name with spaces'), self.assertEqual(get_cache_key('name with spaces'),
'dbtemplates::name-with-spaces::1') '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

@ -21,10 +21,9 @@ def get_cache_backend():
cache = get_cache_backend() cache = get_cache_backend()
def get_cache_key(name, site=None): def get_cache_key(name):
if site is None: current_site = Site.objects.get_current()
site = Site.objects.get_current() return f"dbtemplates::{slugify(name)}::{current_site.pk}"
return f"dbtemplates::{slugify(name)}::{site.pk}"
def get_cache_notfound_key(name): def get_cache_notfound_key(name):
@ -58,5 +57,4 @@ def remove_cached_template(instance, **kwargs):
Called via Django's signals to remove cached templates, if the template Called via Django's signals to remove cached templates, if the template
in the database was changed or deleted. in the database was changed or deleted.
""" """
for site in instance.sites.all(): cache.delete(get_cache_key(instance.name))
cache.delete(get_cache_key(instance.name, site=site))

View file

@ -1,20 +1,7 @@
Changelog Changelog
========= =========
v5.0 (unreleased) v4.0 (2022-08-29)
-----------------
.. warning::
This is a backwards-incompatible release!
* Dropped support for Python 3.7 and Django < 4.2.
* Added support for Python 3.11, 3.12, 3.13, 3.14.
* Django 5.x and 6.0 support
v4.0 (2022-09-3)
----------------- -----------------
.. warning:: .. warning::

View file

@ -3,7 +3,8 @@ Setup
1. Get the source from the `Git repository`_ or install it from the 1. Get the source from the `Git repository`_ or install it from the
Python Package Index by running ``pip install django-dbtemplates``. Python Package Index by running ``pip install django-dbtemplates``.
2. Edit the settings.py of your Django site: 2. Follow the instructions in the INSTALL file
3. Edit the settings.py of your Django site:
* Add ``dbtemplates`` to the ``INSTALLED_APPS`` setting * Add ``dbtemplates`` to the ``INSTALLED_APPS`` setting
@ -60,8 +61,8 @@ Setup
from the database to be used to override templates in other locations, from the database to be used to override templates in other locations,
put ``dbtemplates.loader.Loader`` at the beginning of ``loaders``. put ``dbtemplates.loader.Loader`` at the beginning of ``loaders``.
3. Sync your database ``python manage.py migrate`` 4. Sync your database ``python manage.py migrate``
4. Restart your Django server 5. Restart your Django server
.. _Git repository: https://github.com/jazzband/django-dbtemplates/ .. _Git repository: https://github.com/jazzband/django-dbtemplates/

View file

@ -1,52 +0,0 @@
[build-system]
requires = [
"setuptools>=61.2",
"setuptools_scm",
]
build-backend = "setuptools.build_meta"
[project]
name = "django-dbtemplates"
authors = [{name = "Jannis Leidel", email = "jannis@leidel.info"}]
description = "Template loader for templates stored in the database"
readme = "README.rst"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Framework :: Django",
]
requires-python = ">=3.8"
dependencies = ["django-appconf >= 0.4"]
dynamic = ["version"]
[project.urls]
Documentation = "https://django-dbtemplates.readthedocs.io/"
Changelog = "https://django-dbtemplates.readthedocs.io/en/latest/changelog.html"
Source = "https://github.com/jazzband/django-dbtemplates"
[tool.setuptools]
zip-safe = false
include-package-data = false
[tool.setuptools.packages]
find = {namespaces = false}
[tool.setuptools.package-data]
dbtemplates = [
"locale/*/LC_MESSAGES/*",
"static/dbtemplates/css/*.css",
"static/dbtemplates/js/*.js",
]

View file

@ -1 +0,0 @@
django

4
setup.cfg Normal file
View file

@ -0,0 +1,4 @@
[build_sphinx]
source-dir = docs/
build-dir = docs/_build
all_files = 1

48
setup.py Normal file
View file

@ -0,0 +1,48 @@
import os
import io
from setuptools import setup, find_packages
def read(*parts):
filename = os.path.join(os.path.dirname(__file__), *parts)
with open(filename, encoding="utf-8") as fp:
return fp.read()
setup(
name="django-dbtemplates",
use_scm_version={"version_scheme": "post-release"},
setup_requires=["setuptools_scm"],
description="Template loader for templates stored in the database",
long_description=read("README.rst"),
author="Jannis Leidel",
author_email="jannis@leidel.info",
url="https://django-dbtemplates.readthedocs.io/",
packages=find_packages(),
zip_safe=False,
package_data={
"dbtemplates": [
"locale/*/LC_MESSAGES/*",
"static/dbtemplates/css/*.css",
"static/dbtemplates/js/*.js",
],
},
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.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Framework :: Django",
],
python_requires=">=3.7",
install_requires=["django-appconf >= 0.4"],
)

37
tox.ini
View file

@ -1,54 +1,41 @@
[tox] [tox]
minversion = 4.0 skipsdist = True
usedevelop = True
minversion = 1.8
envlist = envlist =
flake8 flake8
py3{8,9,10,11,12}-dj42 py3{7,8,9,10,11}-dj32
py3{10,11,12,13,14}-dj52 py3{8,9,10,11}-dj{40,41,main}
py3{12,13,14}-dj60
py3{12,13,14}-djmain
coverage
[gh-actions] [gh-actions]
python = python =
3.7: py37
3.8: py38 3.8: py38
3.9: py39 3.9: py39
3.10: py310 3.10: py310
3.10: py310, flake8 3.10: py310, flake8
3.11: py311 3.11: py311
3.12: py312
3.13: py313
3.14: py314
[testenv] [testenv]
skipsdist = true
package = editable
basepython = basepython =
py37: python3.7
py38: python3.8 py38: python3.8
py39: python3.9 py39: python3.9
py310: python3.10 py310: python3.10
py311: python3.11 py311: python3.11
py312: python3.12 usedevelop = true
py313: python3.13
py314: python3.14
setenv = setenv =
DJANGO_SETTINGS_MODULE = dbtemplates.test_settings DJANGO_SETTINGS_MODULE = dbtemplates.test_settings
deps = deps =
-r requirements/tests.txt -r requirements/tests.txt
dj42: Django>=4.2,<4.3 dj32: Django<3.3
dj52: Django>=5.2,<5.3 dj40: Django<4.1
dj60: Django>=6.0,<6.1 dj41: Django<4.2
djmain: https://github.com/django/django/archive/main.tar.gz#egg=django djmain: https://github.com/django/django/archive/main.tar.gz#egg=django
commands = commands =
python --version python --version
python -c "import django ; print(django.VERSION)" coverage run {envbindir}/django-admin test -v2 {posargs:dbtemplates}
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 report
coverage xml coverage xml