Merge branch 'release/1.1'

This commit is contained in:
Jannis Leidel 2011-07-06 21:28:40 +02:00
commit f01a530200
32 changed files with 553 additions and 272 deletions

View file

@ -1,4 +1,4 @@
[django-dbtemplates.django]
[django-dbtemplates.main]
file_filter = dbtemplates/locale/<lang>/LC_MESSAGES/django.po
source_file = dbtemplates/locale/en/LC_MESSAGES/django.po
source_lang = en

View file

@ -1,4 +1,4 @@
Copyright (c) 2007-2010, Jannis Leidel and contributors
Copyright (c) 2007-2011, Jannis Leidel and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without

View file

@ -1,4 +1,4 @@
VERSION = (1, 0, 1, "f", 0) # following PEP 386
VERSION = (1, 1, 0, "f", 0) # following PEP 386
DEV_N = None

View file

@ -4,13 +4,13 @@ from django.contrib import admin
from django.utils.translation import ungettext, ugettext_lazy as _
from django.utils.safestring import mark_safe
from dbtemplates import settings
from dbtemplates.conf import settings
from dbtemplates.models import (Template,
remove_cached_template, add_template_to_cache)
# Check if django-reversion is installed and use reversions' VersionAdmin
# as the base admin class if yes
if settings.USE_REVERSION:
if settings.DBTEMPLATES_USE_REVERSION:
from reversion.admin import VersionAdmin as TemplateModelAdmin
else:
from django.contrib.admin import ModelAdmin as TemplateModelAdmin
@ -22,9 +22,9 @@ class CodeMirrorTextArea(forms.Textarea):
content field of the Template model.
"""
class Media:
css = dict(screen=[
posixpath.join(settings.MEDIA_PREFIX, 'css/editor.css')])
js = [posixpath.join(settings.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):
result = []
@ -43,15 +43,15 @@ class CodeMirrorTextArea(forms.Textarea):
lineNumbers: true
});
</script>
""" % dict(media_prefix=settings.MEDIA_PREFIX, name=name))
""" % dict(media_prefix=settings.DBTEMPLATES_MEDIA_PREFIX, name=name))
return mark_safe(u"".join(result))
if settings.USE_CODEMIRROR:
if settings.DBTEMPLATES_USE_CODEMIRROR:
TemplateContentTextArea = CodeMirrorTextArea
else:
TemplateContentTextArea = forms.Textarea
if settings.AUTO_POPULATE_CONTENT:
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.")
@ -86,8 +86,10 @@ class TemplateAdmin(TemplateModelAdmin):
'classes': ('collapse',),
}),
)
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']

43
dbtemplates/conf.py Normal file
View file

@ -0,0 +1,43 @@
import posixpath
from django.core.exceptions import ImproperlyConfigured
from dbtemplates.utils.settings import AppSettings
class DbTemplatesSettings(AppSettings):
USE_CODEMIRROR = False
USE_REVERSION = False
ADD_DEFAULT_SITE = True
AUTO_POPULATE_CONTENT = True
MEDIA_PREFIX = None
CACHE_BACKEND = None
def configure_media_prefix(self, value):
if value is None:
base_url = getattr(self, "STATIC_URL", None)
if base_url is None:
base_url = self.MEDIA_URL
value = posixpath.join(base_url, "dbtemplates/")
return value
def configure_cache_backend(self, value):
# If we are on Django 1.3 AND using the new CACHES setting..
if hasattr(self, "CACHES"):
if "dbtemplates" in self.CACHES:
return "dbtemplates"
else:
return "default"
if isinstance(value, basestring) and value.startswith("dbtemplates."):
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 self.INSTALLED_APPS:
raise ImproperlyConfigured("Please add 'reversion' to your "
"INSTALLED_APPS setting to make use of it in dbtemplates.")
return value
settings = DbTemplatesSettings("DBTEMPLATES")

View file

@ -1,14 +1,13 @@
import warnings
from django import VERSION
from django.conf import settings
from django.contrib.sites.models import Site
from django.template import TemplateDoesNotExist
from dbtemplates.conf import settings
from dbtemplates.models import Template
from dbtemplates.utils import cache, get_cache_key
from dbtemplates.utils.cache import cache, get_cache_key, set_and_return
from django.template.loader import BaseLoader
def load_template_source(template_name, template_dirs=None, annoy=True):
class Loader(BaseLoader):
"""
A custom template loader to load templates from the database.
@ -17,43 +16,29 @@ def load_template_source(template_name, template_dirs=None, annoy=True):
it falls back to query the database field ``name`` with the template path
and ``sites`` with the current site.
"""
if VERSION[:2] >= (1, 2) and annoy:
# For backward compatibility
warnings.warn(
"`dbtemplates.loader.load_template_source` is deprecated; "
"use `dbtemplates.loader.Loader` instead.", DeprecationWarning)
site = Site.objects.get_current()
display_name = 'db:%s:%s:%s' % (settings.DATABASE_ENGINE,
template_name, site.domain)
cache_key = get_cache_key(template_name)
if cache:
try:
backend_template = cache.get(cache_key)
if backend_template:
return backend_template, template_name
except:
pass
try:
template = Template.on_site.get(name__exact=template_name)
# Save in cache backend explicitly if manually deleted or invalidated
is_usable = True
def load_template_source(self, template_name, template_dirs=None):
site = Site.objects.get_current()
display_name = 'dbtemplates:%s:%s:%s' % (settings.DATABASE_ENGINE,
template_name, site.domain)
cache_key = get_cache_key(template_name)
if cache:
cache.set(cache_key, template.content)
return (template.content, display_name)
except:
pass
raise TemplateDoesNotExist(template_name)
load_template_source.is_usable = True
if VERSION[:2] >= (1, 2):
# providing a class based loader for Django >= 1.2, yay!
from django.template.loader import BaseLoader
class Loader(BaseLoader):
__doc__ = load_template_source.__doc__
is_usable = True
def load_template_source(self, template_name, template_dirs=None):
return load_template_source(
template_name, template_dirs, annoy=False)
try:
backend_template = cache.get(cache_key)
if backend_template:
return backend_template, template_name
except:
pass
try:
template = Template.objects.get(name__exact=template_name)
return set_and_return(cache_key, template.content, display_name)
except (Template.MultipleObjectsReturned, Template.DoesNotExist):
try:
template = Template.objects.get(
name__exact=template_name, sites__in=[site.id])
return set_and_return(
cache_key, template.content, display_name)
except Template.DoesNotExist:
pass
raise TemplateDoesNotExist(template_name)

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-11-07 00:02+0100\n"
"POT-Creation-Date: 2011-07-06 21:19+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -16,28 +16,28 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: admin.py:53
#: admin.py:55
msgid ""
"Leaving this empty causes Django to look for a template with the given name "
"and populate this field with its content."
msgstr ""
#: admin.py:77
#: admin.py:81
msgid "Advanced"
msgstr ""
#: admin.py:80
#: admin.py:84
msgid "Date/time"
msgstr ""
#: admin.py:98
#: admin.py:100
#, python-format
msgid "Cache of one template successfully invalidated."
msgid_plural "Cache of %(count)d templates successfully invalidated."
msgstr[0] ""
msgstr[1] ""
#: admin.py:102
#: admin.py:104
msgid "Invalidate cache of selected templates"
msgstr ""
@ -52,34 +52,34 @@ msgstr[1] ""
msgid "Repopulate cache with selected templates"
msgstr ""
#: admin.py:119 models.py:29
#: admin.py:120 models.py:25
msgid "sites"
msgstr ""
#: models.py:26
#: models.py:22
msgid "name"
msgstr ""
#: models.py:27
#: models.py:23
msgid "Example: 'flatpages/default.html'"
msgstr ""
#: models.py:28
#: models.py:24
msgid "content"
msgstr ""
#: models.py:30
#: models.py:27
msgid "creation date"
msgstr ""
#: models.py:32
#: models.py:29
msgid "last changed"
msgstr ""
#: models.py:40
#: models.py:37
msgid "template"
msgstr ""
#: models.py:41
#: models.py:38
msgid "templates"
msgstr ""

View file

@ -1,15 +1,15 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
# Ville Säävuori <ville@syneus.fi>, 2011.
msgid ""
msgstr ""
"Project-Id-Version: django-dbtemplates\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Jaakko Holster <holster@iki.fi>\n"
"POT-Creation-Date: 2011-01-31 11:10+0100\n"
"PO-Revision-Date: 2011-01-31 10:08+0000\n"
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
"POT-Creation-Date: 2010-11-07 00:02+0100\n"
"PO-Revision-Date: 2011-06-19 11:22+0000\n"
"Last-Translator: Uninen <ville@syneus.fi>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -22,8 +22,8 @@ msgid ""
"Leaving this empty causes Django to look for a template with the given name "
"and populate this field with its content."
msgstr ""
"Mikäli kenttä on tyhjä, Django etsii samannimistä sivupohjaa ja täyttää "
"kentän sen sisällöllä."
"Jos tämä jätetään tyhjäksi, Django etsiin annetulla nimellä olevan "
"mallipohjan ja täyttää tähän kenttään sen sisällön."
#: admin.py:77
msgid "Advanced"
@ -37,25 +37,25 @@ msgstr "Päiväys/aika"
#, python-format
msgid "Cache of one template successfully invalidated."
msgid_plural "Cache of %(count)d templates successfully invalidated."
msgstr[0] "Sivupohja poistettu välimuistista."
msgstr[1] "%(count)d sivupohjaa poistettu välimuistista."
msgstr[0] "Yhden mallipohjan välimuisti on onnistuneesti tyhjennetty."
msgstr[1] "%(count)d mallipohjan välimusti on onnistuneesti tyhjennetty."
#: admin.py:102
msgid "Invalidate cache of selected templates"
msgstr "Poista valitut sivupohjat välimuistista."
msgstr "Tyhjennä valittujen mallipohjien välimuisti."
#: admin.py:111
#, python-format
msgid "Cache successfully repopulated with one template."
msgid_plural "Cache successfully repopulated with %(count)d templates."
msgstr[0] "Sivupohja lisätty välimuistiin."
msgstr[1] "%(count)d sivupohjaa lisätty välimuistiin."
msgstr[0] "Yhden mallipohjan välimuisti on täytetty onnistuneesti."
msgstr[1] "%(count)d mallipohjan välimuisti on täytetty onnistuneesti."
#: admin.py:115
msgid "Repopulate cache with selected templates"
msgstr "Lisää valitut sivupohjat välimuistiin."
msgstr "Täytä valittujen mallipohjien välimuisti."
#: admin.py:119
#: admin.py:119 models.py:29
msgid "sites"
msgstr "sivustot"
@ -65,24 +65,26 @@ msgstr "nimi"
#: models.py:27
msgid "Example: 'flatpages/default.html'"
msgstr "Esimerkki: 'flatpages/default.html'"
msgstr "Esimerkiksi: 'flatpages/default.html'"
#: models.py:28
msgid "content"
msgstr "sisältö"
msgstr "sisätö"
#: models.py:30
msgid "creation date"
msgstr "luotu"
msgstr "luontipäivä"
#: models.py:32
msgid "last changed"
msgstr "muokattu"
msgstr "viimeksi muutettu"
#: models.py:40
msgid "template"
msgstr "sivupohja"
msgstr "mallipohja"
#: models.py:41
msgid "templates"
msgstr "sivupohjat"
msgstr "mallipohjat"

View file

@ -1,16 +1,16 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#
# Herson Hersonls <hersonls@gmail.com>, 2011.
msgid ""
msgstr ""
"Project-Id-Version: django-dbtemplates\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Diego Búrigo Zacarão <diegobz@fedoraproject.org>\n"
"Language-Team: Brazilian Portuguese <fedora-trans-pt_br@redhat.com>\n"
"POT-Creation-Date: 2011-01-31 11:10+0100\n"
"PO-Revision-Date: 2011-01-31 10:08+0000\n"
"Report-Msgid-Bugs-To: https://github.com/jezdez/django-dbtemplates/issues\n"
"POT-Creation-Date: 2010-11-07 00:02+0100\n"
"PO-Revision-Date: 2011-06-01 18:11+0000\n"
"Last-Translator: hersonls <hersonls@gmail.com>\n"
"Language-Team: Portuguese (Brazilian) (http://www.transifex.net/projects/p/django-dbtemplates/team/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@ -27,7 +27,7 @@ msgstr ""
#: admin.py:77
msgid "Advanced"
msgstr ""
msgstr "Avançado"
#: admin.py:80
msgid "Date/time"
@ -86,3 +86,5 @@ msgstr "modelo"
#: models.py:41
msgid "templates"
msgstr "modelos"

View file

@ -32,7 +32,7 @@ msgid "Date/time"
msgstr "日期/时间"
#: admin.py:98
#, python-format
#, fuzzy, python-format
msgid "Cache of one template successfully invalidated."
msgid_plural "Cache of %(count)d templates successfully invalidated."
msgstr[0] "该模板的缓存已经成功撤销。"
@ -42,7 +42,7 @@ msgid "Invalidate cache of selected templates"
msgstr "撤销选中模板的缓存"
#: admin.py:111
#, python-format
#, fuzzy, python-format
msgid "Cache successfully repopulated with one template."
msgid_plural "Cache successfully repopulated with %(count)d templates."
msgstr[0] "该模板的缓存已经成功启用。"

View file

@ -1,11 +1,12 @@
import os
import codecs
from optparse import make_option
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management.base import CommandError, NoArgsCommand
from django.template.loaders.app_directories import app_template_dirs
from dbtemplates.conf import settings
from dbtemplates.models import Template
ALWAYS_ASK, FILES_TO_DATABASE, DATABASE_TO_FILES = ('0', '1', '2')
@ -24,13 +25,16 @@ class Command(NoArgsCommand):
"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"))
"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):
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 = ".%s" % extension
@ -53,10 +57,12 @@ class Command(NoArgsCommand):
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(templatedir)[1][1:]
name = path.split(templatedir)[1]
if name.startswith('/'):
name = name[1:]
try:
t = Template.on_site.get(name__exact=name)
except Template.DoesNotExist:
@ -67,7 +73,7 @@ class Command(NoArgsCommand):
" (y/[n]): """ % (name, path))
if force or confirm.lower().startswith('y'):
t = Template(name=name,
content=open(path, "r").read())
content=codecs.open(path, "r").read())
t.save()
t.sites.add(site)
else:
@ -83,16 +89,23 @@ class Command(NoArgsCommand):
path, t.__repr__()))
else:
confirm = overwrite
if confirm == '' or confirm in (
FILES_TO_DATABASE, DATABASE_TO_FILES):
if confirm in ('', FILES_TO_DATABASE, DATABASE_TO_FILES):
if confirm == FILES_TO_DATABASE:
t.content = open(path, 'r').read()
t.content = codecs.open(path, 'r').read()
t.save()
t.sites.add(site)
if delete:
try:
os.remove(path)
except OSError:
raise CommandError(
u"Couldn't delete %s" % path)
elif confirm == DATABASE_TO_FILES:
try:
f = open(path, 'w')
f = codecs.open(path, 'w')
f.write(t.content)
finally:
f.close()
if delete:
t.delete()
break

