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