]> git.openstreetmap.org Git - osqa.git/blob - forum/views/admin.py
Some more changes in node management.
[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"), _("Node management"))
422 def node_management(request):
423     if request.is_ajax():
424         if request.POST and request.POST.get('filtername', None) and request.GET:
425             params = pagination.generate_uri(request.GET, ('page',))
426             current_filters = settings.NODE_MAN_FILTERS.value
427             current_filters.add((request.POST['filtername'], params))
428             settings.NODE_MAN_FILTERS.set_value(current_filters)
429             return HttpResponse('OK')
430
431         return HttpResponse('ERROR')
432
433     if request.POST:
434         selected_nodes = request.POST.getlist('_selected_node')
435
436         if selected_nodes and request.POST.get('action', None):
437             action = request.POST['action']
438             selected_nodes = Node.objects.filter(id__in=selected_nodes)
439
440             message = _("No action performed")
441
442             if action == 'delete_selected':
443                 for node in selected_nodes:
444                     if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
445                         DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
446                         
447                 message = _("All selected nodes marked as deleted")
448
449             if action == "close_selected":
450                 for node in selected_nodes:
451                     if node.node_type == "question" and (not node.nis.closed):
452                         CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
453
454                 message = _("Selected questions were closed")
455
456             if action == "hard_delete_selected":
457                 ids = [n.id for n in selected_nodes]
458
459                 for id in ids:
460                     try:
461                         node = Node.objects.get(id=id)
462                         node.delete()
463                     except:
464                         pass                        
465
466                 message = _("All selected nodes deleted")
467
468             request.user.message_set.create(message=message)
469
470             params = pagination.generate_uri(request.GET, ('page',))
471             return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
472
473
474     nodes = Node.objects.all()
475
476     if (request.GET):
477         filter_form = NodeManFilterForm(request.GET)
478     else:
479         filter_form = NodeManFilterForm({'node_type': 'all', 'state_type': 'any'})
480
481     authors = request.GET.getlist('authors')
482     tags = request.GET.getlist('tags')
483
484     if filter_form.is_valid():
485         data = filter_form.cleaned_data
486
487         if data['node_type'] != 'all':
488             nodes = nodes.filter(node_type=data['node_type'])
489
490         if (data['state_type'] != 'any'):
491             nodes = nodes.filter_state(**{str(data['state_type']): True})
492
493         if (authors):
494             nodes = nodes.filter(author__id__in=authors)
495             authors = User.objects.filter(id__in=authors)
496
497         if (tags):
498             nodes = nodes.filter(tags__id__in=tags)
499             tags = Tag.objects.filter(id__in=tags)
500
501         if data['text']:
502             filter = None
503
504             if data['text_in'] == 'title' or data['text_in'] == 'both':
505                 filter = models.Q(title__icontains=data['text'])
506
507             if data['text_in'] == 'body' or data['text_in'] == 'both':
508                 sec_filter = models.Q(body__icontains=data['text'])
509                 if filter:
510                     filter = filter | sec_filter
511                 else:
512                     filter = sec_filter
513
514             if filter:
515                 nodes = nodes.filter(filter)
516
517     node_types = [('all', _("all"))] + [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
518     state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
519
520     return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
521     'nodes': nodes,
522     'node_types': node_types,
523     'state_types': state_types,
524     'filter_form': filter_form,
525     'authors': authors,
526     'tags': tags,
527     'hide_menu': True
528     }))
529
530
531
532
533