]> git.openstreetmap.org Git - osqa.git/blob - forum/views/admin.py
Adds a new function in the profile menu for admins to suspend users, indefinetly...
[osqa.git] / forum / views / admin.py
1 from datetime import datetime, timedelta
2 import time
3
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, HttpResponseForbidden, Http404
7 from django.template import RequestContext
8 from django.utils.translation import ugettext as _
9 from django.utils import simplejson
10 from django.db import models
11 from forum.settings.base import Setting
12 from forum.forms import MaintenanceModeForm, PageForm
13 from forum.settings.forms import SettingsSetForm
14
15 from forum.models import Question, Answer, User, Node, Action, Page
16 from forum.actions import NewPageAction, EditPageAction, PublishAction
17 from forum import settings
18
19 def super_user_required(fn):
20     def wrapper(request, *args, **kwargs):
21         if request.user.is_authenticated() and request.user.is_superuser:
22             return fn(request, *args, **kwargs)
23         else:
24             return HttpResponseForbidden()
25
26     return wrapper
27
28 def admin_page(fn):
29     @super_user_required
30     def wrapper(request, *args, **kwargs):
31         res = fn(request, *args, **kwargs)
32         if isinstance(res, tuple):
33             template, context = res
34             context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
35             context['allsets'] = Setting.sets
36             context['othersets'] = sorted(
37                     [s for s in Setting.sets.values() if not s.name in
38                     ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'badges', 'about', 'faq', 'sidebar',
39                     'form', 'moderation', 'css', 'headandfoot', 'head')]
40                     , lambda s1, s2: s1.weight - s2.weight)
41
42             unsaved = request.session.get('previewing_settings', {})
43             context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
44
45             return render_to_response(template, context, context_instance=RequestContext(request))
46         else:
47             return res
48
49     return wrapper
50
51 @admin_page
52 def dashboard(request):
53     return ('osqaadmin/dashboard.html', {
54     'settings_pack': unicode(settings.SETTINGS_PACK),
55     'statistics': get_statistics(),
56     'recent_activity': get_recent_activity(),
57     'flagged_posts': get_flagged_posts(),
58     })
59
60 @super_user_required
61 def interface_switch(request):
62     if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
63         settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
64
65     return HttpResponseRedirect(reverse('admin_index'))
66
67 @admin_page
68 def statistics(request):
69     today = datetime.now()
70     last_month = today - timedelta(days=30)
71
72     last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
73                                                                                ).order_by('added_at').values_list(
74             'added_at', flat=True)
75
76     last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
77     qgraph_data = simplejson.dumps([
78     (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
79     for i, d in enumerate(last_month_questions)
80     ])
81
82     last_month_users = User.objects.filter(date_joined__gt=last_month
83                                            ).order_by('date_joined').values_list('date_joined', flat=True)
84
85     last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
86
87     ugraph_data = simplejson.dumps([
88     (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
89     for i, d in enumerate(last_month_users)
90     ])
91
92     return 'osqaadmin/statistics.html', {
93     'graphs': [
94             {
95             'id': 'questions_graph',
96             'caption': _("Questions Graph"),
97             'data': qgraph_data
98             }, {
99             'id': 'userss_graph',
100             'caption': _("Users Graph"),
101             'data': ugraph_data
102             }
103             ]
104     }
105
106
107 @admin_page
108 def settings_set(request, set_name):
109     set = Setting.sets.get(set_name, {})
110     current_preview = request.session.get('previewing_settings', {})
111
112     if set is None:
113         raise Http404
114
115     if request.POST:
116         form = SettingsSetForm(set, data=request.POST, files=request.FILES)
117
118         if form.is_valid():
119             if 'preview' in request.POST:
120                 current_preview.update(form.cleaned_data)
121                 request.session['previewing_settings'] = current_preview
122
123                 return HttpResponseRedirect(reverse('index'))
124             else:
125                 for s in set:
126                     current_preview.pop(s.name, None)
127
128                 request.session['previewing_settings'] = current_preview
129
130                 if not 'reset' in request.POST:
131                     form.save()
132                     request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
133
134                     if set_name in ('minrep', 'badges', 'repgain'):
135                         settings.SETTINGS_PACK.set_value("custom")
136
137                 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
138     else:
139         form = SettingsSetForm(set, unsaved=current_preview)
140
141     return 'osqaadmin/set.html', {
142     'form': form,
143     'markdown': set.markdown,
144     }
145
146 @super_user_required
147 def get_default(request, set_name, var_name):
148     set = Setting.sets.get(set_name, None)
149     if set is None: raise Http404
150
151     setting = dict([(s.name, s) for s in set]).get(var_name, None)
152     if setting is None: raise Http404
153
154     setting.to_default()
155
156     if request.is_ajax():
157         return HttpResponse(setting.default)
158     else:
159         return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
160
161
162 def get_recent_activity():
163     return Action.objects.order_by('-action_date')[0:30]
164
165 def get_flagged_posts():
166     return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
167
168 def get_statistics():
169     return {
170     'total_users': User.objects.all().count(),
171     'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
172     'total_questions': Question.objects.filter_state(deleted=False).count(),
173     'questions_last_24': Question.objects.filter_state(deleted=False).filter(
174             added_at__gt=(datetime.now() - timedelta(days=1))).count(),
175     'total_answers': Answer.objects.filter_state(deleted=False).count(),
176     'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
177             added_at__gt=(datetime.now() - timedelta(days=1))).count(),
178     }
179
180 @super_user_required
181 def go_bootstrap(request):
182 #todo: this is the quick and dirty way of implementing a bootstrap mode
183     try:
184         from forum_modules.default_badges import settings as dbsets
185         dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
186         dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
187         dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
188         dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
189         dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
190         dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
191         dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
192         dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
193         dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
194         dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
195         dbsets.STELLAR_QUESTION_FAVS.set_value(3)
196         dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
197         dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
198         dbsets.CIVIC_DUTY_VOTES.set_value(15)
199         dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
200         dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
201         dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
202         dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
203         dbsets.GURU_UP_VOTES.set_value(4)
204         dbsets.NECROMANCER_UP_VOTES.set_value(2)
205         dbsets.NECROMANCER_DIF_DAYS.set_value(30)
206         dbsets.TAXONOMIST_USE_COUNT.set_value(5)
207     except:
208         pass
209
210     settings.REP_TO_VOTE_UP.set_value(0)
211     settings.REP_TO_VOTE_DOWN.set_value(15)
212     settings.REP_TO_FLAG.set_value(15)
213     settings.REP_TO_COMMENT.set_value(0)
214     settings.REP_TO_LIKE_COMMENT.set_value(0)
215     settings.REP_TO_UPLOAD.set_value(0)
216     settings.REP_TO_CREATE_TAGS.set_value(0)
217     settings.REP_TO_CLOSE_OWN.set_value(60)
218     settings.REP_TO_REOPEN_OWN.set_value(120)
219     settings.REP_TO_RETAG.set_value(150)
220     settings.REP_TO_EDIT_WIKI.set_value(200)
221     settings.REP_TO_EDIT_OTHERS.set_value(400)
222     settings.REP_TO_CLOSE_OTHERS.set_value(600)
223     settings.REP_TO_DELETE_COMMENTS.set_value(400)
224     settings.REP_TO_VIEW_FLAGS.set_value(30)
225
226     settings.INITIAL_REP.set_value(1)
227     settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
228     settings.REP_GAIN_BY_UPVOTED.set_value(15)
229     settings.REP_LOST_BY_DOWNVOTED.set_value(1)
230     settings.REP_LOST_BY_DOWNVOTING.set_value(0)
231     settings.REP_GAIN_BY_ACCEPTED.set_value(25)
232     settings.REP_GAIN_BY_ACCEPTING.set_value(5)
233     settings.REP_LOST_BY_FLAGGED.set_value(2)
234     settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
235     settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
236
237     settings.SETTINGS_PACK.set_value("bootstrap")
238
239     request.user.message_set.create(message=_('Bootstrap mode enabled'))
240     return HttpResponseRedirect(reverse('admin_index'))
241
242 @super_user_required
243 def go_defaults(request):
244     for setting in Setting.sets['badges']:
245         setting.to_default()
246     for setting in Setting.sets['minrep']:
247         setting.to_default()
248     for setting in Setting.sets['repgain']:
249         setting.to_default()
250
251     settings.SETTINGS_PACK.set_value("default")
252
253     request.user.message_set.create(message=_('All values reverted to defaults'))
254     return HttpResponseRedirect(reverse('admin_index'))
255
256
257 @super_user_required
258 def recalculate_denormalized(request):
259     for n in Node.objects.all():
260         n = n.leaf
261         n.score = n.votes.aggregate(score=models.Sum('value'))['score']
262         if not n.score: n.score = 0
263         n.save()
264
265     for u in User.objects.all():
266         u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
267         u.save()
268
269     request.user.message_set.create(message=_('All values recalculated'))
270     return HttpResponseRedirect(reverse('admin_index'))
271
272 @admin_page
273 def maintenance(request):
274     if request.POST:
275         if 'close' in request.POST or 'adjust' in request.POST:
276             form = MaintenanceModeForm(request.POST)
277
278             if form.is_valid():
279                 settings.MAINTAINANCE_MODE.set_value({
280                 'allow_ips': form.cleaned_data['ips'],
281                 'message': form.cleaned_data['message']})
282
283                 if 'close' in request.POST:
284                     message = _('Maintenance mode enabled')
285                 else:
286                     message = _('Settings adjusted')
287
288                 request.user.message_set.create(message=message)
289
290                 return HttpResponseRedirect(reverse('admin_maintenance'))
291         elif 'open' in request.POST:
292             settings.MAINTAINANCE_MODE.set_value(None)
293             request.user.message_set.create(message=_("Your site is now running normally"))
294             return HttpResponseRedirect(reverse('admin_maintenance'))
295     else:
296         form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
297                                             'message': _('Currently down for maintenance. We\'ll be back soon')})
298
299     return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
300                                            })
301
302
303 @admin_page
304 def flagged_posts(request):
305     return ('osqaadmin/flagged_posts.html', {
306     'flagged_posts': get_flagged_posts(),
307     })
308
309 @admin_page
310 def static_pages(request):
311     pages = Page.objects.all()
312
313     return ('osqaadmin/static_pages.html', {
314     'pages': pages,
315     })
316
317 @admin_page
318 def edit_page(request, id=None):
319     if id:
320         page = get_object_or_404(Page, id=id)
321     else:
322         page = None
323
324     if request.POST:
325         form = PageForm(page, request.POST)
326
327         if form.is_valid():
328             if form.has_changed():
329                 if not page:
330                     page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
331                                                                                                  ).node
332                 else:
333                     EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
334                             data=form.cleaned_data)
335
336             if ('publish' in request.POST) and (not page.published):
337                 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
338             elif ('unpublish' in request.POST) and page.published:
339                 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
340
341             return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
342
343     else:
344         form = PageForm(page)
345
346     if page:
347         published = page.published
348     else:
349         published = False
350
351     return ('osqaadmin/edit_page.html', {
352     'page': page,
353     'form': form,
354     'published': published
355     })
356
357 @admin_page
358 def moderation(request):
359     if request.POST:
360         if not 'ids' in request.POST:
361             verify = None
362         else:
363             sort = {
364             'high-rep': '-reputation',
365             'newer': '-date_joined',
366             'older': 'date_joined',
367             }.get(request.POST.get('sort'), None)
368
369             if sort:
370                 try:
371                     limit = int(request.POST['limit'])
372                 except:
373                     limit = 5
374
375                 verify = User.objects.order_by(sort)[:limit]
376             else:
377                 verify = None
378
379         if verify:
380             possible_cheaters = []
381             verify = User.objects.order_by(sort)[:5]
382
383             cheat_score_sort = lambda c1, c2: cmp(c2.fdata['fake_score'], c1.fdata['fake_score'])
384
385             for user in verify:
386                 possible_fakes = []
387                 affecters = User.objects.filter(actions__node__author=user, actions__canceled=False).annotate(
388                         affect_count=models.Count('actions')).order_by('-affect_count')
389                 user_ips = set(Action.objects.filter(user=user).values_list('ip', flat=True).distinct('ip'))
390
391                 for affecter in affecters:
392                     if affecter == user:
393                         continue
394
395                     data = {'affect_count': affecter.affect_count}
396
397                     total_actions = affecter.actions.filter(canceled=False).exclude(node=None).count()
398                     ratio = (float(affecter.affect_count) / float(total_actions)) * 100
399
400                     if total_actions > 10 and ratio > 50:
401                         data['total_actions'] = total_actions
402                         data['action_ratio'] = ratio
403
404                         affecter_ips = set(
405                                 Action.objects.filter(user=affecter).values_list('ip', flat=True).distinct('ip'))
406                         cross_ips = len(user_ips & affecter_ips)
407
408                         data['cross_ip_count'] = cross_ips
409                         data['total_ip_count'] = len(affecter_ips)
410                         data['cross_ip_ratio'] = (float(data['cross_ip_count']) / float(data['total_ip_count'])) * 100
411
412                         if affecter.email_isvalid:
413                             email_score = 0
414                         else:
415                             email_score = 50.0
416
417                         data['fake_score'] = ((data['cross_ip_ratio'] + data['action_ratio'] + email_score) / 100) * 4
418
419                         affecter.fdata = data
420                         possible_fakes.append(affecter)
421
422                 if len(possible_fakes) > 0:
423                     possible_fakes = sorted(possible_fakes, cheat_score_sort)
424                     possible_cheaters.append((user, possible_fakes))
425
426             return ('osqaadmin/moderation.html', {'cheaters': possible_cheaters})
427
428     return ('osqaadmin/moderation.html', {})
429
430
431