]> git.openstreetmap.org Git - osqa.git/blob - forum/views/admin.py
don't focus recaptcha field
[osqa.git] / forum / views / admin.py
1 from datetime import datetime, timedelta
2 import time
3
4 from django.views.decorators.csrf import csrf_exempt
5 from django.shortcuts import render_to_response, get_object_or_404
6 from django.core.urlresolvers import reverse
7 from django.http import HttpResponseRedirect, HttpResponse, Http404
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
13 from forum.http_responses import HttpResponseUnauthorized
14 from forum.settings.base import Setting
15 from forum.forms import MaintenanceModeForm, PageForm, CreateUserForm
16 from forum.settings.forms import SettingsSetForm
17 from forum.utils import pagination, html
18 from forum.utils.mail import send_template_email
19 from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
20 from forum.models.node import NodeMetaClass
21 from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
22 from forum import settings
23
24 TOOLS = {}
25
26 def super_user_required(fn):
27     def wrapper(request, *args, **kwargs):
28         if request.user.is_authenticated() and request.user.is_superuser:
29             return fn(request, *args, **kwargs)
30         else:
31             return HttpResponseUnauthorized(request)
32
33     return wrapper
34
35 def staff_user_required(fn):
36     def wrapper(request, *args, **kwargs):
37         if request.user.is_authenticated() and (request.user.is_staff or request.user.is_superuser):
38             return fn(request, *args, **kwargs)
39         else:
40             return HttpResponseUnauthorized(request)
41
42     return wrapper
43
44 def admin_page_wrapper(fn, request, *args, **kwargs):
45     res = fn(request, *args, **kwargs)
46     if isinstance(res, HttpResponse):
47         return res
48
49     template, context = res
50     context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
51     context['allsets'] = Setting.sets
52     context['othersets'] = sorted(
53             [s for s in Setting.sets.values() if not s.name in
54             ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar',
55             'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')]
56             , lambda s1, s2: s1.weight - s2.weight)
57
58     context['tools'] = [(name, fn.label) for name, fn in TOOLS.items()]
59
60     # Show the navigation only to moderators and super users
61     if not context.has_key("hide_navigation"):
62         context['hide_navigation'] = not request.user.is_superuser
63
64     unsaved = request.session.get('previewing_settings', {})
65     context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
66
67     return render_to_response(template, context, context_instance=RequestContext(request))
68
69 def admin_page(fn):
70     @super_user_required
71     def wrapper(request, *args, **kwargs):
72         return admin_page_wrapper(fn, request, *args, **kwargs)
73
74     return wrapper
75
76 def moderation_page(fn):
77     @staff_user_required
78     def wrapper(request, *args, **kwargs):
79         return admin_page_wrapper(fn, request, *args, **kwargs)
80
81     return wrapper
82
83 def admin_tools_page(name, label):    
84     def decorator(fn):
85         fn = admin_page(fn)
86         fn.label = label
87         TOOLS[name] = fn
88
89         return fn
90     return decorator
91
92 class ActivityPaginatorContext(pagination.PaginatorContext):
93     def __init__(self):
94         super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
95
96 @admin_page
97 def dashboard(request):
98     return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), {
99     'settings_pack': unicode(settings.SETTINGS_PACK),
100     'statistics': get_statistics(),
101     'recent_activity': get_recent_activity(),
102     'flagged_posts': get_flagged_posts(),
103     }))
104
105 @super_user_required
106 def interface_switch(request):
107     if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
108         settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
109
110     return HttpResponseRedirect(reverse('admin_index'))
111
112 @admin_page
113 def statistics(request):
114     today = datetime.now()
115     last_month = today - timedelta(days=30)
116
117     last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
118                                                                                ).order_by('added_at').values_list(
119             'added_at', flat=True)
120
121     last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
122     qgraph_data = simplejson.dumps([
123     (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
124     for i, d in enumerate(last_month_questions)
125     ])
126
127     last_month_users = User.objects.filter(date_joined__gt=last_month
128                                            ).order_by('date_joined').values_list('date_joined', flat=True)
129
130     last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
131
132     ugraph_data = simplejson.dumps([
133     (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
134     for i, d in enumerate(last_month_users)
135     ])
136
137     return 'osqaadmin/statistics.html', {
138     'graphs': [
139             {
140             'id': 'questions_graph',
141             'caption': _("Questions Graph"),
142             'data': qgraph_data
143             }, {
144             'id': 'userss_graph',
145             'caption': _("Users Graph"),
146             'data': ugraph_data
147             }
148             ]
149     }
150
151 @admin_page
152 def tools_page(request, name):
153     if not name in TOOLS:
154         raise Http404
155
156     return TOOLS[name](request)
157
158
159 @admin_page
160 def settings_set(request, set_name):
161     set = Setting.sets.get(set_name, {})
162     current_preview = request.session.get('previewing_settings', {})
163
164     if set is None:
165         raise Http404
166
167     if request.POST:
168         form = SettingsSetForm(set, data=request.POST, files=request.FILES)
169
170         if form.is_valid():
171             if 'preview' in request.POST:
172                 current_preview.update(form.cleaned_data)
173                 request.session['previewing_settings'] = current_preview
174
175                 return HttpResponseRedirect(reverse('index'))
176             else:
177                 for s in set:
178                     current_preview.pop(s.name, None)
179
180                 request.session['previewing_settings'] = current_preview
181
182                 if not 'reset' in request.POST:
183                     form.save()
184                     request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
185
186                     if set_name in ('minrep', 'badges', 'repgain'):
187                         settings.SETTINGS_PACK.set_value("custom")
188
189                 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
190     else:
191         form = SettingsSetForm(set, unsaved=current_preview)
192
193     return 'osqaadmin/set.html', {
194     'form': form,
195     'markdown': set.markdown,
196     }
197
198 @super_user_required
199 def get_default(request, set_name, var_name):
200     set = Setting.sets.get(set_name, None)
201     if set is None: raise Http404
202
203     setting = dict([(s.name, s) for s in set]).get(var_name, None)
204     if setting is None: raise Http404
205
206     setting.to_default()
207
208     if request.is_ajax():
209         return HttpResponse(setting.default)
210     else:
211         return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
212
213
214 def get_recent_activity():
215     return Action.objects.order_by('-action_date')
216
217 def get_flagged_posts():
218     return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
219
220 def get_statistics():
221     return {
222     'total_users': User.objects.all().count(),
223     'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
224     'total_questions': Question.objects.filter_state(deleted=False).count(),
225     'questions_last_24': Question.objects.filter_state(deleted=False).filter(
226             added_at__gt=(datetime.now() - timedelta(days=1))).count(),
227     'total_answers': Answer.objects.filter_state(deleted=False).count(),
228     'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
229             added_at__gt=(datetime.now() - timedelta(days=1))).count(),
230     }
231
232 @super_user_required
233 def go_bootstrap(request):
234 #todo: this is the quick and dirty way of implementing a bootstrap mode
235     try:
236         from forum_modules.default_badges import settings as dbsets
237         dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
238         dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
239         dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
240         dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
241         dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
242         dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
243         dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
244         dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
245         dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
246         dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
247         dbsets.STELLAR_QUESTION_FAVS.set_value(3)
248         dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
249         dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
250         dbsets.CIVIC_DUTY_VOTES.set_value(15)
251         dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
252         dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
253         dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
254         dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
255         dbsets.GURU_UP_VOTES.set_value(4)
256         dbsets.NECROMANCER_UP_VOTES.set_value(2)
257         dbsets.NECROMANCER_DIF_DAYS.set_value(30)
258         dbsets.TAXONOMIST_USE_COUNT.set_value(5)
259     except:
260         pass
261
262     settings.REP_TO_VOTE_UP.set_value(0)
263     settings.REP_TO_VOTE_DOWN.set_value(15)
264     settings.REP_TO_FLAG.set_value(15)
265     settings.REP_TO_COMMENT.set_value(0)
266     settings.REP_TO_LIKE_COMMENT.set_value(0)
267     settings.REP_TO_UPLOAD.set_value(0)
268     settings.REP_TO_CREATE_TAGS.set_value(0)
269     settings.REP_TO_CLOSE_OWN.set_value(60)
270     settings.REP_TO_REOPEN_OWN.set_value(120)
271     settings.REP_TO_RETAG.set_value(150)
272     settings.REP_TO_EDIT_WIKI.set_value(200)
273     settings.REP_TO_EDIT_OTHERS.set_value(400)
274     settings.REP_TO_CLOSE_OTHERS.set_value(600)
275     settings.REP_TO_DELETE_COMMENTS.set_value(400)
276     settings.REP_TO_VIEW_FLAGS.set_value(30)
277
278     settings.INITIAL_REP.set_value(1)
279     settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
280     settings.REP_GAIN_BY_UPVOTED.set_value(15)
281     settings.REP_LOST_BY_DOWNVOTED.set_value(1)
282     settings.REP_LOST_BY_DOWNVOTING.set_value(0)
283     settings.REP_GAIN_BY_ACCEPTED.set_value(25)
284     settings.REP_GAIN_BY_ACCEPTING.set_value(5)
285     settings.REP_LOST_BY_FLAGGED.set_value(2)
286     settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
287     settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
288
289     settings.SETTINGS_PACK.set_value("bootstrap")
290
291     request.user.message_set.create(message=_('Bootstrap mode enabled'))
292     return HttpResponseRedirect(reverse('admin_index'))
293
294 @super_user_required
295 def go_defaults(request):
296     for setting in Setting.sets['badges']:
297         setting.to_default()
298     for setting in Setting.sets['minrep']:
299         setting.to_default()
300     for setting in Setting.sets['repgain']:
301         setting.to_default()
302
303     settings.SETTINGS_PACK.set_value("default")
304
305     request.user.message_set.create(message=_('All values reverted to defaults'))
306     return HttpResponseRedirect(reverse('admin_index'))
307
308
309 @super_user_required
310 def recalculate_denormalized(request):
311     for n in Node.objects.all():
312         n = n.leaf
313         n.score = n.votes.aggregate(score=models.Sum('value'))['score']
314         if not n.score: n.score = 0
315         n.save()
316
317     for u in User.objects.all():
318         u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
319         u.save()
320
321     request.user.message_set.create(message=_('All values recalculated'))
322     return HttpResponseRedirect(reverse('admin_index'))
323
324 @admin_page
325 def maintenance(request):
326     if request.POST:
327         if 'close' in request.POST or 'adjust' in request.POST:
328             form = MaintenanceModeForm(request.POST)
329
330             if form.is_valid():
331                 settings.MAINTAINANCE_MODE.set_value({
332                 'allow_ips': form.cleaned_data['ips'],
333                 'message': form.cleaned_data['message']})
334
335                 if 'close' in request.POST:
336                     message = _('Maintenance mode enabled')
337                 else:
338                     message = _('Settings adjusted')
339
340                 request.user.message_set.create(message=message)
341
342                 return HttpResponseRedirect(reverse('admin_maintenance'))
343         elif 'open' in request.POST:
344             settings.MAINTAINANCE_MODE.set_value(None)
345             request.user.message_set.create(message=_("Your site is now running normally"))
346             return HttpResponseRedirect(reverse('admin_maintenance'))
347     else:
348         form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
349                                             'message': _('Currently down for maintenance. We\'ll be back soon')})
350
351     return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
352                                            })
353
354
355 @moderation_page
356 def flagged_posts(request):
357     return ('osqaadmin/flagged_posts.html', {
358     'flagged_posts': get_flagged_posts(),
359     })
360
361 @admin_page
362 def static_pages(request):
363     pages = Page.objects.all()
364
365     return ('osqaadmin/static_pages.html', {
366     'pages': pages,
367     })
368
369 @admin_page
370 def edit_page(request, id=None):
371     if id:
372         page = get_object_or_404(Page, id=id)
373     else:
374         page = None
375
376     if request.POST:
377         form = PageForm(page, request.POST)
378
379         if form.is_valid():
380             if form.has_changed():
381                 if not page:
382                     page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
383                                                                                                  ).node
384                 else:
385                     EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
386                             data=form.cleaned_data)
387
388             if ('publish' in request.POST) and (not page.published):
389                 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
390             elif ('unpublish' in request.POST) and page.published:
391                 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
392
393             return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
394
395     else:
396         form = PageForm(page)
397
398     if page:
399         published = page.published
400     else:
401         published = False
402
403     return ('osqaadmin/edit_page.html', {
404     'page': page,
405     'form': form,
406     'published': published
407     })
408
409 @admin_page
410 def delete_page(request, id=None):
411     page = get_object_or_404(Page, id=id)
412     page.delete()
413     return HttpResponseRedirect(reverse('admin_static_pages'))
414
415 @admin_tools_page(_('createuser'), _("Create new user"))
416 def create_user(request):
417     if request.POST:
418         form = CreateUserForm(request.POST)
419
420         if form.is_valid():
421             user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
422             user_.set_password(form.cleaned_data['password1'])
423
424             if not form.cleaned_data.get('validate_email', False):
425                 user_.email_isvalid = True
426
427             user_.save()
428             UserJoinsAction(user=user_).save()
429
430             request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
431                     user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
432
433             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
434     else:
435         form = CreateUserForm()
436
437     return ('osqaadmin/createuser.html', {
438         'form': form,
439     })
440
441 class NodeManagementPaginatorContext(pagination.PaginatorContext):
442     def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
443         super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
444             (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
445             (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
446             (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
447             (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
448             (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
449             (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
450             (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
451             (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
452             (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
453             (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
454         ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
455
456 @admin_tools_page(_("nodeman"), _("Bulk management"))
457 def node_management(request):
458     if request.POST:
459         params = pagination.generate_uri(request.GET, ('page',))
460
461         if "save_filter" in request.POST:
462             filter_name = request.POST.get('filter_name', _('filter'))
463             params = pagination.generate_uri(request.GET, ('page',))
464             current_filters = settings.NODE_MAN_FILTERS.value
465             current_filters.append((filter_name, params))
466             settings.NODE_MAN_FILTERS.set_value(current_filters)
467
468         elif r"execute" in request.POST:
469             selected_nodes = request.POST.getlist('_selected_node')
470
471             if selected_nodes and request.POST.get('action', None):
472                 action = str(request.POST['action'])
473                 selected_nodes = Node.objects.filter(id__in=selected_nodes)
474
475                 message = _("No action performed")
476
477                 if action == 'delete_selected':
478                     for node in selected_nodes:
479                         if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
480                             DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
481
482                     message = _("All selected nodes marked as deleted")
483
484                 if action == 'undelete_selected':
485                     for node in selected_nodes:
486                         if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
487                             node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
488
489                     message = _("All selected nodes undeleted")
490
491                 if action == "close_selected":
492                     for node in selected_nodes:
493                         if node.node_type == "question" and (not node.nis.closed):
494                             CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
495
496                     message = _("Selected questions were closed")
497
498                 if action == "hard_delete_selected":
499                     ids = [n.id for n in selected_nodes]
500
501                     for id in ids:
502                         try:
503                             node = Node.objects.get(id=id)
504                             node.delete()
505                         except:
506                             pass
507
508                     message = _("All selected nodes deleted")
509
510                 request.user.message_set.create(message=message)
511
512                 params = pagination.generate_uri(request.GET, ('page',))
513                 
514             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
515
516
517     nodes = Node.objects.all()
518
519     text = request.GET.get('text', '')
520     text_in = request.GET.get('text_in', 'body')
521
522     authors = request.GET.getlist('authors')
523     tags = request.GET.getlist('tags')
524
525     type_filter = request.GET.getlist('node_type')
526     state_filter = request.GET.getlist('state_type')
527     state_filter_type = request.GET.get('state_filter_type', 'any')
528
529     if type_filter:
530         nodes = nodes.filter(node_type__in=type_filter)
531
532     state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
533     state_filter = [s for s in state_filter if s in state_types]
534
535     if state_filter:
536         if state_filter_type == 'all':
537             nodes = nodes.all_states(*state_filter)
538         else:
539             nodes = nodes.any_state(*state_filter)
540
541     if (authors):
542         nodes = nodes.filter(author__id__in=authors)
543         authors = User.objects.filter(id__in=authors)
544
545     if (tags):
546         nodes = nodes.filter(tags__id__in=tags)
547         tags = Tag.objects.filter(id__in=tags)
548
549     if text:
550         text_in = request.GET.get('text_in', 'body')
551         filter = None
552
553         if text_in == 'title' or text_in == 'both':
554             filter = models.Q(title__icontains=text)
555
556         if text_in == 'body' or text_in == 'both':
557             sec_filter = models.Q(body__icontains=text)
558             if filter:
559                 filter = filter | sec_filter
560             else:
561                 filter = sec_filter
562
563         if filter:
564             nodes = nodes.filter(filter)
565
566     node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
567
568     return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
569     'nodes': nodes,
570     'text': text,
571     'text_in': text_in,
572     'type_filter': type_filter,
573     'state_filter': state_filter,
574     'state_filter_type': state_filter_type,
575     'node_types': node_types,
576     'state_types': state_types,
577     'authors': authors,
578     'tags': tags,
579     'hide_navigation': True
580     }))
581
582 @csrf_exempt
583 @super_user_required
584 def test_email_settings(request):
585     user = request.user
586
587     send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user })
588
589     return render_to_response(
590         'osqaadmin/test_email_settings.html',
591         { 'user': user, },
592         RequestContext(request)
593     )