1 from datetime import datetime, timedelta
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
14 from forum.settings.forms import SettingsSetForm
16 from forum.models import Question, Answer, User, Node, Action, Page
17 from forum.actions import NewPageAction, EditPageAction, PublishAction
18 from forum import settings
20 def super_user_required(fn):
21 def wrapper(request, *args, **kwargs):
22 if request.user.is_authenticated() and request.user.is_superuser:
23 return fn(request, *args, **kwargs)
25 return HttpResponseUnauthorized(request)
31 def wrapper(request, *args, **kwargs):
32 res = fn(request, *args, **kwargs)
33 if isinstance(res, tuple):
34 template, context = res
35 context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
36 context['allsets'] = Setting.sets
37 context['othersets'] = sorted(
38 [s for s in Setting.sets.values() if not s.name in
39 ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'badges', 'about', 'faq', 'sidebar',
40 'form', 'moderation', 'css', 'headandfoot', 'head')]
41 , lambda s1, s2: s1.weight - s2.weight)
43 unsaved = request.session.get('previewing_settings', {})
44 context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
46 return render_to_response(template, context, context_instance=RequestContext(request))
53 def dashboard(request):
54 return ('osqaadmin/dashboard.html', {
55 'settings_pack': unicode(settings.SETTINGS_PACK),
56 'statistics': get_statistics(),
57 'recent_activity': get_recent_activity(),
58 'flagged_posts': get_flagged_posts(),
62 def interface_switch(request):
63 if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
64 settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
66 return HttpResponseRedirect(reverse('admin_index'))
69 def statistics(request):
70 today = datetime.now()
71 last_month = today - timedelta(days=30)
73 last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
74 ).order_by('added_at').values_list(
75 'added_at', flat=True)
77 last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
78 qgraph_data = simplejson.dumps([
79 (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
80 for i, d in enumerate(last_month_questions)
83 last_month_users = User.objects.filter(date_joined__gt=last_month
84 ).order_by('date_joined').values_list('date_joined', flat=True)
86 last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
88 ugraph_data = simplejson.dumps([
89 (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
90 for i, d in enumerate(last_month_users)
93 return 'osqaadmin/statistics.html', {
96 'id': 'questions_graph',
97 'caption': _("Questions Graph"),
100 'id': 'userss_graph',
101 'caption': _("Users Graph"),
109 def settings_set(request, set_name):
110 set = Setting.sets.get(set_name, {})
111 current_preview = request.session.get('previewing_settings', {})
117 form = SettingsSetForm(set, data=request.POST, files=request.FILES)
120 if 'preview' in request.POST:
121 current_preview.update(form.cleaned_data)
122 request.session['previewing_settings'] = current_preview
124 return HttpResponseRedirect(reverse('index'))
127 current_preview.pop(s.name, None)
129 request.session['previewing_settings'] = current_preview
131 if not 'reset' in request.POST:
133 request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
135 if set_name in ('minrep', 'badges', 'repgain'):
136 settings.SETTINGS_PACK.set_value("custom")
138 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
140 form = SettingsSetForm(set, unsaved=current_preview)
142 return 'osqaadmin/set.html', {
144 'markdown': set.markdown,
148 def get_default(request, set_name, var_name):
149 set = Setting.sets.get(set_name, None)
150 if set is None: raise Http404
152 setting = dict([(s.name, s) for s in set]).get(var_name, None)
153 if setting is None: raise Http404
157 if request.is_ajax():
158 return HttpResponse(setting.default)
160 return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
163 def get_recent_activity():
164 return Action.objects.order_by('-action_date')[0:30]
166 def get_flagged_posts():
167 return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
169 def get_statistics():
171 'total_users': User.objects.all().count(),
172 'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
173 'total_questions': Question.objects.filter_state(deleted=False).count(),
174 'questions_last_24': Question.objects.filter_state(deleted=False).filter(
175 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
176 'total_answers': Answer.objects.filter_state(deleted=False).count(),
177 'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
178 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
182 def go_bootstrap(request):
183 #todo: this is the quick and dirty way of implementing a bootstrap mode
185 from forum_modules.default_badges import settings as dbsets
186 dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
187 dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
188 dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
189 dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
190 dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
191 dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
192 dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
193 dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
194 dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
195 dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
196 dbsets.STELLAR_QUESTION_FAVS.set_value(3)
197 dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
198 dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
199 dbsets.CIVIC_DUTY_VOTES.set_value(15)
200 dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
201 dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
202 dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
203 dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
204 dbsets.GURU_UP_VOTES.set_value(4)
205 dbsets.NECROMANCER_UP_VOTES.set_value(2)
206 dbsets.NECROMANCER_DIF_DAYS.set_value(30)
207 dbsets.TAXONOMIST_USE_COUNT.set_value(5)
211 settings.REP_TO_VOTE_UP.set_value(0)
212 settings.REP_TO_VOTE_DOWN.set_value(15)
213 settings.REP_TO_FLAG.set_value(15)
214 settings.REP_TO_COMMENT.set_value(0)
215 settings.REP_TO_LIKE_COMMENT.set_value(0)
216 settings.REP_TO_UPLOAD.set_value(0)
217 settings.REP_TO_CREATE_TAGS.set_value(0)
218 settings.REP_TO_CLOSE_OWN.set_value(60)
219 settings.REP_TO_REOPEN_OWN.set_value(120)
220 settings.REP_TO_RETAG.set_value(150)
221 settings.REP_TO_EDIT_WIKI.set_value(200)
222 settings.REP_TO_EDIT_OTHERS.set_value(400)
223 settings.REP_TO_CLOSE_OTHERS.set_value(600)
224 settings.REP_TO_DELETE_COMMENTS.set_value(400)
225 settings.REP_TO_VIEW_FLAGS.set_value(30)
227 settings.INITIAL_REP.set_value(1)
228 settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
229 settings.REP_GAIN_BY_UPVOTED.set_value(15)
230 settings.REP_LOST_BY_DOWNVOTED.set_value(1)
231 settings.REP_LOST_BY_DOWNVOTING.set_value(0)
232 settings.REP_GAIN_BY_ACCEPTED.set_value(25)
233 settings.REP_GAIN_BY_ACCEPTING.set_value(5)
234 settings.REP_LOST_BY_FLAGGED.set_value(2)
235 settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
236 settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
238 settings.SETTINGS_PACK.set_value("bootstrap")
240 request.user.message_set.create(message=_('Bootstrap mode enabled'))
241 return HttpResponseRedirect(reverse('admin_index'))
244 def go_defaults(request):
245 for setting in Setting.sets['badges']:
247 for setting in Setting.sets['minrep']:
249 for setting in Setting.sets['repgain']:
252 settings.SETTINGS_PACK.set_value("default")
254 request.user.message_set.create(message=_('All values reverted to defaults'))
255 return HttpResponseRedirect(reverse('admin_index'))
259 def recalculate_denormalized(request):
260 for n in Node.objects.all():
262 n.score = n.votes.aggregate(score=models.Sum('value'))['score']
263 if not n.score: n.score = 0
266 for u in User.objects.all():
267 u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
270 request.user.message_set.create(message=_('All values recalculated'))
271 return HttpResponseRedirect(reverse('admin_index'))
274 def maintenance(request):
276 if 'close' in request.POST or 'adjust' in request.POST:
277 form = MaintenanceModeForm(request.POST)
280 settings.MAINTAINANCE_MODE.set_value({
281 'allow_ips': form.cleaned_data['ips'],
282 'message': form.cleaned_data['message']})
284 if 'close' in request.POST:
285 message = _('Maintenance mode enabled')
287 message = _('Settings adjusted')
289 request.user.message_set.create(message=message)
291 return HttpResponseRedirect(reverse('admin_maintenance'))
292 elif 'open' in request.POST:
293 settings.MAINTAINANCE_MODE.set_value(None)
294 request.user.message_set.create(message=_("Your site is now running normally"))
295 return HttpResponseRedirect(reverse('admin_maintenance'))
297 form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
298 'message': _('Currently down for maintenance. We\'ll be back soon')})
300 return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
305 def flagged_posts(request):
306 return ('osqaadmin/flagged_posts.html', {
307 'flagged_posts': get_flagged_posts(),
311 def static_pages(request):
312 pages = Page.objects.all()
314 return ('osqaadmin/static_pages.html', {
319 def edit_page(request, id=None):
321 page = get_object_or_404(Page, id=id)
326 form = PageForm(page, request.POST)
329 if form.has_changed():
331 page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
334 EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
335 data=form.cleaned_data)
337 if ('publish' in request.POST) and (not page.published):
338 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
339 elif ('unpublish' in request.POST) and page.published:
340 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
342 return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
345 form = PageForm(page)
348 published = page.published
352 return ('osqaadmin/edit_page.html', {
355 'published': published
359 def moderation(request):
361 if not 'ids' in request.POST:
365 'high-rep': '-reputation',
366 'newer': '-date_joined',
367 'older': 'date_joined',
368 }.get(request.POST.get('sort'), None)
372 limit = int(request.POST['limit'])
376 verify = User.objects.order_by(sort)[:limit]
381 possible_cheaters = []
382 verify = User.objects.order_by(sort)[:5]
384 cheat_score_sort = lambda c1, c2: cmp(c2.fdata['fake_score'], c1.fdata['fake_score'])
388 affecters = User.objects.filter(actions__node__author=user, actions__canceled=False).annotate(
389 affect_count=models.Count('actions')).order_by('-affect_count')
390 user_ips = set(Action.objects.filter(user=user).values_list('ip', flat=True).distinct('ip'))
392 for affecter in affecters:
396 data = {'affect_count': affecter.affect_count}
398 total_actions = affecter.actions.filter(canceled=False).exclude(node=None).count()
399 ratio = (float(affecter.affect_count) / float(total_actions)) * 100
401 if total_actions > 10 and ratio > 50:
402 data['total_actions'] = total_actions
403 data['action_ratio'] = ratio
406 Action.objects.filter(user=affecter).values_list('ip', flat=True).distinct('ip'))
407 cross_ips = len(user_ips & affecter_ips)
409 data['cross_ip_count'] = cross_ips
410 data['total_ip_count'] = len(affecter_ips)
411 data['cross_ip_ratio'] = (float(data['cross_ip_count']) / float(data['total_ip_count'])) * 100
413 if affecter.email_isvalid:
418 data['fake_score'] = ((data['cross_ip_ratio'] + data['action_ratio'] + email_score) / 100) * 4
420 affecter.fdata = data
421 possible_fakes.append(affecter)
423 if len(possible_fakes) > 0:
424 possible_fakes = sorted(possible_fakes, cheat_score_sort)
425 possible_cheaters.append((user, possible_fakes))
427 return ('osqaadmin/moderation.html', {'cheaters': possible_cheaters})
429 return ('osqaadmin/moderation.html', {})