X-Git-Url: https://git.openstreetmap.org./osqa.git/blobdiff_plain/700b6dec7e4d655fee91e26f9757442162047281..9162dea1adb758b31ab40323d28ca585ed667eaf:/forum/views/admin.py diff --git a/forum/views/admin.py b/forum/views/admin.py index 7b10f7c..a834b71 100644 --- a/forum/views/admin.py +++ b/forum/views/admin.py @@ -1,22 +1,30 @@ from datetime import datetime, timedelta +import json import time +from django.views.decorators.csrf import csrf_exempt from django.shortcuts import render_to_response, get_object_or_404 from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect, HttpResponse, Http404 -from forum.http_responses import HttpResponseUnauthorized from django.template import RequestContext from django.utils.translation import ugettext as _ -from django.utils import simplejson from django.db import models + +from django.contrib import messages + +from forum.http_responses import HttpResponseUnauthorized from forum.settings.base import Setting -from forum.forms import MaintenanceModeForm, PageForm +from forum.forms import MaintenanceModeForm, PageForm, CreateUserForm from forum.settings.forms import SettingsSetForm - -from forum.models import Question, Answer, User, Node, Action, Page -from forum.actions import NewPageAction, EditPageAction, PublishAction +from forum.utils import pagination, html +from forum.utils.mail import send_template_email +from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag +from forum.models.node import NodeMetaClass +from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction from forum import settings +TOOLS = {} + def super_user_required(fn): def wrapper(request, *args, **kwargs): if request.user.is_authenticated() and request.user.is_superuser: @@ -26,37 +34,75 @@ def super_user_required(fn): return wrapper +def staff_user_required(fn): + def wrapper(request, *args, **kwargs): + if request.user.is_authenticated() and (request.user.is_staff or request.user.is_superuser): + return fn(request, *args, **kwargs) + else: + return HttpResponseUnauthorized(request) + + return wrapper + +def admin_page_wrapper(fn, request, *args, **kwargs): + res = fn(request, *args, **kwargs) + if isinstance(res, HttpResponse): + return res + + template, context = res + context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html" + context['allsets'] = Setting.sets + context['othersets'] = sorted( + [s for s in Setting.sets.values() if not s.name in + ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar', + 'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')] + , lambda s1, s2: s1.weight - s2.weight) + + context['tools'] = [(name, fn.label) for name, fn in TOOLS.items()] + + # Show the navigation only to moderators and super users + if not context.has_key("hide_navigation"): + context['hide_navigation'] = not request.user.is_superuser + + unsaved = request.session.get('previewing_settings', {}) + context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)]) + + return render_to_response(template, context, context_instance=RequestContext(request)) + def admin_page(fn): @super_user_required def wrapper(request, *args, **kwargs): - res = fn(request, *args, **kwargs) - if isinstance(res, tuple): - template, context = res - context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html" - context['allsets'] = Setting.sets - context['othersets'] = sorted( - [s for s in Setting.sets.values() if not s.name in - ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar', - 'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')] - , lambda s1, s2: s1.weight - s2.weight) - - unsaved = request.session.get('previewing_settings', {}) - context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)]) - - return render_to_response(template, context, context_instance=RequestContext(request)) - else: - return res + return admin_page_wrapper(fn, request, *args, **kwargs) + + return wrapper + +def moderation_page(fn): + @staff_user_required + def wrapper(request, *args, **kwargs): + return admin_page_wrapper(fn, request, *args, **kwargs) return wrapper +def admin_tools_page(name, label): + def decorator(fn): + fn = admin_page(fn) + fn.label = label + TOOLS[name] = fn + + return fn + return decorator + +class ActivityPaginatorContext(pagination.PaginatorContext): + def __init__(self): + super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40) + @admin_page def dashboard(request): - return ('osqaadmin/dashboard.html', { + return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), { 'settings_pack': unicode(settings.SETTINGS_PACK), 'statistics': get_statistics(), 'recent_activity': get_recent_activity(), 'flagged_posts': get_flagged_posts(), - }) + })) @super_user_required def interface_switch(request): @@ -75,7 +121,7 @@ def statistics(request): 'added_at', flat=True) last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count() - qgraph_data = simplejson.dumps([ + qgraph_data = json.dumps([ (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions) for i, d in enumerate(last_month_questions) ]) @@ -85,7 +131,7 @@ def statistics(request): last_month_n_users = User.objects.filter(date_joined__lt=last_month).count() - ugraph_data = simplejson.dumps([ + ugraph_data = json.dumps([ (time.mktime(d.timetuple()) * 1000, i + last_month_n_users) for i, d in enumerate(last_month_users) ]) @@ -104,6 +150,13 @@ def statistics(request): ] } +@admin_page +def tools_page(request, name): + if not name in TOOLS: + raise Http404 + + return TOOLS[name](request) + @admin_page def settings_set(request, set_name): @@ -130,7 +183,7 @@ def settings_set(request, set_name): if not 'reset' in request.POST: form.save() - request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name) + messages.info(request, _("'%s' settings saved succesfully") % set_name) if set_name in ('minrep', 'badges', 'repgain'): settings.SETTINGS_PACK.set_value("custom") @@ -161,7 +214,7 @@ def get_default(request, set_name, var_name): def get_recent_activity(): - return Action.objects.order_by('-action_date')[0:30] + return Action.objects.order_by('-action_date') def get_flagged_posts(): return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30] @@ -237,7 +290,7 @@ def go_bootstrap(request): settings.SETTINGS_PACK.set_value("bootstrap") - request.user.message_set.create(message=_('Bootstrap mode enabled')) + messages.info(request, _('Bootstrap mode enabled')) return HttpResponseRedirect(reverse('admin_index')) @super_user_required @@ -251,7 +304,7 @@ def go_defaults(request): settings.SETTINGS_PACK.set_value("default") - request.user.message_set.create(message=_('All values reverted to defaults')) + messages.info(request, ('All values reverted to defaults')) return HttpResponseRedirect(reverse('admin_index')) @@ -267,7 +320,7 @@ def recalculate_denormalized(request): u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation'] u.save() - request.user.message_set.create(message=_('All values recalculated')) + messages.info(request, _('All values recalculated')) return HttpResponseRedirect(reverse('admin_index')) @admin_page @@ -286,12 +339,12 @@ def maintenance(request): else: message = _('Settings adjusted') - request.user.message_set.create(message=message) + messages.info(request, message) return HttpResponseRedirect(reverse('admin_maintenance')) elif 'open' in request.POST: settings.MAINTAINANCE_MODE.set_value(None) - request.user.message_set.create(message=_("Your site is now running normally")) + messages.info(request, _("Your site is now running normally")) return HttpResponseRedirect(reverse('admin_maintenance')) else: form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'], @@ -301,7 +354,7 @@ def maintenance(request): }) -@admin_page +@moderation_page def flagged_posts(request): return ('osqaadmin/flagged_posts.html', { 'flagged_posts': get_flagged_posts(), @@ -356,77 +409,187 @@ def edit_page(request, id=None): }) @admin_page -def moderation(request): +def delete_page(request, id=None): + page = get_object_or_404(Page, id=id) + page.delete() + return HttpResponseRedirect(reverse('admin_static_pages')) + +@admin_tools_page(_('createuser'), _("Create new user")) +def create_user(request): if request.POST: - if not 'ids' in request.POST: - verify = None - else: - sort = { - 'high-rep': '-reputation', - 'newer': '-date_joined', - 'older': 'date_joined', - }.get(request.POST.get('sort'), None) - - if sort: - try: - limit = int(request.POST['limit']) - except: - limit = 5 - - verify = User.objects.order_by(sort)[:limit] - else: - verify = None + form = CreateUserForm(request.POST) + + if form.is_valid(): + user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email']) + user_.set_password(form.cleaned_data['password1']) + + if not form.cleaned_data.get('validate_email', False): + user_.email_isvalid = True + + user_.save() + UserJoinsAction(user=user_).save() + + messages.info(request, _("New user created sucessfully. %s.") % html.hyperlink( + user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank")) + + return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'})) + else: + form = CreateUserForm() + + return ('osqaadmin/createuser.html', { + 'form': form, + }) + +class NodeManagementPaginatorContext(pagination.PaginatorContext): + def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100): + super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=( + (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")), + (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")), + (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")), + (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")), + (_('score'), pagination.SimpleSort(_('score'), '-score', "")), + (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")), + (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")), + (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")), + (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")), + (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")), + ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix) + +@admin_tools_page(_("nodeman"), _("Bulk management")) +def node_management(request): + if request.POST: + params = pagination.generate_uri(request.GET, ('page',)) - if verify: - possible_cheaters = [] - verify = User.objects.order_by(sort)[:5] + if "save_filter" in request.POST: + filter_name = request.POST.get('filter_name', _('filter')) + params = pagination.generate_uri(request.GET, ('page',)) + current_filters = settings.NODE_MAN_FILTERS.value + current_filters.append((filter_name, params)) + settings.NODE_MAN_FILTERS.set_value(current_filters) - cheat_score_sort = lambda c1, c2: cmp(c2.fdata['fake_score'], c1.fdata['fake_score']) + elif r"execute" in request.POST: + selected_nodes = request.POST.getlist('_selected_node') - for user in verify: - possible_fakes = [] - affecters = User.objects.filter(actions__node__author=user, actions__canceled=False).annotate( - affect_count=models.Count('actions')).order_by('-affect_count') - user_ips = set(Action.objects.filter(user=user).values_list('ip', flat=True).distinct('ip')) + if selected_nodes and request.POST.get('action', None): + action = str(request.POST['action']) + selected_nodes = Node.objects.filter(id__in=selected_nodes) - for affecter in affecters: - if affecter == user: - continue + message = _("No action performed") - data = {'affect_count': affecter.affect_count} + if action == 'delete_selected': + for node in selected_nodes: + if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted): + DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save() - total_actions = affecter.actions.filter(canceled=False).exclude(node=None).count() - ratio = (float(affecter.affect_count) / float(total_actions)) * 100 + message = _("All selected nodes marked as deleted") - if total_actions > 10 and ratio > 50: - data['total_actions'] = total_actions - data['action_ratio'] = ratio + if action == 'undelete_selected': + for node in selected_nodes: + if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted): + node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR']) - affecter_ips = set( - Action.objects.filter(user=affecter).values_list('ip', flat=True).distinct('ip')) - cross_ips = len(user_ips & affecter_ips) + message = _("All selected nodes undeleted") - data['cross_ip_count'] = cross_ips - data['total_ip_count'] = len(affecter_ips) - data['cross_ip_ratio'] = (float(data['cross_ip_count']) / float(data['total_ip_count'])) * 100 + if action == "close_selected": + for node in selected_nodes: + if node.node_type == "question" and (not node.nis.closed): + CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save() - if affecter.email_isvalid: - email_score = 0 - else: - email_score = 50.0 + message = _("Selected questions were closed") - data['fake_score'] = ((data['cross_ip_ratio'] + data['action_ratio'] + email_score) / 100) * 4 + if action == "hard_delete_selected": + ids = [n.id for n in selected_nodes] - affecter.fdata = data - possible_fakes.append(affecter) + for id in ids: + try: + node = Node.objects.get(id=id) + node.delete() + except: + pass - if len(possible_fakes) > 0: - possible_fakes = sorted(possible_fakes, cheat_score_sort) - possible_cheaters.append((user, possible_fakes)) + message = _("All selected nodes deleted") - return ('osqaadmin/moderation.html', {'cheaters': possible_cheaters}) + messages.info(request, message) - return ('osqaadmin/moderation.html', {}) + params = pagination.generate_uri(request.GET, ('page',)) + + return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params) + nodes = Node.objects.all() + + text = request.GET.get('text', '') + text_in = request.GET.get('text_in', 'body') + + authors = request.GET.getlist('authors') + tags = request.GET.getlist('tags') + + type_filter = request.GET.getlist('node_type') + state_filter = request.GET.getlist('state_type') + state_filter_type = request.GET.get('state_filter_type', 'any') + + if type_filter: + nodes = nodes.filter(node_type__in=type_filter) + + state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type') + state_filter = [s for s in state_filter if s in state_types] + + if state_filter: + if state_filter_type == 'all': + nodes = nodes.all_states(*state_filter) + else: + nodes = nodes.any_state(*state_filter) + + if (authors): + nodes = nodes.filter(author__id__in=authors) + authors = User.objects.filter(id__in=authors) + + if (tags): + nodes = nodes.filter(tags__id__in=tags) + tags = Tag.objects.filter(id__in=tags) + + if text: + text_in = request.GET.get('text_in', 'body') + filter = None + + if text_in == 'title' or text_in == 'both': + filter = models.Q(title__icontains=text) + + if text_in == 'body' or text_in == 'both': + sec_filter = models.Q(body__icontains=text) + if filter: + filter = filter | sec_filter + else: + filter = sec_filter + + if filter: + nodes = nodes.filter(filter) + + node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()] + + return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), { + 'nodes': nodes, + 'text': text, + 'text_in': text_in, + 'type_filter': type_filter, + 'state_filter': state_filter, + 'state_filter_type': state_filter_type, + 'node_types': node_types, + 'state_types': state_types, + 'authors': authors, + 'tags': tags, + 'hide_navigation': True + })) + +@csrf_exempt +@super_user_required +def test_email_settings(request): + user = request.user + + send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user }) + return render_to_response( + 'osqaadmin/test_email_settings.html', + { 'user': user, }, + RequestContext(request) + )