forked from expo/troggle
[svn] Updates to allow subcave tree with nice admin.
This commit is contained in:
0
feincms/module/page/__init__.py
Normal file
0
feincms/module/page/__init__.py
Normal file
33
feincms/module/page/admin.py
Normal file
33
feincms/module/page/admin.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
from feincms.admin import editor
|
||||
from feincms.module.page.models import Page
|
||||
|
||||
|
||||
class PageAdmin(editor.ItemEditorMixin, editor.TreeEditorMixin, admin.ModelAdmin):
|
||||
# the fieldsets config here is used for the add_view, it has no effect
|
||||
# for the change_view which is completely customized anyway
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('active', 'in_navigation', 'template', 'title', 'slug',
|
||||
'parent', 'language'),
|
||||
}),
|
||||
(_('Other options'), {
|
||||
'classes': ('collapse',),
|
||||
'fields': ('override_url', 'meta_keywords', 'meta_description'),
|
||||
}),
|
||||
)
|
||||
list_display=('__unicode__', 'active', 'in_navigation',
|
||||
'language', 'template')
|
||||
list_filter=('active', 'in_navigation', 'language', 'template')
|
||||
search_fields = ('title', 'slug', '_content_title', '_page_title',
|
||||
'meta_keywords', 'meta_description')
|
||||
prepopulated_fields={
|
||||
'slug': ('title',),
|
||||
}
|
||||
|
||||
show_on_top = ('title', 'active', 'in_navigation')
|
||||
|
||||
admin.site.register(Page, PageAdmin)
|
||||
232
feincms/module/page/models.py
Normal file
232
feincms/module/page/models.py
Normal file
@@ -0,0 +1,232 @@
|
||||
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)
|
||||
|
||||
0
feincms/module/page/templatetags/__init__.py
Normal file
0
feincms/module/page/templatetags/__init__.py
Normal file
63
feincms/module/page/templatetags/feincms_page_tags.py
Normal file
63
feincms/module/page/templatetags/feincms_page_tags.py
Normal file
@@ -0,0 +1,63 @@
|
||||
from django import template
|
||||
from feincms.module.page.models import Page
|
||||
from feincms.templatetags.utils import *
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
class NavigationNode(SimpleAssignmentNodeWithVarAndArgs):
|
||||
"""
|
||||
Example:
|
||||
{% feincms_navigation of feincms_page as sublevel level=2 %}
|
||||
{% for p in sublevel %}
|
||||
<a href="{{ p.get_absolute_url }}">{{ p.title }}</a>
|
||||
{% endfor %}
|
||||
"""
|
||||
|
||||
def what(self, instance, args):
|
||||
level = int(args.get('level', 1))
|
||||
|
||||
if level <= 1:
|
||||
return Page.objects.toplevel_navigation()
|
||||
|
||||
# mptt starts counting at 0, NavigationNode at 1; if we need the submenu
|
||||
# of the current page, we have to add 2 to the mptt level
|
||||
if instance.level+2 == level:
|
||||
return instance.children.in_navigation()
|
||||
|
||||
try:
|
||||
return instance.get_ancestors()[level-2].children.in_navigation()
|
||||
except IndexError:
|
||||
return []
|
||||
register.tag('feincms_navigation', do_simple_assignment_node_with_var_and_args_helper(NavigationNode))
|
||||
|
||||
|
||||
class ParentLinkNode(SimpleNodeWithVarAndArgs):
|
||||
"""
|
||||
{% feincms_parentlink of feincms_page level=1 %}
|
||||
"""
|
||||
|
||||
def what(self, page, args):
|
||||
level = int(args.get('level', 1))
|
||||
|
||||
if page.level+1 == level:
|
||||
return page.get_absolute_url()
|
||||
elif page.level+1 < level:
|
||||
return '#'
|
||||
|
||||
try:
|
||||
return page.get_ancestors()[level-1].get_absolute_url()
|
||||
except IndexError:
|
||||
return '#'
|
||||
register.tag('feincms_parentlink', do_simple_node_with_var_and_args_helper(ParentLinkNode))
|
||||
|
||||
|
||||
class BestMatchNode(SimpleAssignmentNodeWithVar):
|
||||
"""
|
||||
{% feincms_bestmatch for request.path as feincms_page %}
|
||||
"""
|
||||
|
||||
def what(self, path):
|
||||
return Page.objects.best_match_for_path(path)
|
||||
register.tag('feincms_bestmatch', do_simple_assignment_node_with_var_helper(BestMatchNode))
|
||||
|
||||
Reference in New Issue
Block a user