From 4ce7d412b21657188234c9b9dc3cce544cd4a13d Mon Sep 17 00:00:00 2001 From: Alexander Burdeinyi Date: Sat, 26 Nov 2016 00:38:54 +0200 Subject: [PATCH] comments admin management improved --- comments/admin.py | 75 ++++++++++++++++++++- comments/forms.py | 1 + static/admin/css/comments_manage.css | 6 ++ static/admin/js/comments_manage.js | 24 +++++++ static/js/jquery.truncator.js | 98 ++++++++++++++++++++++++++++ 5 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 static/admin/css/comments_manage.css create mode 100644 static/admin/js/comments_manage.js create mode 100644 static/js/jquery.truncator.js diff --git a/comments/admin.py b/comments/admin.py index 03341b4e..6a3142b3 100644 --- a/comments/admin.py +++ b/comments/admin.py @@ -1,14 +1,32 @@ # -*- coding: utf-8 -*- +from functools import update_wrapper, partial + from django.contrib import admin +from django.contrib.admin.util import unquote +from django.conf.urls import patterns, url +from django.core.exceptions import PermissionDenied +from django.core.urlresolvers import reverse_lazy, reverse +from django.http import Http404 from django.utils.translation import ugettext_lazy as _ +from django.utils.html import escape +from django.utils.encoding import force_text from functions.admin import DefaultAdmin +from functions.http import JsonResponse from .models import Comment class CommentAdmin(DefaultAdmin): - list_display = ['text', 'ip', 'user', 'hidden',] + class Media: + js = ( + "js/jquery.truncator.js", + "admin/js/comments_manage.js", + ) + # css = { + # 'all': ("admin/css/comments_manage.css",), + # } + list_display = ['text', 'ip', 'user', 'hide'] list_select_related = True list_filter = ('hidden', ) date_hierarchy = 'created' @@ -21,4 +39,59 @@ class CommentAdmin(DefaultAdmin): 'text', )}), ) + def hide(self, obj): + body = u'{alt}' \ + u' {label}' \ + u' | {label2}'\ + .format(**self.get_yesno_image_data(obj)) + return body + hide.short_description = _(u'Модерирование') + hide.allow_tags = True + + def urls(self): + urlpatterns = self.get_urls() + def wrap(view): + def wrapper(*args, **kwargs): + return self.admin_site.admin_view(view)(*args, **kwargs) + return update_wrapper(wrapper, view) + info = self.model._meta.app_label, self.model._meta.module_name + _urlpatterns = patterns('', + url(r'^(?P\d+)/ajax/(?Phide|show|banro|unbanro)/$', + wrap(self.ajax_view), + name='%s_%s_ajax' % info), + ) + return _urlpatterns + urlpatterns + urls = property(urls) + + def get_yesno_image_data(self, obj): + return { + 'url': '/static/admin/img/icon-{0}.gif'.format('no' if obj.hidden else 'yes'), + 'alt': str(not obj.hidden), + 'href': reverse('admin:comments_comment_ajax', args=[obj.id, 'hide' if not obj.hidden else 'show']), + 'label': unicode(_(u'Опубликовать') if obj.hidden else _(u'Скрыть')), + 'href2': reverse('admin:comments_comment_ajax', args=[obj.id, 'banro' if not obj.user.readonly else 'unbanro']), + 'label2': unicode(_(u'Снять RO') if obj.user.readonly else _(u'Наложить RO')), + } + + def ajax_view(self, request, object_id, extra_context=None, *args, **kwargs): + "The 'change' admin view for this model." + action = kwargs.get('action') + model = self.model + opts = model._meta + obj = self.get_object(request, unquote(object_id)) + if not self.has_change_permission(request, obj) or not request.is_ajax(): + raise PermissionDenied + if obj is None: + raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)}) + + if action in ['hide', 'show']: + obj.hidden = True if action == 'hide' else False + obj.save() + else: + obj.user.readonly = True if action == 'banro' else False + obj.user.save() + + return JsonResponse(dict({'success': True}, **self.get_yesno_image_data(obj))) + ajax_view.csrf_exempt = True + admin.site.register(Comment, CommentAdmin) diff --git a/comments/forms.py b/comments/forms.py index 878d0b3f..314c1fb4 100644 --- a/comments/forms.py +++ b/comments/forms.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from django import forms +from django.utils.translation import ugettext as _ from functions.forms import EmptySelect from .models import Comment diff --git a/static/admin/css/comments_manage.css b/static/admin/css/comments_manage.css new file mode 100644 index 00000000..36298255 --- /dev/null +++ b/static/admin/css/comments_manage.css @@ -0,0 +1,6 @@ +div.results tr div.moderation { + display: none; +} +div.results tr:hover div.moderation { + display: inline; +} \ No newline at end of file diff --git a/static/admin/js/comments_manage.js b/static/admin/js/comments_manage.js new file mode 100644 index 00000000..ef43e07d --- /dev/null +++ b/static/admin/js/comments_manage.js @@ -0,0 +1,24 @@ +$(document).ready(function () { + $('div.results a.action').on('click', function (e) { + e.preventDefault(); + _this = $(this); + $.ajax({ + url: _this.attr('href'), + type: 'post', + success: function (data) { + if (data.success==true) { + img = _this.siblings('img'); + img.attr({ + src: data.url, + alt: data.alt + }); + img.next().attr('href', data.href).html(data.label).next().attr('href', data.href2).html(data.label2); + } + } + }); + }); + $('div.results tbody th a').truncate({ + max_length: 50, + }); +}); + diff --git a/static/js/jquery.truncator.js b/static/js/jquery.truncator.js new file mode 100644 index 00000000..fc63ea22 --- /dev/null +++ b/static/js/jquery.truncator.js @@ -0,0 +1,98 @@ +// HTML Truncator for jQuery +// by Henrik Nyh 2008-02-28. +// Free to modify and redistribute with credit. + +(function($) { + + var trailing_whitespace = true; + + $.fn.truncate = function(options) { + + var opts = $.extend({}, $.fn.truncate.defaults, options); + + $(this).each(function() { + + var content_length = $.trim(squeeze($(this).text())).length; + if (content_length <= opts.max_length) + return; // bail early if not overlong + + var actual_max_length = opts.max_length - opts.more.length - 3; // 3 for " ()" + var truncated_node = recursivelyTruncate(this, actual_max_length); + var full_node = $(this).hide(); + + truncated_node.insertAfter(full_node); + + findNodeForMore(truncated_node).append(' ('+opts.more+')'); + findNodeForLess(full_node).append(' ('+opts.less+')'); + + truncated_node.find('a:last').click(function() { + truncated_node.hide(); full_node.show(); return false; + }); + full_node.find('a:last').click(function() { + truncated_node.show(); full_node.hide(); return false; + }); + + }); + } + + // Note that the " (…more)" bit counts towards the max length – so a max + // length of 10 would truncate "1234567890" to "12 (…more)". + $.fn.truncate.defaults = { + max_length: 100, + more: '…more', + less: 'less' + }; + + function recursivelyTruncate(node, max_length) { + return (node.nodeType == 3) ? truncateText(node, max_length) : truncateNode(node, max_length); + } + + function truncateNode(node, max_length) { + var node = $(node); + var new_node = node.clone().empty(); + var truncatedChild; + node.contents().each(function() { + var remaining_length = max_length - new_node.text().length; + if (remaining_length == 0) return; // breaks the loop + truncatedChild = recursivelyTruncate(this, remaining_length); + if (truncatedChild) new_node.append(truncatedChild); + }); + return new_node; + } + + function truncateText(node, max_length) { + var text = squeeze(node.data); + if (trailing_whitespace) // remove initial whitespace if last text + text = text.replace(/^ /, ''); // node had trailing whitespace. + trailing_whitespace = !!text.match(/ $/); + var text = text.slice(0, max_length); + // Ensure HTML entities are encoded + // http://debuggable.com/posts/encode-html-entities-with-jquery:480f4dd6-13cc-4ce9-8071-4710cbdd56cb + text = $('
').text(text).html(); + return text; + } + + // Collapses a sequence of whitespace into a single space. + function squeeze(string) { + return string.replace(/\s+/g, ' '); + } + + // Finds the last, innermost block-level element + function findNodeForMore(node) { + var $node = $(node); + var last_child = $node.children(":last"); + if (!last_child) return node; + var display = last_child.css('display'); + if (!display || display=='inline') return $node; + return findNodeForMore(last_child); + }; + + // Finds the last child if it's a p; otherwise the parent + function findNodeForLess(node) { + var $node = $(node); + var last_child = $node.children(":last"); + if (last_child && last_child.is('p')) return last_child; + return node; + }; + +})(jQuery);