diff --git a/categories/admin.py b/categories/admin.py index 495fcf0..a34e314 100644 --- a/categories/admin.py +++ b/categories/admin.py @@ -1,99 +1,48 @@ -from ellington.categories.models import Hierarchy, Category, Alias +from models import Category from django.contrib import admin from django import forms -from ellington.core.forms import RequiredModelForm +from django.template.defaultfilters import slugify +from mptt.forms import TreeNodeChoiceField +from editor import TreeEditorMixin -class CategoryAdminForm(RequiredModelForm): - required_if_other_not_given = { - 'hierarchy': 'parent', - 'parent': 'hierarchy', - } - +class CategoryAdminForm(forms.ModelForm): + parent = TreeNodeChoiceField(queryset=Category.tree.all(), level_indicator=u'+-', required=False) class Meta: model = Category - - def clean_name(self): - if '/' in self.cleaned_data['name']: - raise forms.ValidationError, "A category name can't contain slashes." - return self.cleaned_data['name'] - + + def clean_slug(self): + self.cleaned_data['slug'] = slugify(self.cleaned_data['name']) + return self.cleaned_data['slug'] + def clean(self): super(CategoryAdminForm, self).clean() - if 'slug' in self.cleaned_data and 'parent' in self.cleaned_data and 'hierarchy' in self.cleaned_data: - if self.cleaned_data['parent'] is not None: - # inherit from parent - self.cleaned_data['hierarchy'] = self.cleaned_data['parent'].hierarchy - - #Validate slug - kwargs = {} - if self.cleaned_data.get('hierarchy', False): - kwargs['hierarchy__pk'] = int(self.cleaned_data['hierarchy'].id) - kwargs['parent__isnull'] = True - else: - kwargs['parent__pk'] = int(self.cleaned_data['parent'].id) - this_level_slugs = [c.slug for c in Category.objects.filter(**kwargs) if c.id != self.cleaned_data.get("id", None)] - if self.cleaned_data['slug'] in this_level_slugs: - raise forms.ValidationError("A category slug must be unique among categories at its level.") - - #Validate Category Parent - "Makes sure the category doesn't set itself or any of its children as its parent." - if not self.cleaned_data['parent']: - return self.cleaned_data - - p_data = int(self.cleaned_data['parent'].pk) - h_data = self.cleaned_data.get('hierarchy', False) - if h_data: - h_data = int(h_data.pk) - if p_data and h_data: - p = Category.objects.get(pk=p_data) - if p.hierarchy_id != h_data: - raise forms.ValidationError("This parent is not within the selected hierarchy.") - - # Check that the parent isn't a child of this category - # Requires that we look up "this" object; if it doesn't exist - # we can assume we're at the add stage and return - this_id = self.cleaned_data.get("id", None) - if not this_id: - return self.cleaned_data - - try: - selected_parent = Category.objects.get(pk=p_data) - except Category.DoesNotExist: - return self.cleaned_data - - if selected_parent.id == this_id: - raise forms.ValidationError("A category can't be its own parent.") - - try: - this_category = Category.objects.get(pk=p_data) - except Category.DoesNotExist: - return self.cleaned_data - - for c in this_category.get_all_children(): - if c.id == this_id: - raise forms.ValidationError("A category can't set a child category to be its own parent.") - return self.cleaned_data + # Validate slug is valid in that level + kwargs = {} + if self.cleaned_data.get('parent', None) is None: + kwargs['parent__isnull'] = True else: - raise forms.ValidationError("Cannot clean data") + kwargs['parent__pk'] = int(self.cleaned_data['parent'].id) + this_level_slugs = [c['slug'] for c in Category.objects.filter(**kwargs).values('id','slug') if c['id'] != self.instance.id] + if self.cleaned_data['slug'] in this_level_slugs: + raise forms.ValidationError("A category slug must be unique among categories at its level.") + + # Validate Category Parent + # Make sure the category doesn't set itself or any of its children as its parent." + if self.cleaned_data.get('parent', None) is None or self.instance.id is None: + return self.cleaned_data + elif self.cleaned_data['parent'].id == self.instance.id: + raise forms.ValidationError("You can't set the parent of the category to itself.") + elif self.cleaned_data['parent'].id in [i[0] for i in self.instance.get_descendants().values_list('id')]: + raise forms.ValidationError("You can't set the parent of the category to a descendant.") + return self.cleaned_data -class CategoryAdmin(admin.ModelAdmin): +class CategoryAdmin(TreeEditorMixin, admin.ModelAdmin): form=CategoryAdminForm - fields = ('hierarchy', 'parent', 'name', 'slug') list_display = ('__unicode__',) - list_filter = ('hierarchy',) - search_fields = ('name', 'path') + search_fields = (('name',)) prepopulated_fields = {'slug': ('name',)} -class HierarchyAdmin(admin.ModelAdmin): - prepopulated_fields = {'slug': ('name',)} - -class AliasAdmin(admin.ModelAdmin): - list_display = ['category', 'parent'] - search_fields = ['category__name', 'parent__name'] - admin.site.register(Category, CategoryAdmin) -admin.site.register(Hierarchy, HierarchyAdmin) -admin.site.register(Alias, AliasAdmin) diff --git a/categories/media/css/images/title.gif b/categories/media/css/images/title.gif new file mode 100644 index 0000000..f92b596 Binary files /dev/null and b/categories/media/css/images/title.gif differ diff --git a/categories/media/css/jquery.alerts.css b/categories/media/css/jquery.alerts.css new file mode 100644 index 0000000..0b5e1e8 --- /dev/null +++ b/categories/media/css/jquery.alerts.css @@ -0,0 +1,54 @@ +#popup_container { + font-family: Arial, sans-serif; + font-size: 12px; + min-width: 300px; /* Dialog will be no smaller than this */ + max-width: 600px; /* Dialog will wrap after this width */ + background: #FFF; + border: solid 1px #666; + color: #000; +} + +#popup_title { + font-size: 14px; + font-weight: bold; + text-align: center; + line-height: 1.75em; + color: #666; + background: #eee url(images/title.gif) top repeat-x; + border: solid 1px #FFF; + border-bottom: solid 1px #666; + cursor: default; + padding: 0em; + margin: 0em; +} + +#popup_content { + background: 16px 16px no-repeat url(../img/info.gif); + padding: 1em 1.75em; + margin: 0em; +} + +#popup_content.alert { + background-image: url(../img/info.gif); +} + +#popup_content.confirm { + background-image: url(../img/important.gif); +} + +#popup_content.prompt { + background-image: url(../img/help.gif); +} + +#popup_message { + padding-left: 48px; +} + +#popup_panel { + text-align: center; + margin: 1em 0em 0em 1em; +} + +#popup_prompt { + margin: .5em 0em; +} \ No newline at end of file diff --git a/categories/media/css/jquery.treeTable.css b/categories/media/css/jquery.treeTable.css new file mode 100644 index 0000000..5305653 --- /dev/null +++ b/categories/media/css/jquery.treeTable.css @@ -0,0 +1,47 @@ +/* jQuery TreeTable Core 2.0 stylesheet + * + * This file contains styles that are used to display the tree table. Each tree + * table is assigned the +treeTable+ class. + * ========================================================================= */ + +/* jquery.treeTable.collapsible + * ------------------------------------------------------------------------- */ +.treeTable tr td .expander { + background-position: left center; + background-repeat: no-repeat; + cursor: pointer; + padding: 0; + zoom: 1; /* IE7 Hack */ +} + +.treeTable tr.collapsed td .expander { + background-image: url(../img/toggle-expand-dark.png); +} + +.treeTable tr.expanded td .expander { + background-image: url(../img/toggle-collapse-dark.png); +} + +/* jquery.treeTable.sortable + * ------------------------------------------------------------------------- */ +.treeTable tr.selected, .treeTable tr.accept { + /*background-color: #7799ff; + color: #fff;*/ +} + +.treeTable tr.append { + border-bottom: 2px solid #7799ff; +} + +.treeTable tr.collapsed.selected td .expander, .treeTable tr.collapsed.accept td .expander { + /*background-image: url(../img/toggle-expand-light.png);*/ +} + +.treeTable tr.expanded.selected td .expander, .treeTable tr.expanded.accept td .expander { + /*background-image: url(../img/toggle-collapse-light.png);*/ +} + +.treeTable .ui-draggable-dragging { + color: #000; + z-index: 1; +} \ No newline at end of file diff --git a/categories/media/css/layout.css b/categories/media/css/layout.css new file mode 100644 index 0000000..116f9f7 --- /dev/null +++ b/categories/media/css/layout.css @@ -0,0 +1,217 @@ +#content.colM { + width:745px; +} + +.navi_tab { + float:left; + padding: 3px 10px 3px 10px; + cursor:pointer; + border: 1px solid #ccc; + border-bottom:none; + min-width:100px; + margin-top:3px; + height:13px; + font-weight: normal; + background-image:none; + color: black; +} +.tab_active { + margin-top: 0; + height:16px; + font-weight: bold; + background-image:url('../img/default-bg.gif'); + color: white; +} + +#main { + clear:both; + padding: 10px 10px 10px 10px; + border: 1px solid #ccc; + margin: 0 0 10px 0; +} +.panel { + display:none; + position:relative; + padding-bottom:50px; +} + +.order-machine { + margin-bottom:10px; +} + +.order-item { + margin: 0 0 10px 0; + position:relative; +} + +.order-item .handle { + display: block; + height: 100%; + width: 650px; + cursor: move; +} + +.item-delete { + cursor:pointer; + float:right; + margin:3px 0px 0px 5px; +} +.active-item { + background-color:#eee; +} + +.highlight { + height: 26px; + background-color:#def; + margin: 0 0 10px 0; +} + +.button { + margin:5px; padding:5px; + font-weight: bold; + background-image:url('../img/nav-bg.gif'); + cursor:pointer; + border: 1px solid #678; + width:30px; +} +th { vertical-align: middle; } + +#test-log { padding: 5px; border: 1px solid #ccc; } + +#overview span { margin-right:15px; margin-left:5px;} + +.richtextcontent {display:none;} + +select { + max-width: 580px; +} + +textarea { + width: 580px; + margin-top:5px; + margin-bottom:5px; +} + +.item-minimize { + width: 15px; + height:15px; + float: left; + cursor: pointer; + margin-left:-17px; +} + +.item-minimize-disabled { + width: 15px; + height:15px; + float: left; + margin-left:-17px; +} + +.machine-control { + height:50px; + padding: 5px 0px 5px 0px; + border: 1px solid #ccc; + background-color:#fff; + width:743px; + position:absolute; + left:-11px; + bottom:-11px; +} + +.control-unit { + float:left; + padding-left:15px; + padding-right:10px; + border-right: 1px solid #eee; +} + +.control-unit span { + font-weight:bold; +} + +.empty-machine-msg { + margin:10px 0px 5px 300px; + font-style: italic; + font-size:14px; + color:#999; +} + +td span select { + width:600px; +} + +.popup_bg { + width:100%; height:100%; + background-color:white; + opacity: 0.5; + filter:alpha(opacity=50); + position:absolute; + top:0px; left:0px; +} + +#sitetree-wrapper { + margin: 0; + padding: 20px 1px 0 1px; +} + +#sitetree { + width: 100%; +} + +#sitetree tr td { + overflow: hidden; + white-space: nowrap; +} + +.title-col { + position:relative; + z-index:2; + background-color:none; +} +td div.wrap { + border: 0px solid #000; + margin: -5px; + padding: 4px; + position:relative; +} + +.insert-as-child { + position:absolute; + bottom:-3px; + left: 15px; + height:15px; + width:100%; + border: 0px solid #0f0; +} +.insert-as-sibling { + position:absolute; + top:0px; + left: 15px; + height:10px; + width:100%; + border: 0px solid #f00; +} + +.nohover { + background-color: #eef; +} + +.flash { + background-color: #afa; +} + +.hover-as-child { + background-color:#cdf; +} + +.hover-as-sibling { + border: 1px solid #aee; +} + +.move-node { + cursor: move; + margin-left:5px; +} +.del-page { + cursor: pointer; +} diff --git a/categories/media/helper.js b/categories/media/helper.js new file mode 100644 index 0000000..9029fdc --- /dev/null +++ b/categories/media/helper.js @@ -0,0 +1,85 @@ +function region_append(region, obj, modname) { + var wrp = []; + wrp.push('
'); + wrp.push('

