]> git.openstreetmap.org Git - osqa.git/blob - forum/views/admin.py
Adds a head content injection spot for modules, and a new ui object for template...
[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, 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
15
16 from forum.models import Question, Answer, User, Node, Action, Page
17 from forum.actions import NewPageAction, EditPageAction, PublishAction
18 from forum import settings
19
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)
24         else:
25             return HttpResponseUnauthorized(request)
26
27     return wrapper
28
29 def admin_page(fn):
30     @super_user_required
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', 'view')]
41                     , lambda s1, s2: s1.weight - s2.weight)
42
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)])
45
46             return render_to_response(template, context, context_instance=RequestContext(request))
47         else:
48             return res
49
50     return wrapper
51
52 @admin_page
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(),
59     })
60
61 @super_user_required
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')
65
66     return HttpResponseRedirect(reverse('admin_index'))
67
68 @admin_page
69 def statistics(request):
70     today = datetime.now()
71     last_month = today - timedelta(days=30)
72
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)
76
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)
81     ])
82
83     last_month_users = User.objects.filter(date_joined__gt=last_month
84                                            ).order_by('date_joined').values_list('date_joined', flat=True)
85
86     last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
87
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)
91     ])
92
93     return 'osqaadmin/statistics.html', {
94     'graphs': [
95             {
96             'id': 'questions_graph',
97             'caption': _("Questions Graph"),
98             'data': qgraph_data
99             }, {
100             'id': 'userss_graph',
101             'caption': _("Users Graph"),
102             'data': ugraph_data
103             }
104             ]
105     }
106
107
108 @admin_page
109 def settings_set(request, set_name):
110     set = Setting.sets.get(set_name, {})
111     current_preview = request.session.get('previewing_settings', {})
112
113     if set is None:
114         raise Http404
115
116     if request.POST:
117         form = SettingsSetForm(set, data=request.POST, files=request.FILES)
118
119         if form.is_valid():
120             if 'preview' in request.POST:
121                 current_preview.update(form.cleaned_data)
122                 request.session['previewing_settings'] = current_preview
123
124                 return HttpResponseRedirect(reverse('index'))
125             else:
126                 for s in set:
127                     current_preview.pop(s.name, None)
128
129                 request.session['previewing_settings'] = current_preview
130
131                 if not 'reset' in request.POST:
132                     form.save()
133                     request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
134
135                     if set_name in ('minrep', 'badges', 'repgain'):
136                         settings.SETTINGS_PACK.set_value("custom")
137
138                 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
139     else:
140         form = SettingsSetForm(set, unsaved=current_preview)
141
142     return 'osqaadmin/set.html', {
143     'form': form,
144     'markdown': set.markdown,
145     }
146
147 @super_user_required
148 def get_default(request, set_name, var_name):
149     set = Setting.sets.get(set_name, None)
150     if set is None: raise Http404
151
152     setting = dict([(s.name, s) for s in set]).get(var_name, None)
153     if setting is None: raise Http404
154
155     setting.to_default()
156
157     if request.is_ajax():
158         return HttpResponse(setting.default)
159     else:
160         return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
161
162
163 def get_recent_activity():
164     return Action.objects.order_by('-action_date')[0:30]
165
166 def get_flagged_posts():
167     return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
168
169 def get_statistics():
170     return {
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(),
179     }
180
181 @super_user_required
182 def go_bootstrap(request):
183 #todo: this is the quick and dirty way of implementing a bootstrap mode
184     try:
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)
208     except:
209         pass
210
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)
226
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)
237
238     settings.SETTINGS_PACK.set_value("bootstrap")
239
240     request.user.message_set.create(message=_('Bootstrap mode enabled'))
241     return HttpResponseRedirect(reverse('admin_index'))
242
243 @super_user_required
244 def go_defaults(request):
245     for setting in Setting.sets['badges']:
246         setting.to_default()
247     for setting in Setting.sets['minrep']:
248         setting.to_default()
249     for setting in Setting.sets['repgain']:
250         setting.to_default()
251
252     settings.SETTINGS_PACK.set_value("default")
253
254     request.user.message_set.create(message=_('All values reverted to defaults'))
255     return HttpResponseRedirect(reverse('admin_index'))
256
257
258 @super_user_required
259 def recalculate_denormalized(request):
260     for n in Node.objects.all():
261         n = n.leaf
262         n.score = n.votes.aggregate(score=models.Sum('value'))['score']
263         if not n.score: n.score = 0
264         n.save()
265
266     for u in User.objects.all():
267         u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
268         u.save()
269
270     request.user.message_set.create(message=_('All values recalculated'))
271     return HttpResponseRedirect(reverse('admin_index'))
272
273 @admin_page
274 def maintenance(request):
275     if request.POST:
276         if 'close' in request.POST or 'adjust' in request.POST:
277             form = MaintenanceModeForm(request.POST)
278
279             if form.is_valid():
280                 settings.MAINTAINANCE_MODE.set_value({
281                 'allow_ips': form.cleaned_data['ips'],
282                 'message': form.cleaned_data['message']})
283
284                 if 'close' in request.POST:
285                     message = _('Maintenance mode enabled')
286                 else:
287                     message = _('Settings adjusted')
288
289                 request.user.message_set.create(message=message)
290
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'))
296     else:
297         form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
298                                             'message': _('Currently down for maintenance. We\'ll be back soon')})
299
300     return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
301                                            })
302
303
304 @admin_page
305 def flagged_posts(request):
306     return ('osqaadmin/flagged_posts.html', {
307     'flagged_posts': get_flagged_posts(),
308     })
309
310 @admin_page
311 def static_pages(request):
312     pages = Page.objects.all()
313
314     return ('osqaadmin/static_pages.html', {
315     'pages': pages,
316     })
317
318 @admin_page
319 def edit_page(request, id=None):
320     if id:
321         page = get_object_or_404(Page, id=id)
322     else:
323         page = None
324
325     if request.POST:
326         form = PageForm(page, request.POST)
327
328         if form.is_valid():
329             if form.has_changed():
330                 if not page:
331                     page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
332                                                                                                  ).node
333                 else:
334                     EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
335                             data=form.cleaned_data)
336
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'])
341
342             return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
343
344     else:
345         form = PageForm(page)
346
347     if page:
348         published = page.published
349     else:
350         published = False
351
352     return ('osqaadmin/edit_page.html', {
353     'page': page,
354     'form': form,
355     'published': published
356     })
357
358 @admin_page
359 def moderation(request):
360     if request.POST:
361         if not 'ids' in request.POST:
362             verify = None
363         else:
364             sort = {
365             'high-rep': '-reputation',
366             'newer': '-date_joined',
367             'older': 'date_joined',
368             }.get(request.POST.get('sort'), None)
369
370             if sort:
371                 try:
372                     limit = int(request.POST['limit'])
373                 except:
374                     limit = 5
375
376                 verify = User.objects.order_by(sort)[:limit]
377             else:
378                 verify = None
379
380         if verify:
381             possible_cheaters = []
382             verify = User.objects.order_by(sort)[:5]
383
384             cheat_score_sort = lambda c1, c2: cmp(c2.fdata['fake_score'], c1.fdata['fake_score'])
385
386             for user in verify:
387                 possible_fakes = []
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'))
391
392                 for affecter in affecters:
393                     if affecter == user:
394                         continue
395
396                     data = {'affect_count': affecter.affect_count}
397
398                     total_actions = affecter.actions.filter(canceled=False).exclude(node=None).count()
399                     ratio = (float(affecter.affect_count) / float(total_actions)) * 100
400
401                     if total_actions > 10 and ratio > 50:
402                         data['total_actions'] = total_actions
403                         data['action_ratio'] = ratio
404
405                         affecter_ips = set(
406                                 Action.objects.filter(user=affecter).values_list('ip', flat=True).distinct('ip'))
407                         cross_ips = len(user_ips & affecter_ips)
408
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
412
413                         if affecter.email_isvalid:
414                             email_score = 0
415                         else:
416                             email_score = 50.0
417
418                         data['fake_score'] = ((data['cross_ip_ratio'] + data['action_ratio'] + email_score) / 100) * 4
419
420                         affecter.fdata = data
421                         possible_fakes.append(affecter)
422
423                 if len(possible_fakes) > 0:
424                     possible_fakes = sorted(possible_fakes, cheat_score_sort)
425                     possible_cheaters.append((user, possible_fakes))
426
427             return ('osqaadmin/moderation.html', {'cheaters': possible_cheaters})
428
429     return ('osqaadmin/moderation.html', {})
430
431
432