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)