'+modname+'

'); + wrp.push('
'); + wrp.push('
'); + + $("#"+REGIONS[region]+"_body").children("div.order-machine").append(wrp.join("")) + .children("fieldset.order-item:last").children(".item-content").append(obj); +} + +function create_new_spare_form(form, modvar, last_id) { + // create new spare form + var new_form = form.html().replace( + new RegExp(modvar+'-'+last_id, 'g'), + modvar+'-'+(last_id+1)); + new_form = '
'+new_form+'
'; + $("#"+modvar+"_set").append(new_form); +} + +function set_item_field_value(item, field, value) { + // item: DOM object created by 'region_append' function + // field: "order-field" | "delete-field" | "region-choice-field" + if (field=="delete-field") + item.find("."+field).attr("checked",value); + else if (field=="region-choice-field") { + var old_region_id = REGION_MAP.indexOf(item.find("."+field).val()); + item.find("."+field).val(REGION_MAP[value]); + + old_region_item = $("#"+REGIONS[old_region_id]+"_body"); + if (old_region_item.children("div.order-machine").children().length == 0) + old_region_item.children("div.empty-machine-msg").show(); + else + old_region_item.children("div.empty-machine-msg").hide(); + + new_region_item = $("#"+REGIONS[value]+"_body"); + new_region_item.children("div.empty-machine-msg").hide(); + } + else + item.find("."+field).val(value); +} + +function move_item (region_id, item) { + poorify_rich(item); + $("#"+REGIONS[region_id]+"_body").children("div.order-machine").append(item); + set_item_field_value(item, "region-choice-field", region_id); + richify_poor(item); +} + +function poorify_rich(item){ + item.children(".item-content").hide(); + if (item.find("div[id^=richtext]").length > 0) { + var editor_id = item.find(".mceEditor").prev().attr("id"); + tinyMCE.execCommand('mceRemoveControl', false, editor_id); + } +} +function richify_poor(item){ + item.children(".item-content").show(); + if (item.find("div[id^=richtext]").length > 0) { + var editor_id = item.find('textarea[name*=richtext]:visible').attr("id"); + tinyMCE.execCommand('mceAddControl', false, editor_id); + } +} + +function zucht_und_ordnung(move_item) { + for (var i=0; i' + + '

