gnocchi-cms / gnocchi / cms /

from django.db import models
from django.contrib.contenttypes import generic
from django.utils.safestring import mark_safe
from django.utils import simplejson
from django.core.validators import RegexValidator, ValidationError
from django.core.urlresolvers import reverse
from django.template import Template as DjangoTemplate
from django.template.loader import get_template

# Used to clear cache on change
from django.db.models.signals import post_save
from gnocchi.cms import settings
from django.core.cache import cache

from import AttrHelper

# Translations
from django.utils.translation import ugettext_lazy as _

# DB stored templates

class Template(models.Model):
    '''A Database backed Template'''
    path = models.CharField(max_length=500, db_index=True,
        help_text='This is NOT a URL')
    description = models.CharField(max_length=200, blank=True)
    content = models.TextField()

    class Meta:
        verbose_name = _('template')
        verbose_name_plural = _('templates')
        ordering = ('path',)
    def __unicode__(self):
        return self.path
    def render(self, context):
        return DjangoTemplate(self.content).render(context)

# URL tree model

class PageManager(models.Manager):
    def published(self):
        '''Filter for only published pages'''
        return self.get_query_set().filter(is_published=True)

class Page(models.Model, AttrHelper):
    '''Heirarchy of objects providing content'''
    parent = models.ForeignKey('self', blank=True, null=True,
    path = models.SlugField(blank=True,
        help_text = 'This will form part of the URL')
    title = models.CharField(max_length=512)

    is_published = models.BooleanField(default=True)

    template = models.ForeignKey(Template, blank=True, null=True)
    content = models.TextField(blank=True)

    attributes = generic.GenericRelation('tools.Attribute')

    objects = PageManager()

    order = models.PositiveIntegerField(default=0)

    class Meta:
        verbose_name = _('page')
        verbose_name_plural = _('pages')
        unique_together = (
            ('parent', 'path',),
        ordering = ('order',)

    def _load_cache(self, force=False):
        if not hasattr(self, '_attributes') or force:
            self._attributes = dict(
                self.attributes.values_list('name', 'value')
                self.fragment_set.values_list('name', 'content')

    def __unicode__(self):
        return u'[%s] %s' % (self.get_absolute_url(), self.title,)

    def get_absolute_url(self):
        paths = [self.path, ]
        node = self.parent
        while node:
            paths.insert(0, node.path)
            node = node.parent
        return '/%s' % '/'.join(paths)
    get_absolute_url.short_description = 'url'

    def get_template(self):
        '''Returns the template to render this page'''
        if self.template_id:
            return self.template
        elif self.parent_id:
            return self.parent.get_template()
        return get_template(settings.DEFAULT_TEMPLATE)

    ## Tree-related functions
    def is_root(self):
        '''Return True if this is a root Page'''
        return self.parent == None

    def get_root(self):
        '''Find the root of this Page'''
        node = self
        while node.parent:
            node = node.parent
        return node

    def get_all_roots(cls):
        '''Helper function to get all root pages'''
        return Page.objects.published().filter(parent=None)

    ## Permissions hook
    def can_view(self, user):
        '''Test if a User has permission to view this page.'''
        if user.is_authenticated():
            if user.is_staff:
                return True
                return self.pageaccess_set.get(group__user=user).permit
            except PageAccess.DoesNotExist:

            return self.pageaccess_set.get(group=None).permit
        except PageAccess.DoesNotExist:
            # No explicit rule == Permit
            return True

class Fragment(models.Model):
    '''Content fragment of a Page'''
    page = models.ForeignKey(Page)
    name = models.CharField(max_length=200)
    content = models.TextField(blank=True)
    class Meta:
        verbose_name = _('fragment')
        verbose_name_plural = _('fragments')
        unique_together = (
            ('page', 'name',)

class PageAccess(models.Model):
    page = models.ForeignKey(Page)
    # who does this apply to?
    group = models.ForeignKey('auth.Group', null=True, blank=True)
    # What do we say?
    permit = models.BooleanField(default=True)

    def __unicode__(self):
        return u'Access for %s to %s' % ( or 'Default',
    class Meta:
        unique_together = (
            ('page', 'group',),
        ordering = ('group',)

# Style Sheets

class StyleSheet(models.Model):
    '''Helpful wrapper for DB stored StyleSheets with Templating'''
    name = models.SlugField()
    description = models.CharField(max_length=1024, blank=True)
    content = models.TextField(blank=True)
    class Meta:
        verbose_name = _('style sheet')
        verbose_name_plural = _('style sheets')
    def __unicode__(self):
        return u'[%s] %s' % (, self.description,)
    def get_absolute_url(self):
        return reverse('gnocchi.cms.views.css', kwargs={'name':})
    def render(self, context):
        return DjangoTemplate(self.content).render(context)

# Global context variables

class ContextVariable(models.Model):
        ('str', 'Text',),
        ('bool', 'Boolean',),
        ('int', 'Integer',),
        ('float', 'Number',),
        ('json', 'JSON',),
    # functions to convert from storage
    LOAD_MAP = {
        'bool': bool,
        'int': int,
        'float': float,
        'str': mark_safe,
        'json': simplejson.loads
    # functions to convert to storage [default: unicode()]
    STORE_MAP = {
        'json': simplejson.dumps
    name = models.CharField(max_length=64, validators=[
    group = models.CharField(max_length=64, validators=[
    description = models.CharField(max_length=200, blank=True)
    type = models.CharField(max_length=32, choices = TYPE_CHOICES)
    value = models.CharField(max_length=1024)
    class Meta:
        verbose_name = _('context variable')
        verbose_name_plural = _('context variables')
    def clean(self):
        except Exception, e:
            raise ValidationError, "Value %r not valid for type %s (%r)" % (
                self.value, self.type, e
        if not isinstance(self.value, basestring):
            self.value = self.set_value(self.value)

    def get_value(self):
        return self.LOAD_MAP.get(self.type, lambda x: x)(self.value)
    def set_value(self, value):
        return self.STORE_MAP.get(self.type, unicode)(value)
    def full_name(self):
        return u'%s.%s' % (,,)

def cv_clear_cache(sender, **kwargs):
    cache.delete('gnocchi-%s' % settings.CV_NAMESPACE)
post_save.connect(cv_clear_cache, sender=ContextVariable)