1 from datetime import datetime, timedelta
2 import os, time, csv, random
4 from django.shortcuts import render_to_response, get_object_or_404
5 from django.core.urlresolvers import reverse
6 from django.http import HttpResponseRedirect, HttpResponse, Http404
7 from forum.http_responses import HttpResponseUnauthorized
8 from django.template import RequestContext
9 from django.utils.translation import ugettext as _
10 from django.utils import simplejson
11 from django.db import models
12 from forum.settings.base import Setting
13 from forum.forms import MaintenanceModeForm, PageForm, CreateUserForm
14 from forum.settings.forms import SettingsSetForm
15 from forum.utils import pagination, html
16 from forum.utils.mail import send_template_email
18 from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
19 from forum.models.node import NodeMetaClass
20 from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
21 from forum import settings
25 def super_user_required(fn):
26 def wrapper(request, *args, **kwargs):
27 if request.user.is_authenticated() and request.user.is_superuser:
28 return fn(request, *args, **kwargs)
30 return HttpResponseUnauthorized(request)
34 def staff_user_required(fn):
35 def wrapper(request, *args, **kwargs):
36 if request.user.is_authenticated() and (request.user.is_staff or request.user.is_superuser):
37 return fn(request, *args, **kwargs)
39 return HttpResponseUnauthorized(request)
43 def admin_page_wrapper(fn, request, *args, **kwargs):
44 res = fn(request, *args, **kwargs)
45 if isinstance(res, HttpResponse):
48 template, context = res
49 context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
50 context['allsets'] = Setting.sets
51 context['othersets'] = sorted(
52 [s for s in Setting.sets.values() if not s.name in
53 ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar',
54 'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')]
55 , lambda s1, s2: s1.weight - s2.weight)
57 context['tools'] = [(name, fn.label) for name, fn in TOOLS.items()]
59 unsaved = request.session.get('previewing_settings', {})
60 context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
62 return render_to_response(template, context, context_instance=RequestContext(request))
66 def wrapper(request, *args, **kwargs):
67 return admin_page_wrapper(fn, request, *args, **kwargs)
71 def moderation_page(fn):
73 def wrapper(request, *args, **kwargs):
74 return admin_page_wrapper(fn, request, *args, **kwargs)
78 def admin_tools_page(name, label):
87 class ActivityPaginatorContext(pagination.PaginatorContext):
89 super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
92 def dashboard(request):
93 return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), {
94 'settings_pack': unicode(settings.SETTINGS_PACK),
95 'statistics': get_statistics(),
96 'recent_activity': get_recent_activity(),
97 'flagged_posts': get_flagged_posts(),
101 def interface_switch(request):
102 if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
103 settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
105 return HttpResponseRedirect(reverse('admin_index'))
108 def statistics(request):
109 today = datetime.now()
110 last_month = today - timedelta(days=30)
112 last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
113 ).order_by('added_at').values_list(
114 'added_at', flat=True)
116 last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
117 qgraph_data = simplejson.dumps([
118 (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
119 for i, d in enumerate(last_month_questions)
122 last_month_users = User.objects.filter(date_joined__gt=last_month
123 ).order_by('date_joined').values_list('date_joined', flat=True)
125 last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
127 ugraph_data = simplejson.dumps([
128 (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
129 for i, d in enumerate(last_month_users)
132 return 'osqaadmin/statistics.html', {
135 'id': 'questions_graph',
136 'caption': _("Questions Graph"),
139 'id': 'userss_graph',
140 'caption': _("Users Graph"),
147 def tools_page(request, name):
148 if not name in TOOLS:
151 return TOOLS[name](request)
155 def settings_set(request, set_name):
156 set = Setting.sets.get(set_name, {})
157 current_preview = request.session.get('previewing_settings', {})
163 form = SettingsSetForm(set, data=request.POST, files=request.FILES)
166 if 'preview' in request.POST:
167 current_preview.update(form.cleaned_data)
168 request.session['previewing_settings'] = current_preview
170 return HttpResponseRedirect(reverse('index'))
173 current_preview.pop(s.name, None)
175 request.session['previewing_settings'] = current_preview
177 if not 'reset' in request.POST:
179 request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
181 if set_name in ('minrep', 'badges', 'repgain'):
182 settings.SETTINGS_PACK.set_value("custom")
184 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
186 form = SettingsSetForm(set, unsaved=current_preview)
188 return 'osqaadmin/set.html', {
190 'markdown': set.markdown,
194 def get_default(request, set_name, var_name):
195 set = Setting.sets.get(set_name, None)
196 if set is None: raise Http404
198 setting = dict([(s.name, s) for s in set]).get(var_name, None)
199 if setting is None: raise Http404
203 if request.is_ajax():
204 return HttpResponse(setting.default)
206 return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
209 def get_recent_activity():
210 return Action.objects.order_by('-action_date')
212 def get_flagged_posts():
213 return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
215 def get_statistics():
217 'total_users': User.objects.all().count(),
218 'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
219 'total_questions': Question.objects.filter_state(deleted=False).count(),
220 'questions_last_24': Question.objects.filter_state(deleted=False).filter(
221 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
222 'total_answers': Answer.objects.filter_state(deleted=False).count(),
223 'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
224 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
228 def go_bootstrap(request):
229 #todo: this is the quick and dirty way of implementing a bootstrap mode
231 from forum_modules.default_badges import settings as dbsets
232 dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
233 dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
234 dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
235 dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
236 dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
237 dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
238 dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
239 dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
240 dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
241 dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
242 dbsets.STELLAR_QUESTION_FAVS.set_value(3)
243 dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
244 dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
245 dbsets.CIVIC_DUTY_VOTES.set_value(15)
246 dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
247 dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
248 dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
249 dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
250 dbsets.GURU_UP_VOTES.set_value(4)
251 dbsets.NECROMANCER_UP_VOTES.set_value(2)
252 dbsets.NECROMANCER_DIF_DAYS.set_value(30)
253 dbsets.TAXONOMIST_USE_COUNT.set_value(5)
257 settings.REP_TO_VOTE_UP.set_value(0)
258 settings.REP_TO_VOTE_DOWN.set_value(15)
259 settings.REP_TO_FLAG.set_value(15)
260 settings.REP_TO_COMMENT.set_value(0)
261 settings.REP_TO_LIKE_COMMENT.set_value(0)
262 settings.REP_TO_UPLOAD.set_value(0)
263 settings.REP_TO_CREATE_TAGS.set_value(0)
264 settings.REP_TO_CLOSE_OWN.set_value(60)
265 settings.REP_TO_REOPEN_OWN.set_value(120)
266 settings.REP_TO_RETAG.set_value(150)
267 settings.REP_TO_EDIT_WIKI.set_value(200)
268 settings.REP_TO_EDIT_OTHERS.set_value(400)
269 settings.REP_TO_CLOSE_OTHERS.set_value(600)
270 settings.REP_TO_DELETE_COMMENTS.set_value(400)
271 settings.REP_TO_VIEW_FLAGS.set_value(30)
273 settings.INITIAL_REP.set_value(1)
274 settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
275 settings.REP_GAIN_BY_UPVOTED.set_value(15)
276 settings.REP_LOST_BY_DOWNVOTED.set_value(1)
277 settings.REP_LOST_BY_DOWNVOTING.set_value(0)
278 settings.REP_GAIN_BY_ACCEPTED.set_value(25)
279 settings.REP_GAIN_BY_ACCEPTING.set_value(5)
280 settings.REP_LOST_BY_FLAGGED.set_value(2)
281 settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
282 settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
284 settings.SETTINGS_PACK.set_value("bootstrap")
286 request.user.message_set.create(message=_('Bootstrap mode enabled'))
287 return HttpResponseRedirect(reverse('admin_index'))
290 def go_defaults(request):
291 for setting in Setting.sets['badges']:
293 for setting in Setting.sets['minrep']:
295 for setting in Setting.sets['repgain']:
298 settings.SETTINGS_PACK.set_value("default")
300 request.user.message_set.create(message=_('All values reverted to defaults'))
301 return HttpResponseRedirect(reverse('admin_index'))
305 def recalculate_denormalized(request):
306 for n in Node.objects.all():
308 n.score = n.votes.aggregate(score=models.Sum('value'))['score']
309 if not n.score: n.score = 0
312 for u in User.objects.all():
313 u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
316 request.user.message_set.create(message=_('All values recalculated'))
317 return HttpResponseRedirect(reverse('admin_index'))
320 def maintenance(request):
322 if 'close' in request.POST or 'adjust' in request.POST:
323 form = MaintenanceModeForm(request.POST)
326 settings.MAINTAINANCE_MODE.set_value({
327 'allow_ips': form.cleaned_data['ips'],
328 'message': form.cleaned_data['message']})
330 if 'close' in request.POST:
331 message = _('Maintenance mode enabled')
333 message = _('Settings adjusted')
335 request.user.message_set.create(message=message)
337 return HttpResponseRedirect(reverse('admin_maintenance'))
338 elif 'open' in request.POST:
339 settings.MAINTAINANCE_MODE.set_value(None)
340 request.user.message_set.create(message=_("Your site is now running normally"))
341 return HttpResponseRedirect(reverse('admin_maintenance'))
343 form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
344 'message': _('Currently down for maintenance. We\'ll be back soon')})
346 return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
351 def flagged_posts(request):
352 return ('osqaadmin/flagged_posts.html', {
353 'flagged_posts': get_flagged_posts(),
357 def static_pages(request):
358 pages = Page.objects.all()
360 return ('osqaadmin/static_pages.html', {
365 def edit_page(request, id=None):
367 page = get_object_or_404(Page, id=id)
372 form = PageForm(page, request.POST)
375 if form.has_changed():
377 page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
380 EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
381 data=form.cleaned_data)
383 if ('publish' in request.POST) and (not page.published):
384 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
385 elif ('unpublish' in request.POST) and page.published:
386 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
388 return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
391 form = PageForm(page)
394 published = page.published
398 return ('osqaadmin/edit_page.html', {
401 'published': published
405 def delete_page(request, id=None):
406 page = get_object_or_404(Page, id=id)
408 return HttpResponseRedirect(reverse('admin_static_pages'))
410 @admin_tools_page(_('createuser'), _("Create new user"))
411 def create_user(request):
413 form = CreateUserForm(request.POST)
416 user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
417 user_.set_password(form.cleaned_data['password1'])
419 if not form.cleaned_data.get('validate_email', False):
420 user_.email_isvalid = True
423 UserJoinsAction(user=user_).save()
425 request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
426 user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
428 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
430 form = CreateUserForm()
432 return ('osqaadmin/createuser.html', {
436 class NodeManagementPaginatorContext(pagination.PaginatorContext):
437 def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
438 super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
439 (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
440 (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
441 (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
442 (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
443 (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
444 (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
445 (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
446 (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
447 (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
448 (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
449 ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
451 @admin_tools_page(_("nodeman"), _("Bulk management"))
452 def node_management(request):
454 params = pagination.generate_uri(request.GET, ('page',))
456 if "save_filter" in request.POST:
457 filter_name = request.POST.get('filter_name', _('filter'))
458 params = pagination.generate_uri(request.GET, ('page',))
459 current_filters = settings.NODE_MAN_FILTERS.value
460 current_filters.append((filter_name, params))
461 settings.NODE_MAN_FILTERS.set_value(current_filters)
463 elif r"execute" in request.POST:
464 selected_nodes = request.POST.getlist('_selected_node')
466 if selected_nodes and request.POST.get('action', None):
467 action = request.POST['action']
468 selected_nodes = Node.objects.filter(id__in=selected_nodes)
470 message = _("No action performed")
472 if action == 'delete_selected':
473 for node in selected_nodes:
474 if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
475 DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
477 message = _("All selected nodes marked as deleted")
479 if action == 'undelete_selected':
480 for node in selected_nodes:
481 if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
482 node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
484 message = _("All selected nodes undeleted")
486 if action == "close_selected":
487 for node in selected_nodes:
488 if node.node_type == "question" and (not node.nis.closed):
489 CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
491 message = _("Selected questions were closed")
493 if action == "hard_delete_selected":
494 ids = [n.id for n in selected_nodes]
498 node = Node.objects.get(id=id)
503 message = _("All selected nodes deleted")
505 request.user.message_set.create(message=message)
507 params = pagination.generate_uri(request.GET, ('page',))
509 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
512 nodes = Node.objects.all()
514 text = request.GET.get('text', '')
515 text_in = request.GET.get('text_in', 'body')
517 authors = request.GET.getlist('authors')
518 tags = request.GET.getlist('tags')
520 type_filter = request.GET.getlist('node_type')
521 state_filter = request.GET.getlist('state_type')
522 state_filter_type = request.GET.get('state_filter_type', 'any')
525 nodes = nodes.filter(node_type__in=type_filter)
527 state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
528 state_filter = [s for s in state_filter if s in state_types]
531 if state_filter_type == 'all':
532 nodes = nodes.all_states(*state_filter)
534 nodes = nodes.any_state(*state_filter)
537 nodes = nodes.filter(author__id__in=authors)
538 authors = User.objects.filter(id__in=authors)
541 nodes = nodes.filter(tags__id__in=tags)
542 tags = Tag.objects.filter(id__in=tags)
545 text_in = request.GET.get('text_in', 'body')
548 if text_in == 'title' or text_in == 'both':
549 filter = models.Q(title__icontains=text)
551 if text_in == 'body' or text_in == 'both':
552 sec_filter = models.Q(body__icontains=text)
554 filter = filter | sec_filter
559 nodes = nodes.filter(filter)
561 node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
563 return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
567 'type_filter': type_filter,
568 'state_filter': state_filter,
569 'state_filter_type': state_filter_type,
570 'node_types': node_types,
571 'state_types': state_types,
578 def test_email_settings(request):
581 send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user })
583 return render_to_response(
584 'osqaadmin/test_email_settings.html',
586 RequestContext(request)