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
17 from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
18 from forum.models.node import NodeMetaClass
19 from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
20 from forum import settings
24 def super_user_required(fn):
25 def wrapper(request, *args, **kwargs):
26 if request.user.is_authenticated() and request.user.is_superuser:
27 return fn(request, *args, **kwargs)
29 return HttpResponseUnauthorized(request)
35 def wrapper(request, *args, **kwargs):
36 res = fn(request, *args, **kwargs)
37 if isinstance(res, HttpResponse):
40 template, context = res
41 context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
42 context['allsets'] = Setting.sets
43 context['othersets'] = sorted(
44 [s for s in Setting.sets.values() if not s.name in
45 ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar',
46 'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')]
47 , lambda s1, s2: s1.weight - s2.weight)
49 context['tools'] = TOOLS
51 unsaved = request.session.get('previewing_settings', {})
52 context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
54 return render_to_response(template, context, context_instance=RequestContext(request))
58 def admin_tools_page(name, label):
67 class ActivityPaginatorContext(pagination.PaginatorContext):
69 super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
72 def dashboard(request):
73 return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), {
74 'settings_pack': unicode(settings.SETTINGS_PACK),
75 'statistics': get_statistics(),
76 'recent_activity': get_recent_activity(),
77 'flagged_posts': get_flagged_posts(),
81 def interface_switch(request):
82 if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
83 settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
85 return HttpResponseRedirect(reverse('admin_index'))
88 def statistics(request):
89 today = datetime.now()
90 last_month = today - timedelta(days=30)
92 last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
93 ).order_by('added_at').values_list(
94 'added_at', flat=True)
96 last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
97 qgraph_data = simplejson.dumps([
98 (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
99 for i, d in enumerate(last_month_questions)
102 last_month_users = User.objects.filter(date_joined__gt=last_month
103 ).order_by('date_joined').values_list('date_joined', flat=True)
105 last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
107 ugraph_data = simplejson.dumps([
108 (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
109 for i, d in enumerate(last_month_users)
112 return 'osqaadmin/statistics.html', {
115 'id': 'questions_graph',
116 'caption': _("Questions Graph"),
119 'id': 'userss_graph',
120 'caption': _("Users Graph"),
127 def tools_page(request, name):
128 if not name in TOOLS:
131 return TOOLS[name](request)
135 def settings_set(request, set_name):
136 set = Setting.sets.get(set_name, {})
137 current_preview = request.session.get('previewing_settings', {})
143 form = SettingsSetForm(set, data=request.POST, files=request.FILES)
146 if 'preview' in request.POST:
147 current_preview.update(form.cleaned_data)
148 request.session['previewing_settings'] = current_preview
150 return HttpResponseRedirect(reverse('index'))
153 current_preview.pop(s.name, None)
155 request.session['previewing_settings'] = current_preview
157 if not 'reset' in request.POST:
159 request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
161 if set_name in ('minrep', 'badges', 'repgain'):
162 settings.SETTINGS_PACK.set_value("custom")
164 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
166 form = SettingsSetForm(set, unsaved=current_preview)
168 return 'osqaadmin/set.html', {
170 'markdown': set.markdown,
174 def get_default(request, set_name, var_name):
175 set = Setting.sets.get(set_name, None)
176 if set is None: raise Http404
178 setting = dict([(s.name, s) for s in set]).get(var_name, None)
179 if setting is None: raise Http404
183 if request.is_ajax():
184 return HttpResponse(setting.default)
186 return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
189 def get_recent_activity():
190 return Action.objects.order_by('-action_date')
192 def get_flagged_posts():
193 return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
195 def get_statistics():
197 'total_users': User.objects.all().count(),
198 'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
199 'total_questions': Question.objects.filter_state(deleted=False).count(),
200 'questions_last_24': Question.objects.filter_state(deleted=False).filter(
201 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
202 'total_answers': Answer.objects.filter_state(deleted=False).count(),
203 'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
204 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
208 def go_bootstrap(request):
209 #todo: this is the quick and dirty way of implementing a bootstrap mode
211 from forum_modules.default_badges import settings as dbsets
212 dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
213 dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
214 dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
215 dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
216 dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
217 dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
218 dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
219 dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
220 dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
221 dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
222 dbsets.STELLAR_QUESTION_FAVS.set_value(3)
223 dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
224 dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
225 dbsets.CIVIC_DUTY_VOTES.set_value(15)
226 dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
227 dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
228 dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
229 dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
230 dbsets.GURU_UP_VOTES.set_value(4)
231 dbsets.NECROMANCER_UP_VOTES.set_value(2)
232 dbsets.NECROMANCER_DIF_DAYS.set_value(30)
233 dbsets.TAXONOMIST_USE_COUNT.set_value(5)
237 settings.REP_TO_VOTE_UP.set_value(0)
238 settings.REP_TO_VOTE_DOWN.set_value(15)
239 settings.REP_TO_FLAG.set_value(15)
240 settings.REP_TO_COMMENT.set_value(0)
241 settings.REP_TO_LIKE_COMMENT.set_value(0)
242 settings.REP_TO_UPLOAD.set_value(0)
243 settings.REP_TO_CREATE_TAGS.set_value(0)
244 settings.REP_TO_CLOSE_OWN.set_value(60)
245 settings.REP_TO_REOPEN_OWN.set_value(120)
246 settings.REP_TO_RETAG.set_value(150)
247 settings.REP_TO_EDIT_WIKI.set_value(200)
248 settings.REP_TO_EDIT_OTHERS.set_value(400)
249 settings.REP_TO_CLOSE_OTHERS.set_value(600)
250 settings.REP_TO_DELETE_COMMENTS.set_value(400)
251 settings.REP_TO_VIEW_FLAGS.set_value(30)
253 settings.INITIAL_REP.set_value(1)
254 settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
255 settings.REP_GAIN_BY_UPVOTED.set_value(15)
256 settings.REP_LOST_BY_DOWNVOTED.set_value(1)
257 settings.REP_LOST_BY_DOWNVOTING.set_value(0)
258 settings.REP_GAIN_BY_ACCEPTED.set_value(25)
259 settings.REP_GAIN_BY_ACCEPTING.set_value(5)
260 settings.REP_LOST_BY_FLAGGED.set_value(2)
261 settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
262 settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
264 settings.SETTINGS_PACK.set_value("bootstrap")
266 request.user.message_set.create(message=_('Bootstrap mode enabled'))
267 return HttpResponseRedirect(reverse('admin_index'))
270 def go_defaults(request):
271 for setting in Setting.sets['badges']:
273 for setting in Setting.sets['minrep']:
275 for setting in Setting.sets['repgain']:
278 settings.SETTINGS_PACK.set_value("default")
280 request.user.message_set.create(message=_('All values reverted to defaults'))
281 return HttpResponseRedirect(reverse('admin_index'))
285 def recalculate_denormalized(request):
286 for n in Node.objects.all():
288 n.score = n.votes.aggregate(score=models.Sum('value'))['score']
289 if not n.score: n.score = 0
292 for u in User.objects.all():
293 u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
296 request.user.message_set.create(message=_('All values recalculated'))
297 return HttpResponseRedirect(reverse('admin_index'))
300 def maintenance(request):
302 if 'close' in request.POST or 'adjust' in request.POST:
303 form = MaintenanceModeForm(request.POST)
306 settings.MAINTAINANCE_MODE.set_value({
307 'allow_ips': form.cleaned_data['ips'],
308 'message': form.cleaned_data['message']})
310 if 'close' in request.POST:
311 message = _('Maintenance mode enabled')
313 message = _('Settings adjusted')
315 request.user.message_set.create(message=message)
317 return HttpResponseRedirect(reverse('admin_maintenance'))
318 elif 'open' in request.POST:
319 settings.MAINTAINANCE_MODE.set_value(None)
320 request.user.message_set.create(message=_("Your site is now running normally"))
321 return HttpResponseRedirect(reverse('admin_maintenance'))
323 form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
324 'message': _('Currently down for maintenance. We\'ll be back soon')})
326 return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
331 def flagged_posts(request):
332 return ('osqaadmin/flagged_posts.html', {
333 'flagged_posts': get_flagged_posts(),
337 def static_pages(request):
338 pages = Page.objects.all()
340 return ('osqaadmin/static_pages.html', {
345 def edit_page(request, id=None):
347 page = get_object_or_404(Page, id=id)
352 form = PageForm(page, request.POST)
355 if form.has_changed():
357 page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
360 EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
361 data=form.cleaned_data)
363 if ('publish' in request.POST) and (not page.published):
364 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
365 elif ('unpublish' in request.POST) and page.published:
366 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
368 return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
371 form = PageForm(page)
374 published = page.published
378 return ('osqaadmin/edit_page.html', {
381 'published': published
384 @admin_tools_page(_('createuser'), _("Create new user"))
385 def create_user(request):
387 form = CreateUserForm(request.POST)
390 user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
391 user_.set_password(form.cleaned_data['password1'])
393 if not form.cleaned_data.get('validate_email', False):
394 user_.email_isvalid = True
397 UserJoinsAction(user=user_).save()
399 request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
400 user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
402 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
404 form = CreateUserForm()
406 return ('osqaadmin/createuser.html', {
410 class NodeManagementPaginatorContext(pagination.PaginatorContext):
411 def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
412 super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
413 (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
414 (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
415 (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
416 (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
417 (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
418 (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
419 (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
420 (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
421 (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
422 (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
423 ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
425 @admin_tools_page(_("nodeman"), _("Bulk management"))
426 def node_management(request):
428 params = pagination.generate_uri(request.GET, ('page',))
430 if "save_filter" in request.POST:
431 filter_name = request.POST.get('filter_name', _('filter'))
432 params = pagination.generate_uri(request.GET, ('page',))
433 current_filters = settings.NODE_MAN_FILTERS.value
434 current_filters.append((filter_name, params))
435 settings.NODE_MAN_FILTERS.set_value(current_filters)
437 elif r"execute" in request.POST:
438 selected_nodes = request.POST.getlist('_selected_node')
440 if selected_nodes and request.POST.get('action', None):
441 action = request.POST['action']
442 selected_nodes = Node.objects.filter(id__in=selected_nodes)
444 message = _("No action performed")
446 if action == 'delete_selected':
447 for node in selected_nodes:
448 if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
449 DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
451 message = _("All selected nodes marked as deleted")
453 if action == 'undelete_selected':
454 for node in selected_nodes:
455 if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
456 node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
458 message = _("All selected nodes undeleted")
460 if action == "close_selected":
461 for node in selected_nodes:
462 if node.node_type == "question" and (not node.nis.closed):
463 CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
465 message = _("Selected questions were closed")
467 if action == "hard_delete_selected":
468 ids = [n.id for n in selected_nodes]
472 node = Node.objects.get(id=id)
477 message = _("All selected nodes deleted")
479 request.user.message_set.create(message=message)
481 params = pagination.generate_uri(request.GET, ('page',))
483 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
486 nodes = Node.objects.all()
488 text = request.GET.get('text', '')
489 text_in = request.GET.get('text_in', 'body')
491 authors = request.GET.getlist('authors')
492 tags = request.GET.getlist('tags')
494 type_filter = request.GET.getlist('node_type')
495 state_filter = request.GET.getlist('state_type')
496 state_filter_type = request.GET.get('state_filter_type', 'any')
499 nodes = nodes.filter(node_type__in=type_filter)
501 state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
502 state_filter = [s for s in state_filter if s in state_types]
505 if state_filter_type == 'all':
506 nodes = nodes.all_states(*state_filter)
508 nodes = nodes.any_state(*state_filter)
511 nodes = nodes.filter(author__id__in=authors)
512 authors = User.objects.filter(id__in=authors)
515 nodes = nodes.filter(tags__id__in=tags)
516 tags = Tag.objects.filter(id__in=tags)
519 text_in = request.GET.get('text_in', 'body')
522 if text_in == 'title' or text_in == 'both':
523 filter = models.Q(title__icontains=text)
525 if text_in == 'body' or text_in == 'both':
526 sec_filter = models.Q(body__icontains=text)
528 filter = filter | sec_filter
533 nodes = nodes.filter(filter)
535 node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
537 return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
541 'type_filter': type_filter,
542 'state_filter': state_filter,
543 'state_filter_type': state_filter_type,
544 'node_types': node_types,
545 'state_types': state_types,