diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..1fa4289 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,6 @@ +[run] +source = dbtemplates +branch = 1 + +[report] +omit = *tests*,*migrations* diff --git a/.gitignore b/.gitignore index 9d5809f..5636b3d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ MANIFEST build dist *.egg-info -example/example.db docs/_build .tox/ *.egg/ diff --git a/.travis.yml b/.travis.yml index 7d2e5fc..126daef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,34 +1,32 @@ +language: python +python: 3.5 sudo: false cache: pip -language: python -python: -- 2.6 -- 2.7 -install: -- pip install -e . -- pip install -r requirements/tests.txt Django==$DJANGO -before_script: -- flake8 dbtemplates --ignore=E501 --exclude=migrations -script: -- coverage run --branch --source=dbtemplates `which django-admin.py` test dbtemplates -- coverage report --omit="dbtemplates/test*,dbtemplates/migrations*" env: - global: - - DJANGO_SETTINGS_MODULE=dbtemplates.test_settings - matrix: - - DJANGO="1.4.5 importlib" - - DJANGO="1.5.1 importlib" - - DJANGO=1.7.8 - - DJANGO=1.8.11 - - DJANGO=1.9.4 -matrix: - exclude: - - python: 2.6 - env: DJANGO=1.7.8 - - python: 2.6 - env: DJANGO=1.8.11 - - python: 2.6 - env: DJANGO=1.9.4 + - TOXENV=flake8-py27 + - TOXENV=flake8-py35 + - TOXENV=readme-py27 + - TOXENV=py27-dj18 + - TOXENV=py27-dj19 + - TOXENV=py27-dj110 + - TOXENV=py27-djmaster + - TOXENV=py34-dj18 + - TOXENV=py34-dj19 + - TOXENV=py34-dj110 + - TOXENV=py34-djmaster + - TOXENV=py35-dj18 + - TOXENV=py35-dj19 + - TOXENV=py35-dj110 + - TOXENV=py35-djmaster + - TOXENV=pypy-dj18 + - TOXENV=pypy-dj19 + - TOXENV=pypy-dj110 + - TOXENV=pypy-djmaster +install: + - pip install tox codecov +script: tox -v +after_success: + - codecov deploy: provider: pypi user: jazzband @@ -39,4 +37,4 @@ deploy: tags: true repo: jazzband/django-dbtemplates python: "2.7" - condition: "$DJANGO = 1.8.11" + condition: "$TOXENV = py27-dj110" diff --git a/LICENSE b/LICENSE index 00c83fd..4dbe380 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2007-2012, Jannis Leidel and contributors +Copyright (c) 2007-2016, Jannis Leidel and contributors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/dbtemplates/__init__.py b/dbtemplates/__init__.py index 974a8ff..f2dc0e4 100644 --- a/dbtemplates/__init__.py +++ b/dbtemplates/__init__.py @@ -1,2 +1 @@ -# following PEP 386 -__version__ = "1.3.2" +__version__ = "2.0" diff --git a/dbtemplates/loader.py b/dbtemplates/loader.py index 5d1de78..c542f7e 100644 --- a/dbtemplates/loader.py +++ b/dbtemplates/loader.py @@ -1,17 +1,12 @@ -import django from django.contrib.sites.models import Site from django.db import router from django.template import 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) -if django.VERSION[:2] >= (1, 8): - from django.template.loaders.base import Loader as BaseLoader -else: - from django.template.loader import BaseLoader # noqa - class Loader(BaseLoader): """ diff --git a/dbtemplates/management/commands/check_template_syntax.py b/dbtemplates/management/commands/check_template_syntax.py index 214f531..c82ea96 100644 --- a/dbtemplates/management/commands/check_template_syntax.py +++ b/dbtemplates/management/commands/check_template_syntax.py @@ -1,13 +1,13 @@ -from django.core.management.base import CommandError, NoArgsCommand +from django.core.management.base import CommandError, BaseCommand from dbtemplates.models import Template from dbtemplates.utils.template import check_template_syntax -class Command(NoArgsCommand): +class Command(BaseCommand): help = "Ensures templates stored in the database don't have syntax errors." - def handle_noargs(self, **options): + def handle(self, **options): errors = [] for template in Template.objects.all(): valid, error = check_template_syntax(template) @@ -16,6 +16,4 @@ class Command(NoArgsCommand): if errors: raise CommandError( 'Some templates contained errors\n%s' % '\n'.join(errors)) - # NOTE: printing instead of using self.stdout.write to maintain - # Django 1.2 compatibility - print('OK') + self.stdout.write('OK') diff --git a/dbtemplates/management/commands/create_error_templates.py b/dbtemplates/management/commands/create_error_templates.py index cbaa1d5..f09fe48 100644 --- a/dbtemplates/management/commands/create_error_templates.py +++ b/dbtemplates/management/commands/create_error_templates.py @@ -1,7 +1,5 @@ import sys -from optparse import make_option - -from django.core.management.base import CommandError, NoArgsCommand +from django.core.management.base import CommandError, BaseCommand from django.contrib.sites.models import Site from dbtemplates.models import Template @@ -26,14 +24,15 @@ TEMPLATES = { } -class Command(NoArgsCommand): +class Command(BaseCommand): help = "Creates the default error templates as database template objects." - option_list = NoArgsCommand.option_list + ( - make_option("-f", "--force", action="store_true", dest="force", - default=False, - help="overwrite existing database templates"),) - def handle_noargs(self, **options): + 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() diff --git a/dbtemplates/management/commands/sync_templates.py b/dbtemplates/management/commands/sync_templates.py index 3529e82..c4b616b 100644 --- a/dbtemplates/management/commands/sync_templates.py +++ b/dbtemplates/management/commands/sync_templates.py @@ -1,56 +1,54 @@ import os import codecs -from optparse import make_option -from django import VERSION from django.contrib.sites.models import Site -from django.core.management.base import CommandError, NoArgsCommand +from django.core.management.base import CommandError, BaseCommand +from django.template.utils import get_app_template_dirs +from django.template.loader import _engine_list try: from django.utils.six import input as raw_input except ImportError: pass -from dbtemplates.conf import settings from dbtemplates.models import Template ALWAYS_ASK, FILES_TO_DATABASE, DATABASE_TO_FILES = ('0', '1', '2') DIRS = [] - -if VERSION[:2] < (1, 8): - from django.template.loaders.app_directories import app_template_dirs - DIRS = settings.TEMPLATE_DIRS -else: - from django.template.utils import get_app_template_dirs - from django.template.loader import _engine_list - for engine in _engine_list(): - DIRS.extend(engine.dirs) - app_template_dirs = get_app_template_dirs('templates') +for engine in _engine_list(): + DIRS.extend(engine.dirs) +app_template_dirs = get_app_template_dirs('templates') -class Command(NoArgsCommand): +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"), - make_option("-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"), - make_option("-a", "--app-first", - action="store_true", dest="app_first", default=False, - help="look for templates in applications " - "directories before project templates"), - make_option("-d", "--delete", - action="store_true", dest="delete", default=False, - help="Delete templates after syncing")) - def handle_noargs(self, **options): + 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]") + 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') @@ -66,10 +64,6 @@ class Command(NoArgsCommand): 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.") - if app_first: tpl_dirs = app_template_dirs + DIRS else: diff --git a/dbtemplates/migrations/0001_initial.py b/dbtemplates/migrations/0001_initial.py index 4d912be..279daca 100644 --- a/dbtemplates/migrations/0001_initial.py +++ b/dbtemplates/migrations/0001_initial.py @@ -36,7 +36,7 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'templates', }, bases=(models.Model,), - managers = [ + managers=[ ('objects', django.db.models.manager.Manager()), ('on_site', django.contrib.sites.managers.CurrentSiteManager( b'sites')), diff --git a/dbtemplates/models.py b/dbtemplates/models.py index b8e668c..e459fe3 100644 --- a/dbtemplates/models.py +++ b/dbtemplates/models.py @@ -9,12 +9,7 @@ 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: - from django.utils.timezone import now -except ImportError: - from datetime import datetime - now = datetime.now +from django.utils.timezone import now class Template(models.Model): diff --git a/dbtemplates/south_migrations/0001_initial.py b/dbtemplates/south_migrations/0001_initial.py deleted file mode 100644 index 0e08603..0000000 --- a/dbtemplates/south_migrations/0001_initial.py +++ /dev/null @@ -1,55 +0,0 @@ -# encoding: utf-8 -import datetime -from south.db import db -from south.v2 import SchemaMigration -from django.db import models - - -class Migration(SchemaMigration): - - def forwards(self, orm): - - # Adding model 'Template' - db.create_table('django_template', ( - ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), - ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=100)), - ('content', self.gf('django.db.models.fields.TextField')(blank=True)), - ('creation_date', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - ('last_changed', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)), - )) - db.send_create_signal('dbtemplates', ['Template']) - - # Adding M2M table for field sites on 'Template' - db.create_table('django_template_sites', ( - ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)), - ('template', models.ForeignKey(orm['dbtemplates.template'], null=False)), - ('site', models.ForeignKey(orm['sites.site'], null=False)) - )) - db.create_unique('django_template_sites', ['template_id', 'site_id']) - - def backwards(self, orm): - # Deleting model 'Template' - db.delete_table('django_template') - - # Removing M2M table for field sites on 'Template' - db.delete_table('django_template_sites') - - models = { - 'dbtemplates.template': { - 'Meta': {'ordering': "('name',)", 'object_name': 'Template', 'db_table': "'django_template'"}, - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'last_changed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), - 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False'}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['dbtemplates'] diff --git a/dbtemplates/south_migrations/0002_auto__del_unique_template_name.py b/dbtemplates/south_migrations/0002_auto__del_unique_template_name.py deleted file mode 100644 index 418a3ab..0000000 --- a/dbtemplates/south_migrations/0002_auto__del_unique_template_name.py +++ /dev/null @@ -1,34 +0,0 @@ -# encoding: utf-8 -from south.db import db -from south.v2 import SchemaMigration - - -class Migration(SchemaMigration): - - def forwards(self, orm): - # Removing unique constraint on 'Template', fields ['name'] - db.delete_unique('django_template', ['name']) - - def backwards(self, orm): - # Adding unique constraint on 'Template', fields ['name'] - db.create_unique('django_template', ['name']) - - models = { - 'dbtemplates.template': { - 'Meta': {'ordering': "('name',)", 'object_name': 'Template', 'db_table': "'django_template'"}, - 'content': ('django.db.models.fields.TextField', [], {'blank': 'True'}), - 'creation_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'last_changed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'sites': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sites.Site']", 'symmetrical': 'False'}) - }, - 'sites.site': { - 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, - 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) - } - } - - complete_apps = ['dbtemplates'] diff --git a/dbtemplates/south_migrations/__init__.py b/dbtemplates/south_migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/dbtemplates/test_cases.py b/dbtemplates/test_cases.py index be9578e..53d2cdf 100644 --- a/dbtemplates/test_cases.py +++ b/dbtemplates/test_cases.py @@ -99,17 +99,17 @@ class DbTemplatesTestCase(TestCase): self.assertEqual(admin_base_template, template.content) def test_sync_templates(self): - old_template_dirs = settings.TEMPLATE_DIRS + 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 = codecs.open(temp_template_path, 'w') try: temp_template.write('temp test') - settings.TEMPLATE_DIRS = (temp_template_dir,) + 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.TEMPLATE_DIRS + sync_templates.DIRS = settings.TEMPLATES[0]['DIRS'] self.assertFalse( Template.objects.filter(name='temp_test.html').exists()) @@ -133,7 +133,7 @@ class DbTemplatesTestCase(TestCase): Template.objects.filter(name='temp_test.html').exists()) finally: temp_template.close() - settings.TEMPLATE_DIRS = old_template_dirs + settings.TEMPLATES[0]['DIRS'] = old_template_dirs shutil.rmtree(temp_template_dir) def test_get_cache(self): diff --git a/dbtemplates/test_settings.py b/dbtemplates/test_settings.py index 0b53c97..31a1946 100644 --- a/dbtemplates/test_settings.py +++ b/dbtemplates/test_settings.py @@ -1,6 +1,3 @@ - -import django - DBTEMPLATES_CACHE_BACKEND = 'dummy://' DATABASE_ENGINE = 'sqlite3' @@ -37,5 +34,11 @@ TEMPLATE_LOADERS = ( 'dbtemplates.loader.Loader', ) -if django.VERSION[:2] <= (1, 6): - TEST_RUNNER = 'discover_runner.DiscoverRunner' +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'loaders': TEMPLATE_LOADERS, + } + }, +] diff --git a/dbtemplates/utils/cache.py b/dbtemplates/utils/cache.py index 077c597..1d8b246 100644 --- a/dbtemplates/utils/cache.py +++ b/dbtemplates/utils/cache.py @@ -9,16 +9,10 @@ def get_cache_backend(): """ Compatibilty wrapper for getting Django's cache backend instance """ - try: - from django.core.cache import _create_cache - except ImportError: - # Django < 1.7 - from django.core.cache import get_cache as _get_cache - return _get_cache(settings.DBTEMPLATES_CACHE_BACKEND) - + from django.core.cache import _create_cache cache = _create_cache(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 + # 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 diff --git a/dbtemplates/utils/template.py b/dbtemplates/utils/template.py index 0f4dfb3..8fd74be 100644 --- a/dbtemplates/utils/template.py +++ b/dbtemplates/utils/template.py @@ -1,29 +1,14 @@ -from django import VERSION from django.template import (Template, TemplateDoesNotExist, TemplateSyntaxError) from importlib import import_module def get_loaders(): - if VERSION[:2] < (1, 8): - from django.template.loader import template_source_loaders - if template_source_loaders is None: - try: - from django.template.loader import find_template as finder - except ImportError: - from django.template.loader import find_template_source as finder # noqa - try: - source, name = finder('test') - except TemplateDoesNotExist: - pass - from django.template.loader import template_source_loaders - return template_source_loaders or [] - else: - from django.template.loader import _engine_list - loaders = [] - for engine in _engine_list(): - loaders.extend(engine.engine.template_loaders) - return 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): @@ -45,15 +30,6 @@ def get_template_source(name): pass except TemplateDoesNotExist: pass - if source is None and VERSION[:2] < (1, 2): - # Django supported template source extraction still :/ - try: - from django.template.loader import find_template_source - template, origin = find_template_source(name, None) - if not hasattr(template, 'render'): - return template - except (ImportError, TemplateDoesNotExist): - pass return None diff --git a/setup.py b/setup.py index 20a2a81..73d13e1 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,28 @@ +import ast import os -import re import codecs from setuptools import setup, find_packages +class VersionFinder(ast.NodeVisitor): + def __init__(self): + self.version = None + + def visit_Assign(self, node): + if node.targets[0].id == '__version__': + self.version = node.value.s + + def read(*parts): filename = os.path.join(os.path.dirname(__file__), *parts) with codecs.open(filename, encoding='utf-8') as fp: return fp.read() -def find_version(*file_paths): - version_file = read(*file_paths) - version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", - version_file, re.M) - if version_match: - return version_match.group(1) - raise RuntimeError("Unable to find version string.") +def find_version(*parts): + finder = VersionFinder() + finder.visit(ast.parse(read(*parts))) + return finder.version setup( @@ -27,7 +33,7 @@ setup( author='Jannis Leidel', author_email='jannis@leidel.info', url='https://django-dbtemplates.readthedocs.io/', - packages=find_packages(exclude=['example']), + packages=find_packages(), zip_safe=False, package_data={ 'dbtemplates': [ @@ -44,9 +50,10 @@ setup( 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.5', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', 'Framework :: Django', ], install_requires=['django-appconf >= 0.4'], diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..61f1bb6 --- /dev/null +++ b/tox.ini @@ -0,0 +1,46 @@ +[tox] +skipsdist = True +usedevelop = True +minversion = 1.8 +envlist = + flake8-py27, + flake8-py35, + readme-py27, + py{27,34,35,py}-dj{18,19,110,master} + +[testenv] +basepython = + py27: python2.7 + py34: python3.4 + py35: python3.5 + pypy: pypy +usedevelop = true +setenv = + DJANGO_SETTINGS_MODULE = dbtemplates.test_settings +deps = + -rrequirements/tests.txt + dj18: https://github.com/django/django/archive/stable/1.8.x.tar.gz#egg=django + dj19: https://github.com/django/django/archive/stable/1.9.x.tar.gz#egg=django + dj110: https://github.com/django/django/archive/stable/1.10.x.tar.gz#egg=django + djmaster: https://github.com/django/django/archive/master.tar.gz#egg=django + +commands = + python --version + coverage run {envbindir}/django-admin.py test -v2 {posargs:dbtemplates} + coverage report + +[testenv:readme-py27] +commands = python setup.py check -r -s +deps = readme_renderer + +[testenv:flake8-py27] +commands = flake8 dbtemplates +deps = flake8 + +[testenv:flake8-py35] +commands = flake8 dbtemplates +deps = flake8 + +[flake8] +exclude=.tox +ignore=E501,E127,E128,E124