diff --git a/flatblocks/admin.py b/flatblocks/admin.py index d54e68b..ae048c2 100644 --- a/flatblocks/admin.py +++ b/flatblocks/admin.py @@ -4,7 +4,8 @@ class FlatBlockAdmin(admin.ModelAdmin): ordering = ['slug', ] - list_display = ('slug', 'header') - search_fields = ('slug', 'header', 'content') + list_display = ('slug', 'header', 'site', ) + list_filter = ('site', ) + search_fields = ('slug', 'header', 'content', 'site__domain', 'site__name', ) admin.site.register(FlatBlock, FlatBlockAdmin) diff --git a/flatblocks/locale/de/LC_MESSAGES/django.mo b/flatblocks/locale/de/LC_MESSAGES/django.mo index d45ed53..ffe488c 100644 Binary files a/flatblocks/locale/de/LC_MESSAGES/django.mo and b/flatblocks/locale/de/LC_MESSAGES/django.mo differ diff --git a/flatblocks/locale/no/LC_MESSAGES/django.mo b/flatblocks/locale/no/LC_MESSAGES/django.mo index dea4539..5b9e428 100644 Binary files a/flatblocks/locale/no/LC_MESSAGES/django.mo and b/flatblocks/locale/no/LC_MESSAGES/django.mo differ diff --git a/flatblocks/locale/ru/LC_MESSAGES/django.mo b/flatblocks/locale/ru/LC_MESSAGES/django.mo index 59a9187..dc0f3b7 100644 Binary files a/flatblocks/locale/ru/LC_MESSAGES/django.mo and b/flatblocks/locale/ru/LC_MESSAGES/django.mo differ diff --git a/flatblocks/migrations/0002_auto__add_field_flatblock_site__del_unique_flatblock_slug__add_unique_.py b/flatblocks/migrations/0002_auto__add_field_flatblock_site__del_unique_flatblock_slug__add_unique_.py new file mode 100644 index 0000000..8c2195b --- /dev/null +++ b/flatblocks/migrations/0002_auto__add_field_flatblock_site__del_unique_flatblock_slug__add_unique_.py @@ -0,0 +1,55 @@ +# -*- coding: 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 'FlatBlock', fields ['slug'] + db.delete_unique('flatblocks_flatblock', ['slug']) + + # Adding field 'FlatBlock.site' + db.add_column('flatblocks_flatblock', 'site', + self.gf('django.db.models.fields.related.ForeignKey')(default=1, related_name='flatblocks', to=orm['sites.Site']), + keep_default=False) + + # Adding index on 'FlatBlock', fields ['slug'] + db.create_index('flatblocks_flatblock', ['slug']) + + # Adding unique constraint on 'FlatBlock', fields ['slug', 'site'] + db.create_unique('flatblocks_flatblock', ['slug', 'site_id']) + + def backwards(self, orm): + # Removing unique constraint on 'FlatBlock', fields ['slug', 'site'] + db.delete_unique('flatblocks_flatblock', ['slug', 'site_id']) + + # Removing index on 'FlatBlock', fields ['slug'] + db.delete_index('flatblocks_flatblock', ['slug']) + + # Deleting field 'FlatBlock.site' + db.delete_column('flatblocks_flatblock', 'site_id') + + # Adding unique constraint on 'FlatBlock', fields ['slug'] + db.create_unique('flatblocks_flatblock', ['slug']) + + models = { + 'flatblocks.flatblock': { + 'Meta': {'unique_together': "(('slug', 'site'),)", 'object_name': 'FlatBlock'}, + 'content': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'header': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'flatblocks'", 'to': "orm['sites.Site']"}), + 'slug': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}) + }, + '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 = ['flatblocks'] \ No newline at end of file diff --git a/flatblocks/models.py b/flatblocks/models.py index a749308..c351df3 100644 --- a/flatblocks/models.py +++ b/flatblocks/models.py @@ -1,3 +1,4 @@ +from django.contrib.sites.models import Site from django.db import models from django.utils.translation import ugettext_lazy as _ from django.core.cache import cache @@ -11,7 +12,7 @@ class FlatBlock(models.Model): basically a piece of content with a given name (slug) and an optional title (header) which you can, for example, use in a sidebar of a website. """ - slug = models.CharField(max_length=255, unique=True, + slug = models.CharField(max_length=255, db_index=True, verbose_name=_('Slug'), help_text=_("A unique name used for reference in the templates")) header = models.CharField(blank=True, null=True, max_length=255, @@ -19,6 +20,14 @@ class FlatBlock(models.Model): help_text=_("An optional header for this content")) content = models.TextField(verbose_name=_('Content'), blank=True, null=True) + site = models.ForeignKey(Site, related_name='flatblocks', verbose_name=_('Site')) + + class Meta: + verbose_name = _('Flat block') + verbose_name_plural = _('Flat blocks') + unique_together = ( + ('slug', 'site', ), + ) def __unicode__(self): return u"%s" % (self.slug,) @@ -27,7 +36,3 @@ def save(self, *args, **kwargs): super(FlatBlock, self).save(*args, **kwargs) # Now also invalidate the cache used in the templatetag cache.delete('%s%s' % (CACHE_PREFIX, self.slug, )) - - class Meta: - verbose_name = _('Flat block') - verbose_name_plural = _('Flat blocks') diff --git a/flatblocks/settings.py b/flatblocks/settings.py index 75f8a9e..5a141bd 100644 --- a/flatblocks/settings.py +++ b/flatblocks/settings.py @@ -1,5 +1,13 @@ from django.conf import settings +from django.core.cache import cache CACHE_PREFIX = getattr(settings, 'FLATBLOCKS_CACHE_PREFIX', 'flatblocks_') AUTOCREATE_STATIC_BLOCKS = getattr(settings, 'FLATBLOCKS_AUTOCREATE_STATIC_BLOCKS', False) + +STRICT_DEFAULT_CHECK = getattr(settings, + 'FLATBLOCKS_STRICT_DEFAULT_CHECK', False) +STRICT_DEFAULT_CHECK_UPDATE = getattr(settings, + 'FLATBLOCKS_STRICT_DEFAULT_CHECK_UPDATE', False) + +CACHE_TIMEOUT = getattr(settings, 'FLATBLOCKS_CACHE_TIMEOUT', cache.default_timeout) diff --git a/flatblocks/templatetags/flatblock_tags.py b/flatblocks/templatetags/flatblock_tags.py index 6b353e0..83e468b 100644 --- a/flatblocks/templatetags/flatblock_tags.py +++ b/flatblocks/templatetags/flatblock_tags.py @@ -42,11 +42,14 @@ """ from django import template -from django.template import loader -from django.db import models +from django.contrib.sites.models import Site from django.core.cache import cache +# from django.db import models +from django.template import loader +from django.template import debug as template_debug from flatblocks import settings +from flatblocks.models import FlatBlock import logging @@ -54,7 +57,6 @@ register = template.Library() logger = logging.getLogger(__name__) -FlatBlock = models.get_model('flatblocks', 'flatblock') class BasicFlatBlockWrapper(object): def prepare(self, parser, token): @@ -73,6 +75,16 @@ def prepare(self, parser, token): self.cache_time = 0 self.tpl_name = None tag_name, self.slug, args = tokens[0], tokens[1], tokens[2:] + + try: + # Split the arguments in two sections, the "core" ones + # and the ones for default content feature + with_index = args.index('with-default') + default_args = args[with_index:] + args = args[:with_index] + except ValueError: + default_args = [] + num_args = len(args) if num_args == 0: # Only the block name was specified @@ -90,6 +102,39 @@ def prepare(self, parser, token): self.tpl_name = args[2] else: raise template.TemplateSyntaxError, "%r tag should have between 1 and 4 arguments" % (tokens[0],) + + if len(default_args) > 0: + # If we got arguments for default content, or, at least, the + # 'with-default' token was specified, parse until the end of + # the closing tag, and keep the parsed nodelist for later + # rendering + end_tag_name = 'end_%s' % tag_name + self.inner_nodelist = parser.parse((end_tag_name, )) + parser.delete_first_token() + + if len(default_args) > 2: + raise template.TemplateSyntaxError( + u'Too many arguments for this block header') + + # If an argument was specified after 'with-default', it is used + # as the flatblock's header + + if len(default_args) == 2: + self.default_header = default_args[1] + if self.default_header[0] == self.default_header[-1] and \ + self.default_header[0] in ('"', "'"): + self.default_header = self.default_header[1:-1] + self.default_header_is_variable = False + else: + self.default_header_is_variable = True + else: + self.default_header = None + self.default_header_is_variable = False + else: + self.default_header = None + self.default_header_is_variable = False + self.inner_nodelist = None + # Check to see if the slug is properly double/single quoted if not (self.slug[0] == self.slug[-1] and self.slug[0] in ('"', "'")): self.is_variable = True @@ -108,19 +153,29 @@ def __call__(self, parser, token): self.prepare(parser, token) return FlatBlockNode(self.slug, self.is_variable, self.cache_time, template_name=self.tpl_name, - tpl_is_variable=self.tpl_is_variable) + tpl_is_variable=self.tpl_is_variable, + default_header=self.default_header, + default_header_is_variable=self.default_header_is_variable, + default_content=self.inner_nodelist) class PlainFlatBlockWrapper(BasicFlatBlockWrapper): def __call__(self, parser, token): self.prepare(parser, token) - return FlatBlockNode(self.slug, self.is_variable, self.cache_time, False) + return FlatBlockNode( + self.slug, self.is_variable, self.cache_time, False, + default_header=self.default_header, + default_header_is_variable=self.default_header_is_variable, + default_content=self.inner_nodelist, + ) do_get_flatblock = BasicFlatBlockWrapper() do_plain_flatblock = PlainFlatBlockWrapper() class FlatBlockNode(template.Node): def __init__(self, slug, is_variable, cache_time=0, with_template=True, - template_name=None, tpl_is_variable=False): + template_name=None, tpl_is_variable=False, + default_header=None, default_header_is_variable=None, + default_content=None): if template_name is None: self.template_name = 'flatblocks/flatblock.html' else: @@ -133,43 +188,97 @@ def __init__(self, slug, is_variable, cache_time=0, with_template=True, self.cache_time = cache_time self.with_template = with_template + self.default_header_is_variable = default_header_is_variable + self.default_header = template.Variable(default_header)\ + if default_header_is_variable \ + else default_header + self.default_content = default_content + def render(self, context): + current_site = Site.objects.get_current() if self.is_variable: real_slug = template.Variable(self.slug).resolve(context) else: real_slug = self.slug + if isinstance(self.template_name, template.Variable): real_template = self.template_name.resolve(context) else: real_template = self.template_name + + if isinstance(self.default_header, template.Variable): + real_default_header = self.default_header.resolve(context) + else: + real_default_header = self.default_header + + if isinstance(self.default_content, + (template.NodeList, template_debug.DebugNodeList)): + real_default_contents = self.default_content.render(context) + else: + real_default_contents = self.default_content + # Eventually we want to pass the whole context to the template so that # users have the maximum of flexibility of what to do in there. if self.with_template: - new_ctx = template.Context({}) + new_ctx = template.Context() new_ctx.update(context) + else: + new_ctx = None + try: flatblock = None if self.cache_time != 0: cache_key = settings.CACHE_PREFIX + real_slug flatblock = cache.get(cache_key) + if flatblock is None: + flatblock_created = False # if flatblock's slug is hard-coded in template then it is # safe and convenient to auto-create block if it doesn't exist. # This behaviour can be configured using the # FLATBLOCKS_AUTOCREATE_STATIC_BLOCKS setting if self.is_variable or not settings.AUTOCREATE_STATIC_BLOCKS: - flatblock = FlatBlock.objects.get(slug=real_slug) + flatblock = FlatBlock.objects.get(slug=real_slug, site=current_site) else: - flatblock, _ = FlatBlock.objects.get_or_create( - slug=real_slug, - defaults = {'content': real_slug} - ) + # try: + # flatblock = FlatBlock.objects.get(slug=real_slug, site=current_site) + # except FlatBlock.DoesNotExist: + # flatblock = FlatBlock.objects.create( + # slug=real_slug, + # content=real_default_contents or real_slug, + # header=real_default_header, + # site=current_site + # ) + # flatblock.save() + # flatblock_created = True + flatblock, flatblock_created = FlatBlock.objects.get_or_create( + slug=real_slug, site=current_site, defaults={ + 'content': real_default_contents or real_slug, + 'header': real_default_header, + } + ) + + # If the flatblock exists, but its fields are empty, and + # the STRICT_DEFAULT_CHECK is True, then update the fields + # with the default contents. + flatblock_updated = False + if not flatblock_created and settings.STRICT_DEFAULT_CHECK: + if not flatblock.header and not real_default_header is None: + flatblock.header = real_default_header + flatblock_updated = True + if not flatblock.content and self.default_content: + flatblock.content = real_default_contents or real_slug + flatblock_updated = True + + if flatblock_updated and settings.STRICT_DEFAULT_CHECK_UPDATE: + flatblock.save() + if self.cache_time != 0: if self.cache_time is None or self.cache_time == 'None': logger.debug("Caching %s for the cache's default timeout" % (real_slug,)) - cache.set(cache_key, flatblock) + cache.set(cache_key, flatblock, settings.CACHE_TIMEOUT) else: logger.debug("Caching %s for %s seconds" % (real_slug, str(self.cache_time))) @@ -177,14 +286,26 @@ def render(self, context): else: logger.debug("Don't cache %s" % (real_slug,)) - if self.with_template: - tmpl = loader.get_template(real_template) - new_ctx.update({'flatblock':flatblock}) - return tmpl.render(new_ctx) - else: - return flatblock.content + return self.flatblock_output(real_template, flatblock, new_ctx) except FlatBlock.DoesNotExist: + if real_default_contents: + flatblock = FlatBlock( + slug=real_slug, + content=real_default_contents, + header=real_default_header, + site=current_site, + ) + return self.flatblock_output(real_template, flatblock, new_ctx) return '' + def flatblock_output(self, template_name, flatblock, context=None): + if not self.with_template: + return flatblock.content + tmpl = loader.get_template(template_name) + context = context or {} + context.update({'flatblock': flatblock, }) + return tmpl.render(template.Context(context)) + + register.tag('flatblock', do_get_flatblock) register.tag('plain_flatblock', do_plain_flatblock) diff --git a/flatblocks/tests.py b/flatblocks/tests.py index b12b96f..4118c11 100644 --- a/flatblocks/tests.py +++ b/flatblocks/tests.py @@ -2,6 +2,7 @@ from django.test import TestCase from django.core.cache import cache from django.contrib.auth.models import User +from django.contrib.sites.models import Site from django import db from flatblocks.models import FlatBlock @@ -15,7 +16,8 @@ def setUp(self): self.testblock = FlatBlock.objects.create( slug='block', header='HEADER', - content='CONTENT' + content='CONTENT', + site=Site.objects.get_current(), ) self.admin = User.objects.create_superuser('admin', 'admin@localhost', 'adminpwd') @@ -34,7 +36,7 @@ def testCacheReset(self): Tests if FlatBlock.save() resets the cache. """ tpl = template.Template('{% load flatblock_tags %}{% flatblock "block" 60 %}') - tpl.render(template.Context({})) + tpl.render(template.Context()) name = '%sblock' % settings.CACHE_PREFIX self.assertNotEquals(None, cache.get(name)) block = FlatBlock.objects.get(slug='block') @@ -55,17 +57,18 @@ def setUp(self): self.testblock = FlatBlock.objects.create( slug='block', header='HEADER', - content='CONTENT' + content='CONTENT', + site=Site.objects.get_current(), ) def testLoadingTaglib(self): """Tests if the taglib defined in this app can be loaded""" tpl = template.Template('{% load flatblock_tags %}') - tpl.render(template.Context({})) + tpl.render(template.Context()) def testExistingPlain(self): tpl = template.Template('{% load flatblock_tags %}{% plain_flatblock "block" %}') - self.assertEqual(u'CONTENT', tpl.render(template.Context({})).strip()) + self.assertEqual(u'CONTENT', tpl.render(template.Context()).strip()) def testExistingTemplate(self): expected = """