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 unsaved = request.session.get('previewing_settings', {})
61 context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
63 return render_to_response(template, context, context_instance=RequestContext(request))
67 def wrapper(request, *args, **kwargs):
68 return admin_page_wrapper(fn, request, *args, **kwargs)
72 def moderation_page(fn):
74 def wrapper(request, *args, **kwargs):
75 return admin_page_wrapper(fn, request, *args, **kwargs)
79 def admin_tools_page(name, label):
88 class ActivityPaginatorContext(pagination.PaginatorContext):
90 super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
93 def dashboard(request):
94 return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), {
95 'settings_pack': unicode(settings.SETTINGS_PACK),
96 'statistics': get_statistics(),
97 'recent_activity': get_recent_activity(),
98 'flagged_posts': get_flagged_posts(),
102 def interface_switch(request):
103 if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
104 settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
106 return HttpResponseRedirect(reverse('admin_index'))
109 def statistics(request):
110 today = datetime.now()
111 last_month = today - timedelta(days=30)
113 last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
114 ).order_by('added_at').values_list(
115 'added_at', flat=True)
117 last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
118 qgraph_data = simplejson.dumps([
119 (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
120 for i, d in enumerate(last_month_questions)
123 last_month_users = User.objects.filter(date_joined__gt=last_month
124 ).order_by('date_joined').values_list('date_joined', flat=True)
126 last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
128 ugraph_data = simplejson.dumps([
129 (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
130 for i, d in enumerate(last_month_users)
133 return 'osqaadmin/statistics.html', {
136 'id': 'questions_graph',
137 'caption': _("Questions Graph"),
140 'id': 'userss_graph',
141 'caption': _("Users Graph"),
148 def tools_page(request, name):
149 if not name in TOOLS:
152 return TOOLS[name](request)
156 def settings_set(request, set_name):
157 set = Setting.sets.get(set_name, {})
158 current_preview = request.session.get('previewing_settings', {})
164 form = SettingsSetForm(set, data=request.POST, files=request.FILES)
167 if 'preview' in request.POST:
168 current_preview.update(form.cleaned_data)
169 request.session['previewing_settings'] = current_preview
171 return HttpResponseRedirect(reverse('index'))
174 current_preview.pop(s.name, None)
176 request.session['previewing_settings'] = current_preview
178 if not 'reset' in request.POST:
180 request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
182 if set_name in ('minrep', 'badges', 'repgain'):
183 settings.SETTINGS_PACK.set_value("custom")
185 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
187 form = SettingsSetForm(set, unsaved=current_preview)
189 return 'osqaadmin/set.html', {
191 'markdown': set.markdown,
195 def get_default(request, set_name, var_name):
196 set = Setting.sets.get(set_name, None)
197 if set is None: raise Http404
199 setting = dict([(s.name, s) for s in set]).get(var_name, None)
200 if setting is None: raise Http404
204 if request.is_ajax():
205 return HttpResponse(setting.default)
207 return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
210 def get_recent_activity():
211 return Action.objects.order_by('-action_date')
213 def get_flagged_posts():
214 return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
216 def get_statistics():
218 'total_users': User.objects.all().count(),
219 'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
220 'total_questions': Question.objects.filter_state(deleted=False).count(),
221 'questions_last_24': Question.objects.filter_state(deleted=False).filter(
222 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
223 'total_answers': Answer.objects.filter_state(deleted=False).count(),
224 'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
225 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
229 def go_bootstrap(request):
230 #todo: this is the quick and dirty way of implementing a bootstrap mode
232 from forum_modules.default_badges import settings as dbsets
233 dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
234 dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
235 dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
236 dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
237 dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
238 dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
239 dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
240 dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
241 dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
242 dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
243 dbsets.STELLAR_QUESTION_FAVS.set_value(3)
244 dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
245 dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
246 dbsets.CIVIC_DUTY_VOTES.set_value(15)
247 dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
248 dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
249 dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
250 dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
251 dbsets.GURU_UP_VOTES.set_value(4)
252 dbsets.NECROMANCER_UP_VOTES.set_value(2)
253 dbsets.NECROMANCER_DIF_DAYS.set_value(30)
254 dbsets.TAXONOMIST_USE_COUNT.set_value(5)
258 settings.REP_TO_VOTE_UP.set_value(0)
259 settings.REP_TO_VOTE_DOWN.set_value(15)
260 settings.REP_TO_FLAG.set_value(15)
261 settings.REP_TO_COMMENT.set_value(0)
262 settings.REP_TO_LIKE_COMMENT.set_value(0)
263 settings.REP_TO_UPLOAD.set_value(0)
264 settings.REP_TO_CREATE_TAGS.set_value(0)
265 settings.REP_TO_CLOSE_OWN.set_value(60)
266 settings.REP_TO_REOPEN_OWN.set_value(120)
267 settings.REP_TO_RETAG.set_value(150)
268 settings.REP_TO_EDIT_WIKI.set_value(200)
269 settings.REP_TO_EDIT_OTHERS.set_value(400)
270 settings.REP_TO_CLOSE_OTHERS.set_value(600)
271 settings.REP_TO_DELETE_COMMENTS.set_value(400)
272 settings.REP_TO_VIEW_FLAGS.set_value(30)
274 settings.INITIAL_REP.set_value(1)
275 settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
276 settings.REP_GAIN_BY_UPVOTED.set_value(15)
277 settings.REP_LOST_BY_DOWNVOTED.set_value(1)
278 settings.REP_LOST_BY_DOWNVOTING.set_value(0)
279 settings.REP_GAIN_BY_ACCEPTED.set_value(25)
280 settings.REP_GAIN_BY_ACCEPTING.set_value(5)
281 settings.REP_LOST_BY_FLAGGED.set_value(2)
282 settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
283 settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
285 settings.SETTINGS_PACK.set_value("bootstrap")
287 request.user.message_set.create(message=_('Bootstrap mode enabled'))
288 return HttpResponseRedirect(reverse('admin_index'))
291 def go_defaults(request):
292 for setting in Setting.sets['badges']:
294 for setting in Setting.sets['minrep']:
296 for setting in Setting.sets['repgain']:
299 settings.SETTINGS_PACK.set_value("default")
301 request.user.message_set.create(message=_('All values reverted to defaults'))
302 return HttpResponseRedirect(reverse('admin_index'))
306 def recalculate_denormalized(request):
307 for n in Node.objects.all():
309 n.score = n.votes.aggregate(score=models.Sum('value'))['score']
310 if not n.score: n.score = 0
313 for u in User.objects.all():
314 u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
317 request.user.message_set.create(message=_('All values recalculated'))
318 return HttpResponseRedirect(reverse('admin_index'))
321 def maintenance(request):
323 if 'close' in request.POST or 'adjust' in request.POST:
324 form = MaintenanceModeForm(request.POST)
327 settings.MAINTAINANCE_MODE.set_value({
328 'allow_ips': form.cleaned_data['ips'],
329 'message': form.cleaned_data['message']})
331 if 'close' in request.POST:
332 message = _('Maintenance mode enabled')
334 message = _('Settings adjusted')
336 request.user.message_set.create(message=message)
338 return HttpResponseRedirect(reverse('admin_maintenance'))
339 elif 'open' in request.POST:
340 settings.MAINTAINANCE_MODE.set_value(None)
341 request.user.message_set.create(message=_("Your site is now running normally"))
342 return HttpResponseRedirect(reverse('admin_maintenance'))
344 form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
345 'message': _('Currently down for maintenance. We\'ll be back soon')})
347 return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
352 def flagged_posts(request):
353 return ('osqaadmin/flagged_posts.html', {
354 'flagged_posts': get_flagged_posts(),
358 def static_pages(request):
359 pages = Page.objects.all()
361 return ('osqaadmin/static_pages.html', {
366 def edit_page(request, id=None):
368 page = get_object_or_404(Page, id=id)
373 form = PageForm(page, request.POST)
376 if form.has_changed():
378 page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
381 EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
382 data=form.cleaned_data)
384 if ('publish' in request.POST) and (not page.published):
385 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
386 elif ('unpublish' in request.POST) and page.published:
387 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
389 return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
392 form = PageForm(page)
395 published = page.published
399 return ('osqaadmin/edit_page.html', {
402 'published': published
406 def delete_page(request, id=None):
407 page = get_object_or_404(Page, id=id)
409 return HttpResponseRedirect(reverse('admin_static_pages'))
411 @admin_tools_page(_('createuser'), _("Create new user"))
412 def create_user(request):
414 form = CreateUserForm(request.POST)
417 user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
418 user_.set_password(form.cleaned_data['password1'])
420 if not form.cleaned_data.get('validate_email', False):
421 user_.email_isvalid = True
424 UserJoinsAction(user=user_).save()
426 request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
427 user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
429 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
431 form = CreateUserForm()
433 return ('osqaadmin/createuser.html', {
437 class NodeManagementPaginatorContext(pagination.PaginatorContext):
438 def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
439 super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
440 (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
441 (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
442 (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
443 (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
444 (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
445 (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
446 (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
447 (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
448 (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
449 (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
450 ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
452 @admin_tools_page(_("nodeman"), _("Bulk management"))
453 def node_management(request):
455 params = pagination.generate_uri(request.GET, ('page',))
457 if "save_filter" in request.POST:
458 filter_name = request.POST.get('filter_name', _('filter'))
459 params = pagination.generate_uri(request.GET, ('page',))
460 current_filters = settings.NODE_MAN_FILTERS.value
461 current_filters.append((filter_name, params))
462 settings.NODE_MAN_FILTERS.set_value(current_filters)
464 elif r"execute" in request.POST:
465 selected_nodes = request.POST.getlist('_selected_node')
467 if selected_nodes and request.POST.get('action', None):
468 action = str(request.POST['action'])
469 selected_nodes = Node.objects.filter(id__in=selected_nodes)
471 message = _("No action performed")
473 if action == 'delete_selected':
474 for node in selected_nodes:
475 if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
476 DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
478 message = _("All selected nodes marked as deleted")
480 if action == 'undelete_selected':
481 for node in selected_nodes:
482 if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
483 node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
485 message = _("All selected nodes undeleted")
487 if action == "close_selected":
488 for node in selected_nodes:
489 if node.node_type == "question" and (not node.nis.closed):
490 CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
492 message = _("Selected questions were closed")
494 if action == "hard_delete_selected":
495 ids = [n.id for n in selected_nodes]
499 node = Node.objects.get(id=id)
504 message = _("All selected nodes deleted")
506 request.user.message_set.create(message=message)
508 params = pagination.generate_uri(request.GET, ('page',))
510 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
513 nodes = Node.objects.all()
515 text = request.GET.get('text', '')
516 text_in = request.GET.get('text_in', 'body')
518 authors = request.GET.getlist('authors')
519 tags = request.GET.getlist('tags')
521 type_filter = request.GET.getlist('node_type')
522 state_filter = request.GET.getlist('state_type')
523 state_filter_type = request.GET.get('state_filter_type', 'any')
526 nodes = nodes.filter(node_type__in=type_filter)
528 state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
529 state_filter = [s for s in state_filter if s in state_types]
532 if state_filter_type == 'all':
533 nodes = nodes.all_states(*state_filter)
535 nodes = nodes.any_state(*state_filter)
538 nodes = nodes.filter(author__id__in=authors)
539 authors = User.objects.filter(id__in=authors)
542 nodes = nodes.filter(tags__id__in=tags)
543 tags = Tag.objects.filter(id__in=tags)
546 text_in = request.GET.get('text_in', 'body')
549 if text_in == 'title' or text_in == 'both':
550 filter = models.Q(title__icontains=text)
552 if text_in == 'body' or text_in == 'both':
553 sec_filter = models.Q(body__icontains=text)
555 filter = filter | sec_filter
560 nodes = nodes.filter(filter)
562 node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
564 return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
568 'type_filter': type_filter,
569 'state_filter': state_filter,
570 'state_filter_type': state_filter_type,
571 'node_types': node_types,
572 'state_types': state_types,
580 def test_email_settings(request):
583 send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user })
585 return render_to_response(
586 'osqaadmin/test_email_settings.html',
588 RequestContext(request)