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, NodeManFilterForm, 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 (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
416 (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
417 (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
418 (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
419 ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
421 @admin_tools_page(_("nodeman"), _("Bulk management"))
422 def node_management(request):
423 if request.POST and "save_filter" in request.POST:
424 filter_name = request.POST.get('filter_name', _('filter'))
425 params = pagination.generate_uri(request.GET, ('page',))
426 current_filters = settings.NODE_MAN_FILTERS.value
427 current_filters.append((filter_name, params))
428 settings.NODE_MAN_FILTERS.set_value(current_filters)
430 if request.POST and "execute" in request.POST:
431 selected_nodes = request.POST.getlist('_selected_node')
433 if selected_nodes and request.POST.get('action', None):
434 action = request.POST['action']
435 selected_nodes = Node.objects.filter(id__in=selected_nodes)
437 message = _("No action performed")
439 if action == 'delete_selected':
440 for node in selected_nodes:
441 if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
442 DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
444 message = _("All selected nodes marked as deleted")
446 if action == "close_selected":
447 for node in selected_nodes:
448 if node.node_type == "question" and (not node.nis.closed):
449 CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
451 message = _("Selected questions were closed")
453 if action == "hard_delete_selected":
454 ids = [n.id for n in selected_nodes]
458 node = Node.objects.get(id=id)
463 message = _("All selected nodes deleted")
465 request.user.message_set.create(message=message)
467 params = pagination.generate_uri(request.GET, ('page',))
468 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
471 nodes = Node.objects.all()
474 filter_form = NodeManFilterForm(request.GET)
476 filter_form = NodeManFilterForm({'node_type': 'all', 'state_type': 'any'})
478 authors = request.GET.getlist('authors')
479 tags = request.GET.getlist('tags')
481 if filter_form.is_valid():
482 data = filter_form.cleaned_data
484 if data['node_type'] != 'all':
485 nodes = nodes.filter(node_type=data['node_type'])
487 if (data['state_type'] != 'any'):
488 nodes = nodes.filter_state(**{str(data['state_type']): True})
491 nodes = nodes.filter(author__id__in=authors)
492 authors = User.objects.filter(id__in=authors)
495 nodes = nodes.filter(tags__id__in=tags)
496 tags = Tag.objects.filter(id__in=tags)
501 if data['text_in'] == 'title' or data['text_in'] == 'both':
502 filter = models.Q(title__icontains=data['text'])
504 if data['text_in'] == 'body' or data['text_in'] == 'both':
505 sec_filter = models.Q(body__icontains=data['text'])
507 filter = filter | sec_filter
512 nodes = nodes.filter(filter)
514 node_types = [('all', _("all"))] + [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
515 state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
517 return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
519 'node_types': node_types,
520 'state_types': state_types,
521 'filter_form': filter_form,