1 from datetime import datetime, timedelta
4 from django.views.decorators.csrf import csrf_exempt
5 from django.shortcuts import render_to_response, get_object_or_404
6 from django.core.urlresolvers import reverse
7 from django.http import HttpResponseRedirect, HttpResponse, Http404
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
13 from forum.http_responses import HttpResponseUnauthorized
14 from forum.settings.base import Setting
15 from forum.forms import MaintenanceModeForm, PageForm, CreateUserForm
16 from forum.settings.forms import SettingsSetForm
17 from forum.utils import pagination, html
18 from forum.utils.mail import send_template_email
19 from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
20 from forum.models.node import NodeMetaClass
21 from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
22 from forum import settings
26 def super_user_required(fn):
27 def wrapper(request, *args, **kwargs):
28 if request.user.is_authenticated() and request.user.is_superuser:
29 return fn(request, *args, **kwargs)
31 return HttpResponseUnauthorized(request)
35 def staff_user_required(fn):
36 def wrapper(request, *args, **kwargs):
37 if request.user.is_authenticated() and (request.user.is_staff or request.user.is_superuser):
38 return fn(request, *args, **kwargs)
40 return HttpResponseUnauthorized(request)
44 def admin_page_wrapper(fn, request, *args, **kwargs):
45 res = fn(request, *args, **kwargs)
46 if isinstance(res, HttpResponse):
49 template, context = res
50 context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
51 context['allsets'] = Setting.sets
52 context['othersets'] = sorted(
53 [s for s in Setting.sets.values() if not s.name in
54 ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar',
55 'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')]
56 , lambda s1, s2: s1.weight - s2.weight)
58 context['tools'] = [(name, fn.label) for name, fn in TOOLS.items()]
60 # Show the navigation only to moderators and super users
61 if not context.has_key("hide_navigation"):
62 context['hide_navigation'] = not request.user.is_superuser
64 unsaved = request.session.get('previewing_settings', {})
65 context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
67 return render_to_response(template, context, context_instance=RequestContext(request))
71 def wrapper(request, *args, **kwargs):
72 return admin_page_wrapper(fn, request, *args, **kwargs)
76 def moderation_page(fn):
78 def wrapper(request, *args, **kwargs):
79 return admin_page_wrapper(fn, request, *args, **kwargs)
83 def admin_tools_page(name, label):
92 class ActivityPaginatorContext(pagination.PaginatorContext):
94 super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
97 def dashboard(request):
98 return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), {
99 'settings_pack': unicode(settings.SETTINGS_PACK),
100 'statistics': get_statistics(),
101 'recent_activity': get_recent_activity(),
102 'flagged_posts': get_flagged_posts(),
106 def interface_switch(request):
107 if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
108 settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
110 return HttpResponseRedirect(reverse('admin_index'))
113 def statistics(request):
114 today = datetime.now()
115 last_month = today - timedelta(days=30)
117 last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
118 ).order_by('added_at').values_list(
119 'added_at', flat=True)
121 last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
122 qgraph_data = simplejson.dumps([
123 (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
124 for i, d in enumerate(last_month_questions)
127 last_month_users = User.objects.filter(date_joined__gt=last_month
128 ).order_by('date_joined').values_list('date_joined', flat=True)
130 last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
132 ugraph_data = simplejson.dumps([
133 (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
134 for i, d in enumerate(last_month_users)
137 return 'osqaadmin/statistics.html', {
140 'id': 'questions_graph',
141 'caption': _("Questions Graph"),
144 'id': 'userss_graph',
145 'caption': _("Users Graph"),
152 def tools_page(request, name):
153 if not name in TOOLS:
156 return TOOLS[name](request)
160 def settings_set(request, set_name):
161 set = Setting.sets.get(set_name, {})
162 current_preview = request.session.get('previewing_settings', {})
168 form = SettingsSetForm(set, data=request.POST, files=request.FILES)
171 if 'preview' in request.POST:
172 current_preview.update(form.cleaned_data)
173 request.session['previewing_settings'] = current_preview
175 return HttpResponseRedirect(reverse('index'))
178 current_preview.pop(s.name, None)
180 request.session['previewing_settings'] = current_preview
182 if not 'reset' in request.POST:
184 request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
186 if set_name in ('minrep', 'badges', 'repgain'):
187 settings.SETTINGS_PACK.set_value("custom")
189 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
191 form = SettingsSetForm(set, unsaved=current_preview)
193 return 'osqaadmin/set.html', {
195 'markdown': set.markdown,
199 def get_default(request, set_name, var_name):
200 set = Setting.sets.get(set_name, None)
201 if set is None: raise Http404
203 setting = dict([(s.name, s) for s in set]).get(var_name, None)
204 if setting is None: raise Http404
208 if request.is_ajax():
209 return HttpResponse(setting.default)
211 return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
214 def get_recent_activity():
215 return Action.objects.order_by('-action_date')
217 def get_flagged_posts():
218 return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
220 def get_statistics():
222 'total_users': User.objects.all().count(),
223 'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
224 'total_questions': Question.objects.filter_state(deleted=False).count(),
225 'questions_last_24': Question.objects.filter_state(deleted=False).filter(
226 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
227 'total_answers': Answer.objects.filter_state(deleted=False).count(),
228 'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
229 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
233 def go_bootstrap(request):
234 #todo: this is the quick and dirty way of implementing a bootstrap mode
236 from forum_modules.default_badges import settings as dbsets
237 dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
238 dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
239 dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
240 dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
241 dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
242 dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
243 dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
244 dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
245 dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
246 dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
247 dbsets.STELLAR_QUESTION_FAVS.set_value(3)
248 dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
249 dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
250 dbsets.CIVIC_DUTY_VOTES.set_value(15)
251 dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
252 dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
253 dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
254 dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
255 dbsets.GURU_UP_VOTES.set_value(4)
256 dbsets.NECROMANCER_UP_VOTES.set_value(2)
257 dbsets.NECROMANCER_DIF_DAYS.set_value(30)
258 dbsets.TAXONOMIST_USE_COUNT.set_value(5)
262 settings.REP_TO_VOTE_UP.set_value(0)
263 settings.REP_TO_VOTE_DOWN.set_value(15)
264 settings.REP_TO_FLAG.set_value(15)
265 settings.REP_TO_COMMENT.set_value(0)
266 settings.REP_TO_LIKE_COMMENT.set_value(0)
267 settings.REP_TO_UPLOAD.set_value(0)
268 settings.REP_TO_CREATE_TAGS.set_value(0)
269 settings.REP_TO_CLOSE_OWN.set_value(60)
270 settings.REP_TO_REOPEN_OWN.set_value(120)
271 settings.REP_TO_RETAG.set_value(150)
272 settings.REP_TO_EDIT_WIKI.set_value(200)
273 settings.REP_TO_EDIT_OTHERS.set_value(400)
274 settings.REP_TO_CLOSE_OTHERS.set_value(600)
275 settings.REP_TO_DELETE_COMMENTS.set_value(400)
276 settings.REP_TO_VIEW_FLAGS.set_value(30)
278 settings.INITIAL_REP.set_value(1)
279 settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
280 settings.REP_GAIN_BY_UPVOTED.set_value(15)
281 settings.REP_LOST_BY_DOWNVOTED.set_value(1)
282 settings.REP_LOST_BY_DOWNVOTING.set_value(0)
283 settings.REP_GAIN_BY_ACCEPTED.set_value(25)
284 settings.REP_GAIN_BY_ACCEPTING.set_value(5)
285 settings.REP_LOST_BY_FLAGGED.set_value(2)
286 settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
287 settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
289 settings.SETTINGS_PACK.set_value("bootstrap")
291 request.user.message_set.create(message=_('Bootstrap mode enabled'))
292 return HttpResponseRedirect(reverse('admin_index'))
295 def go_defaults(request):
296 for setting in Setting.sets['badges']:
298 for setting in Setting.sets['minrep']:
300 for setting in Setting.sets['repgain']:
303 settings.SETTINGS_PACK.set_value("default")
305 request.user.message_set.create(message=_('All values reverted to defaults'))
306 return HttpResponseRedirect(reverse('admin_index'))
310 def recalculate_denormalized(request):
311 for n in Node.objects.all():
313 n.score = n.votes.aggregate(score=models.Sum('value'))['score']
314 if not n.score: n.score = 0
317 for u in User.objects.all():
318 u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
321 request.user.message_set.create(message=_('All values recalculated'))
322 return HttpResponseRedirect(reverse('admin_index'))
325 def maintenance(request):
327 if 'close' in request.POST or 'adjust' in request.POST:
328 form = MaintenanceModeForm(request.POST)
331 settings.MAINTAINANCE_MODE.set_value({
332 'allow_ips': form.cleaned_data['ips'],
333 'message': form.cleaned_data['message']})
335 if 'close' in request.POST:
336 message = _('Maintenance mode enabled')
338 message = _('Settings adjusted')
340 request.user.message_set.create(message=message)
342 return HttpResponseRedirect(reverse('admin_maintenance'))
343 elif 'open' in request.POST:
344 settings.MAINTAINANCE_MODE.set_value(None)
345 request.user.message_set.create(message=_("Your site is now running normally"))
346 return HttpResponseRedirect(reverse('admin_maintenance'))
348 form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
349 'message': _('Currently down for maintenance. We\'ll be back soon')})
351 return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
356 def flagged_posts(request):
357 return ('osqaadmin/flagged_posts.html', {
358 'flagged_posts': get_flagged_posts(),
362 def static_pages(request):
363 pages = Page.objects.all()
365 return ('osqaadmin/static_pages.html', {
370 def edit_page(request, id=None):
372 page = get_object_or_404(Page, id=id)
377 form = PageForm(page, request.POST)
380 if form.has_changed():
382 page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
385 EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
386 data=form.cleaned_data)
388 if ('publish' in request.POST) and (not page.published):
389 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
390 elif ('unpublish' in request.POST) and page.published:
391 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
393 return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
396 form = PageForm(page)
399 published = page.published
403 return ('osqaadmin/edit_page.html', {
406 'published': published
410 def delete_page(request, id=None):
411 page = get_object_or_404(Page, id=id)
413 return HttpResponseRedirect(reverse('admin_static_pages'))
415 @admin_tools_page(_('createuser'), _("Create new user"))
416 def create_user(request):
418 form = CreateUserForm(request.POST)
421 user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
422 user_.set_password(form.cleaned_data['password1'])
424 if not form.cleaned_data.get('validate_email', False):
425 user_.email_isvalid = True
428 UserJoinsAction(user=user_).save()
430 request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
431 user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
433 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
435 form = CreateUserForm()
437 return ('osqaadmin/createuser.html', {
441 class NodeManagementPaginatorContext(pagination.PaginatorContext):
442 def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
443 super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
444 (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
445 (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
446 (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
447 (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
448 (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
449 (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
450 (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
451 (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
452 (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
453 (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
454 ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
456 @admin_tools_page(_("nodeman"), _("Bulk management"))
457 def node_management(request):
459 params = pagination.generate_uri(request.GET, ('page',))
461 if "save_filter" in request.POST:
462 filter_name = request.POST.get('filter_name', _('filter'))
463 params = pagination.generate_uri(request.GET, ('page',))
464 current_filters = settings.NODE_MAN_FILTERS.value
465 current_filters.append((filter_name, params))
466 settings.NODE_MAN_FILTERS.set_value(current_filters)
468 elif r"execute" in request.POST:
469 selected_nodes = request.POST.getlist('_selected_node')
471 if selected_nodes and request.POST.get('action', None):
472 action = str(request.POST['action'])
473 selected_nodes = Node.objects.filter(id__in=selected_nodes)
475 message = _("No action performed")
477 if action == 'delete_selected':
478 for node in selected_nodes:
479 if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
480 DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
482 message = _("All selected nodes marked as deleted")
484 if action == 'undelete_selected':
485 for node in selected_nodes:
486 if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
487 node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
489 message = _("All selected nodes undeleted")
491 if action == "close_selected":
492 for node in selected_nodes:
493 if node.node_type == "question" and (not node.nis.closed):
494 CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
496 message = _("Selected questions were closed")
498 if action == "hard_delete_selected":
499 ids = [n.id for n in selected_nodes]
503 node = Node.objects.get(id=id)
508 message = _("All selected nodes deleted")
510 request.user.message_set.create(message=message)
512 params = pagination.generate_uri(request.GET, ('page',))
514 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
517 nodes = Node.objects.all()
519 text = request.GET.get('text', '')
520 text_in = request.GET.get('text_in', 'body')
522 authors = request.GET.getlist('authors')
523 tags = request.GET.getlist('tags')
525 type_filter = request.GET.getlist('node_type')
526 state_filter = request.GET.getlist('state_type')
527 state_filter_type = request.GET.get('state_filter_type', 'any')
530 nodes = nodes.filter(node_type__in=type_filter)
532 state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
533 state_filter = [s for s in state_filter if s in state_types]
536 if state_filter_type == 'all':
537 nodes = nodes.all_states(*state_filter)
539 nodes = nodes.any_state(*state_filter)
542 nodes = nodes.filter(author__id__in=authors)
543 authors = User.objects.filter(id__in=authors)
546 nodes = nodes.filter(tags__id__in=tags)
547 tags = Tag.objects.filter(id__in=tags)
550 text_in = request.GET.get('text_in', 'body')
553 if text_in == 'title' or text_in == 'both':
554 filter = models.Q(title__icontains=text)
556 if text_in == 'body' or text_in == 'both':
557 sec_filter = models.Q(body__icontains=text)
559 filter = filter | sec_filter
564 nodes = nodes.filter(filter)
566 node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
568 return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
572 'type_filter': type_filter,
573 'state_filter': state_filter,
574 'state_filter_type': state_filter_type,
575 'node_types': node_types,
576 'state_types': state_types,
579 'hide_navigation': True
584 def test_email_settings(request):
587 send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user })
589 return render_to_response(
590 'osqaadmin/test_email_settings.html',
592 RequestContext(request)