]> git.openstreetmap.org Git - osqa.git/blob - forum/views/admin.py
A small fix in the admin decorators.
[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, HttpResponse):
38             return res
39
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)
48
49         context['tools'] = TOOLS
50
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)])
53
54         return render_to_response(template, context, context_instance=RequestContext(request))
55
56     return wrapper
57
58 def admin_tools_page(name, label):    
59     def decorator(fn):
60         fn = admin_page(fn)
61         fn.label = label
62         TOOLS[name] = fn
63
64         return fn
65     return decorator
66
67 class ActivityPaginatorContext(pagination.PaginatorContext):
68     def __init__(self):
69         super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
70
71 @admin_page
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(),
78     }))
79
80 @super_user_required
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')
84
85     return HttpResponseRedirect(reverse('admin_index'))
86
87 @admin_page
88 def statistics(request):
89     today = datetime.now()
90     last_month = today - timedelta(days=30)
91
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)
95
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)
100     ])
101
102     last_month_users = User.objects.filter(date_joined__gt=last_month
103                                            ).order_by('date_joined').values_list('date_joined', flat=True)
104
105     last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
106
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)
110     ])
111
112     return 'osqaadmin/statistics.html', {
113     'graphs': [
114             {
115             'id': 'questions_graph',
116             'caption': _("Questions Graph"),
117             'data': qgraph_data
118             }, {
119             'id': 'userss_graph',
120             'caption': _("Users Graph"),
121             'data': ugraph_data
122             }
123             ]
124     }
125
126 @admin_page
127 def tools_page(request, name):
128     if not name in TOOLS:
129         raise Http404
130
131     return TOOLS[name](request)
132
133
134 @admin_page
135 def settings_set(request, set_name):
136     set = Setting.sets.get(set_name, {})
137     current_preview = request.session.get('previewing_settings', {})
138
139     if set is None:
140         raise Http404
141
142     if request.POST:
143         form = SettingsSetForm(set, data=request.POST, files=request.FILES)
144
145         if form.is_valid():
146             if 'preview' in request.POST:
147                 current_preview.update(form.cleaned_data)
148                 request.session['previewing_settings'] = current_preview
149
150                 return HttpResponseRedirect(reverse('index'))
151             else:
152                 for s in set:
153                     current_preview.pop(s.name, None)
154
155                 request.session['previewing_settings'] = current_preview
156
157                 if not 'reset' in request.POST:
158                     form.save()
159                     request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
160
161                     if set_name in ('minrep', 'badges', 'repgain'):
162                         settings.SETTINGS_PACK.set_value("custom")
163
164                 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
165     else:
166         form = SettingsSetForm(set, unsaved=current_preview)
167
168     return 'osqaadmin/set.html', {
169     'form': form,
170     'markdown': set.markdown,
171     }
172
173 @super_user_required
174 def get_default(request, set_name, var_name):
175     set = Setting.sets.get(set_name, None)
176     if set is None: raise Http404
177
178     setting = dict([(s.name, s) for s in set]).get(var_name, None)
179     if setting is None: raise Http404
180
181     setting.to_default()
182
183     if request.is_ajax():
184         return HttpResponse(setting.default)
185     else:
186         return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
187
188
189 def get_recent_activity():
190     return Action.objects.order_by('-action_date')
191
192 def get_flagged_posts():
193     return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
194
195 def get_statistics():
196     return {
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(),
205     }
206
207 @super_user_required
208 def go_bootstrap(request):
209 #todo: this is the quick and dirty way of implementing a bootstrap mode
210     try:
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)
234     except:
235         pass
236
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)
252
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)
263
264     settings.SETTINGS_PACK.set_value("bootstrap")
265
266     request.user.message_set.create(message=_('Bootstrap mode enabled'))
267     return HttpResponseRedirect(reverse('admin_index'))
268
269 @super_user_required
270 def go_defaults(request):
271     for setting in Setting.sets['badges']:
272         setting.to_default()
273     for setting in Setting.sets['minrep']:
274         setting.to_default()
275     for setting in Setting.sets['repgain']:
276         setting.to_default()
277
278     settings.SETTINGS_PACK.set_value("default")
279
280     request.user.message_set.create(message=_('All values reverted to defaults'))
281     return HttpResponseRedirect(reverse('admin_index'))
282
283
284 @super_user_required
285 def recalculate_denormalized(request):
286     for n in Node.objects.all():
287         n = n.leaf
288         n.score = n.votes.aggregate(score=models.Sum('value'))['score']
289         if not n.score: n.score = 0
290         n.save()
291
292     for u in User.objects.all():
293         u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
294         u.save()
295
296     request.user.message_set.create(message=_('All values recalculated'))
297     return HttpResponseRedirect(reverse('admin_index'))
298
299 @admin_page
300 def maintenance(request):
301     if request.POST:
302         if 'close' in request.POST or 'adjust' in request.POST:
303             form = MaintenanceModeForm(request.POST)
304
305             if form.is_valid():
306                 settings.MAINTAINANCE_MODE.set_value({
307                 'allow_ips': form.cleaned_data['ips'],
308                 'message': form.cleaned_data['message']})
309
310                 if 'close' in request.POST:
311                     message = _('Maintenance mode enabled')
312                 else:
313                     message = _('Settings adjusted')
314
315                 request.user.message_set.create(message=message)
316
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'))
322     else:
323         form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
324                                             'message': _('Currently down for maintenance. We\'ll be back soon')})
325
326     return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
327                                            })
328
329
330 @admin_page
331 def flagged_posts(request):
332     return ('osqaadmin/flagged_posts.html', {
333     'flagged_posts': get_flagged_posts(),
334     })
335
336 @admin_page
337 def static_pages(request):
338     pages = Page.objects.all()
339
340     return ('osqaadmin/static_pages.html', {
341     'pages': pages,
342     })
343
344 @admin_page
345 def edit_page(request, id=None):
346     if id:
347         page = get_object_or_404(Page, id=id)
348     else:
349         page = None
350
351     if request.POST:
352         form = PageForm(page, request.POST)
353
354         if form.is_valid():
355             if form.has_changed():
356                 if not page:
357                     page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
358                                                                                                  ).node
359                 else:
360                     EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
361                             data=form.cleaned_data)
362
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'])
367
368             return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
369
370     else:
371         form = PageForm(page)
372
373     if page:
374         published = page.published
375     else:
376         published = False
377
378     return ('osqaadmin/edit_page.html', {
379     'page': page,
380     'form': form,
381     'published': published
382     })
383
384
385
386 @admin_tools_page(_("nodeman"), _("Node management"))
387 def node_management(request):
388     nodes = Node.objects.all()
389
390     if (request.GET):
391         filter_form = NodeManFilterForm(request.GET)
392     else:
393         filter_form = NodeManFilterForm({'node_type': 'all', 'state_type': 'any'})
394
395     if filter_form.is_valid():
396         data = filter_form.cleaned_data
397
398         if data['node_type'] != 'all':
399             nodes = nodes.filter(node_type=data['node_type'])
400
401         if (data['state_type'] != 'any'):
402             nodes = nodes.filter_state(**{str(data['state_type']): True})
403
404         if data['text']:
405             filter = None
406
407             if data['text_in'] == 'title' or data['text_in'] == 'both':
408                 filter = models.Q(title__icontains=data['text'])
409
410             if data['text_in'] == 'body' or data['text_in'] == 'both':
411                 sec_filter = models.Q(body__icontains=data['text'])
412                 if filter:
413                     filter = filter | sec_filter
414                 else:
415                     filter = sec_filter
416
417             if filter:
418                 nodes = nodes.filter(filter)
419
420
421     node_types = [('all', _("all"))] + [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
422     state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
423
424     return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", ActivityPaginatorContext()), {
425     'nodes': nodes,
426     'node_types': node_types,
427     'state_types': state_types,
428     'filter_form': filter_form,
429     'hide_menu': True
430     }))
431
432
433
434
435
436
437