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
404 @admin_tools_page(_('createuser'), _("Create new user"))
405 def create_user(request):
407 form = CreateUserForm(request.POST)
410 user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
411 user_.set_password(form.cleaned_data['password1'])
413 if not form.cleaned_data.get('validate_email', False):
414 user_.email_isvalid = True
417 UserJoinsAction(user=user_).save()
419 request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
420 user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
422 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
424 form = CreateUserForm()
426 return ('osqaadmin/createuser.html', {
430 class NodeManagementPaginatorContext(pagination.PaginatorContext):
431 def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
432 super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
433 (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
434 (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
435 (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
436 (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
437 (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
438 (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
439 (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
440 (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
441 (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
442 (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
443 ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
445 @admin_tools_page(_("nodeman"), _("Bulk management"))
446 def node_management(request):
448 params = pagination.generate_uri(request.GET, ('page',))
450 if "save_filter" in request.POST:
451 filter_name = request.POST.get('filter_name', _('filter'))
452 params = pagination.generate_uri(request.GET, ('page',))
453 current_filters = settings.NODE_MAN_FILTERS.value
454 current_filters.append((filter_name, params))
455 settings.NODE_MAN_FILTERS.set_value(current_filters)
457 elif r"execute" in request.POST:
458 selected_nodes = request.POST.getlist('_selected_node')
460 if selected_nodes and request.POST.get('action', None):
461 action = request.POST['action']
462 selected_nodes = Node.objects.filter(id__in=selected_nodes)
464 message = _("No action performed")
466 if action == 'delete_selected':
467 for node in selected_nodes:
468 if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
469 DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
471 message = _("All selected nodes marked as deleted")
473 if action == 'undelete_selected':
474 for node in selected_nodes:
475 if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
476 node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
478 message = _("All selected nodes undeleted")
480 if action == "close_selected":
481 for node in selected_nodes:
482 if node.node_type == "question" and (not node.nis.closed):
483 CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
485 message = _("Selected questions were closed")
487 if action == "hard_delete_selected":
488 ids = [n.id for n in selected_nodes]
492 node = Node.objects.get(id=id)
497 message = _("All selected nodes deleted")
499 request.user.message_set.create(message=message)
501 params = pagination.generate_uri(request.GET, ('page',))
503 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
506 nodes = Node.objects.all()
508 text = request.GET.get('text', '')
509 text_in = request.GET.get('text_in', 'body')
511 authors = request.GET.getlist('authors')
512 tags = request.GET.getlist('tags')
514 type_filter = request.GET.getlist('node_type')
515 state_filter = request.GET.getlist('state_type')
516 state_filter_type = request.GET.get('state_filter_type', 'any')
519 nodes = nodes.filter(node_type__in=type_filter)
521 state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
522 state_filter = [s for s in state_filter if s in state_types]
525 if state_filter_type == 'all':
526 nodes = nodes.all_states(*state_filter)
528 nodes = nodes.any_state(*state_filter)
531 nodes = nodes.filter(author__id__in=authors)
532 authors = User.objects.filter(id__in=authors)
535 nodes = nodes.filter(tags__id__in=tags)
536 tags = Tag.objects.filter(id__in=tags)
539 text_in = request.GET.get('text_in', 'body')
542 if text_in == 'title' or text_in == 'both':
543 filter = models.Q(title__icontains=text)
545 if text_in == 'body' or text_in == 'both':
546 sec_filter = models.Q(body__icontains=text)
548 filter = filter | sec_filter
553 nodes = nodes.filter(filter)
555 node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
557 return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
561 'type_filter': type_filter,
562 'state_filter': state_filter,
563 'state_filter_type': state_filter_type,
564 'node_types': node_types,
565 'state_types': state_types,
572 def test_email_settings(request):
575 send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user })
577 return render_to_response(
578 'osqaadmin/test_email_settings.html',
580 RequestContext(request)