forked from expo/troggle
233 lines
7.1 KiB
Python
233 lines
7.1 KiB
Python
|
from django.conf import settings
|
||
|
from django.db import models
|
||
|
from django.db.models import Q
|
||
|
from django.http import Http404
|
||
|
from django.utils import translation
|
||
|
from django.utils.translation import ugettext_lazy as _
|
||
|
|
||
|
import mptt
|
||
|
|
||
|
from feincms.models import TypeRegistryMetaClass, Region, Template,\
|
||
|
Base, ContentProxy
|
||
|
|
||
|
|
||
|
def get_object(path, fail_silently=False):
|
||
|
dot = path.rindex('.')
|
||
|
try:
|
||
|
return getattr(__import__(path[:dot], {}, {}, ['']), path[dot+1:])
|
||
|
except ImportError:
|
||
|
if not fail_silently:
|
||
|
raise
|
||
|
|
||
|
return None
|
||
|
|
||
|
|
||
|
class PagePretender(object):
|
||
|
def __init__(self, **kwargs):
|
||
|
for k, v in kwargs.items():
|
||
|
setattr(self, k, v)
|
||
|
|
||
|
def get_absolute_url(self):
|
||
|
return self.url
|
||
|
|
||
|
|
||
|
class NavigationExtension(object):
|
||
|
__metaclass__ = TypeRegistryMetaClass
|
||
|
name = _('navigation extension')
|
||
|
|
||
|
def children(self, page, **kwargs):
|
||
|
raise NotImplementedError
|
||
|
|
||
|
|
||
|
class PageManager(models.Manager):
|
||
|
def active(self):
|
||
|
return self.filter(active=True)
|
||
|
|
||
|
def page_for_path(self, path, raise404=False):
|
||
|
"""
|
||
|
Return a page for a path.
|
||
|
|
||
|
Example:
|
||
|
Page.objects.page_for_path(request.path)
|
||
|
"""
|
||
|
|
||
|
stripped = path.strip('/')
|
||
|
|
||
|
try:
|
||
|
return self.active().filter(override_url='/%s/' % stripped)[0]
|
||
|
except IndexError:
|
||
|
pass
|
||
|
|
||
|
tokens = stripped.split('/')
|
||
|
|
||
|
count = len(tokens)
|
||
|
|
||
|
filters = {'%sisnull' % ('parent__' * count): True}
|
||
|
|
||
|
for n, token in enumerate(tokens):
|
||
|
filters['%sslug' % ('parent__' * (count-n-1))] = token
|
||
|
|
||
|
try:
|
||
|
return self.active().filter(**filters)[0]
|
||
|
except IndexError:
|
||
|
if raise404:
|
||
|
raise Http404
|
||
|
raise self.model.DoesNotExist
|
||
|
|
||
|
def page_for_path_or_404(self, path):
|
||
|
"""
|
||
|
Wrapper for page_for_path which raises a Http404 if no page
|
||
|
has been found for the passed path.
|
||
|
"""
|
||
|
return self.page_for_path(path, raise404=True)
|
||
|
|
||
|
def best_match_for_path(self, path, raise404=False):
|
||
|
"""
|
||
|
Return the best match for a path.
|
||
|
"""
|
||
|
|
||
|
tokens = path.strip('/').split('/')
|
||
|
|
||
|
for count in range(len(tokens), -1, -1):
|
||
|
try:
|
||
|
return self.page_for_path('/'.join(tokens[:count]))
|
||
|
except self.model.DoesNotExist:
|
||
|
pass
|
||
|
|
||
|
if raise404:
|
||
|
raise Http404
|
||
|
return None
|
||
|
|
||
|
def in_navigation(self):
|
||
|
return self.active().filter(in_navigation=True)
|
||
|
|
||
|
def toplevel_navigation(self):
|
||
|
return self.in_navigation().filter(parent__isnull=True)
|
||
|
|
||
|
def for_request(self, request, raise404=False):
|
||
|
page = self.page_for_path(request.path, raise404)
|
||
|
page.setup_request(request)
|
||
|
return page
|
||
|
|
||
|
def for_request_or_404(self, request):
|
||
|
return self.page_for_path_or_404(request.path, raise404=True)
|
||
|
|
||
|
def best_match_for_request(self, request, raise404=False):
|
||
|
page = self.best_match_for_path(request.path, raise404)
|
||
|
page.setup_request(request)
|
||
|
return page
|
||
|
|
||
|
def from_request(self, request):
|
||
|
if hasattr(request, '_feincms_page'):
|
||
|
return request._feincms_page
|
||
|
|
||
|
return self.for_request(request)
|
||
|
|
||
|
|
||
|
class Page(Base):
|
||
|
active = models.BooleanField(_('active'), default=False)
|
||
|
|
||
|
# structure and navigation
|
||
|
title = models.CharField(_('title'), max_length=100,
|
||
|
help_text=_('This is used for the generated navigation too.'))
|
||
|
slug = models.SlugField()
|
||
|
parent = models.ForeignKey('self', blank=True, null=True, related_name='children')
|
||
|
in_navigation = models.BooleanField(_('in navigation'), default=True)
|
||
|
override_url = models.CharField(_('override URL'), max_length=200, blank=True,
|
||
|
help_text=_('Override the target URL for the navigation.'))
|
||
|
redirect_to = models.CharField(_('redirect to'), max_length=200, blank=True,
|
||
|
help_text=_('Target URL for automatic redirects.'))
|
||
|
_cached_url = models.CharField(_('Cached URL'), max_length=200, blank=True,
|
||
|
editable=False, default='')
|
||
|
|
||
|
# navigation extensions
|
||
|
NE_CHOICES = [(
|
||
|
'%s.%s' % (cls.__module__, cls.__name__), cls.name) for cls in NavigationExtension.types]
|
||
|
navigation_extension = models.CharField(_('navigation extension'),
|
||
|
choices=NE_CHOICES, blank=True, max_length=50,
|
||
|
help_text=_('Select the module providing subpages for this page if you need to customize the navigation.'))
|
||
|
|
||
|
# content
|
||
|
_content_title = models.TextField(_('content title'), blank=True,
|
||
|
help_text=_('The first line is the main title, the following lines are subtitles.'))
|
||
|
|
||
|
# meta stuff TODO keywords and description?
|
||
|
_page_title = models.CharField(_('page title'), max_length=100, blank=True,
|
||
|
help_text=_('Page title for browser window. Same as title by default.'))
|
||
|
meta_keywords = models.TextField(_('meta keywords'), blank=True,
|
||
|
help_text=_('This will be prepended to the default keyword list.'))
|
||
|
meta_description = models.TextField(_('meta description'), blank=True,
|
||
|
help_text=_('This will be prepended to the default description.'))
|
||
|
|
||
|
# language
|
||
|
language = models.CharField(_('language'), max_length=10,
|
||
|
choices=settings.LANGUAGES)
|
||
|
translations = models.ManyToManyField('self', blank=True)
|
||
|
|
||
|
class Meta:
|
||
|
ordering = ['tree_id', 'lft']
|
||
|
verbose_name = _('page')
|
||
|
verbose_name_plural = _('pages')
|
||
|
|
||
|
objects = PageManager()
|
||
|
|
||
|
def __unicode__(self):
|
||
|
return u'%s (%s)' % (self.title, self.get_absolute_url())
|
||
|
|
||
|
def save(self, *args, **kwargs):
|
||
|
super(Page, self).save(*args, **kwargs)
|
||
|
pages = self.get_descendants(include_self=True)
|
||
|
for page in pages:
|
||
|
page._generate_cached_url()
|
||
|
|
||
|
def _generate_cached_url(self):
|
||
|
if self.override_url:
|
||
|
self._cached_url = self.override_url
|
||
|
if self.is_root_node():
|
||
|
self._cached_url = u'/%s/' % (self.slug)
|
||
|
else:
|
||
|
self._cached_url = u'/%s/%s/' % ('/'.join([page.slug for page in self.get_ancestors()]), self.slug)
|
||
|
|
||
|
super(Page, self).save()
|
||
|
|
||
|
def get_absolute_url(self):
|
||
|
return self._cached_url
|
||
|
|
||
|
@property
|
||
|
def page_title(self):
|
||
|
if self._page_title:
|
||
|
return self._page_title
|
||
|
return self.content_title
|
||
|
|
||
|
@property
|
||
|
def content_title(self):
|
||
|
if not self._content_title:
|
||
|
return self.title
|
||
|
|
||
|
try:
|
||
|
return self._content_title.splitlines()[0]
|
||
|
except IndexError:
|
||
|
return u''
|
||
|
|
||
|
@property
|
||
|
def content_subtitle(self):
|
||
|
return u'\n'.join(self._content_title.splitlines()[1:])
|
||
|
|
||
|
def setup_request(self, request):
|
||
|
translation.activate(self.language)
|
||
|
request.LANGUAGE_CODE = translation.get_language()
|
||
|
request._feincms_page = self
|
||
|
|
||
|
def extended_navigation(self):
|
||
|
if not self.navigation_extension:
|
||
|
return []
|
||
|
|
||
|
cls = get_object(self.navigation_extension, fail_silently=True)
|
||
|
if not cls:
|
||
|
return []
|
||
|
|
||
|
return cls().children(self)
|
||
|
|
||
|
mptt.register(Page)
|
||
|
|