View file

@ -0,0 +1,57 @@
# 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']

View file

@ -0,0 +1,39 @@
# 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):
# 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']

View file

View file

@ -9,9 +9,9 @@ from django.utils.translation import gettext_lazy as _
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
from dbtemplates import settings
from dbtemplates.utils import (add_default_site, add_template_to_cache,
remove_cached_template, get_template_source)
from dbtemplates.conf import settings
from dbtemplates.utils.cache import add_template_to_cache, remove_cached_template
from dbtemplates.utils.template import get_template_source
class Template(models.Model):
@ -19,10 +19,11 @@ 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.
"""
name = models.CharField(_('name'), unique=True, max_length=100,
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'))
sites = models.ManyToManyField(Site, verbose_name=_('sites'),
blank=True, null=True)
creation_date = models.DateTimeField(_('creation date'),
default=datetime.now)
last_changed = models.DateTimeField(_('last changed'),
@ -58,11 +59,24 @@ class Template(models.Model):
self.last_changed = datetime.now()
# If content is empty look for a template with the given name and
# populate the template instance with its content.
if settings.AUTO_POPULATE_CONTENT and not self.content:
if settings.DBTEMPLATES_AUTO_POPULATE_CONTENT and not self.content:
self.populate()
super(Template, self).save(*args, **kwargs)
def add_default_site(instance, **kwargs):
"""
Called via Django's signals to cache the templates, if the template
in the database was added or changed, only if
DBTEMPLATES_ADD_DEFAULT_SITE setting is set.
"""
if not settings.DBTEMPLATES_ADD_DEFAULT_SITE:
return
current_site = Site.objects.get_current()
if current_site not in instance.sites.all():
instance.sites.add(current_site)
signals.post_save.connect(add_default_site, sender=Template)
signals.post_save.connect(add_template_to_cache, sender=Template)
signals.pre_delete.connect(remove_cached_template, sender=Template)

View file

@ -1,36 +0,0 @@
import posixpath
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
if "dbtemplates" in getattr(settings, "CACHES", {}):
# If we are on Django 1.3 AND using the new CACHES setting..
cache = "dbtemplates"
else:
# ..or fall back to the old CACHE_BACKEND setting
cache = getattr(settings, "DBTEMPLATES_CACHE_BACKEND", None)
if not cache:
raise ImproperlyConfigured("Please specify a dbtemplates "
"cache backend in your settings.")
elif isinstance(cache, basestring) and cache.startswith("dbtemplates."):
raise ImproperlyConfigured("Please upgrade to one of the supported "
"backends as defined in the Django docs.")
CACHE_BACKEND = cache
ADD_DEFAULT_SITE = getattr(settings, 'DBTEMPLATES_ADD_DEFAULT_SITE', True)
AUTO_POPULATE_CONTENT = getattr(
settings, 'DBTEMPLATES_AUTO_POPULATE_CONTENT', True)
base_url = getattr(settings, "STATIC_URL", None)
if base_url is None:
base_url = settings.MEDIA_URL
MEDIA_PREFIX = getattr(settings, 'DBTEMPLATES_MEDIA_PREFIX',
posixpath.join(base_url, "dbtemplates/"))
USE_REVERSION = getattr(settings, 'DBTEMPLATES_USE_REVERSION', False)
if USE_REVERSION and 'reversion'not in settings.INSTALLED_APPS:
raise ImproperlyConfigured("Please add reversion to your "
"INSTALLED_APPS setting to make use of it in dbtemplates.")
USE_CODEMIRROR = getattr(settings, 'DBTEMPLATES_USE_CODEMIRROR', False)

View file

@ -1,66 +1,93 @@
from django import VERSION
from __future__ import with_statement
import codecs
import os
import shutil
import tempfile
from django.core.management import call_command
from django.template import loader, Context
from django.test import TestCase
from django.contrib.sites.models import Site
from dbtemplates import settings
from dbtemplates.loader import load_template_source
from dbtemplates.models import Template, get_template_source
from dbtemplates.conf import settings
from dbtemplates.models import Template
from dbtemplates.utils.template import get_template_source
from dbtemplates.management.commands.sync_templates import (FILES_TO_DATABASE,
DATABASE_TO_FILES)
class DbTemplatesTestCase(TestCase):
def setUp(self):
self.site1, created1 = Site.objects.get_or_create(domain="example.com", name="example.com")
self.site2, created2 = Site.objects.get_or_create(domain="example.org", name="example.org")
self.site1, created1 = Site.objects.get_or_create(
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')
self.t2.sites.add(self.site2)
def test_basiscs(self):
t1 = Template(name='base.html', content='<html><head></head><body>{% block content %}Welcome at {{ title }}{% endblock %}</body></html>')
t1.save()
self.assertEqual(Site.objects.get_current(), t1.sites.all()[0])
self.assertTrue("Welcome at" in t1.content)
self.assertEqual(list(self.t1.sites.all()), [self.site1])
self.assertTrue("base" in self.t1.content)
self.assertEqual(list(Template.objects.filter(sites=self.site1)),
[self.t1, self.t2])
self.assertEqual(list(self.t2.sites.all()), [self.site1, self.site2])
t2 = Template(name='sub.html', content='{% extends "base.html" %}{% block content %}This is {{ title }}{% endblock %}')
t2.save()
t2.sites.add(self.site2)
self.assertEqual(list(Template.objects.filter(sites=self.site1)), [t1, t2])
self.assertEqual(list(t2.sites.all()), [self.site1, self.site2])
def test_empty_sites(self):
old_add_default_site = settings.DBTEMPLATES_ADD_DEFAULT_SITE
try:
settings.DBTEMPLATES_ADD_DEFAULT_SITE = False
self.t3 = Template.objects.create(
name='footer.html', content='footer')
self.assertEqual(list(self.t3.sites.all()), [])
finally:
settings.DBTEMPLATES_ADD_DEFAULT_SITE = old_add_default_site
def test_load_templates(self):
self.test_basiscs()
original_template_source_loaders = loader.template_source_loaders
loader.template_source_loaders = [load_template_source]
try:
result1 = loader.get_template("base.html").render(Context({'title':'MainPage'}))
self.assertEqual(result1, u'<html><head></head><body>Welcome at MainPage</body></html>')
result2 = loader.get_template("sub.html").render(Context({'title':'SubPage'}))
self.assertEqual(result2, u'<html><head></head><body>This is SubPage</body></html>')
if VERSION[:2] >= (1, 2):
from dbtemplates.loader import Loader
dbloader = Loader()
loader.template_source_loaders = [dbloader.load_template_source]
result = loader.get_template("base.html").render(Context({'title':'MainPage'}))
self.assertEqual(result, u'<html><head></head><body>Welcome at MainPage</body></html>')
result2 = loader.get_template("sub.html").render(Context({'title':'SubPage'}))
self.assertEqual(result2, u'<html><head></head><body>This is SubPage</body></html>')
finally:
loader.template_source_loaders = original_template_source_loaders
result = loader.get_template("base.html").render(Context({}))
self.assertEqual(result, 'base')
result2 = loader.get_template("sub.html").render(Context({}))
self.assertEqual(result2, 'sub')
def test_error_templates_creation(self):
call_command('create_error_templates', force=True, verbosity=0)
self.assertEqual(list(Template.objects.filter(sites=self.site1)),
list(Template.objects.filter()))
def test_disabling_default_site(self):
old_add_default_site = settings.ADD_DEFAULT_SITE
settings.ADD_DEFAULT_SITE = False
t3 = Template.objects.create(name='footer.html', content='ohai')
self.assertEqual(list(t3.sites.all()), [])
settings.ADD_DEFAULT_SITE = old_add_default_site
list(Template.objects.filter()))
self.assertTrue(Template.objects.filter(name='404.html').exists())
def test_automatic_sync(self):
admin_base_template = get_template_source('admin/base.html')
template = Template.objects.create(name='admin/base.html')
self.assertEqual(admin_base_template, template.content)
def test_sync_templates(self):
old_template_dirs = settings.TEMPLATE_DIRS
temp_template_dir = tempfile.mkdtemp('dbtemplates')
last_path_part = temp_template_dir.split('/')[-1]
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,)
self.assertFalse(Template.objects.filter(name='temp_test.html').exists())
call_command('sync_templates',
force=True, verbosity=0, overwrite=FILES_TO_DATABASE)
self.assertTrue(Template.objects.filter(name='temp_test.html').exists())
t = Template.objects.get(name='temp_test.html')
t.content = 'temp test modified'
t.save()
call_command('sync_templates',
force=True, verbosity=0, overwrite=DATABASE_TO_FILES)
self.assertTrue('modified' in codecs.open(temp_template_path).read())
call_command('sync_templates',
force=True, verbosity=0, delete=True, overwrite=DATABASE_TO_FILES)
self.assertTrue(os.path.exists(temp_template_path))
self.assertFalse(Template.objects.filter(name='temp_test.html').exists())
finally:
temp_template.close()
settings.TEMPLATE_DIRS = old_template_dirs
shutil.rmtree(temp_template_dir)

View file

View file

@ -0,0 +1,40 @@
from django.core.cache import get_cache
from django.contrib.sites.models import Site
from dbtemplates.conf import settings
def get_cache_backend():
return get_cache(settings.CACHE_BACKEND)
cache = get_cache_backend()
def get_cache_key(name):
current_site = Site.objects.get_current()
return 'dbtemplates::%s::%s' % (name, current_site.pk)
def set_and_return(cache_key, content, display_name):
# Save in cache backend explicitly if manually deleted or invalidated
if cache:
cache.set(cache_key, content)
return (content, display_name)
def add_template_to_cache(instance, **kwargs):
"""
Called via Django's signals to cache the templates, if the template
in the database was added or changed.
"""
remove_cached_template(instance)
cache.set(get_cache_key(instance.name), instance.content)
def remove_cached_template(instance, **kwargs):
"""
Called via Django's signals to remove cached templates, if the template
in the database was changed or deleted.
"""
cache.delete(get_cache_key(instance.name))

View file

@ -0,0 +1,106 @@
from inspect import getmembers
from django.conf import settings
class AppSettings(object):
"""
An app setting object to be used for handling app setting defaults
gracefully and providing a nice API for them. Say you have an app
called ``myapp`` and want to define a few defaults, and refer to the
defaults easily in the apps code. Add a ``settings.py`` to your app::
from path.to.utils import AppSettings
class MyAppSettings(AppSettings):
SETTING_1 = "one"
SETTING_2 = (
"two",
)
Then initialize the setting with the correct prefix in the location of
of your choice, e.g. ``conf.py`` of the app module::
settings = MyAppSettings(prefix="MYAPP")
The ``MyAppSettings`` instance will automatically look at Django's
global setting to determine each of the settings and respect the
provided ``prefix``. E.g. adding this to your site's ``settings.py``
will set the ``SETTING_1`` setting accordingly::
MYAPP_SETTING_1 = "uno"
Usage
-----
Instead of using ``from django.conf import settings`` as you would
usually do, you can switch to using your apps own settings module
to access the app settings::
from myapp.conf import settings
print myapp_settings.MYAPP_SETTING_1
``AppSettings`` instances also work as pass-throughs for other
global settings that aren't related to the app. For example the
following code is perfectly valid::
from myapp.conf import settings
if "myapp" in settings.INSTALLED_APPS:
print "yay, myapp is installed!"
Custom handling
---------------
Each of the settings can be individually configured with callbacks.
For example, in case a value of a setting depends on other settings
or other dependencies. The following example sets one setting to a
different value depending on a global setting::
from django.conf import settings
class MyCustomAppSettings(AppSettings):
ENABLED = True
def configure_enabled(self, value):
return value and not self.DEBUG
custom_settings = MyCustomAppSettings("MYAPP")
The value of ``custom_settings.MYAPP_ENABLED`` will vary depending on the
value of the global ``DEBUG`` setting.
Each of the app settings can be customized by providing
a method ``configure_<lower_setting_name>`` that takes the default
value as defined in the class attributes as the only parameter.
The method needs to return the value to be use for the setting in
question.
"""
def __dir__(self):
return sorted(list(set(self.__dict__.keys() + dir(settings))))
__members__ = lambda self: self.__dir__()
def __getattr__(self, name):
if name.startswith(self._prefix):
raise AttributeError("%r object has no attribute %r" %
(self.__class__.__name__, name))
return getattr(settings, name)
def __setattr__(self, name, value):
super(AppSettings, self).__setattr__(name, value)
if name in dir(settings):
setattr(settings, name, value)
def __init__(self, prefix):
super(AppSettings, self).__setattr__('_prefix', prefix)
for setting, class_value in getmembers(self.__class__):
if setting == setting.upper():
prefixed = "%s_%s" % (prefix.upper(), setting.upper())
configured_value = getattr(settings, prefixed, class_value)
callback_name = "configure_%s" % setting.lower()
callback = getattr(self, callback_name, None)
if callable(callback):
configured_value = callback(configured_value)
delattr(self.__class__, setting)
setattr(self, prefixed, configured_value)

View file

@ -1,52 +1,7 @@
from django import VERSION
from django.core.cache import get_cache
from django.template import TemplateDoesNotExist
from django.utils.importlib import import_module
from django.contrib.sites.models import Site
from dbtemplates import settings
def get_cache_backend():
return get_cache(settings.CACHE_BACKEND)
cache = get_cache_backend()
def add_default_site(instance, **kwargs):
"""
Called via Django's signals to cache the templates, if the template
in the database was added or changed, only if DBTEMPLATES_ADD_DEFAULT_SITE
setting is set.
"""
if settings.ADD_DEFAULT_SITE:
current_site = Site.objects.get_current()
if current_site not in instance.sites.all():
instance.sites.add(current_site)
def get_cache_key(name):
current_site = Site.objects.get_current()
return 'dbtemplates::%s::%s' % (name, current_site.pk)
def add_template_to_cache(instance, **kwargs):
"""
Called via Django's signals to cache the templates, if the template
in the database was added or changed.
"""
remove_cached_template(instance)
cache.set(get_cache_key(instance.name), instance.content)
def remove_cached_template(instance, **kwargs):
"""
Called via Django's signals to remove cached templates, if the template
in the database was changed or deleted.
"""
cache.delete(get_cache_key(instance.name))
def get_loaders():
from django.template.loader import template_source_loaders
@ -79,6 +34,8 @@ def get_template_source(name):
source, origin = load_template_source(name)
if source:
return source
except NotImplementedError:
pass
except TemplateDoesNotExist:
pass
if source is None and VERSION[:2] < (1, 2):

View file

@ -1,6 +1,52 @@
Changelog
=========
1.1 (07-06-11)
--------------
* **BACKWARDS-INCOMPATIBLE** Requires Django 1.2 or higher.
For previous Django versions use an older versions of dbtemplates, e.g.::
$ pip install django-dbtemplates<1.1
* Added South migrations.
.. note::
If you are using South in your Django project, you can easily enable
dbtemplates' migrations, *faking* the first migration by using the
``--fake`` option of South's ``migrate`` management command::
$ manage.py migrate --fake 0001 dbtemplates
Then run the rest of the migrations::
$ manage.py migrate dbtemplates
* Removed uniqueness on the ``name`` field of the ``Template`` model. This is
needed because there isn't a ``unique_together`` for M2M fields in Django
such as the ``sites`` field in the ``Template`` model.
* Made the ``sites`` field optional to support a way to apply a template to
all sites.
* Added ``--delete`` option to ``sync_templates`` managment command to delete
the file or database entry after syncing (depending on used ``--overwrite``
mode).
* Updated translations.
* Fixed issue with incorrectly splitting paths in ``sync_templates``.
* Extended tests.
* Fixed issue with cache settings handling.
1.0.1 (04-14-11)
----------------
* Minor bugfixes with regard to the new cache handling.
1.0 (04-11-11)
--------------

View file

@ -38,16 +38,16 @@ master_doc = 'index'
# General information about the project.
project = u'django-dbtemplates'
copyright = u'2010, Jannis Leidel'
copyright = u'2007-2011, Jannis Leidel'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '1.0'
version = '1.1'
# The full version, including alpha/beta/rc tags.
release = '1.0.1'
release = '1.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View file

@ -1 +1,2 @@
django-staticfiles
south
django>=1.3

View file

@ -70,10 +70,10 @@ SECRET_KEY = 'e-%(1e1f8ar2v)_8d!%-75a2ag(w(ht*l%n-wts5$li!5=97)8'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
'django.template.loaders.eggs.load_template_source',
'dbtemplates.loader.load_template_source',
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
'django.template.loaders.eggs.Loader',
'dbtemplates.loader.Loader',
)
MIDDLEWARE_CLASSES = (
@ -98,8 +98,9 @@ INSTALLED_APPS = (
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.flatpages',
'django.contrib.staticfiles',
'dbtemplates',
'staticfiles',
'south',
#'reversion',
)

View file

@ -16,9 +16,3 @@ urlpatterns = patterns('',
# Uncomment the next line to enable the admin:
(r'^admin/', include(admin.site.urls)),
)
# the following is used to serve up local media files like images
if settings.DEBUG:
urlpatterns += patterns("",
(r"", include("staticfiles.urls")),
)

View file

@ -15,6 +15,11 @@ if not settings.configured:
'django.contrib.admin',
'dbtemplates',
],
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
'dbtemplates.loader.Loader',
)
)
from django.test.simple import run_tests

17
tox.ini
View file

@ -2,7 +2,6 @@
downloadcache = .tox/_download/
distribute = False
envlist =
py25-1.1.X, py26-1.1.X, py27-1.1.X,
py25-1.2.X, py26-1.2.X, py27-1.2.X,
py25-1.3.X, py26-1.3.X, py27-1.3.X
@ -10,22 +9,6 @@ envlist =
commands =
python runtests.py
[testenv:py25-1.1.X]
basepython = python2.5
deps =
django==1.1.4
[testenv:py26-1.1.X]
basepython = python2.6
deps =
django==1.1.4
[testenv:py27-1.1.X]
basepython = python2.7
deps =
django==1.1.4
[testenv:py25-1.2.X]
basepython = python2.5
deps =