summaryrefslogtreecommitdiffstats
path: root/feincms/module
diff options
context:
space:
mode:
Diffstat (limited to 'feincms/module')
-rw-r--r--feincms/module/__init__.py0
-rw-r--r--feincms/module/page/__init__.py0
-rw-r--r--feincms/module/page/admin.py33
-rw-r--r--feincms/module/page/models.py232
-rw-r--r--feincms/module/page/templatetags/__init__.py0
-rw-r--r--feincms/module/page/templatetags/feincms_page_tags.py63
6 files changed, 328 insertions, 0 deletions
diff --git a/feincms/module/__init__.py b/feincms/module/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/feincms/module/__init__.py
diff --git a/feincms/module/page/__init__.py b/feincms/module/page/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/feincms/module/page/__init__.py
diff --git a/feincms/module/page/admin.py b/feincms/module/page/admin.py
new file mode 100644
index 0000000..486605f
--- /dev/null
+++ b/feincms/module/page/admin.py
@@ -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)
diff --git a/feincms/module/page/models.py b/feincms/module/page/models.py
new file mode 100644
index 0000000..623ab88
--- /dev/null
+++ b/feincms/module/page/models.py
@@ -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)
+
diff --git a/feincms/module/page/templatetags/__init__.py b/feincms/module/page/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/feincms/module/page/templatetags/__init__.py
diff --git a/feincms/module/page/templatetags/feincms_page_tags.py b/feincms/module/page/templatetags/feincms_page_tags.py
new file mode 100644
index 0000000..68b2ee3
--- /dev/null
+++ b/feincms/module/page/templatetags/feincms_page_tags.py
@@ -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))
+