' + + '' + + ''); + + if( $.alerts.dialogClass ) $("#popup_container").addClass($.alerts.dialogClass); + + // IE6 Fix + var pos = ($.browser.msie && parseInt($.browser.version) <= 6 ) ? 'absolute' : 'fixed'; + + $("#popup_container").css({ + position: pos, + zIndex: 99999, + padding: 0, + margin: 0 + }); + + $("#popup_title").text(title); + $("#popup_content").addClass(type); + $("#popup_message").text(msg); + $("#popup_message").html( $("#popup_message").text().replace(/\n/g, '
') ); + + $("#popup_container").css({ + minWidth: $("#popup_container").outerWidth(), + maxWidth: $("#popup_container").outerWidth() + }); + + $.alerts._reposition(); + $.alerts._maintainPosition(true); + + switch( type ) { + case 'alert': + $("#popup_message").after(''); + $("#popup_ok").click( function() { + $.alerts._hide(); + callback(true); + }); + $("#popup_ok").focus().keypress( function(e) { + if( e.keyCode == 13 || e.keyCode == 27 ) $("#popup_ok").trigger('click'); + }); + break; + case 'confirm': + $("#popup_message").after(''); + $("#popup_ok").click( function() { + $.alerts._hide(); + if( callback ) callback(true); + }); + $("#popup_cancel").click( function() { + $.alerts._hide(); + if( callback ) callback(false); + }); + $("#popup_ok").focus(); + $("#popup_ok, #popup_cancel").keypress( function(e) { + if( e.keyCode == 13 ) $("#popup_ok").trigger('click'); + if( e.keyCode == 27 ) $("#popup_cancel").trigger('click'); + }); + break; + case 'prompt': + $("#popup_message").append('
').after(''); + $("#popup_prompt").width( $("#popup_message").width() ); + $("#popup_ok").click( function() { + var val = $("#popup_prompt").val(); + $.alerts._hide(); + if( callback ) callback( val ); + }); + $("#popup_cancel").click( function() { + $.alerts._hide(); + if( callback ) callback( null ); + }); + $("#popup_prompt, #popup_ok, #popup_cancel").keypress( function(e) { + if( e.keyCode == 13 ) $("#popup_ok").trigger('click'); + if( e.keyCode == 27 ) $("#popup_cancel").trigger('click'); + }); + if( value ) $("#popup_prompt").val(value); + $("#popup_prompt").focus().select(); + break; + } + + // Make draggable + if( $.alerts.draggable ) { + try { + $("#popup_container").draggable({ handle: $("#popup_title") }); + $("#popup_title").css({ cursor: 'move' }); + } catch(e) { /* requires jQuery UI draggables */ } + } + }, + + _hide: function() { + $("#popup_container").remove(); + $.alerts._overlay('hide'); + $.alerts._maintainPosition(false); + }, + + _overlay: function(status) { + switch( status ) { + case 'show': + $.alerts._overlay('hide'); + $("BODY").append(''); + $("#popup_overlay").css({ + position: 'absolute', + zIndex: 99998, + top: '0px', + left: '0px', + width: '100%', + height: $(document).height(), + background: $.alerts.overlayColor, + opacity: $.alerts.overlayOpacity, + display: 'none' + }); + break; + case 'hide': + $("#popup_overlay").remove(); + break; + } + }, + + _reposition: function() { + var top = (($(window).height() / 2) - ($("#popup_container").outerHeight() / 2)) + $.alerts.verticalOffset; + var left = (($(window).width() / 2) - ($("#popup_container").outerWidth() / 2)) + $.alerts.horizontalOffset; + if( top < 0 ) top = 0; + if( left < 0 ) left = 0; + + // IE6 fix + if( $.browser.msie && parseInt($.browser.version) <= 6 ) top = top + $(window).scrollTop(); + + $("#popup_container").css({ + top: top + 'px', + left: left + 'px' + }); + $("#popup_overlay").height( $(document).height() ); + }, + + _maintainPosition: function(status) { + if( $.alerts.repositionOnResize ) { + switch(status) { + case true: + $(window).bind('resize', function() { + $.alerts._reposition(); + }); + break; + case false: + $(window).unbind('resize'); + break; + } + } + } + + } + + // Shortuct functions + jAlert = function(message, title, callback) { + $.alerts.alert(message, title, callback); + } + + jConfirm = function(message, title, callback) { + $.alerts.confirm(message, title, callback); + }; + + jPrompt = function(message, value, title, callback) { + $.alerts.prompt(message, value, title, callback); + }; + +})(jQuery); \ No newline at end of file diff --git a/categories/media/jquery.json-1.3.js b/categories/media/jquery.json-1.3.js new file mode 100644 index 0000000..225ca82 --- /dev/null +++ b/categories/media/jquery.json-1.3.js @@ -0,0 +1,156 @@ +/* + * jQuery JSON Plugin + * version: 1.0 (2008-04-17) + * + * This document is licensed as free software under the terms of the + * MIT License: http://www.opensource.org/licenses/mit-license.php + * + * Brantley Harris technically wrote this plugin, but it is based somewhat + * on the JSON.org website's http://www.json.org/json2.js, which proclaims: + * "NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.", a sentiment that + * I uphold. I really just cleaned it up. + * + * It is also based heavily on MochiKit's serializeJSON, which is + * copywrited 2005 by Bob Ippolito. + */ + +(function($) { + function toIntegersAtLease(n) + // Format integers to have at least two digits. + { + return n < 10 ? '0' + n : n; + } + + Date.prototype.toJSON = function(date) + // Yes, it polutes the Date namespace, but we'll allow it here, as + // it's damned usefull. + { + return this.getUTCFullYear() + '-' + + toIntegersAtLease(this.getUTCMonth()) + '-' + + toIntegersAtLease(this.getUTCDate()); + }; + + var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g; + var meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + + $.quoteString = function(string) + // Places quotes around a string, inteligently. + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can safely slap some quotes around it. + // Otherwise we must also replace the offending characters with safe escape + // sequences. + { + if (escapeable.test(string)) + { + return '"' + string.replace(escapeable, function (a) + { + var c = meta[a]; + if (typeof c === 'string') { + return c; + } + c = a.charCodeAt(); + return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16); + }) + '"'; + } + return '"' + string + '"'; + }; + + $.toJSON = function(o, compact) + { + var type = typeof(o); + + if (type == "undefined") + return "undefined"; + else if (type == "number" || type == "boolean") + return o + ""; + else if (o === null) + return "null"; + + // Is it a string? + if (type == "string") + { + return $.quoteString(o); + } + + // Does it have a .toJSON function? + if (type == "object" && typeof o.toJSON == "function") + return o.toJSON(compact); + + // Is it an array? + if (type != "function" && typeof(o.length) == "number") + { + var ret = []; + for (var i = 0; i < o.length; i++) { + ret.push( $.toJSON(o[i], compact) ); + } + if (compact) + return "[" + ret.join(",") + "]"; + else + return "[" + ret.join(", ") + "]"; + } + + // If it's a function, we have to warn somebody! + if (type == "function") { + throw new TypeError("Unable to convert object of type 'function' to json."); + } + + // It's probably an object, then. + var ret = []; + for (var k in o) { + var name; + type = typeof(k); + + if (type == "number") + name = '"' + k + '"'; + else if (type == "string") + name = $.quoteString(k); + else + continue; //skip non-string or number keys + + var val = $.toJSON(o[k], compact); + if (typeof(val) != "string") { + // skip non-serializable values + continue; + } + + if (compact) + ret.push(name + ":" + val); + else + ret.push(name + ": " + val); + } + return "{" + ret.join(", ") + "}"; + }; + + $.compactJSON = function(o) + { + return $.toJSON(o, true); + }; + + $.evalJSON = function(src) + // Evals JSON that we know to be safe. + { + return eval("(" + src + ")"); + }; + + $.secureEvalJSON = function(src) + // Evals JSON in a way that is *more* secure. + { + var filtered = src; + filtered = filtered.replace(/\\["\\\/bfnrtu]/g, '@'); + filtered = filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + filtered = filtered.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + + if (/^[\],:{}\s]*$/.test(filtered)) + return eval("(" + src + ")"); + else + throw new SyntaxError("Error parsing JSON, source is not valid."); + }; +})(jQuery); diff --git a/categories/media/jquery.livequery.js b/categories/media/jquery.livequery.js new file mode 100644 index 0000000..dde8ad8 --- /dev/null +++ b/categories/media/jquery.livequery.js @@ -0,0 +1,250 @@ +/*! Copyright (c) 2008 Brandon Aaron (http://brandonaaron.net) + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + * Version: 1.0.3 + * Requires jQuery 1.1.3+ + * Docs: http://docs.jquery.com/Plugins/livequery + */ + +(function($) { + +$.extend($.fn, { + livequery: function(type, fn, fn2) { + var self = this, q; + + // Handle different call patterns + if ($.isFunction(type)) + fn2 = fn, fn = type, type = undefined; + + // See if Live Query already exists + $.each( $.livequery.queries, function(i, query) { + if ( self.selector == query.selector && self.context == query.context && + type == query.type && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) ) + // Found the query, exit the each loop + return (q = query) && false; + }); + + // Create new Live Query if it wasn't found + q = q || new $.livequery(this.selector, this.context, type, fn, fn2); + + // Make sure it is running + q.stopped = false; + + // Run it immediately for the first time + q.run(); + + // Contnue the chain + return this; + }, + + expire: function(type, fn, fn2) { + var self = this; + + // Handle different call patterns + if ($.isFunction(type)) + fn2 = fn, fn = type, type = undefined; + + // Find the Live Query based on arguments and stop it + $.each( $.livequery.queries, function(i, query) { + if ( self.selector == query.selector && self.context == query.context && + (!type || type == query.type) && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) && !this.stopped ) + $.livequery.stop(query.id); + }); + + // Continue the chain + return this; + } +}); + +$.livequery = function(selector, context, type, fn, fn2) { + this.selector = selector; + this.context = context || document; + this.type = type; + this.fn = fn; + this.fn2 = fn2; + this.elements = []; + this.stopped = false; + + // The id is the index of the Live Query in $.livequery.queries + this.id = $.livequery.queries.push(this)-1; + + // Mark the functions for matching later on + fn.$lqguid = fn.$lqguid || $.livequery.guid++; + if (fn2) fn2.$lqguid = fn2.$lqguid || $.livequery.guid++; + + // Return the Live Query + return this; +}; + +$.livequery.prototype = { + stop: function() { + var query = this; + + if ( this.type ) + // Unbind all bound events + this.elements.unbind(this.type, this.fn); + else if (this.fn2) + // Call the second function for all matched elements + this.elements.each(function(i, el) { + query.fn2.apply(el); + }); + + // Clear out matched elements + this.elements = []; + + // Stop the Live Query from running until restarted + this.stopped = true; + }, + + run: function() { + // Short-circuit if stopped + if ( this.stopped ) return; + var query = this; + + var oEls = this.elements, + els = $(this.selector, this.context), + nEls = els.not(oEls); + + // Set elements to the latest set of matched elements + this.elements = els; + + if (this.type) { + // Bind events to newly matched elements + nEls.bind(this.type, this.fn); + + // Unbind events to elements no longer matched + if (oEls.length > 0) + $.each(oEls, function(i, el) { + if ( $.inArray(el, els) < 0 ) + $.event.remove(el, query.type, query.fn); + }); + } + else { + // Call the first function for newly matched elements + nEls.each(function() { + query.fn.apply(this); + }); + + // Call the second function for elements no longer matched + if ( this.fn2 && oEls.length > 0 ) + $.each(oEls, function(i, el) { + if ( $.inArray(el, els) < 0 ) + query.fn2.apply(el); + }); + } + } +}; + +$.extend($.livequery, { + guid: 0, + queries: [], + queue: [], + running: false, + timeout: null, + + checkQueue: function() { + if ( $.livequery.running && $.livequery.queue.length ) { + var length = $.livequery.queue.length; + // Run each Live Query currently in the queue + while ( length-- ) + $.livequery.queries[ $.livequery.queue.shift() ].run(); + } + }, + + pause: function() { + // Don't run anymore Live Queries until restarted + $.livequery.running = false; + }, + + play: function() { + // Restart Live Queries + $.livequery.running = true; + // Request a run of the Live Queries + $.livequery.run(); + }, + + registerPlugin: function() { + $.each( arguments, function(i,n) { + // Short-circuit if the method doesn't exist + if (!$.fn[n]) return; + + // Save a reference to the original method + var old = $.fn[n]; + + // Create a new method + $.fn[n] = function() { + // Call the original method + var r = old.apply(this, arguments); + + // Request a run of the Live Queries + $.livequery.run(); + + // Return the original methods result + return r; + } + }); + }, + + run: function(id) { + if (id != undefined) { + // Put the particular Live Query in the queue if it doesn't already exist + if ( $.inArray(id, $.livequery.queue) < 0 ) + $.livequery.queue.push( id ); + } + else + // Put each Live Query in the queue if it doesn't already exist + $.each( $.livequery.queries, function(id) { + if ( $.inArray(id, $.livequery.queue) < 0 ) + $.livequery.queue.push( id ); + }); + + // Clear timeout if it already exists + if ($.livequery.timeout) clearTimeout($.livequery.timeout); + // Create a timeout to check the queue and actually run the Live Queries + $.livequery.timeout = setTimeout($.livequery.checkQueue, 20); + }, + + stop: function(id) { + if (id != undefined) + // Stop are particular Live Query + $.livequery.queries[ id ].stop(); + else + // Stop all Live Queries + $.each( $.livequery.queries, function(id) { + $.livequery.queries[ id ].stop(); + }); + } +}); + +// Register core DOM manipulation methods +$.livequery.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove'); + +// Run Live Queries when the Document is ready +$(function() { $.livequery.play(); }); + + +// Save a reference to the original init method +var init = $.prototype.init; + +// Create a new init method that exposes two new properties: selector and context +$.prototype.init = function(a,c) { + // Call the original init and save the result + var r = init.apply(this, arguments); + + // Copy over properties if they exist already + if (a && a.selector) + r.context = a.context, r.selector = a.selector; + + // Set properties + if ( typeof a == 'string' ) + r.context = c || document, r.selector = a; + + // Return the result + return r; +}; + +// Give the init function the jQuery prototype for later instantiation (needed after Rev 4091) +$.prototype.init.prototype = $.prototype; + +})(jQuery); \ No newline at end of file diff --git a/categories/media/jquery.treeTable.js b/categories/media/jquery.treeTable.js new file mode 100644 index 0000000..57c3721 --- /dev/null +++ b/categories/media/jquery.treeTable.js @@ -0,0 +1,384 @@ +/* jQuery treeTable Plugin 2.2 - http://ludo.cubicphuse.nl/jquery-plugins/treeTable/ */ +(function($) { + // Helps to make options available to all functions + // TODO: This gives problems when there are both expandable and non-expandable + // trees on a page. The options shouldn't be global to all these instances! + var options; + + $.fn.treeTable = function(opts) { + options = $.extend({}, $.fn.treeTable.defaults, opts); + + return this.each(function() { + $(this).addClass("treeTable").find("tbody tr").each(function() { + // Initialize root nodes only whenever possible + if(!options.expandable || $(this)[0].className.search("child-of-") == -1) { + initialize($(this)); + } + }); + }); + }; + + $.fn.treeTable.defaults = { + childPrefix: "child-of-", + expandable: true, + indent: 19, + initialState: "collapsed", + treeColumn: 0 + }; + + // Recursively hide all node's children in a tree + $.fn.collapse = function() { + $(this).addClass("collapsed"); + + childrenOf($(this)).each(function() { + initialize($(this)); + + if(!$(this).hasClass("collapsed")) { + $(this).collapse(); + } + + $(this).hide(); + }); + + return this; + }; + + // Recursively show all node's children in a tree + $.fn.expand = function() { + $(this).removeClass("collapsed").addClass("expanded"); + + childrenOf($(this)).each(function() { + initialize($(this)); + + if($(this).is(".expanded.parent")) { + $(this).expand(); + } + + $(this).show(); + }); + + return this; + }; + + // Add an entire branch to +destination+ + $.fn.appendBranchTo = function(destination) { + var node = $(this); + var parent = parentOf(node); + + var ancestorNames = $.map(ancestorsOf(destination), function(a) { return a.id; }); + + // Conditions: + // 1: +node+ should not be inserted in a location in a branch if this would + // result in +node+ being an ancestor of itself. + // 2: +node+ should not have a parent OR the destination should not be the + // same as +node+'s current parent (this last condition prevents +node+ + // from being moved to the same location where it already is). + // 3: +node+ should not be inserted as a child of +node+ itself. + if($.inArray(node[0].id, ancestorNames) == -1 && (!parent || (destination.attr("id") != parent[0].id)) && destination.attr("id") != node[0].id) { + indent(node, ancestorsOf(node).length * options.indent * -1); // Remove indentation + + if(parent) { node.removeClass(options.childPrefix + parent[0].id); } + + var dest_id = $(destination).attr("id"); + while ($(".child-of-"+dest_id).length > 0) { + var move_to = $(".child-of-"+dest_id+":last"); + dest_id = move_to.attr("id"); + } + + node.addClass(options.childPrefix + destination.attr("id")); + if (move_to) + moveChild(node, move_to); // Recursively move nodes to new location + else + moveChild(node, destination); + indent(node, ancestorsOf(node).length * options.indent); + } + + return this; + }; + + $.fn.insertBranchBefore = function(destination) { + var node = $(this); + var parent = parentOf_jQuery(node); + var dest_parent = parentOf_jQuery(destination); + + if ($(this).attr("id")==destination.attr("id")) + return false; + + var ancestorNames = $.map(ancestorsOf_jQuery(destination), function(a) { return a.id; }); + + indent(node, ancestorsOf_jQuery(node).length * options.indent * -1); // Remove indentation + + if(parent) { node.removeClass(options.childPrefix + parent[0].id); } + + if (dest_parent) + node.addClass(options.childPrefix + dest_parent.attr("id")); + + moveBefore(node, destination); // Recursively move nodes to new location + indent(node, (ancestorsOf_jQuery(node).length * options.indent)); + + return this; + }; + + // Add reverse() function from JS Arrays + $.fn.reverse = function() { + return this.pushStack(this.get().reverse(), arguments); + }; + + // Toggle an entire branch + $.fn.toggleBranch = function() { + if($(this).hasClass("collapsed")) { + $(this).expand(); + } else { + $(this).removeClass("expanded").collapse(); + } + + return this; + }; + + // === Private functions + + function ancestorsOf(node) { + var ancestors = []; + while(node = parentOf(node)) { + ancestors[ancestors.length] = node[0]; + } + return ancestors; + }; + + function childrenOf(node) { + return $("table.treeTable tbody tr." + options.childPrefix + node[0].id); + }; + + function indent(node, value) { + var cell = $(node.children("td")[options.treeColumn]); + var padding = parseInt(cell.css("padding-left"), 10) + value; + + cell.css("padding-left", + padding + "px"); + + childrenOf(node).each(function() { + indent($(this), value); + }); + }; + + function initialize(node) { + if(!node.hasClass("initialized")) { + node.addClass("initialized"); + + var childNodes = childrenOf(node); + + if(!node.hasClass("parent") && childNodes.length > 0) { + node.addClass("parent"); + } + + if(node.hasClass("parent")) { + var cell = $(node.children("td")[options.treeColumn]); + var padding = parseInt(cell.css("padding-left"), 10) + options.indent; + + childNodes.each(function() { + $($(this).children("td")[options.treeColumn]).css("padding-left", padding + "px"); + }); + + if(options.expandable) { + cell.children(":first").children("span").prepend(''); + //$(cell[0].firstChild).click(function() { node.toggleBranch(); }); + + // Check for a class set explicitly by the user, otherwise set the default class + if(!(node.hasClass("expanded") || node.hasClass("collapsed"))) { + node.addClass(options.initialState); + } + + if(node.hasClass("collapsed")) { + node.collapse(); + } else if (node.hasClass("expanded")) { + node.expand(); + } + } + } else { + var cell = $(node.children("td")[options.treeColumn]); + cell.children(":first").children("span").prepend(''); + } + node.children(":first").addClass("padded"); + } + }; + + function move(node, destination) { + node.insertAfter(destination); + childrenOf(node).reverse().each(function() { move($(this), node[0]); }); + }; + + function moveChild(node, destination) { + node.insertAfter(destination) + childrenOf(node).reverse().each(function() { move($(this), node[0]); }); + + }; + + function moveBefore(node, destination) { + node.insertBefore(destination) + childrenOf(node).reverse().each(function() { move($(this), node[0]); }); + }; + + function parentOf(node) { + + var classNames = node[0].className.split(' '); + + for(key in classNames) { + if(classNames[key].match("child-of-")) { + return $("#" + classNames[key].substring(9)); + } + } + }; +})(jQuery); + +// public functions +function handle_drop_event(source, dest, method){ + var ancestorNames = $.map(ancestorsOf_jQuery(dest), function(a) { return a.attr("id"); }); + if (method=="child") + dest.find(".wrap").removeClass("hover-as-child").addClass("nohover"); + else // method == "sibling" + dest.find("div:first").remove(); + // do not drop on itself or its own children, if method == "child" + if ( (method == "sibling") || (source.attr("id") != dest.attr("id") && $.inArray(source.attr("id"), ancestorNames) == -1) ) { + var source_child_of = null; + if (source.attr("class").match(/child-of-node-(\d+)/)) + source_child_of = source.attr("class").match(/child-of-node-(\d+)/)[0]; + var dest_child_of = "child-of-" + dest.attr("id"); + if (source_child_of && $("."+source_child_of).length - 1 == 0) { + var parent_id = "#" + source_child_of.substring(9); + $(parent_id).removeClass("parent"); + if ($(parent_id).hasClass("expanded")) + $(parent_id).removeClass("expanded").addClass("collapsed"); + $(parent_id+" .title-col span").removeClass("expander"); + } + if (method=="child") { + if ($("."+dest_child_of).length == 0) { + var parent_id = "#" + dest_child_of.substring(9); + $(parent_id).addClass("parent").find(".title-col span").addClass("expander"); + } + if (!dest.hasClass("expanded")) + dest.expand(); + // *** INSERT *** + source.appendBranchTo(dest); + } else // method == "sibling" + source.insertBranchBefore(dest); + } + source.find(".wrap").switchClass("nohover","flash",0).switchClass("flash","nohover",500); +} + +function handle_page_delete(node) { + var item_id = node.attr("class").match(/item-id-(\d+)/)[1]; + var parent_id = null; + if (node.attr("class").match(/child-of-node-(\d+)/)) + parent_id = node.attr("class").match(/child-of-node-(\d+)/)[1]; + var popup_bg = ''; + $("body").append(popup_bg); + if (node.hasClass("parent")){ + jAlert(DELETE_MESSAGES[4], DELETE_MESSAGES[3], function(){ + $(".popup_bg").remove(); + }); + } else { + jConfirm(DELETE_MESSAGES[0], DELETE_MESSAGES[1], function(r) { + if (r==true) { + $.post('.', {'__cmd': 'delete_item', 'item_id': item_id}, function(data){ + if (data == "OK") { + if (parent_id && $(".child-of-node-"+parent_id).length == 1) { + $("#node-"+parent_id).removeClass("parent") + .removeClass("expanded").addClass("collapsed") + .find(".expander").removeClass("expander"); + } + node.remove(); + $("body").append(popup_bg); + jAlert(DELETE_MESSAGES[2], DELETE_MESSAGES[2], function(){ + $(".popup_bg").remove(); + }); + } + }); + } + $(".popup_bg").remove(); + }); + } +} + +function parentOf_jQuery(node) { + if (node.attr("class").match(/child-of-node-(\d+)/)) { + var parent_id = node.attr("class").match(/child-of-node-(\d+)/)[1]; + return $("#node-"+parent_id); + } + return null; +}; + +function ancestorsOf_jQuery(node) { + var ancestors = []; + while(node = parentOf_jQuery(node)) { + ancestors[ancestors.length] = node; + } + return ancestors; +}; + +function save_page_tree() { + var send_tree = new Array(); + + // prepare tree + var i = 0; + var ancestor_tree_ids = []; + var ancestor_indices = []; + var tree_id = 0; + $("#sitetree tbody tr").each(function(){ + // 0 = tree_id, 1 = parent_id, 2 = left, 3 = right, 4 = level, 5 = item_id + var classNames = $(this).attr("class").split(' '); + var is_child = false; + var is_parent = false; + var parent_id = null; + var item_id = ""; + var left = ""; + var right = ""; + var level = ""; + + // gather information + for (key in classNames) { + if(classNames[key].match("item-id-")) + item_id = parseInt(classNames[key].substring(8)); + if(classNames[key].match("parent")) + is_parent = true; + if(classNames[key].match("child-of-")) { + is_child = true; + var node_parent_id = classNames[key].substring(9); + parent_id = parseInt($("#"+node_parent_id).attr("class").match(/item-id-(\d+)/)[1]) + } + } + // save info + var inArray = ancestor_tree_ids.indexOf(parent_id); + while ( ( is_child && inArray < ancestor_tree_ids.length - 1 && inArray >= 0) || ( !is_child && ancestor_tree_ids.length > 0 ) ) { + send_tree[ancestor_indices.pop()][3] = i++; + ancestor_tree_ids.pop(); + } + if (!is_child) { + tree_id++; + i = 0; + } + left = i++; + level = ancestor_tree_ids.length; + if (is_parent) { + ancestor_tree_ids.push(item_id); + ancestor_indices.push(send_tree.length); + } else { + right = i++; + } + + send_tree.push([tree_id, parent_id?parent_id:null, left, right, level, item_id]); + }); + while (ancestor_tree_ids.length>0) { + send_tree[ancestor_indices.pop()][3] = i++; + ancestor_tree_ids.pop(); + } + + // send tree to url + $.post('.', {'__cmd': 'save_tree', 'tree': $.toJSON(send_tree)}, function(data){ + if (data == "OK") { + var popup_bg = ''; + $("body").append(popup_bg); + jAlert(TREE_SAVED_MESSAGE, TREE_SAVED_MESSAGE, function(){ + $(".popup_bg").remove(); + }); + } + }); +} diff --git a/categories/media/listener.js b/categories/media/listener.js new file mode 100644 index 0000000..60eb14e --- /dev/null +++ b/categories/media/listener.js @@ -0,0 +1,87 @@ +$(document).ready(function(){ + $("#main_wrapper > .navi_tab").click(function(){ + var elem = $(this); + $("#main_wrapper > .navi_tab").removeClass("tab_active"); + elem.addClass("tab_active"); + $("#main > div:visible, #main > fieldset:visible").hide(); + + var tab_str = elem.attr("id").substr(0, elem.attr("id").length-4); + $('#'+tab_str+'_body').show(); + ACTIVE_REGION = REGIONS.indexOf(tab_str); + + if (tab_str == "settings") + $(".machine-control").hide(); + else + $(".machine-control").show(); + + // make it possible to open current tab on page reload + window.location.hash = '#tab_'+tab_str; + }); + + $(".order-machine-add-button").click(function(){ + var select = $(this).prev(); + var modvar = select.val(); + var modname = select.children("option:selected").html(); + var total_forms = $('#id_'+modvar+'-TOTAL_FORMS'); + var last_id = parseInt(total_forms.val()) - 1; + var form = $("#"+modvar+"_set_item_"+last_id); + + // update formset bookkeeping value + total_forms.val(last_id+2); + create_new_spare_form(form, modvar, last_id); + region_append(ACTIVE_REGION, form, modname, modvar); + set_item_field_value(form, "region-choice-field", ACTIVE_REGION); + + attach_dragdrop_handlers(); + init_contentblocks(); + }); + + $(".order-machine-move-button").click(function(){ + var moveTo = $(this).prev().val(); + move_item(REGIONS.indexOf(moveTo), $("#main div.order-machine div.active-item")); + }); + + $(".item-delete").livequery('click',function(){ + popup_bg = ''; + $("body").append(popup_bg); + var item = $(this).parents(".order-item"); + jConfirm(DELETE_MESSAGES[0], DELETE_MESSAGES[1], function(r) { + if (r==true) { + set_item_field_value(item,"delete-field","checked"); + item.fadeOut(200); + } + $(".popup_bg").remove(); + }); + }); + + $(".change-template").click(function(){ + popup_bg = ''; + $("body").append(popup_bg); + jConfirm(CHANGE_TEMPLATE_MESSAGES[1], CHANGE_TEMPLATE_MESSAGES[0], function(r) { + if (r==true) { + var items = $(".panel").children(".order-machine").children(); + move_item(0, items); + $("#overview input.submit_form").click(); + } else { + $(".popup_bg").remove(); + } + }); + }); + + $("fieldset.order-item").livequery('click',function(){ + if($(this).hasClass('active-item')) { + $(this).removeClass('active-item') + } else { + $(".order-item.active-item").removeClass("active-item"); + $(this).addClass("active-item"); + } + }); + + $('form').submit(function(){ + zucht_und_ordnung(false); + var form = $(this); + form.attr('action', form.attr('action')+window.location.hash); + return true; + }); + +}); diff --git a/categories/models.py b/categories/models.py index b47ea9b..d416738 100644 --- a/categories/models.py +++ b/categories/models.py @@ -4,37 +4,15 @@ from django.db import models from django.utils.encoding import force_unicode import mptt -class CategoryTree(models.Model): - """ - A group of categories that are all related under one tree. - - For example: Business, Movie Genre, Music Genre - """ - name = models.CharField(max_length=100) - slug = models.SlugField() - - class Meta: - ordering = ('name',) - - def __unicode__(self): - return self.name - - def get_absolute_url(self): - return reverse("categories_tree", args=[self.slug]) - - class Category(models.Model): - category_tree = models.ForeignKey(CategoryTree, - blank=False, - null=False - related_name="categories") parent = models.ForeignKey('self', blank=True, null=True, related_name="children", - help_text="Leave this blank for a top-level category", + help_text="Leave this blank for an Category Tree", verbose_name='Parent') name = models.CharField(max_length=100) + order = models.IntegerField(blank=True, null=True) slug = models.SlugField() def get_absolute_url(self): @@ -46,10 +24,10 @@ class Category(models.Model): class Meta: verbose_name_plural = 'categories' unique_together = (('parent', 'name'),) - ordering = ('category_tree__name','parent__name','name') + ordering = ('name',) def __unicode__(self): ancestors = self.get_ancestors() - return ' > '.join([force_unicode(i.name) for i in ancestors]) + return ' > '.join([force_unicode(i.name) for i in ancestors]+[self.name,]) mptt.register(Category, order_insertion_by=['name']) diff --git a/categories/templates/admin/editor/_messages.html b/categories/templates/admin/editor/_messages.html new file mode 100644 index 0000000..609c643 --- /dev/null +++ b/categories/templates/admin/editor/_messages.html @@ -0,0 +1,8 @@ +{% load i18n %} + + DELETE_MESSAGES = ["{% trans "Really delete item?" %}", "{% trans "Confirm to delete item" %}", + "{% trans "Item deleted successfully." %}", "{% trans "Cannot delete item" %}", + "{% trans "Cannot delete item, because it is parent of at least one other item." %}"]; + TREE_SAVED_MESSAGE = "{% trans "Tree saved successfully." %}"; + CHANGE_TEMPLATE_MESSAGES = ["{% trans "Change template" %}", + "{% trans "Really change template?
All content will be moved to main region." %}"]; diff --git a/categories/templates/admin/editor/box.html b/categories/templates/admin/editor/box.html new file mode 100644 index 0000000..9d08e3b --- /dev/null +++ b/categories/templates/admin/editor/box.html @@ -0,0 +1 @@ +
{{ content|safe }}
diff --git a/categories/templates/admin/editor/editor.html b/categories/templates/admin/editor/editor.html new file mode 100644 index 0000000..af8dea3 --- /dev/null +++ b/categories/templates/admin/editor/editor.html @@ -0,0 +1,57 @@ +{% extends "admin/change_form.html" %} +{% load i18n admin_modify adminmedia %} + + +{% block extrahead %}{{ block.super }} + + + + + + + + + + + + + + +{% for inc in object.feincms_item_editor_includes.head %}{% include inc %}{% endfor %} + + +{% endblock %} + +{% block content %} +
+ +
+
+{% if is_popup %}{% endif %} + + + {{ form }} +
+ +
+ +
+ +
+
+ +
+{% endblock %} diff --git a/categories/templates/admin/editor/editor_done.html b/categories/templates/admin/editor/editor_done.html new file mode 100644 index 0000000..5ec0b5d --- /dev/null +++ b/categories/templates/admin/editor/editor_done.html @@ -0,0 +1,12 @@ + + + +
+ {{ content|safe }} +
+ + diff --git a/categories/templates/admin/editor/item_editor.html b/categories/templates/admin/editor/item_editor.html new file mode 100644 index 0000000..8e45aae --- /dev/null +++ b/categories/templates/admin/editor/item_editor.html @@ -0,0 +1,252 @@ +{% 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 }} + + + + + + + + + + + + + + +{% for inc in object.feincms_item_editor_includes.head %}{% include inc %}{% endfor %} + + + + + + +{% endblock %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} + +
+ +{% block object-tools %} + +{% endblock %} + +
+
+ + + + +
+ {% for field in top_fieldset %} +
+ {% ifequal field.name "language" %} + {# Maybe this should be moved somewhere else, since this is specific for translated objects... #} + {% with object.available_translations as translations %} + {% if translations %} +
+ {% trans "available translations" %}: + {% for translation in translations %} + {{ translation.language|upper }}{% if not forloop.last %},{% endif %} + {% endfor %} +
+ {% endif %} + {% endwith %} + {% endifequal %} + + {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endfor %} + + {% if object_form.template_key %} {# Maybe this should be moved somewhere else, since this is page-module-specific #} +
+ {{ object_form.template_key.label_tag }} + {{ object_form.template_key }}{{ object_form.template_key.errors }} + +
+ {% endif %} +
+ +
+ + {% for region in object.template.regions %} + + {% endfor %} +
+
+ {% for field in settings_fieldset %} +
+ {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endfor %} +
+ + {% for region in object.template.regions %} +
+
+ {% trans "Region empty" %} +
+
+ {% if region.inherited %} + {% trans "Content from the parent site is automatically inherited. To override this behaviour, add some content." %} + {% endif %} +
+ +
+ +
+
+ {% trans "Add new item" %}:
+ + +
+
+ {% trans "Move selected item to" %}:
+ + +
+
+
+ {% endfor %} +
+
+ + + +
+{% for formset in inline_formsets %} +
+
+ {{ formset.management_form }} +
+ {% for form in formset.forms %} +
+ {% for field in form %} + {% if field.is_hidden %} + {{ field }} + {% else %} +
+ {{ field.label_tag }}{{ field }} + {% if field.field.help_text %}

{{ field.field.help_text|safe }}

{% endif %} +
+ {% endif %} + {% endfor %} +
+ {% endfor %} +
+{% endfor %} +
+ +
+
+ +
+{% endblock %} + diff --git a/categories/templates/admin/editor/tools.html b/categories/templates/admin/editor/tools.html new file mode 100644 index 0000000..ea78b21 --- /dev/null +++ b/categories/templates/admin/editor/tools.html @@ -0,0 +1,81 @@ +{% load i18n %} +
+ {% trans "edit" %} + +
+ + + diff --git a/categories/templates/admin/editor/tree_editor.html b/categories/templates/admin/editor/tree_editor.html new file mode 100644 index 0000000..a0980c4 --- /dev/null +++ b/categories/templates/admin/editor/tree_editor.html @@ -0,0 +1,167 @@ +{% extends "admin/change_list.html" %} +{% load i18n admin_modify adminmedia %} +{% block title %}{{ block.super }}{% endblock %} + +{% block extrahead %}{{ block.super }} + + + + + + + + + + + + + + + + + + + +{% endblock %} + +{% block content %} + +
+ {% block object-tools %} + {% if has_add_permission %} + + {% endif %} + {% endblock %} + + + +
+ {% trans "Expand all" %} + {% trans "Collapse all" %} +
+ +
+ + + + {% for header in result_headers %} + {{ header.text|capfirst }} + {% endfor %} + + + + + + +
{% trans "Delete" %}
+
+ +
+ +{% endblock %}