forked from expo/troggle
[svn] Updates to allow subcave tree with nice admin.
This commit is contained in:
parent
99949d466a
commit
54a62999c0
databaseReset.py
expo
feincms
__init__.py
admin
content
locale/de/LC_MESSAGES
management
models.pymodule
shortcuts.pytemplates
templatetags
views
parsers
settings.pytemplates
@ -14,7 +14,7 @@ def reload_db():
|
|||||||
cursor.execute("create database %s" % settings.DATABASE_NAME)
|
cursor.execute("create database %s" % settings.DATABASE_NAME)
|
||||||
cursor.execute("ALTER DATABASE %s CHARACTER SET=utf8" % settings.DATABASE_NAME)
|
cursor.execute("ALTER DATABASE %s CHARACTER SET=utf8" % settings.DATABASE_NAME)
|
||||||
cursor.execute("USE %s" % settings.DATABASE_NAME)
|
cursor.execute("USE %s" % settings.DATABASE_NAME)
|
||||||
management.call_command('syncdb')
|
management.call_command('syncdb', interactive=False)
|
||||||
user = User.objects.create_user('m', 'm@m.com', 'm')
|
user = User.objects.create_user('m', 'm@m.com', 'm')
|
||||||
user.is_staff = True
|
user.is_staff = True
|
||||||
user.is_superuser = True
|
user.is_superuser = True
|
||||||
@ -50,6 +50,8 @@ def import_surveys():
|
|||||||
parsers.surveys.parseSurveys(logfile=settings.LOGFILE)
|
parsers.surveys.parseSurveys(logfile=settings.LOGFILE)
|
||||||
|
|
||||||
def reset():
|
def reset():
|
||||||
|
""" Wipe the troggle database and import everything from legacy data
|
||||||
|
"""
|
||||||
reload_db()
|
reload_db()
|
||||||
make_dirs()
|
make_dirs()
|
||||||
import_cavetab()
|
import_cavetab()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from troggle.expo.models import *
|
from troggle.expo.models import *
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from feincms.admin import editor
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
import django.forms as forms
|
import django.forms as forms
|
||||||
from expo.forms import LogbookEntryForm
|
from expo.forms import LogbookEntryForm
|
||||||
@ -69,10 +70,12 @@ class CaveAdmin(TroggleModelAdmin):
|
|||||||
#inlines = (QMInline,)
|
#inlines = (QMInline,)
|
||||||
extra = 4
|
extra = 4
|
||||||
|
|
||||||
|
class SubcaveAdmin(editor.TreeEditorMixin,TroggleModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Photo)
|
admin.site.register(Photo)
|
||||||
admin.site.register(Subcave)
|
admin.site.register(Subcave, SubcaveAdmin)
|
||||||
admin.site.register(Cave, CaveAdmin)
|
admin.site.register(Cave, CaveAdmin)
|
||||||
admin.site.register(Area)
|
admin.site.register(Area)
|
||||||
admin.site.register(OtherCaveName)
|
admin.site.register(OtherCaveName)
|
||||||
@ -91,3 +94,7 @@ admin.site.register(QM, QMAdmin)
|
|||||||
admin.site.register(Survey, SurveyAdmin)
|
admin.site.register(Survey, SurveyAdmin)
|
||||||
admin.site.register(ScannedImage)
|
admin.site.register(ScannedImage)
|
||||||
|
|
||||||
|
try:
|
||||||
|
mptt.register(Subcave, order_insertion_by=['name'])
|
||||||
|
except mptt.AlreadyRegistered:
|
||||||
|
print "mptt already registered"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import urllib, urlparse, string, os, datetime
|
import urllib, urlparse, string, os, datetime
|
||||||
|
import troggle.mptt as mptt
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
@ -463,31 +464,35 @@ class Entrance(TroggleModel):
|
|||||||
return f[1]
|
return f[1]
|
||||||
|
|
||||||
class Subcave(TroggleModel):
|
class Subcave(TroggleModel):
|
||||||
description = models.TextField()
|
description = models.TextField(blank=True, null=True)
|
||||||
name = models.CharField(max_length=200, )
|
title = models.CharField(max_length=200, )
|
||||||
cave = models.ForeignKey('Cave', blank=True, null=True, help_text="Only the top-level subcave should be linked to a cave")
|
cave = models.ForeignKey('Cave', blank=True, null=True, help_text="Only the top-level subcave should be linked to a cave")
|
||||||
parent= models.ForeignKey('Subcave', blank=True, null=True, related_name='children')
|
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
|
||||||
adjoining = models.ManyToManyField('Subcave',blank=True, null=True,)
|
#adjoining = models.ManyToManyField('Subcave',blank=True, null=True,)
|
||||||
survex_file = models.CharField(max_length=200, blank=True, null=True,)
|
legacy_description_path = models.CharField(max_length=600, blank=True, null=True)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.name
|
return self.title
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
urlString=self.name
|
# def get_absolute_url(self):
|
||||||
if self.parent:
|
# urlString=self.name
|
||||||
parent=self.parent
|
# if self.parent:
|
||||||
while parent: #recursively walk up the tree, adding parents to the left of the URL
|
# parent=self.parent
|
||||||
urlString=parent.name+'/'+urlString
|
# while parent: #recursively walk up the tree, adding parents to the left of the URL
|
||||||
if parent.cave:
|
# urlString=parent.name+'/'+urlString
|
||||||
cave=parent.cave
|
# if parent.cave:
|
||||||
parent=parent.parent
|
# cave=parent.cave
|
||||||
urlString='cave/'+unicode(cave.kataster_number)+'/'+urlString
|
# parent=parent.parent
|
||||||
else:
|
# urlString='cave/'+unicode(cave.kataster_number)+'/'+urlString
|
||||||
urlString='cave/'+unicode(self.cave.kataster_number)+'/'+urlString
|
# else:
|
||||||
|
# urlString='cave/'+unicode(self.cave.kataster_number)+'/'+urlString
|
||||||
|
|
||||||
|
|
||||||
return urlparse.urljoin(settings.URL_ROOT, urlString)
|
# return urlparse.urljoin(settings.URL_ROOT, urlString)
|
||||||
|
try:
|
||||||
|
mptt.register(Subcave, order_insertion_by=['title'])
|
||||||
|
except mptt.AlreadyRegistered:
|
||||||
|
print "mptt already registered"
|
||||||
|
|
||||||
class QM(TroggleModel):
|
class QM(TroggleModel):
|
||||||
#based on qm.csv in trunk/expoweb/smkridge/204 which has the fields:
|
#based on qm.csv in trunk/expoweb/smkridge/204 which has the fields:
|
||||||
@ -592,13 +597,15 @@ class ScannedImage(TroggleImageModel):
|
|||||||
return get_scan_path(self,'')
|
return get_scan_path(self,'')
|
||||||
|
|
||||||
class Survey(TroggleModel):
|
class Survey(TroggleModel):
|
||||||
expedition = models.ForeignKey('Expedition')
|
expedition = models.ForeignKey('Expedition') #REDUNDANT (logbook_entry)
|
||||||
wallet_number = models.IntegerField(blank=True,null=True)
|
wallet_number = models.IntegerField(blank=True,null=True)
|
||||||
wallet_letter = models.CharField(max_length=1,blank=True,null=True)
|
wallet_letter = models.CharField(max_length=1,blank=True,null=True)
|
||||||
comments = models.TextField(blank=True,null=True)
|
comments = models.TextField(blank=True,null=True)
|
||||||
location = models.CharField(max_length=400,blank=True,null=True)
|
location = models.CharField(max_length=400,blank=True,null=True) #REDUNDANT
|
||||||
|
subcave = models.ForeignKey('Subcave', blank=True, null=True)
|
||||||
#notes_scan = models.ForeignKey('ScannedImage',related_name='notes_scan',blank=True, null=True) #Replaced by contents field of ScannedImage model
|
#notes_scan = models.ForeignKey('ScannedImage',related_name='notes_scan',blank=True, null=True) #Replaced by contents field of ScannedImage model
|
||||||
survex_block = models.ForeignKey('SurvexBlock',blank=True, null=True)
|
survex_block = models.OneToOneField('SurvexBlock',blank=True, null=True)
|
||||||
|
logbook_entry = models.ForeignKey('LogbookEntry')
|
||||||
centreline_printed_on = models.DateField(blank=True, null=True)
|
centreline_printed_on = models.DateField(blank=True, null=True)
|
||||||
centreline_printed_by = models.ForeignKey('Person',related_name='centreline_printed_by',blank=True,null=True)
|
centreline_printed_by = models.ForeignKey('Person',related_name='centreline_printed_by',blank=True,null=True)
|
||||||
#sketch_scan = models.ForeignKey(ScannedImage,blank=True, null=True) #Replaced by contents field of ScannedImage model
|
#sketch_scan = models.ForeignKey(ScannedImage,blank=True, null=True) #Replaced by contents field of ScannedImage model
|
||||||
@ -608,7 +615,7 @@ class Survey(TroggleModel):
|
|||||||
integrated_into_main_sketch_by = models.ForeignKey('Person' ,related_name='integrated_into_main_sketch_by', blank=True,null=True)
|
integrated_into_main_sketch_by = models.ForeignKey('Person' ,related_name='integrated_into_main_sketch_by', blank=True,null=True)
|
||||||
rendered_image = models.ImageField(upload_to='renderedSurveys',blank=True,null=True)
|
rendered_image = models.ImageField(upload_to='renderedSurveys',blank=True,null=True)
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.expedition.year+"#"+"%02d" % self.wallet_number
|
return self.expedition.year+"#"+"%02d" % int(self.wallet_number)
|
||||||
|
|
||||||
def notes(self):
|
def notes(self):
|
||||||
return self.scannedimage_set.filter(contents='notes')
|
return self.scannedimage_set.filter(contents='notes')
|
||||||
@ -618,5 +625,3 @@ class Survey(TroggleModel):
|
|||||||
|
|
||||||
def elevations(self):
|
def elevations(self):
|
||||||
return self.scannedimage_set.filter(contents='elevation')
|
return self.scannedimage_set.filter(contents='elevation')
|
||||||
|
|
||||||
|
|
||||||
|
0
feincms/__init__.py
Normal file
0
feincms/__init__.py
Normal file
12
feincms/admin/__init__.py
Normal file
12
feincms/admin/__init__.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from feincms.models import Region, Template
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Region,
|
||||||
|
list_display=('title', 'key', 'inherited'),
|
||||||
|
)
|
||||||
|
admin.site.register(Template,
|
||||||
|
list_display=('title', 'path'),
|
||||||
|
)
|
||||||
|
|
183
feincms/admin/editor.py
Normal file
183
feincms/admin/editor.py
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from django import forms, template
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.contrib.admin.options import IncorrectLookupParameters
|
||||||
|
from django.contrib.admin.util import unquote
|
||||||
|
from django.core import serializers
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.db import connection, transaction
|
||||||
|
from django.forms.formsets import all_valid
|
||||||
|
from django.forms.models import inlineformset_factory
|
||||||
|
from django.http import HttpResponseRedirect, HttpResponse
|
||||||
|
from django.shortcuts import render_to_response
|
||||||
|
from django.utils import simplejson
|
||||||
|
from django.utils.encoding import force_unicode
|
||||||
|
from django.utils.functional import update_wrapper
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FEINCMS_ADMIN_MEDIA = getattr(settings, 'FEINCMS_ADMIN_MEDIA', '/media/sys/feincms/')
|
||||||
|
|
||||||
|
|
||||||
|
class ItemEditorMixin(object):
|
||||||
|
"""
|
||||||
|
This mixin needs an attribute on the ModelAdmin class:
|
||||||
|
|
||||||
|
show_on_top::
|
||||||
|
A list of fields which should be displayed at the top of the form.
|
||||||
|
This does not need to (and should not) include ``template''
|
||||||
|
"""
|
||||||
|
|
||||||
|
def change_view(self, request, object_id, extra_context=None):
|
||||||
|
|
||||||
|
if not hasattr(self.model, '_feincms_content_types'):
|
||||||
|
raise ImproperlyConfigured, 'You need to create at least one content type for the %s model.' % (self.model.__name__)
|
||||||
|
|
||||||
|
class ModelForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = self.model
|
||||||
|
|
||||||
|
class SettingsFieldset(forms.ModelForm):
|
||||||
|
# This form class is used solely for presentation, the data will be saved
|
||||||
|
# by the ModelForm above
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = self.model
|
||||||
|
exclude = self.show_on_top+('template',)
|
||||||
|
|
||||||
|
inline_formset_types = [(
|
||||||
|
content_type,
|
||||||
|
inlineformset_factory(self.model, content_type, extra=1)
|
||||||
|
) for content_type in self.model._feincms_content_types]
|
||||||
|
|
||||||
|
opts = self.model._meta
|
||||||
|
app_label = opts.app_label
|
||||||
|
obj = self.model._default_manager.get(pk=unquote(object_id))
|
||||||
|
|
||||||
|
if not self.has_change_permission(request, obj):
|
||||||
|
raise PermissionDenied
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
model_form = ModelForm(request.POST, request.FILES, instance=obj)
|
||||||
|
|
||||||
|
inline_formsets = [
|
||||||
|
formset_class(request.POST, request.FILES, instance=obj,
|
||||||
|
prefix=content_type.__name__.lower())
|
||||||
|
for content_type, formset_class in inline_formset_types]
|
||||||
|
|
||||||
|
if model_form.is_valid() and all_valid(inline_formsets):
|
||||||
|
model_form.save()
|
||||||
|
for formset in inline_formsets:
|
||||||
|
formset.save()
|
||||||
|
return HttpResponseRedirect(".")
|
||||||
|
|
||||||
|
settings_fieldset = SettingsFieldset(request.POST, instance=obj)
|
||||||
|
settings_fieldset.is_valid()
|
||||||
|
else:
|
||||||
|
model_form = ModelForm(instance=obj)
|
||||||
|
inline_formsets = [
|
||||||
|
formset_class(instance=obj, prefix=content_type.__name__.lower())
|
||||||
|
for content_type, formset_class in inline_formset_types]
|
||||||
|
|
||||||
|
settings_fieldset = SettingsFieldset(instance=obj)
|
||||||
|
|
||||||
|
content_types = []
|
||||||
|
for content_type in self.model._feincms_content_types:
|
||||||
|
content_name = content_type._meta.verbose_name
|
||||||
|
content_types.append((content_name, content_type.__name__.lower()))
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': _('Change %s') % force_unicode(opts.verbose_name),
|
||||||
|
'opts': opts,
|
||||||
|
'page': obj,
|
||||||
|
'page_form': model_form,
|
||||||
|
'inline_formsets': inline_formsets,
|
||||||
|
'content_types': content_types,
|
||||||
|
'settings_fieldset': settings_fieldset,
|
||||||
|
'top_fieldset': [model_form[field] for field in self.show_on_top],
|
||||||
|
'FEINCMS_ADMIN_MEDIA': FEINCMS_ADMIN_MEDIA,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_to_response([
|
||||||
|
'admin/feincms/%s/%s/item_editor.html' % (app_label, opts.object_name.lower()),
|
||||||
|
'admin/feincms/%s/item_editor.html' % app_label,
|
||||||
|
'admin/feincms/item_editor.html',
|
||||||
|
], context, context_instance=template.RequestContext(request))
|
||||||
|
|
||||||
|
|
||||||
|
class TreeEditorMixin(object):
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
# handle AJAX requests
|
||||||
|
if request.is_ajax():
|
||||||
|
cmd = request.POST.get('__cmd')
|
||||||
|
if cmd=='save_tree':
|
||||||
|
return self._save_tree(request)
|
||||||
|
elif cmd=='delete_item':
|
||||||
|
return self._delete_item(request)
|
||||||
|
|
||||||
|
return HttpResponse('Oops. AJAX request not understood.')
|
||||||
|
|
||||||
|
from django.contrib.admin.views.main import ChangeList, ERROR_FLAG
|
||||||
|
opts = self.model._meta
|
||||||
|
app_label = opts.app_label
|
||||||
|
|
||||||
|
if not self.has_change_permission(request, None):
|
||||||
|
raise PermissionDenied
|
||||||
|
try:
|
||||||
|
cl = ChangeList(request, self.model, self.list_display,
|
||||||
|
self.list_display_links, self.list_filter, self.date_hierarchy,
|
||||||
|
self.search_fields, self.list_select_related, self.list_per_page,
|
||||||
|
self.list_editable, self)
|
||||||
|
except IncorrectLookupParameters:
|
||||||
|
# Wacky lookup parameters were given, so redirect to the main
|
||||||
|
# changelist page, without parameters, and pass an 'invalid=1'
|
||||||
|
# parameter via the query string. If wacky parameters were given and
|
||||||
|
# the 'invalid=1' parameter was already in the query string, something
|
||||||
|
# is screwed up with the database, so display an error page.
|
||||||
|
if ERROR_FLAG in request.GET.keys():
|
||||||
|
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
|
||||||
|
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'FEINCMS_ADMIN_MEDIA': FEINCMS_ADMIN_MEDIA,
|
||||||
|
'title': cl.title,
|
||||||
|
'is_popup': cl.is_popup,
|
||||||
|
'cl': cl,
|
||||||
|
'has_add_permission': self.has_add_permission(request),
|
||||||
|
'root_path': self.admin_site.root_path,
|
||||||
|
'app_label': app_label,
|
||||||
|
'object_list': self.model._tree_manager.all(),
|
||||||
|
}
|
||||||
|
context.update(extra_context or {})
|
||||||
|
return render_to_response([
|
||||||
|
'admin/feincms/%s/%s/tree_editor.html' % (app_label, opts.object_name.lower()),
|
||||||
|
'admin/feincms/%s/tree_editor.html' % app_label,
|
||||||
|
'admin/feincms/tree_editor.html',
|
||||||
|
], context, context_instance=template.RequestContext(request))
|
||||||
|
|
||||||
|
def _save_tree(self, request):
|
||||||
|
pagetree = simplejson.loads(request.POST['tree'])
|
||||||
|
# 0 = tree_id, 1 = parent_id, 2 = left, 3 = right, 4 = level, 5 = item_id
|
||||||
|
sql = "UPDATE %s SET %s=%%s, %s_id=%%s, %s=%%s, %s=%%s, %s=%%s WHERE %s=%%s" % (
|
||||||
|
self.model._meta.db_table,
|
||||||
|
self.model._meta.tree_id_attr,
|
||||||
|
self.model._meta.parent_attr,
|
||||||
|
self.model._meta.left_attr,
|
||||||
|
self.model._meta.right_attr,
|
||||||
|
self.model._meta.level_attr,
|
||||||
|
self.model._meta.pk.column)
|
||||||
|
|
||||||
|
connection.cursor().executemany(sql, pagetree)
|
||||||
|
transaction.commit_unless_managed()
|
||||||
|
|
||||||
|
return HttpResponse("OK", mimetype="text/plain")
|
||||||
|
|
||||||
|
def _delete_item(self, request):
|
||||||
|
page_id = request.POST['item_id']
|
||||||
|
obj = self.model._default_manager.get(pk=unquote(page_id))
|
||||||
|
obj.delete()
|
||||||
|
return HttpResponse("OK", mimetype="text/plain")
|
||||||
|
|
0
feincms/content/__init__.py
Normal file
0
feincms/content/__init__.py
Normal file
0
feincms/content/file/__init__.py
Normal file
0
feincms/content/file/__init__.py
Normal file
20
feincms/content/file/models.py
Normal file
20
feincms/content/file/models.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class FileContent(models.Model):
|
||||||
|
title = models.CharField(max_length=200)
|
||||||
|
file = models.FileField(_('file'), upload_to='filecontent')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
verbose_name = _('file')
|
||||||
|
verbose_name_plural = _('files')
|
||||||
|
|
||||||
|
def render(self, **kwargs):
|
||||||
|
return render_to_string([
|
||||||
|
'content/file/%s.html' % self.region.key,
|
||||||
|
'content/file/default.html',
|
||||||
|
], {'content': self})
|
||||||
|
|
0
feincms/content/image/__init__.py
Normal file
0
feincms/content/image/__init__.py
Normal file
32
feincms/content/image/models.py
Normal file
32
feincms/content/image/models.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
class ImageContent(models.Model):
|
||||||
|
"""
|
||||||
|
Create an ImageContent like this:
|
||||||
|
|
||||||
|
Cls.create_content_type(ImageContent, POSITION_CHOICES=(
|
||||||
|
('left', 'Left'),
|
||||||
|
('right', Right'),
|
||||||
|
))
|
||||||
|
"""
|
||||||
|
|
||||||
|
image = models.ImageField(_('image'), upload_to='imagecontent')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
verbose_name = _('image')
|
||||||
|
verbose_name_plural = _('images')
|
||||||
|
|
||||||
|
def render(self, **kwargs):
|
||||||
|
return render_to_string([
|
||||||
|
'content/image/%s.html' % self.position,
|
||||||
|
'content/image/default.html',
|
||||||
|
], {'content': self})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def handle_kwargs(cls, POSITION_CHOICES=()):
|
||||||
|
models.CharField(_('position'), max_length=10, choices=POSITION_CHOICES
|
||||||
|
).contribute_to_class(cls, 'position')
|
||||||
|
|
0
feincms/content/richtext/__init__.py
Normal file
0
feincms/content/richtext/__init__.py
Normal file
16
feincms/content/richtext/models.py
Normal file
16
feincms/content/richtext/models.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class RichTextContent(models.Model):
|
||||||
|
text = models.TextField(_('text'), blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
verbose_name = _('rich text')
|
||||||
|
verbose_name_plural = _('rich texts')
|
||||||
|
|
||||||
|
def render(self, **kwargs):
|
||||||
|
return mark_safe(self.text)
|
||||||
|
|
0
feincms/content/rss/__init__.py
Normal file
0
feincms/content/rss/__init__.py
Normal file
39
feincms/content/rss/models.py
Normal file
39
feincms/content/rss/models.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
import feedparser
|
||||||
|
|
||||||
|
|
||||||
|
class RSSContent(models.Model):
|
||||||
|
title = models.CharField(help_text=_('The rss field is updated several times a day. A change in the title will only be visible on the home page after the next feed update.'), max_length=50)
|
||||||
|
link = models.URLField(_('link'))
|
||||||
|
rendered_content = models.TextField(_('Pre-rendered content'), blank=True, editable=False)
|
||||||
|
last_updated = models.DateTimeField(_('Last updated'), blank=True, null=True)
|
||||||
|
max_items = models.IntegerField(_('Max. items'), default=5)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
verbose_name = _('RSS feed')
|
||||||
|
verbose_name_plural = _('RSS feeds')
|
||||||
|
|
||||||
|
def render(self, **kwargs):
|
||||||
|
return mark_safe(self.rendered_content)
|
||||||
|
|
||||||
|
def cache_content(self):
|
||||||
|
print u"Getting RSS feed at %s" % (self.link,)
|
||||||
|
feed = feedparser.parse(self.link)
|
||||||
|
|
||||||
|
print u"Pre-rendering content"
|
||||||
|
self.rendered_content = render_to_string('content/rss/content.html', {
|
||||||
|
'feed_title': self.title,
|
||||||
|
'feed_link': feed['feed']['link'],
|
||||||
|
'entries': feed['entries'][:self.max_items],
|
||||||
|
})
|
||||||
|
self.last_updated = datetime.now()
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
0
feincms/content/video/__init__.py
Normal file
0
feincms/content/video/__init__.py
Normal file
26
feincms/content/video/models.py
Normal file
26
feincms/content/video/models.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
import re
|
||||||
|
|
||||||
|
class VideoContent(models.Model):
|
||||||
|
video = models.URLField(_('video link'),help_text=_('This should be a link to a youtube video, i.e.: http://www.youtube.com/watch?v=zmj1rpzDRZ0'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
verbose_name = _('video')
|
||||||
|
verbose_name_plural = _('videos')
|
||||||
|
|
||||||
|
def render(self, **kwargs):
|
||||||
|
vid = re.search('(?<==)\w+',self.video)
|
||||||
|
ret = """
|
||||||
|
<div class="videocontent">
|
||||||
|
<object width="400" height="330">
|
||||||
|
<param name="movie" value="http://www.youtube.com/v/%s&hl=de&fs=1"></param>
|
||||||
|
<param name="allowFullScreen" value="true"></param>
|
||||||
|
<param name="allowscriptaccess" value="always"></param>
|
||||||
|
<embed src="http://www.youtube.com/v/%s&hl=de&fs=1&rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="400" height="330"></embed>
|
||||||
|
</object>
|
||||||
|
</div>
|
||||||
|
""" % (vid.group(0), vid.group(0))
|
||||||
|
return ret
|
281
feincms/locale/de/LC_MESSAGES/django.po
Normal file
281
feincms/locale/de/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2009-04-23 11:12+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: models.py:33 module/page/models.py:131
|
||||||
|
msgid "title"
|
||||||
|
msgstr "Titel"
|
||||||
|
|
||||||
|
#: models.py:34
|
||||||
|
msgid "key"
|
||||||
|
msgstr "Schlüssel"
|
||||||
|
|
||||||
|
#: models.py:35
|
||||||
|
msgid "inherited"
|
||||||
|
msgstr "Vererbt"
|
||||||
|
|
||||||
|
#: models.py:36
|
||||||
|
msgid ""
|
||||||
|
"Should the content be inherited by subpages if they do not define any "
|
||||||
|
"content for this region?"
|
||||||
|
msgstr ""
|
||||||
|
"Soll der Inhalt dieser Region durch Unterseiten geerbt werden, sofern diese "
|
||||||
|
"keinen eigenen Inhalt definieren?"
|
||||||
|
|
||||||
|
#: models.py:39
|
||||||
|
msgid "region"
|
||||||
|
msgstr "Region"
|
||||||
|
|
||||||
|
#: models.py:40
|
||||||
|
msgid "regions"
|
||||||
|
msgstr "Regionen"
|
||||||
|
|
||||||
|
#: models.py:57
|
||||||
|
msgid "template"
|
||||||
|
msgstr "Template"
|
||||||
|
|
||||||
|
#: models.py:58
|
||||||
|
msgid "templates"
|
||||||
|
msgstr "Templates"
|
||||||
|
|
||||||
|
#: models.py:135
|
||||||
|
msgid "ordering"
|
||||||
|
msgstr "Sortierung"
|
||||||
|
|
||||||
|
#: admin/editor.py:139
|
||||||
|
msgid "Database error"
|
||||||
|
msgstr "Datenbankfehler"
|
||||||
|
|
||||||
|
#: content/file/models.py:8 content/file/models.py:12
|
||||||
|
msgid "file"
|
||||||
|
msgstr "Datei"
|
||||||
|
|
||||||
|
#: content/file/models.py:13
|
||||||
|
msgid "files"
|
||||||
|
msgstr "Dateien"
|
||||||
|
|
||||||
|
#: content/image/models.py:15 content/image/models.py:19
|
||||||
|
msgid "image"
|
||||||
|
msgstr "Bild"
|
||||||
|
|
||||||
|
#: content/image/models.py:20
|
||||||
|
msgid "images"
|
||||||
|
msgstr "Bilder"
|
||||||
|
|
||||||
|
#: content/image/models.py:30
|
||||||
|
msgid "position"
|
||||||
|
msgstr "Position"
|
||||||
|
|
||||||
|
#: content/richtext/models.py:7
|
||||||
|
msgid "text"
|
||||||
|
msgstr "Text"
|
||||||
|
|
||||||
|
#: content/richtext/models.py:11
|
||||||
|
msgid "rich text"
|
||||||
|
msgstr "Text"
|
||||||
|
|
||||||
|
#: content/richtext/models.py:12
|
||||||
|
msgid "rich texts"
|
||||||
|
msgstr "Texte"
|
||||||
|
|
||||||
|
#: content/rss/models.py:12
|
||||||
|
msgid ""
|
||||||
|
"The rss field is updated several times a day. A change in the title will "
|
||||||
|
"only be visible on the home page after the next feed update."
|
||||||
|
msgstr ""
|
||||||
|
"Der RSS Feed wird mehrmals täglich aktualisiert. Eine Änderung des Titels "
|
||||||
|
"erscheint erst nach der nächsten Feed-Aktualisierung auf der Webseite."
|
||||||
|
|
||||||
|
#: content/rss/models.py:13
|
||||||
|
msgid "link"
|
||||||
|
msgstr "Link"
|
||||||
|
|
||||||
|
#: content/rss/models.py:14
|
||||||
|
msgid "Pre-rendered content"
|
||||||
|
msgstr "Vor-gerenderter Inhalt"
|
||||||
|
|
||||||
|
#: content/rss/models.py:15
|
||||||
|
msgid "Last updated"
|
||||||
|
msgstr "Letzte Aktualisierung"
|
||||||
|
|
||||||
|
#: content/rss/models.py:16
|
||||||
|
msgid "Max. items"
|
||||||
|
msgstr "Maximale Anzahl"
|
||||||
|
|
||||||
|
#: content/rss/models.py:20
|
||||||
|
msgid "RSS feed"
|
||||||
|
msgstr "RSS Feed"
|
||||||
|
|
||||||
|
#: content/rss/models.py:21
|
||||||
|
msgid "RSS feeds"
|
||||||
|
msgstr "RSS Feeds"
|
||||||
|
|
||||||
|
#: content/video/models.py:7
|
||||||
|
msgid "video link"
|
||||||
|
msgstr "Video-Link"
|
||||||
|
|
||||||
|
#: content/video/models.py:7
|
||||||
|
msgid ""
|
||||||
|
"This should be a link to a youtube video, i.e.: http://www.youtube.com/watch?"
|
||||||
|
"v=zmj1rpzDRZ0"
|
||||||
|
msgstr ""
|
||||||
|
"Dies sollte ein Link zu einem Youtube-Video sein, z.B.: http://www.youtube."
|
||||||
|
"com/watch?v=zmj1rpzDRZ0"
|
||||||
|
|
||||||
|
#: content/video/models.py:11
|
||||||
|
msgid "video"
|
||||||
|
msgstr "Video"
|
||||||
|
|
||||||
|
#: content/video/models.py:12
|
||||||
|
msgid "videos"
|
||||||
|
msgstr "Videos"
|
||||||
|
|
||||||
|
#: module/page/admin.py:17
|
||||||
|
msgid "Other options"
|
||||||
|
msgstr "Weitere Optionen"
|
||||||
|
|
||||||
|
#: module/page/models.py:36 module/page/models.py:146
|
||||||
|
msgid "navigation extension"
|
||||||
|
msgstr "Navigations-Erweiterung"
|
||||||
|
|
||||||
|
#: module/page/models.py:128 templates/admin/feincms/tree_editor.html:133
|
||||||
|
msgid "active"
|
||||||
|
msgstr "Aktiv"
|
||||||
|
|
||||||
|
#: module/page/models.py:132
|
||||||
|
msgid "This is used for the generated navigation too."
|
||||||
|
msgstr "Dies wird auch für die generierte Navigation verwendet."
|
||||||
|
|
||||||
|
#: module/page/models.py:135
|
||||||
|
msgid "in navigation"
|
||||||
|
msgstr "In der Navigation"
|
||||||
|
|
||||||
|
#: module/page/models.py:136
|
||||||
|
msgid "override URL"
|
||||||
|
msgstr "Überschriebene URL"
|
||||||
|
|
||||||
|
#: module/page/models.py:137
|
||||||
|
msgid "Override the target URL for the navigation."
|
||||||
|
msgstr "Überschreibe die Ziel-URL für die Navigation."
|
||||||
|
|
||||||
|
#: module/page/models.py:138
|
||||||
|
msgid "redirect to"
|
||||||
|
msgstr "Weiterleiten zu"
|
||||||
|
|
||||||
|
#: module/page/models.py:139
|
||||||
|
msgid "Target URL for automatic redirects."
|
||||||
|
msgstr "Ziel-URL für automatische Weiterleitungen."
|
||||||
|
|
||||||
|
#: module/page/models.py:140
|
||||||
|
msgid "Cached URL"
|
||||||
|
msgstr "Zwischengespeicherte URL"
|
||||||
|
|
||||||
|
#: module/page/models.py:148
|
||||||
|
msgid ""
|
||||||
|
"Select the module providing subpages for this page if you need to customize "
|
||||||
|
"the navigation."
|
||||||
|
msgstr "Wähle das Modul aus, welches weitere Navigationspunkte erstellt."
|
||||||
|
|
||||||
|
#: module/page/models.py:151
|
||||||
|
msgid "content title"
|
||||||
|
msgstr "Inhaltstitel"
|
||||||
|
|
||||||
|
#: module/page/models.py:152
|
||||||
|
msgid "The first line is the main title, the following lines are subtitles."
|
||||||
|
msgstr "Die erste Zeile ist der Haupttitel, die weiteren Zeilen Untertitel"
|
||||||
|
|
||||||
|
#: module/page/models.py:155
|
||||||
|
msgid "page title"
|
||||||
|
msgstr "Seitentitel"
|
||||||
|
|
||||||
|
#: module/page/models.py:156
|
||||||
|
msgid "Page title for browser window. Same as title by default."
|
||||||
|
msgstr ""
|
||||||
|
"Seitentitel für das Browser-Fenster. Standardmässig gleich wie der Titel."
|
||||||
|
|
||||||
|
#: module/page/models.py:157
|
||||||
|
msgid "meta keywords"
|
||||||
|
msgstr "Meta Begriffe"
|
||||||
|
|
||||||
|
#: module/page/models.py:158
|
||||||
|
msgid "This will be prepended to the default keyword list."
|
||||||
|
msgstr "Diese Begriffe werden vor die Standard-Begriffsliste eingefügt."
|
||||||
|
|
||||||
|
#: module/page/models.py:159
|
||||||
|
msgid "meta description"
|
||||||
|
msgstr "Meta Beschreibung"
|
||||||
|
|
||||||
|
#: module/page/models.py:160
|
||||||
|
msgid "This will be prepended to the default description."
|
||||||
|
msgstr "Diese Beschreibung wird vor der Standard-Beschreibung eingefügt."
|
||||||
|
|
||||||
|
#: module/page/models.py:163
|
||||||
|
msgid "language"
|
||||||
|
msgstr "Sprache"
|
||||||
|
|
||||||
|
#: module/page/models.py:169
|
||||||
|
msgid "page"
|
||||||
|
msgstr "Seite"
|
||||||
|
|
||||||
|
#: module/page/models.py:170
|
||||||
|
msgid "pages"
|
||||||
|
msgstr "Seiten"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/item_editor.html:122
|
||||||
|
msgid "Home"
|
||||||
|
msgstr "Startseite"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/item_editor.html:134
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Löschen"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/item_editor.html:139
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Speichern"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/item_editor.html:143
|
||||||
|
msgid "Change Template"
|
||||||
|
msgstr "Template ändern"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/item_editor.html:158
|
||||||
|
msgid "Region empty"
|
||||||
|
msgstr "Region leer"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/item_editor.html:162
|
||||||
|
msgid ""
|
||||||
|
"Content from the parent site is automatically inherited. To override this "
|
||||||
|
"behaviour, add some content."
|
||||||
|
msgstr ""
|
||||||
|
"Inhalt wird von der übergeordneten Seite geerbt. Füge Inhalt hinzu, um "
|
||||||
|
"dieses Verhalten zu ändern"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/tree_editor.html:121
|
||||||
|
#, python-format
|
||||||
|
msgid "Add %(name)s"
|
||||||
|
msgstr "%(name)s hinzufügen"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/tree_editor.html:132
|
||||||
|
msgid "Page"
|
||||||
|
msgstr "Seite"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/tree_editor.html:134
|
||||||
|
msgid "in navi"
|
||||||
|
msgstr "Im Menü"
|
||||||
|
|
||||||
|
#: templates/admin/feincms/tree_editor.html:135
|
||||||
|
msgid "delete"
|
||||||
|
msgstr "Löschen"
|
0
feincms/management/__init__.py
Normal file
0
feincms/management/__init__.py
Normal file
0
feincms/management/commands/__init__.py
Normal file
0
feincms/management/commands/__init__.py
Normal file
12
feincms/management/commands/update_rsscontent.py
Normal file
12
feincms/management/commands/update_rsscontent.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.core.management.base import NoArgsCommand
|
||||||
|
|
||||||
|
from feincms.content.rss.models import RSSContent
|
||||||
|
|
||||||
|
class Command(NoArgsCommand):
|
||||||
|
help = "Run this as a cronjob."
|
||||||
|
|
||||||
|
def handle_noargs(self, **options):
|
||||||
|
for cls in RSSContent._feincms_content_models:
|
||||||
|
for content in cls.objects.all():
|
||||||
|
content.cache_content()
|
||||||
|
|
224
feincms/models.py
Normal file
224
feincms/models.py
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import copy
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class TypeRegistryMetaClass(type):
|
||||||
|
"""
|
||||||
|
You can access the list of subclasses as <BaseClass>.types
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(cls, name, bases, attrs):
|
||||||
|
if not hasattr(cls, 'types'):
|
||||||
|
cls.types = []
|
||||||
|
else:
|
||||||
|
cls.types.append(cls)
|
||||||
|
|
||||||
|
|
||||||
|
class Region(models.Model):
|
||||||
|
"""
|
||||||
|
A template region which will be a container for several page contents.
|
||||||
|
|
||||||
|
Often used regions might be "main" and "sidebar"
|
||||||
|
"""
|
||||||
|
|
||||||
|
title = models.CharField(_('title'), max_length=50, unique=True)
|
||||||
|
key = models.CharField(_('key'), max_length=20, unique=True)
|
||||||
|
inherited = models.BooleanField(_('inherited'), default=False,
|
||||||
|
help_text=_('Should the content be inherited by subpages if they do not define any content for this region?'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('region')
|
||||||
|
verbose_name_plural = _('regions')
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
class Template(models.Model):
|
||||||
|
"""
|
||||||
|
A template file on the disk which can be used by pages to render themselves.
|
||||||
|
"""
|
||||||
|
|
||||||
|
title = models.CharField(max_length=200)
|
||||||
|
path = models.CharField(max_length=200)
|
||||||
|
regions = models.ManyToManyField(Region, related_name='templates')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['title']
|
||||||
|
verbose_name = _('template')
|
||||||
|
verbose_name_plural = _('templates')
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
|
||||||
|
def first_template():
|
||||||
|
return Template.objects.all()[0]
|
||||||
|
|
||||||
|
|
||||||
|
class Base(models.Model):
|
||||||
|
template = models.ForeignKey(Template, default=first_template)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
if not hasattr(self, '_content_proxy'):
|
||||||
|
self._content_proxy = ContentProxy(self)
|
||||||
|
|
||||||
|
return self._content_proxy
|
||||||
|
|
||||||
|
def _content_for_region(self, region):
|
||||||
|
if not hasattr(self, '_feincms_content_types'):
|
||||||
|
raise ImproperlyConfigured, 'You need to create at least one content type for the %s model.' % (self.__class__.__name__)
|
||||||
|
|
||||||
|
sql = ' UNION '.join([
|
||||||
|
'SELECT %d, COUNT(id) FROM %s WHERE parent_id=%s AND region_id=%s' % (
|
||||||
|
idx,
|
||||||
|
cls._meta.db_table,
|
||||||
|
self.pk,
|
||||||
|
region.id) for idx, cls in enumerate(self._feincms_content_types)])
|
||||||
|
|
||||||
|
from django.db import connection
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute(sql)
|
||||||
|
|
||||||
|
counts = [row[1] for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
if not any(counts):
|
||||||
|
return []
|
||||||
|
|
||||||
|
contents = []
|
||||||
|
for idx, cnt in enumerate(counts):
|
||||||
|
if cnt:
|
||||||
|
contents += list(
|
||||||
|
self._feincms_content_types[idx].objects.filter(
|
||||||
|
parent=self,
|
||||||
|
region=region).select_related('parent', 'region'))
|
||||||
|
|
||||||
|
return contents
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_content_base(cls):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
ordering = ['ordering']
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'%s on %s, ordering %s' % (self.region, self.parent, self.ordering)
|
||||||
|
|
||||||
|
def render(self, **kwargs):
|
||||||
|
render_fn = getattr(self, 'render_%s' % self.region.key, None)
|
||||||
|
|
||||||
|
if render_fn:
|
||||||
|
return render_fn(**kwargs)
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
attrs = {
|
||||||
|
'__module__': cls.__module__,
|
||||||
|
'__unicode__': __unicode__,
|
||||||
|
'render': render,
|
||||||
|
'Meta': Meta,
|
||||||
|
'parent': models.ForeignKey(cls, related_name='%(class)s_set'),
|
||||||
|
'region': models.ForeignKey(Region, related_name='%s_%%(class)s_set' % cls.__name__.lower()),
|
||||||
|
'ordering': models.IntegerField(_('ordering'), default=0),
|
||||||
|
}
|
||||||
|
|
||||||
|
cls._feincms_content_model = type('%sContent' % cls.__name__,
|
||||||
|
(models.Model,), attrs)
|
||||||
|
|
||||||
|
cls._feincms_content_types = []
|
||||||
|
|
||||||
|
return cls._feincms_content_model
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_content_type(cls, model, **kwargs):
|
||||||
|
if not hasattr(cls, '_feincms_content_model'):
|
||||||
|
cls._create_content_base()
|
||||||
|
|
||||||
|
feincms_content_base = getattr(cls, '_feincms_content_model')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
db_table = '%s_%s' % (cls._meta.db_table, model.__name__.lower())
|
||||||
|
verbose_name = model._meta.verbose_name
|
||||||
|
verbose_name_plural = model._meta.verbose_name_plural
|
||||||
|
|
||||||
|
attrs = {
|
||||||
|
'__module__': cls.__module__,
|
||||||
|
'Meta': Meta,
|
||||||
|
}
|
||||||
|
|
||||||
|
new_type = type(
|
||||||
|
model.__name__,
|
||||||
|
(model, feincms_content_base,), attrs)
|
||||||
|
cls._feincms_content_types.append(new_type)
|
||||||
|
|
||||||
|
if not hasattr(model, '_feincms_content_models'):
|
||||||
|
model._feincms_content_models = []
|
||||||
|
|
||||||
|
model._feincms_content_models.append(new_type)
|
||||||
|
|
||||||
|
if hasattr(new_type, 'handle_kwargs'):
|
||||||
|
new_type.handle_kwargs(**kwargs)
|
||||||
|
else:
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
setattr(new_type, k, v)
|
||||||
|
|
||||||
|
return new_type
|
||||||
|
|
||||||
|
|
||||||
|
class ContentProxy(object):
|
||||||
|
"""
|
||||||
|
This proxy offers attribute-style access to the page contents of regions.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> page = Page.objects.all()[0]
|
||||||
|
>>> page.content.main
|
||||||
|
[A list of all page contents which are assigned to the region with key 'main']
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, item):
|
||||||
|
self.item = item
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
"""
|
||||||
|
Get all item content instances for the specified item and region
|
||||||
|
|
||||||
|
If no item contents could be found for the current item and the region
|
||||||
|
has the inherited flag set, this method will go up the ancestor chain
|
||||||
|
until either some item contents have found or no ancestors are left.
|
||||||
|
"""
|
||||||
|
|
||||||
|
item = self.__dict__['item']
|
||||||
|
|
||||||
|
try:
|
||||||
|
region = item.template.regions.get(key=attr)
|
||||||
|
except Region.DoesNotExist:
|
||||||
|
return []
|
||||||
|
|
||||||
|
def collect_items(obj):
|
||||||
|
contents = obj._content_for_region(region)
|
||||||
|
|
||||||
|
# go to parent if this model has a parent attribute
|
||||||
|
# TODO this should be abstracted into a property/method or something
|
||||||
|
# The link which should be followed is not always '.parent'
|
||||||
|
if not contents and hasattr(obj, 'parent_id') and obj.parent_id and region.inherited:
|
||||||
|
return collect_items(obj.parent)
|
||||||
|
|
||||||
|
return contents
|
||||||
|
|
||||||
|
contents = collect_items(item)
|
||||||
|
contents.sort(key=lambda c: c.ordering)
|
||||||
|
return contents
|
||||||
|
|
0
feincms/module/__init__.py
Normal file
0
feincms/module/__init__.py
Normal file
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))
|
||||||
|
|
13
feincms/shortcuts.py
Normal file
13
feincms/shortcuts.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django.shortcuts import render_to_response
|
||||||
|
from django.template import RequestContext
|
||||||
|
|
||||||
|
from feincms.module.page.models import Page
|
||||||
|
|
||||||
|
|
||||||
|
def render_to_response_best_match(request, template_name, dictionary=None):
|
||||||
|
dictionary = dictionary or {}
|
||||||
|
dictionary['feincms_page'] = Page.objects.best_match_for_request(request)
|
||||||
|
|
||||||
|
return render_to_response(template_name, dictionary,
|
||||||
|
context_instance=RequestContext(request))
|
||||||
|
|
227
feincms/templates/admin/feincms/item_editor.html
Normal file
227
feincms/templates/admin/feincms/item_editor.html
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
{% extends "admin/change_form.html" %}
|
||||||
|
{% load i18n admin_modify adminmedia %}
|
||||||
|
|
||||||
|
{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
|
||||||
|
{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
|
||||||
|
|
||||||
|
{% block extrahead %}{{ block.super }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />
|
||||||
|
|
||||||
|
<script type="text/javascript" src="../../../jsi18n/"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-1.3.min.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.ui.all.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.livequery.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.alerts.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}helper.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}listener.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/media/sys/feinheit/tinymce/tiny_mce.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
tinyMCE.init({
|
||||||
|
mode: "none",
|
||||||
|
theme: "advanced",
|
||||||
|
language: "en",
|
||||||
|
theme_advanced_toolbar_location: "top",
|
||||||
|
theme_advanced_toolbar_align: "left",
|
||||||
|
theme_advanced_statusbar_location: "bottom",
|
||||||
|
theme_advanced_buttons1: "fullscreen,|,formatselect,image,media,code,|,cut,copy,paste,|,bold,italic,|,bullist,numlist,|,link,unlink",
|
||||||
|
theme_advanced_buttons2: "",
|
||||||
|
theme_advanced_buttons3: "",
|
||||||
|
theme_advanced_path: false,
|
||||||
|
theme_advanced_blockformats: "p,h2,h3",
|
||||||
|
theme_advanced_resizing: true,
|
||||||
|
width: '600',
|
||||||
|
height: '300',
|
||||||
|
content_css: "/path_to_your_media/css/preview.css",
|
||||||
|
plugins: "advimage,advlink,fullscreen,table,preview,media,inlinepopups",
|
||||||
|
advimage_update_dimensions_onchange: true,
|
||||||
|
//file_browser_callback: "CustomFileBrowser",
|
||||||
|
relative_urls: false
|
||||||
|
});
|
||||||
|
|
||||||
|
function init_pagecontent() {
|
||||||
|
// handle special page content type needs
|
||||||
|
// this is not really extensible, but it works for now
|
||||||
|
$('.order-machine textarea[name*=richtext]:visible').each(function(){
|
||||||
|
tinyMCE.execCommand('mceAddControl', true, this.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
IMG_ARROW_DOWN_PATH = "{{ FEINCMS_ADMIN_MEDIA }}img/arrow_down.gif";
|
||||||
|
IMG_ARROW_RIGHT_PATH = "{{ FEINCMS_ADMIN_MEDIA }}img/arrow_right.gif";
|
||||||
|
IMG_CIRCLE_PATH = "{{ FEINCMS_ADMIN_MEDIA }}img/circle.gif";
|
||||||
|
IMG_DELETELINK_PATH = "{{ FEINCMS_ADMIN_MEDIA }}img/icon_deletelink.gif";
|
||||||
|
IMG_MOVE_PATH = "{{ FEINCMS_ADMIN_MEDIA }}img/icon_move.gif";
|
||||||
|
|
||||||
|
REGIONS = [];
|
||||||
|
REGION_MAP = [];
|
||||||
|
{% for region in page.template.regions.all %}
|
||||||
|
REGIONS.push('{{ region.key }}');
|
||||||
|
REGION_MAP.push('{{ region.id }}');
|
||||||
|
{% endfor %}
|
||||||
|
ACTIVE_REGION = 0;
|
||||||
|
|
||||||
|
CONTENT_NAMES = {
|
||||||
|
{% for name, value in content_types %}'{{ value }}': '{{ name }}'{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}};
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
// move contents into their corresponding regions and do some simple formatting
|
||||||
|
$("div[id$=_set]").children().each(function(){
|
||||||
|
if (!($(this).hasClass("header"))) {
|
||||||
|
$(this).find("select[name$=region]").addClass("region-choice-field").parents("tr").hide();
|
||||||
|
$(this).find("input[name$=DELETE]").addClass("delete-field").parents("tr").hide();
|
||||||
|
$(this).find("input[name$=ordering]").addClass("order-field").parents("tr").hide();
|
||||||
|
$(this).find("input[name$=id]").hide().prev().hide();
|
||||||
|
$(this).find("input[name$=parent]").hide().prev().hide();
|
||||||
|
|
||||||
|
var region_id = $(this).find(".region-choice-field").val();
|
||||||
|
region_id = REGION_MAP.indexOf(region_id);
|
||||||
|
var content_type = $(this).attr("id").substr(0, $(this).attr("id").indexOf("_"));
|
||||||
|
region_append(region_id,$(this), CONTENT_NAMES[content_type]);
|
||||||
|
set_item_field_value($(this),"region-choice-field",region_id)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// register regions as sortable for drag N drop
|
||||||
|
$(".order-machine").sortable({
|
||||||
|
handle: '.handle',
|
||||||
|
helper: 'clone',
|
||||||
|
stop: function(event, ui) {
|
||||||
|
richify_poor($(ui.item));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// hide content on drag n drop
|
||||||
|
$(".handle").mousedown(function(){
|
||||||
|
poorify_rich($(this).parents(".order-item"));
|
||||||
|
});
|
||||||
|
$(".handle").mouseup(function(){
|
||||||
|
richify_poor($(this).parents(".order-item"));
|
||||||
|
});
|
||||||
|
// convert text areas to rich text editors.
|
||||||
|
init_pagecontent();
|
||||||
|
|
||||||
|
if(window.location.hash) {
|
||||||
|
$(window.location.hash+'_tab').trigger('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
// bring order to chaos
|
||||||
|
zucht_und_ordnung(true);
|
||||||
|
|
||||||
|
{% block extra-init-js %}{% endblock %}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ FEINCMS_ADMIN_MEDIA }}css/layout.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ FEINCMS_ADMIN_MEDIA }}css/jquery.alerts.css" media="screen" />
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="../../../">{% trans "Home" %}</a> ›
|
||||||
|
<a href="../../">{{ opts.app_label|capfirst|escape }}</a> ›
|
||||||
|
<a href="../">{{ opts.verbose_name_plural|capfirst }}</a> ›
|
||||||
|
{{ page.title|truncatewords:"18" }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div id="content-main">
|
||||||
|
|
||||||
|
{% block object-tools %}
|
||||||
|
<ul class="object-tools">
|
||||||
|
{% if page.get_absolute_url %}
|
||||||
|
<li><a target="_blank" href="{{ page.get_absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<form name="main_form" enctype="multipart/form-data" action="." method="post" id="{{ opts.module_name }}_form">
|
||||||
|
|
||||||
|
<div id="overview">
|
||||||
|
<p style="float:right;" class="deletelink-box"><a href="delete/" class="deletelink">{% trans "Delete" %}</a></p>
|
||||||
|
{% for field in top_fieldset %}
|
||||||
|
{{ field.label_tag }}
|
||||||
|
<span>{{ field }}{{ field.errors }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="submit" class="submit_form" value="{% trans 'Save' %}" />
|
||||||
|
<hr/>
|
||||||
|
{{ page_form.template.label_tag }}
|
||||||
|
<span>{{ page_form.template }}{{ page_form.template.errors }}</span>
|
||||||
|
<input type="button" class="cancel" value="{% trans 'Change Template' %}" />
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
<div id="main_wrapper">
|
||||||
|
<div class="navi_tab tab_active" id="settings_tab">Settings</div>
|
||||||
|
{% for region in page.template.regions.all %}<div class="navi_tab tab_inactive" id="{{ region.key }}_tab">{{ region.title }}</div>{% endfor %}
|
||||||
|
<div id="main">
|
||||||
|
<div id="settings_body">
|
||||||
|
<table>
|
||||||
|
{{ settings_fieldset.as_table }}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% for region in page.template.regions.all %}
|
||||||
|
<div id="{{ region.key }}_body" class="panel">
|
||||||
|
<div class="empty-machine-msg">
|
||||||
|
{% trans "Region empty" %}
|
||||||
|
</div>
|
||||||
|
<div class="empty-machine-msg" style="margin-left:20px; margin-top:20px;">
|
||||||
|
{% if region.inherited %}
|
||||||
|
{% trans "Content from the parent site is automatically inherited. To override this behaviour, add some content." %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="order-machine">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="machine-control">
|
||||||
|
<div class="control-unit">
|
||||||
|
<span>Add New item:</span> <br/>
|
||||||
|
<select name="order-machine-add-select">
|
||||||
|
{% for n,v in content_types %} <option value="{{ v }}">{{ n }}</option> {% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="button" class="order-machine-add-button button" value="OK" />
|
||||||
|
</div>
|
||||||
|
<div class="control-unit">
|
||||||
|
<span>Move selected item to:</span> <br/>
|
||||||
|
<select name="order-machine-move-select">
|
||||||
|
{% for r in page.template.regions.all %} {% ifnotequal region r %} <option value="{{ r.key }}">{{ r.title }}</option> {% endifnotequal %} {% endfor %}
|
||||||
|
</select>
|
||||||
|
<input type="button" class="order-machine-move-button button" value="OK" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="inlines" style="display:none">
|
||||||
|
{% for formset in inline_formsets %}
|
||||||
|
<div id="{{ formset.rel_name }}">
|
||||||
|
<div class="header">
|
||||||
|
{{ formset.management_form }}
|
||||||
|
<h3>{{ formset.rel_name }}</h3>
|
||||||
|
</div>
|
||||||
|
{% for form in formset.forms %}
|
||||||
|
<div id="{{ formset.rel_name }}_item_{{ forloop.counter0 }}">
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
145
feincms/templates/admin/feincms/tree_editor.html
Normal file
145
feincms/templates/admin/feincms/tree_editor.html
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
{% extends "admin/change_list.html" %}
|
||||||
|
{% load i18n admin_modify adminmedia mptt_tags %}
|
||||||
|
|
||||||
|
{% block title %}{{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% block extrahead %}{{ block.super }}
|
||||||
|
<script type="text/javascript" src="../../../jsi18n/"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery-1.3.min.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.ui.all.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.livequery.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.alerts.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}helper.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}listener.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.treeTable.js"></script>
|
||||||
|
<script type="text/javascript" src="{{ FEINCMS_ADMIN_MEDIA }}jquery.json-1.3.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ancestors = [{% for page in object_list %}'{{ page.parent_id|default_if_none:"0" }}'{% if not forloop.last %},{% endif %} {% endfor %}];
|
||||||
|
|
||||||
|
tablestr = '';
|
||||||
|
{% for page in object_list %}tablestr += add_row({{ forloop.counter }}, {{ page.id }}, "{{ page.parent_id|default_if_none:"-1" }}", "{{ page.title }}", ["{{ page.active }}", "{{ page.in_navigation }}"]);
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
function add_row(node_id, page_id, parent_id, page_title, attrs) {
|
||||||
|
var str = '<tr id="node-' + node_id + '" class="page-id-' + page_id + ' ';
|
||||||
|
if (parseInt(parent_id) >= 0)
|
||||||
|
str += 'child-of-node-'+ancestors.indexOf(parent_id);
|
||||||
|
str += '">';
|
||||||
|
str += '<td><div class="wrap nohover">';
|
||||||
|
str += '<div class="insert-as-child"></div>';
|
||||||
|
str += '<span class="title-col"><a href="'+page_id+'"><strong>'+page_title+'</strong></a><img class="move-node" src="{{ FEINCMS_ADMIN_MEDIA }}img/icon_move.gif" /></span>';
|
||||||
|
str += '<div class="insert-as-sibling"></div>';
|
||||||
|
str += '</div></td>';
|
||||||
|
for (key in attrs)
|
||||||
|
str += '<td>'+attrs[key]+'</td>';
|
||||||
|
str += '<td><img class="del-page" src="{{ FEINCMS_ADMIN_MEDIA }}img/icon_deletelink.gif"/></td></tr>';
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
// build table
|
||||||
|
$("#sitetree tbody").append(tablestr);
|
||||||
|
// register
|
||||||
|
$("#sitetree").treeTable();
|
||||||
|
// configure draggable
|
||||||
|
$("#sitetree .title-col").draggable({
|
||||||
|
helper: function(){ return $(this).parent().clone(); } ,
|
||||||
|
handle: ".move-node",
|
||||||
|
opacity: .75,
|
||||||
|
refreshPositions: true,
|
||||||
|
revert: "invalid",
|
||||||
|
revertDuration: 300,
|
||||||
|
scroll: true
|
||||||
|
});
|
||||||
|
// configure droppable to insert as child
|
||||||
|
$("#sitetree .insert-as-child").each(function() {
|
||||||
|
$(this).droppable({
|
||||||
|
accept: ".title-col",
|
||||||
|
tolerance: "intersect",
|
||||||
|
drop: function(e, ui) {
|
||||||
|
handle_drop_event($(ui.draggable).parents("tr"), $(this).parents("tr"), "child")
|
||||||
|
},
|
||||||
|
over: function(e, ui) {
|
||||||
|
$(this).parent().removeClass("nohover").addClass("hover-as-child");
|
||||||
|
},
|
||||||
|
out: function(e, ui) {
|
||||||
|
$(this).parent().removeClass("hover-as-child").addClass("nohover");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// configure droppable to insert as sibling
|
||||||
|
$("#sitetree .insert-as-sibling").each(function() {
|
||||||
|
$(this).droppable({
|
||||||
|
accept: ".title-col",
|
||||||
|
tolerance: "intersect",
|
||||||
|
drop: function(e, ui) {
|
||||||
|
handle_drop_event($(ui.draggable).parents("tr"), $(this).parents("tr"), "sibling")
|
||||||
|
},
|
||||||
|
over: function(e, ui) {
|
||||||
|
var row = '<div style="background-color:#bcf; height:4px; width:100%; margin:-8px 0px 4px -5px; position:relative; z-index:10;"></div>'
|
||||||
|
$(row).insertBefore($(this).parent());
|
||||||
|
},
|
||||||
|
out: function(e, ui) {
|
||||||
|
$(this).parent().prev().remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".wrap").live('click',function() {
|
||||||
|
if ($(this).find(".expander").length > 0)
|
||||||
|
$(this).parents("tr").toggleBranch();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".save_tree").click(function(){
|
||||||
|
save_page_tree();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".del-page").click(function(){
|
||||||
|
handle_page_delete($(this).parents("tr"));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ FEINCMS_ADMIN_MEDIA }}css/layout.css" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ FEINCMS_ADMIN_MEDIA }}css/jquery.alerts.css" media="screen" />
|
||||||
|
<link href="{{ FEINCMS_ADMIN_MEDIA }}css/jquery.treeTable.css" rel="stylesheet" type="text/css" />
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div id="content-main">
|
||||||
|
{% block object-tools %}
|
||||||
|
{% if has_add_permission %}
|
||||||
|
<ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}</a></li></ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="button" value="save tree" class="save_tree" style="margin: 20px 5px -10px 460px;"/>
|
||||||
|
|
||||||
|
<div id="sitetree-wrapper">
|
||||||
|
<table id="sitetree" border="1">
|
||||||
|
<thead>
|
||||||
|
<tr id="table_header">
|
||||||
|
<th width="400">{% trans "Page" %}</th>
|
||||||
|
<th>{% trans "active" %}</th>
|
||||||
|
<th>{% trans "in navi" %}</th>
|
||||||
|
<th>{% trans "delete" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
8
feincms/templates/content/rss/content.html
Normal file
8
feincms/templates/content/rss/content.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<h2><a href="{{ feed_link }}">{{ feed_title }}</a></h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for entry in entries %}
|
||||||
|
<li><a href="{{ entry.link }}">{{ entry.title }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
0
feincms/templatetags/__init__.py
Normal file
0
feincms/templatetags/__init__.py
Normal file
75
feincms/templatetags/feincms_tags.py
Normal file
75
feincms/templatetags/feincms_tags.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from django import template
|
||||||
|
from feincms.module.page.models import Page
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def feincms_render_region(page, region, request):
|
||||||
|
"""
|
||||||
|
{% feincms_render_region feincms_page "main" request %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
contents = getattr(page.content, region)
|
||||||
|
|
||||||
|
return u''.join(content.render(request=request) for content in contents)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def feincms_render_content(content, request):
|
||||||
|
"""
|
||||||
|
{% feincms_render_content pagecontent request %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
return content.render(request=request)
|
||||||
|
|
||||||
|
|
||||||
|
class NaviLevelNode(template.Node):
|
||||||
|
""" Gets navigation based on current page OR request, dependant on choice of second parameter (of vs. from).
|
||||||
|
|
||||||
|
Top navigation level is 1.
|
||||||
|
If navigation level + 1 > page.level, the ouput is none, because there is no well-defined sub-sub-navigation for a page.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
1) {% feincms_get_navi_level 1 of page as pages %}
|
||||||
|
2) {% feincms_get_navi_level 1 from request as pages %}
|
||||||
|
|
||||||
|
Side-note: If not using mptt to retrieve pages, the ordering cannot be dertermined by 'id'.
|
||||||
|
Instead, as a "hack", we can sort by field 'lft', because we understand how mptt works :-)
|
||||||
|
"""
|
||||||
|
def __init__(self, level, switch, obj, dummy, varname):
|
||||||
|
self.level = long(int(level) - 1)
|
||||||
|
self.obj = template.Variable(obj)
|
||||||
|
self.varname = varname
|
||||||
|
self.switch = switch
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
if self.switch == 'of':
|
||||||
|
# obj is a Page
|
||||||
|
page = self.obj.resolve(context)
|
||||||
|
else: # self.switch == 'from'
|
||||||
|
# obj is a request
|
||||||
|
page = Page.objects.from_request(self.obj.resolve(context))
|
||||||
|
|
||||||
|
if int(self.level) == 0:
|
||||||
|
# top level
|
||||||
|
pages = Page.objects.filter(in_navigation=True, level=long(0)).order_by('lft')
|
||||||
|
elif self.level <= page.level:
|
||||||
|
ancestor = page.get_ancestors()[int(self.level) - 1]
|
||||||
|
pages = Page.objects.filter(in_navigation=True, parent__pk=ancestor.pk).order_by('lft')
|
||||||
|
elif self.level == page.level + 1:
|
||||||
|
pages = Page.objects.filter(in_navigation=True, parent__pk=page.pk).order_by('lft')
|
||||||
|
else:
|
||||||
|
pages = []
|
||||||
|
|
||||||
|
context[self.varname] = pages
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@register.tag
|
||||||
|
def feincms_get_navi_level(parser, token):
|
||||||
|
try:
|
||||||
|
tag_name, level, switch, obj, dummy, varname = token.split_contents()
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError, "%r tag requires exactly five arguments" % token.contents.split()[0]
|
||||||
|
return NaviLevelNode(level, switch, obj, dummy, varname)
|
||||||
|
|
150
feincms/templatetags/utils.py
Normal file
150
feincms/templatetags/utils.py
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
'''
|
||||||
|
I really hate repeating myself. These are helpers that avoid typing the
|
||||||
|
whole thing over and over when implementing additional template tags
|
||||||
|
|
||||||
|
They help implementing tags of the form
|
||||||
|
|
||||||
|
{% tag as var_name %} (SimpleAssignmentNode)
|
||||||
|
and
|
||||||
|
{% tag of template_var as var_name %} (SimpleAssignmentNodeWithVar)
|
||||||
|
'''
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
def _parse_args(argstr):
|
||||||
|
try:
|
||||||
|
args = {}
|
||||||
|
for token in argstr.split(','):
|
||||||
|
k, v = token.split('=')
|
||||||
|
args[k] = v
|
||||||
|
|
||||||
|
return args
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
raise template.TemplateSyntaxError('Malformed arguments')
|
||||||
|
|
||||||
|
def do_simple_node_with_var_and_args_helper(cls):
|
||||||
|
def _func(parser, token):
|
||||||
|
try:
|
||||||
|
tag_name, of_, in_var_name, args = token.contents.split()
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError
|
||||||
|
|
||||||
|
return cls(tag_name, in_var_name, args)
|
||||||
|
|
||||||
|
return _func
|
||||||
|
|
||||||
|
class SimpleNodeWithVarAndArgs(template.Node):
|
||||||
|
def __init__(self, tag_name, in_var_name, args):
|
||||||
|
self.tag_name = tag_name
|
||||||
|
self.in_var = template.Variable(in_var_name)
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
try:
|
||||||
|
instance = self.in_var.resolve(context)
|
||||||
|
except template.VariableDoesNotExist:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return self.what(instance, _parse_args(self.args))
|
||||||
|
|
||||||
|
def do_simple_node_with_var_helper(cls):
|
||||||
|
def _func(parser, token):
|
||||||
|
try:
|
||||||
|
tag_name, of_, in_var_name = token.contents.split()
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError
|
||||||
|
|
||||||
|
return cls(tag_name, in_var_name)
|
||||||
|
|
||||||
|
return _func
|
||||||
|
|
||||||
|
class SimpleNodeWithVar(template.Node):
|
||||||
|
def __init__(self, tag_name, in_var_name):
|
||||||
|
self.tag_name = tag_name
|
||||||
|
self.in_var = template.Variable(in_var_name)
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
try:
|
||||||
|
instance = self.in_var.resolve(context)
|
||||||
|
except template.VariableDoesNotExist:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return self.what(instance)
|
||||||
|
|
||||||
|
def do_simple_assignment_node_helper(cls):
|
||||||
|
def _func(parser, token):
|
||||||
|
try:
|
||||||
|
tag_name, as_, var_name = token.contents.split()
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError
|
||||||
|
|
||||||
|
return cls(tag_name, var_name)
|
||||||
|
|
||||||
|
return _func
|
||||||
|
|
||||||
|
class SimpleAssignmentNode(template.Node):
|
||||||
|
def __init__(self, tag_name, var_name):
|
||||||
|
self.tag_name = tag_name
|
||||||
|
self.var_name = var_name
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
context[self.var_name] = self.what()
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def do_simple_assignment_node_with_var_helper(cls):
|
||||||
|
def _func(parser, token):
|
||||||
|
try:
|
||||||
|
tag_name, of_, in_var_name, as_, var_name = token.contents.split()
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError
|
||||||
|
|
||||||
|
return cls(tag_name, in_var_name, var_name)
|
||||||
|
|
||||||
|
return _func
|
||||||
|
|
||||||
|
class SimpleAssignmentNodeWithVar(template.Node):
|
||||||
|
def __init__(self, tag_name, in_var_name, var_name):
|
||||||
|
self.tag_name = tag_name
|
||||||
|
self.in_var = template.Variable(in_var_name)
|
||||||
|
self.var_name = var_name
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
try:
|
||||||
|
instance = self.in_var.resolve(context)
|
||||||
|
except template.VariableDoesNotExist:
|
||||||
|
context[self.var_name] = []
|
||||||
|
return ''
|
||||||
|
|
||||||
|
context[self.var_name] = self.what(instance)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def do_simple_assignment_node_with_var_and_args_helper(cls):
|
||||||
|
def _func(parser, token):
|
||||||
|
try:
|
||||||
|
tag_name, of_, in_var_name, as_, var_name, args = token.contents.split()
|
||||||
|
except ValueError:
|
||||||
|
raise template.TemplateSyntaxError
|
||||||
|
|
||||||
|
return cls(tag_name, in_var_name, var_name, args)
|
||||||
|
|
||||||
|
return _func
|
||||||
|
|
||||||
|
class SimpleAssignmentNodeWithVarAndArgs(template.Node):
|
||||||
|
def __init__(self, tag_name, in_var_name, var_name, args):
|
||||||
|
self.tag_name = tag_name
|
||||||
|
self.in_var = template.Variable(in_var_name)
|
||||||
|
self.var_name = var_name
|
||||||
|
self.args = args
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
try:
|
||||||
|
instance = self.in_var.resolve(context)
|
||||||
|
except template.VariableDoesNotExist:
|
||||||
|
context[self.var_name] = []
|
||||||
|
return ''
|
||||||
|
|
||||||
|
context[self.var_name] = self.what(instance, _parse_args(self.args))
|
||||||
|
|
||||||
|
return ''
|
||||||
|
|
0
feincms/views/__init__.py
Normal file
0
feincms/views/__init__.py
Normal file
23
feincms/views/base.py
Normal file
23
feincms/views/base.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import render_to_response
|
||||||
|
from django.template import RequestContext
|
||||||
|
from django.utils import translation
|
||||||
|
|
||||||
|
from feincms.module.page.models import Page
|
||||||
|
|
||||||
|
|
||||||
|
def handler(request, path=None):
|
||||||
|
if path is None:
|
||||||
|
path = request.path
|
||||||
|
|
||||||
|
page = Page.objects.page_for_path_or_404(path)
|
||||||
|
|
||||||
|
if page.redirect_to:
|
||||||
|
return HttpResponseRedirect(page.redirect_to)
|
||||||
|
|
||||||
|
page.setup_request(request)
|
||||||
|
|
||||||
|
return render_to_response(page.template.path, {
|
||||||
|
'feincms_page': page,
|
||||||
|
}, context_instance=RequestContext(request))
|
||||||
|
|
16
feincms/views/decorators.py
Normal file
16
feincms/views/decorators.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
try:
|
||||||
|
from functools import wraps
|
||||||
|
except ImportError:
|
||||||
|
from django.utils.functional import wraps
|
||||||
|
|
||||||
|
from feincms.module.page.models import Page
|
||||||
|
|
||||||
|
|
||||||
|
def add_page_to_extra_context(view_func):
|
||||||
|
def inner(request, *args, **kwargs):
|
||||||
|
kwargs.setdefault('extra_context', {})
|
||||||
|
kwargs['extra_context']['feincms_page'] = Page.objects.best_match_for_request(request)
|
||||||
|
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
return wraps(view_func)(inner)
|
||||||
|
|
0
feincms/views/generic/__init__.py
Normal file
0
feincms/views/generic/__init__.py
Normal file
8
feincms/views/generic/create_update.py
Normal file
8
feincms/views/generic/create_update.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.views.generic import create_update
|
||||||
|
from feincms.views.decorators import add_page_to_extra_context
|
||||||
|
|
||||||
|
|
||||||
|
create_object = add_page_to_extra_context(create_update.create_object)
|
||||||
|
update_object = add_page_to_extra_context(create_update.update_object)
|
||||||
|
delete_object = add_page_to_extra_context(create_update.delete_object)
|
||||||
|
|
12
feincms/views/generic/date_based.py
Normal file
12
feincms/views/generic/date_based.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.views.generic import date_based
|
||||||
|
from feincms.views.decorators import add_page_to_extra_context
|
||||||
|
|
||||||
|
|
||||||
|
archive_index = add_page_to_extra_context(date_based.archive_index)
|
||||||
|
archive_year = add_page_to_extra_context(date_based.archive_year)
|
||||||
|
archive_month = add_page_to_extra_context(date_based.archive_month)
|
||||||
|
archive_week = add_page_to_extra_context(date_based.archive_week)
|
||||||
|
archive_day = add_page_to_extra_context(date_based.archive_day)
|
||||||
|
archive_today = add_page_to_extra_context(date_based.archive_today)
|
||||||
|
object_detail = add_page_to_extra_context(date_based.object_detail)
|
||||||
|
|
7
feincms/views/generic/list_detail.py
Normal file
7
feincms/views/generic/list_detail.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.views.generic import list_detail
|
||||||
|
from feincms.views.decorators import add_page_to_extra_context
|
||||||
|
|
||||||
|
|
||||||
|
object_list = add_page_to_extra_context(list_detail.object_list)
|
||||||
|
object_detail = add_page_to_extra_context(list_detail.object_detail)
|
||||||
|
|
6
feincms/views/generic/simple.py
Normal file
6
feincms/views/generic/simple.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.views.generic import simple
|
||||||
|
from feincms.views.decorators import add_page_to_extra_context
|
||||||
|
|
||||||
|
|
||||||
|
direct_to_template = add_page_to_extra_context(simple.direct_to_template)
|
||||||
|
|
@ -12,6 +12,19 @@ from PIL import Image
|
|||||||
import csv
|
import csv
|
||||||
import re
|
import re
|
||||||
import datetime
|
import datetime
|
||||||
|
from save_carefully import save_carefully
|
||||||
|
|
||||||
|
def get_or_create_placeholder(year):
|
||||||
|
""" All surveys must be related to a logbookentry. We don't have a way to
|
||||||
|
automatically figure out which survey went with which logbookentry,
|
||||||
|
so we create a survey placeholder logbook entry for each year. This
|
||||||
|
function always returns such a placeholder, and creates it if it doesn't
|
||||||
|
exist yet.
|
||||||
|
"""
|
||||||
|
lookupAttribs={'date__year':int(year), 'title':"placeholder for surveys",}
|
||||||
|
nonLookupAttribs={'text':"surveys temporarily attached to this should be re-attached to their actual trips", 'date':datetime.date(int(year),1,1)}
|
||||||
|
placeholder_logbook_entry, newly_created = save_carefully(LogbookEntry, lookupAttribs, nonLookupAttribs)
|
||||||
|
return placeholder_logbook_entry
|
||||||
|
|
||||||
def readSurveysFromCSV(logfile=None):
|
def readSurveysFromCSV(logfile=None):
|
||||||
try:
|
try:
|
||||||
@ -42,13 +55,16 @@ def readSurveysFromCSV(logfile=None):
|
|||||||
logfile.write("Beginning to import surveys from "+str(os.path.join(settings.SURVEYS, "Surveys.csv"))+"\n"+"-"*60+"\n")
|
logfile.write("Beginning to import surveys from "+str(os.path.join(settings.SURVEYS, "Surveys.csv"))+"\n"+"-"*60+"\n")
|
||||||
|
|
||||||
for survey in surveyreader:
|
for survey in surveyreader:
|
||||||
walletNumberLetter = re.match(r'(?P<number>\d*)(?P<letter>[a-zA-Z]*)',survey[header['Survey Number']]) #I hate this, but some surveys have a letter eg 2000#34a. This line deals with that.
|
#I hate this, but some surveys have a letter eg 2000#34a. The next line deals with that.
|
||||||
|
walletNumberLetter = re.match(r'(?P<number>\d*)(?P<letter>[a-zA-Z]*)',survey[header['Survey Number']])
|
||||||
# print walletNumberLetter.groups()
|
# print walletNumberLetter.groups()
|
||||||
|
year=survey[header['Year']]
|
||||||
|
|
||||||
|
|
||||||
surveyobj = Survey(
|
surveyobj = Survey(
|
||||||
expedition = Expedition.objects.filter(year=survey[header['Year']])[0],
|
expedition = Expedition.objects.filter(year=year)[0],
|
||||||
wallet_number = walletNumberLetter.group('number'),
|
wallet_number = walletNumberLetter.group('number'),
|
||||||
|
logbook_entry = get_or_create_placeholder(year),
|
||||||
comments = survey[header['Comments']],
|
comments = survey[header['Comments']],
|
||||||
location = survey[header['Location']]
|
location = survey[header['Location']]
|
||||||
)
|
)
|
||||||
@ -101,7 +117,8 @@ def parseSurveyScans(year, logfile=None):
|
|||||||
if type(surveyNumber)==types.TupleType:
|
if type(surveyNumber)==types.TupleType:
|
||||||
surveyNumber=surveyNumber[0]
|
surveyNumber=surveyNumber[0]
|
||||||
try:
|
try:
|
||||||
survey=Survey.objects.get_or_create(wallet_number=surveyNumber, expedition=year)[0]
|
placeholder=get_or_create_placeholder(year=int(year.year))
|
||||||
|
survey=Survey.objects.get_or_create(wallet_number=surveyNumber, expedition=year, defaults={'logbook_entry':placeholder})[0]
|
||||||
except Survey.MultipleObjectsReturned:
|
except Survey.MultipleObjectsReturned:
|
||||||
survey=Survey.objects.filter(wallet_number=surveyNumber, expedition=year)[0]
|
survey=Survey.objects.filter(wallet_number=surveyNumber, expedition=year)[0]
|
||||||
file=os.path.join(year.year, surveyFolder, scan)
|
file=os.path.join(year.year, surveyFolder, scan)
|
||||||
|
@ -86,6 +86,8 @@ INSTALLED_APPS = (
|
|||||||
'troggle.profiles',
|
'troggle.profiles',
|
||||||
'troggle.expo',
|
'troggle.expo',
|
||||||
'troggle.imagekit',
|
'troggle.imagekit',
|
||||||
|
'mptt', #This is django-mptt (modifed preorder tree traversal) which allows the tree structure of subcaves.
|
||||||
|
'feincms' #This is a little content management app that does the javascript admin page for mptt.
|
||||||
)
|
)
|
||||||
|
|
||||||
from localsettings import * #localsettings needs to take precedence. Call it to override any existing vars.
|
from localsettings import * #localsettings needs to take precedence. Call it to override any existing vars.
|
@ -67,7 +67,7 @@
|
|||||||
<div id="progressTable" class="menuBarItem"> {% if current_expedition.survey_set.all %}✓{% endif %}
|
<div id="progressTable" class="menuBarItem"> {% if current_expedition.survey_set.all %}✓{% endif %}
|
||||||
survey progress table </div>
|
survey progress table </div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if current_survey %}
|
||||||
<h3>Choose a wallet number </h3>
|
<h3>Choose a wallet number </h3>
|
||||||
<center>
|
<center>
|
||||||
<select id="surveyChooser" class="centre" onChange="redirectSurvey()">
|
<select id="surveyChooser" class="centre" onChange="redirectSurvey()">
|
||||||
@ -101,8 +101,10 @@
|
|||||||
<div id="mainSketchIntegration" class="menuBarItem" ">add to main sketch</div>
|
<div id="mainSketchIntegration" class="menuBarItem" ">add to main sketch</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="mainContent">
|
<div id="mainContent">
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user