1 from datetime import datetime, timedelta
 
   5 from django.views.decorators.csrf import csrf_exempt
 
   6 from django.shortcuts import render_to_response, get_object_or_404
 
   7 from django.core.urlresolvers import reverse
 
   8 from django.http import HttpResponseRedirect, HttpResponse, Http404
 
   9 from django.template import RequestContext
 
  10 from django.utils.translation import ugettext as _
 
  11 from django.db import models
 
  13 from django.contrib import messages
 
  15 from forum.http_responses import HttpResponseUnauthorized
 
  16 from forum.settings.base import Setting
 
  17 from forum.forms import MaintenanceModeForm, PageForm, CreateUserForm
 
  18 from forum.settings.forms import SettingsSetForm
 
  19 from forum.utils import pagination, html
 
  20 from forum.utils.mail import send_template_email
 
  21 from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
 
  22 from forum.models.node import NodeMetaClass
 
  23 from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
 
  24 from forum import settings
 
  28 def super_user_required(fn):
 
  29     def wrapper(request, *args, **kwargs):
 
  30         if request.user.is_authenticated() and request.user.is_superuser:
 
  31             return fn(request, *args, **kwargs)
 
  33             return HttpResponseUnauthorized(request)
 
  37 def staff_user_required(fn):
 
  38     def wrapper(request, *args, **kwargs):
 
  39         if request.user.is_authenticated() and (request.user.is_staff or request.user.is_superuser):
 
  40             return fn(request, *args, **kwargs)
 
  42             return HttpResponseUnauthorized(request)
 
  46 def admin_page_wrapper(fn, request, *args, **kwargs):
 
  47     res = fn(request, *args, **kwargs)
 
  48     if isinstance(res, HttpResponse):
 
  51     template, context = res
 
  52     context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
 
  53     context['allsets'] = Setting.sets
 
  54     context['othersets'] = sorted(
 
  55             [s for s in Setting.sets.values() if not s.name in
 
  56             ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar',
 
  57             'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')]
 
  58             , lambda s1, s2: s1.weight - s2.weight)
 
  60     context['tools'] = [(name, fn.label) for name, fn in TOOLS.items()]
 
  62     # Show the navigation only to moderators and super users
 
  63     if not context.has_key("hide_navigation"):
 
  64         context['hide_navigation'] = not request.user.is_superuser
 
  66     unsaved = request.session.get('previewing_settings', {})
 
  67     context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
 
  69     return render_to_response(template, context, context_instance=RequestContext(request))
 
  73     def wrapper(request, *args, **kwargs):
 
  74         return admin_page_wrapper(fn, request, *args, **kwargs)
 
  78 def moderation_page(fn):
 
  80     def wrapper(request, *args, **kwargs):
 
  81         return admin_page_wrapper(fn, request, *args, **kwargs)
 
  85 def admin_tools_page(name, label):    
 
  94 class ActivityPaginatorContext(pagination.PaginatorContext):
 
  96         super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
 
  99 def dashboard(request):
 
 100     return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), {
 
 101     'settings_pack': unicode(settings.SETTINGS_PACK),
 
 102     'statistics': get_statistics(),
 
 103     'recent_activity': get_recent_activity(),
 
 104     'flagged_posts': get_flagged_posts(),
 
 108 def interface_switch(request):
 
 109     if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
 
 110         settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
 
 112     return HttpResponseRedirect(reverse('admin_index'))
 
 115 def statistics(request):
 
 116     today = datetime.now()
 
 117     last_month = today - timedelta(days=30)
 
 119     last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
 
 120                                                                                ).order_by('added_at').values_list(
 
 121             'added_at', flat=True)
 
 123     last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
 
 124     qgraph_data = json.dumps([
 
 125     (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
 
 126     for i, d in enumerate(last_month_questions)
 
 129     last_month_users = User.objects.filter(date_joined__gt=last_month
 
 130                                            ).order_by('date_joined').values_list('date_joined', flat=True)
 
 132     last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
 
 134     ugraph_data = json.dumps([
 
 135     (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
 
 136     for i, d in enumerate(last_month_users)
 
 139     return 'osqaadmin/statistics.html', {
 
 142             'id': 'questions_graph',
 
 143             'caption': _("Questions Graph"),
 
 146             'id': 'userss_graph',
 
 147             'caption': _("Users Graph"),
 
 154 def tools_page(request, name):
 
 155     if not name in TOOLS:
 
 158     return TOOLS[name](request)
 
 162 def settings_set(request, set_name):
 
 163     set = Setting.sets.get(set_name, {})
 
 164     current_preview = request.session.get('previewing_settings', {})
 
 170         form = SettingsSetForm(set, data=request.POST, files=request.FILES)
 
 173             if 'preview' in request.POST:
 
 174                 current_preview.update(form.cleaned_data)
 
 175                 request.session['previewing_settings'] = current_preview
 
 177                 return HttpResponseRedirect(reverse('index'))
 
 180                     current_preview.pop(s.name, None)
 
 182                 request.session['previewing_settings'] = current_preview
 
 184                 if not 'reset' in request.POST:
 
 186                     messages.info(request, _("'%s' settings saved succesfully") % set_name)
 
 188                     if set_name in ('minrep', 'badges', 'repgain'):
 
 189                         settings.SETTINGS_PACK.set_value("custom")
 
 191                 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
 
 193         form = SettingsSetForm(set, unsaved=current_preview)
 
 195     return 'osqaadmin/set.html', {
 
 197     'markdown': set.markdown,
 
 201 def get_default(request, set_name, var_name):
 
 202     set = Setting.sets.get(set_name, None)
 
 203     if set is None: raise Http404
 
 205     setting = dict([(s.name, s) for s in set]).get(var_name, None)
 
 206     if setting is None: raise Http404
 
 210     if request.is_ajax():
 
 211         return HttpResponse(setting.default)
 
 213         return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
 
 216 def get_recent_activity():
 
 217     return Action.objects.order_by('-action_date')
 
 219 def get_flagged_posts():
 
 220     return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
 
 222 def get_statistics():
 
 224     'total_users': User.objects.all().count(),
 
 225     'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
 
 226     'total_questions': Question.objects.filter_state(deleted=False).count(),
 
 227     'questions_last_24': Question.objects.filter_state(deleted=False).filter(
 
 228             added_at__gt=(datetime.now() - timedelta(days=1))).count(),
 
 229     'total_answers': Answer.objects.filter_state(deleted=False).count(),
 
 230     'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
 
 231             added_at__gt=(datetime.now() - timedelta(days=1))).count(),
 
 235 def go_bootstrap(request):
 
 236 #todo: this is the quick and dirty way of implementing a bootstrap mode
 
 238         from forum_modules.default_badges import settings as dbsets
 
 239         dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
 
 240         dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
 
 241         dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
 
 242         dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
 
 243         dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
 
 244         dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
 
 245         dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
 
 246         dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
 
 247         dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
 
 248         dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
 
 249         dbsets.STELLAR_QUESTION_FAVS.set_value(3)
 
 250         dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
 
 251         dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
 
 252         dbsets.CIVIC_DUTY_VOTES.set_value(15)
 
 253         dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
 
 254         dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
 
 255         dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
 
 256         dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
 
 257         dbsets.GURU_UP_VOTES.set_value(4)
 
 258         dbsets.NECROMANCER_UP_VOTES.set_value(2)
 
 259         dbsets.NECROMANCER_DIF_DAYS.set_value(30)
 
 260         dbsets.TAXONOMIST_USE_COUNT.set_value(5)
 
 264     settings.REP_TO_VOTE_UP.set_value(0)
 
 265     settings.REP_TO_VOTE_DOWN.set_value(15)
 
 266     settings.REP_TO_FLAG.set_value(15)
 
 267     settings.REP_TO_COMMENT.set_value(0)
 
 268     settings.REP_TO_LIKE_COMMENT.set_value(0)
 
 269     settings.REP_TO_UPLOAD.set_value(0)
 
 270     settings.REP_TO_CREATE_TAGS.set_value(0)
 
 271     settings.REP_TO_CLOSE_OWN.set_value(60)
 
 272     settings.REP_TO_REOPEN_OWN.set_value(120)
 
 273     settings.REP_TO_RETAG.set_value(150)
 
 274     settings.REP_TO_EDIT_WIKI.set_value(200)
 
 275     settings.REP_TO_EDIT_OTHERS.set_value(400)
 
 276     settings.REP_TO_CLOSE_OTHERS.set_value(600)
 
 277     settings.REP_TO_DELETE_COMMENTS.set_value(400)
 
 278     settings.REP_TO_VIEW_FLAGS.set_value(30)
 
 280     settings.INITIAL_REP.set_value(1)
 
 281     settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
 
 282     settings.REP_GAIN_BY_UPVOTED.set_value(15)
 
 283     settings.REP_LOST_BY_DOWNVOTED.set_value(1)
 
 284     settings.REP_LOST_BY_DOWNVOTING.set_value(0)
 
 285     settings.REP_GAIN_BY_ACCEPTED.set_value(25)
 
 286     settings.REP_GAIN_BY_ACCEPTING.set_value(5)
 
 287     settings.REP_LOST_BY_FLAGGED.set_value(2)
 
 288     settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
 
 289     settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
 
 291     settings.SETTINGS_PACK.set_value("bootstrap")
 
 293     messages.info(request, _('Bootstrap mode enabled'))
 
 294     return HttpResponseRedirect(reverse('admin_index'))
 
 297 def go_defaults(request):
 
 298     for setting in Setting.sets['badges']:
 
 300     for setting in Setting.sets['minrep']:
 
 302     for setting in Setting.sets['repgain']:
 
 305     settings.SETTINGS_PACK.set_value("default")
 
 307     messages.info(request, ('All values reverted to defaults'))
 
 308     return HttpResponseRedirect(reverse('admin_index'))
 
 312 def recalculate_denormalized(request):
 
 313     for n in Node.objects.all():
 
 315         n.score = n.votes.aggregate(score=models.Sum('value'))['score']
 
 316         if not n.score: n.score = 0
 
 319     for u in User.objects.all():
 
 320         u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
 
 323     messages.info(request, _('All values recalculated'))
 
 324     return HttpResponseRedirect(reverse('admin_index'))
 
 327 def maintenance(request):
 
 329         if 'close' in request.POST or 'adjust' in request.POST:
 
 330             form = MaintenanceModeForm(request.POST)
 
 333                 settings.MAINTAINANCE_MODE.set_value({
 
 334                 'allow_ips': form.cleaned_data['ips'],
 
 335                 'message': form.cleaned_data['message']})
 
 337                 if 'close' in request.POST:
 
 338                     message = _('Maintenance mode enabled')
 
 340                     message = _('Settings adjusted')
 
 342                 messages.info(request, message)
 
 344                 return HttpResponseRedirect(reverse('admin_maintenance'))
 
 345         elif 'open' in request.POST:
 
 346             settings.MAINTAINANCE_MODE.set_value(None)
 
 347             messages.info(request, _("Your site is now running normally"))
 
 348             return HttpResponseRedirect(reverse('admin_maintenance'))
 
 350         form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
 
 351                                             'message': _('Currently down for maintenance. We\'ll be back soon')})
 
 353     return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
 
 358 def flagged_posts(request):
 
 359     return ('osqaadmin/flagged_posts.html', {
 
 360     'flagged_posts': get_flagged_posts(),
 
 364 def static_pages(request):
 
 365     pages = Page.objects.all()
 
 367     return ('osqaadmin/static_pages.html', {
 
 372 def edit_page(request, id=None):
 
 374         page = get_object_or_404(Page, id=id)
 
 379         form = PageForm(page, request.POST)
 
 382             if form.has_changed():
 
 384                     page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
 
 387                     EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
 
 388                             data=form.cleaned_data)
 
 390             if ('publish' in request.POST) and (not page.published):
 
 391                 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
 
 392             elif ('unpublish' in request.POST) and page.published:
 
 393                 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
 
 395             return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
 
 398         form = PageForm(page)
 
 401         published = page.published
 
 405     return ('osqaadmin/edit_page.html', {
 
 408     'published': published
 
 412 def delete_page(request, id=None):
 
 413     page = get_object_or_404(Page, id=id)
 
 415     return HttpResponseRedirect(reverse('admin_static_pages'))
 
 417 @admin_tools_page(_('createuser'), _("Create new user"))
 
 418 def create_user(request):
 
 420         form = CreateUserForm(request.POST)
 
 423             user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
 
 424             user_.set_password(form.cleaned_data['password1'])
 
 426             if not form.cleaned_data.get('validate_email', False):
 
 427                 user_.email_isvalid = True
 
 430             UserJoinsAction(user=user_).save()
 
 432             messages.info(request, _("New user created sucessfully. %s.") % html.hyperlink(
 
 433                     user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
 
 435             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
 
 437         form = CreateUserForm()
 
 439     return ('osqaadmin/createuser.html', {
 
 443 class NodeManagementPaginatorContext(pagination.PaginatorContext):
 
 444     def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
 
 445         super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
 
 446             (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
 
 447             (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
 
 448             (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
 
 449             (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
 
 450             (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
 
 451             (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
 
 452             (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
 
 453             (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
 
 454             (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
 
 455             (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
 
 456         ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
 
 458 @admin_tools_page(_("nodeman"), _("Bulk management"))
 
 459 def node_management(request):
 
 461         params = pagination.generate_uri(request.GET, ('page',))
 
 463         if "save_filter" in request.POST:
 
 464             filter_name = request.POST.get('filter_name', _('filter'))
 
 465             params = pagination.generate_uri(request.GET, ('page',))
 
 466             current_filters = settings.NODE_MAN_FILTERS.value
 
 467             current_filters.append((filter_name, params))
 
 468             settings.NODE_MAN_FILTERS.set_value(current_filters)
 
 470         elif r"execute" in request.POST:
 
 471             selected_nodes = request.POST.getlist('_selected_node')
 
 473             if selected_nodes and request.POST.get('action', None):
 
 474                 action = str(request.POST['action'])
 
 475                 selected_nodes = Node.objects.filter(id__in=selected_nodes)
 
 477                 message = _("No action performed")
 
 479                 if action == 'delete_selected':
 
 480                     for node in selected_nodes:
 
 481                         if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
 
 482                             DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
 
 484                     message = _("All selected nodes marked as deleted")
 
 486                 if action == 'undelete_selected':
 
 487                     for node in selected_nodes:
 
 488                         if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
 
 489                             node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
 
 491                     message = _("All selected nodes undeleted")
 
 493                 if action == "close_selected":
 
 494                     for node in selected_nodes:
 
 495                         if node.node_type == "question" and (not node.nis.closed):
 
 496                             CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
 
 498                     message = _("Selected questions were closed")
 
 500                 if action == "hard_delete_selected":
 
 501                     ids = [n.id for n in selected_nodes]
 
 505                             node = Node.objects.get(id=id)
 
 510                     message = _("All selected nodes deleted")
 
 512                 messages.info(request, message)
 
 514                 params = pagination.generate_uri(request.GET, ('page',))
 
 516             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
 
 519     nodes = Node.objects.all()
 
 521     text = request.GET.get('text', '')
 
 522     text_in = request.GET.get('text_in', 'body')
 
 524     authors = request.GET.getlist('authors')
 
 525     tags = request.GET.getlist('tags')
 
 527     type_filter = request.GET.getlist('node_type')
 
 528     state_filter = request.GET.getlist('state_type')
 
 529     state_filter_type = request.GET.get('state_filter_type', 'any')
 
 532         nodes = nodes.filter(node_type__in=type_filter)
 
 534     state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
 
 535     state_filter = [s for s in state_filter if s in state_types]
 
 538         if state_filter_type == 'all':
 
 539             nodes = nodes.all_states(*state_filter)
 
 541             nodes = nodes.any_state(*state_filter)
 
 544         nodes = nodes.filter(author__id__in=authors)
 
 545         authors = User.objects.filter(id__in=authors)
 
 548         nodes = nodes.filter(tags__id__in=tags)
 
 549         tags = Tag.objects.filter(id__in=tags)
 
 552         text_in = request.GET.get('text_in', 'body')
 
 555         if text_in == 'title' or text_in == 'both':
 
 556             filter = models.Q(title__icontains=text)
 
 558         if text_in == 'body' or text_in == 'both':
 
 559             sec_filter = models.Q(body__icontains=text)
 
 561                 filter = filter | sec_filter
 
 566             nodes = nodes.filter(filter)
 
 568     node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
 
 570     return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
 
 574     'type_filter': type_filter,
 
 575     'state_filter': state_filter,
 
 576     'state_filter_type': state_filter_type,
 
 577     'node_types': node_types,
 
 578     'state_types': state_types,
 
 581     'hide_navigation': True
 
 586 def test_email_settings(request):
 
 589     send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user })
 
 591     return render_to_response(
 
 592         'osqaadmin/test_email_settings.html',
 
 594         RequestContext(request)