]> git.openstreetmap.org Git - osqa.git/blob - forum/views/admin.py
Adds the hability to hard delete nodes in the bulk manager, and renames the tool.
[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, CreateUserForm
14 from forum.settings.forms import SettingsSetForm
15 from forum.utils import pagination, html
16
17 from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
18 from forum.models.node import NodeMetaClass
19 from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
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 @admin_tools_page(_('createuser'), _("Create new user"))
385 def create_user(request):
386     if request.POST:
387         form = CreateUserForm(request.POST)
388
389         if form.is_valid():
390             user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
391             user_.set_password(form.cleaned_data['password1'])
392
393             if not form.cleaned_data.get('validate_email', False):
394                 user_.email_isvalid = True
395
396             user_.save()
397             UserJoinsAction(user=user_).save()
398
399             request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
400                     user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
401
402             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
403     else:
404         form = CreateUserForm()
405
406     return ('osqaadmin/createuser.html', {
407         'form': form,
408     })
409
410 class NodeManagementPaginatorContext(pagination.PaginatorContext):
411     def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
412         super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
413             (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
414             (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
415             (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
416             (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
417             (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
418             (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
419         ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
420
421 @admin_tools_page(_("nodeman"), _("Bulk management"))
422 def node_management(request):
423     if request.POST and "save_filter" in request.POST:
424         filter_name = request.POST.get('filter_name', _('filter'))
425         params = pagination.generate_uri(request.GET, ('page',))
426         current_filters = settings.NODE_MAN_FILTERS.value
427         current_filters.append((filter_name, params))
428         settings.NODE_MAN_FILTERS.set_value(current_filters)
429
430     if request.POST and "execute" in request.POST:
431         selected_nodes = request.POST.getlist('_selected_node')
432
433         if selected_nodes and request.POST.get('action', None):
434             action = request.POST['action']
435             selected_nodes = Node.objects.filter(id__in=selected_nodes)
436
437             message = _("No action performed")
438
439             if action == 'delete_selected':
440                 for node in selected_nodes:
441                     if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
442                         DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
443                         
444                 message = _("All selected nodes marked as deleted")
445
446             if action == "close_selected":
447                 for node in selected_nodes:
448                     if node.node_type == "question" and (not node.nis.closed):
449                         CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
450
451                 message = _("Selected questions were closed")
452
453             if action == "hard_delete_selected":
454                 ids = [n.id for n in selected_nodes]
455
456                 for id in ids:
457                     try:
458                         node = Node.objects.get(id=id)
459                         node.delete()
460                     except:
461                         pass                        
462
463                 message = _("All selected nodes deleted")
464
465             request.user.message_set.create(message=message)
466
467             params = pagination.generate_uri(request.GET, ('page',))
468             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
469
470
471     nodes = Node.objects.all()
472
473     if (request.GET):
474         filter_form = NodeManFilterForm(request.GET)
475     else:
476         filter_form = NodeManFilterForm({'node_type': 'all', 'state_type': 'any'})
477
478     authors = request.GET.getlist('authors')
479     tags = request.GET.getlist('tags')
480
481     if filter_form.is_valid():
482         data = filter_form.cleaned_data
483
484         if data['node_type'] != 'all':
485             nodes = nodes.filter(node_type=data['node_type'])
486
487         if (data['state_type'] != 'any'):
488             nodes = nodes.filter_state(**{str(data['state_type']): True})
489
490         if (authors):
491             nodes = nodes.filter(author__id__in=authors)
492             authors = User.objects.filter(id__in=authors)
493
494         if (tags):
495             nodes = nodes.filter(tags__id__in=tags)
496             tags = Tag.objects.filter(id__in=tags)
497
498         if data['text']:
499             filter = None
500
501             if data['text_in'] == 'title' or data['text_in'] == 'both':
502                 filter = models.Q(title__icontains=data['text'])
503
504             if data['text_in'] == 'body' or data['text_in'] == 'both':
505                 sec_filter = models.Q(body__icontains=data['text'])
506                 if filter:
507                     filter = filter | sec_filter
508                 else:
509                     filter = sec_filter
510
511             if filter:
512                 nodes = nodes.filter(filter)
513
514     node_types = [('all', _("all"))] + [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
515     state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
516
517     return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
518     'nodes': nodes,
519     'node_types': node_types,
520     'state_types': state_types,
521     'filter_form': filter_form,
522     'authors': authors,
523     'tags': tags,
524     'hide_menu': True
525     }))
526
527
528
529
530