1 from datetime import datetime, timedelta
2 import os, time, csv, random
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, CreateUserForm
14 from forum.settings.forms import SettingsSetForm
15 from forum.utils import pagination, html
16 from forum.utils.mail import send_template_email
18 from forum.models import Question, Answer, User, Node, Action, Page, NodeState, Tag
19 from forum.models.node import NodeMetaClass
20 from forum.actions import NewPageAction, EditPageAction, PublishAction, DeleteAction, UserJoinsAction, CloseAction
21 from forum import settings
25 def super_user_required(fn):
26 def wrapper(request, *args, **kwargs):
27 if request.user.is_authenticated() and request.user.is_superuser:
28 return fn(request, *args, **kwargs)
30 return HttpResponseUnauthorized(request)
36 def wrapper(request, *args, **kwargs):
37 res = fn(request, *args, **kwargs)
38 if isinstance(res, HttpResponse):
41 template, context = res
42 context['basetemplate'] = settings.DJSTYLE_ADMIN_INTERFACE and "osqaadmin/djstyle_base.html" or "osqaadmin/base.html"
43 context['allsets'] = Setting.sets
44 context['othersets'] = sorted(
45 [s for s in Setting.sets.values() if not s.name in
46 ('basic', 'users', 'email', 'paths', 'extkeys', 'repgain', 'minrep', 'voting', 'accept', 'badges', 'about', 'faq', 'sidebar',
47 'form', 'moderation', 'css', 'headandfoot', 'head', 'view', 'urls')]
48 , lambda s1, s2: s1.weight - s2.weight)
50 context['tools'] = TOOLS
52 unsaved = request.session.get('previewing_settings', {})
53 context['unsaved'] = set([getattr(settings, s).set.name for s in unsaved.keys() if hasattr(settings, s)])
55 return render_to_response(template, context, context_instance=RequestContext(request))
59 def admin_tools_page(name, label):
68 class ActivityPaginatorContext(pagination.PaginatorContext):
70 super (ActivityPaginatorContext, self).__init__('ADMIN_RECENT_ACTIVITY', pagesizes=(20, 40, 80), default_pagesize=40)
73 def dashboard(request):
74 return ('osqaadmin/dashboard.html', pagination.paginated(request, ("recent_activity", ActivityPaginatorContext()), {
75 'settings_pack': unicode(settings.SETTINGS_PACK),
76 'statistics': get_statistics(),
77 'recent_activity': get_recent_activity(),
78 'flagged_posts': get_flagged_posts(),
82 def interface_switch(request):
83 if request.GET and request.GET.get('to', None) and request.GET['to'] in ('default', 'djstyle'):
84 settings.DJSTYLE_ADMIN_INTERFACE.set_value(request.GET['to'] == 'djstyle')
86 return HttpResponseRedirect(reverse('admin_index'))
89 def statistics(request):
90 today = datetime.now()
91 last_month = today - timedelta(days=30)
93 last_month_questions = Question.objects.filter_state(deleted=False).filter(added_at__gt=last_month
94 ).order_by('added_at').values_list(
95 'added_at', flat=True)
97 last_month_n_questions = Question.objects.filter_state(deleted=False).filter(added_at__lt=last_month).count()
98 qgraph_data = simplejson.dumps([
99 (time.mktime(d.timetuple()) * 1000, i + last_month_n_questions)
100 for i, d in enumerate(last_month_questions)
103 last_month_users = User.objects.filter(date_joined__gt=last_month
104 ).order_by('date_joined').values_list('date_joined', flat=True)
106 last_month_n_users = User.objects.filter(date_joined__lt=last_month).count()
108 ugraph_data = simplejson.dumps([
109 (time.mktime(d.timetuple()) * 1000, i + last_month_n_users)
110 for i, d in enumerate(last_month_users)
113 return 'osqaadmin/statistics.html', {
116 'id': 'questions_graph',
117 'caption': _("Questions Graph"),
120 'id': 'userss_graph',
121 'caption': _("Users Graph"),
128 def tools_page(request, name):
129 if not name in TOOLS:
132 return TOOLS[name](request)
136 def settings_set(request, set_name):
137 set = Setting.sets.get(set_name, {})
138 current_preview = request.session.get('previewing_settings', {})
144 form = SettingsSetForm(set, data=request.POST, files=request.FILES)
147 if 'preview' in request.POST:
148 current_preview.update(form.cleaned_data)
149 request.session['previewing_settings'] = current_preview
151 return HttpResponseRedirect(reverse('index'))
154 current_preview.pop(s.name, None)
156 request.session['previewing_settings'] = current_preview
158 if not 'reset' in request.POST:
160 request.user.message_set.create(message=_("'%s' settings saved succesfully") % set_name)
162 if set_name in ('minrep', 'badges', 'repgain'):
163 settings.SETTINGS_PACK.set_value("custom")
165 return HttpResponseRedirect(reverse('admin_set', args=[set_name]))
167 form = SettingsSetForm(set, unsaved=current_preview)
169 return 'osqaadmin/set.html', {
171 'markdown': set.markdown,
175 def get_default(request, set_name, var_name):
176 set = Setting.sets.get(set_name, None)
177 if set is None: raise Http404
179 setting = dict([(s.name, s) for s in set]).get(var_name, None)
180 if setting is None: raise Http404
184 if request.is_ajax():
185 return HttpResponse(setting.default)
187 return HttpResponseRedirect(reverse('admin_set', kwargs={'set_name': set_name}))
190 def get_recent_activity():
191 return Action.objects.order_by('-action_date')
193 def get_flagged_posts():
194 return Action.objects.filter(canceled=False, action_type="flag").order_by('-action_date')[0:30]
196 def get_statistics():
198 'total_users': User.objects.all().count(),
199 'users_last_24': User.objects.filter(date_joined__gt=(datetime.now() - timedelta(days=1))).count(),
200 'total_questions': Question.objects.filter_state(deleted=False).count(),
201 'questions_last_24': Question.objects.filter_state(deleted=False).filter(
202 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
203 'total_answers': Answer.objects.filter_state(deleted=False).count(),
204 'answers_last_24': Answer.objects.filter_state(deleted=False).filter(
205 added_at__gt=(datetime.now() - timedelta(days=1))).count(),
209 def go_bootstrap(request):
210 #todo: this is the quick and dirty way of implementing a bootstrap mode
212 from forum_modules.default_badges import settings as dbsets
213 dbsets.POPULAR_QUESTION_VIEWS.set_value(100)
214 dbsets.NOTABLE_QUESTION_VIEWS.set_value(200)
215 dbsets.FAMOUS_QUESTION_VIEWS.set_value(300)
216 dbsets.NICE_ANSWER_VOTES_UP.set_value(2)
217 dbsets.NICE_QUESTION_VOTES_UP.set_value(2)
218 dbsets.GOOD_ANSWER_VOTES_UP.set_value(4)
219 dbsets.GOOD_QUESTION_VOTES_UP.set_value(4)
220 dbsets.GREAT_ANSWER_VOTES_UP.set_value(8)
221 dbsets.GREAT_QUESTION_VOTES_UP.set_value(8)
222 dbsets.FAVORITE_QUESTION_FAVS.set_value(1)
223 dbsets.STELLAR_QUESTION_FAVS.set_value(3)
224 dbsets.DISCIPLINED_MIN_SCORE.set_value(3)
225 dbsets.PEER_PRESSURE_MAX_SCORE.set_value(-3)
226 dbsets.CIVIC_DUTY_VOTES.set_value(15)
227 dbsets.PUNDIT_COMMENT_COUNT.set_value(10)
228 dbsets.SELF_LEARNER_UP_VOTES.set_value(2)
229 dbsets.STRUNK_AND_WHITE_EDITS.set_value(10)
230 dbsets.ENLIGHTENED_UP_VOTES.set_value(2)
231 dbsets.GURU_UP_VOTES.set_value(4)
232 dbsets.NECROMANCER_UP_VOTES.set_value(2)
233 dbsets.NECROMANCER_DIF_DAYS.set_value(30)
234 dbsets.TAXONOMIST_USE_COUNT.set_value(5)
238 settings.REP_TO_VOTE_UP.set_value(0)
239 settings.REP_TO_VOTE_DOWN.set_value(15)
240 settings.REP_TO_FLAG.set_value(15)
241 settings.REP_TO_COMMENT.set_value(0)
242 settings.REP_TO_LIKE_COMMENT.set_value(0)
243 settings.REP_TO_UPLOAD.set_value(0)
244 settings.REP_TO_CREATE_TAGS.set_value(0)
245 settings.REP_TO_CLOSE_OWN.set_value(60)
246 settings.REP_TO_REOPEN_OWN.set_value(120)
247 settings.REP_TO_RETAG.set_value(150)
248 settings.REP_TO_EDIT_WIKI.set_value(200)
249 settings.REP_TO_EDIT_OTHERS.set_value(400)
250 settings.REP_TO_CLOSE_OTHERS.set_value(600)
251 settings.REP_TO_DELETE_COMMENTS.set_value(400)
252 settings.REP_TO_VIEW_FLAGS.set_value(30)
254 settings.INITIAL_REP.set_value(1)
255 settings.MAX_REP_BY_UPVOTE_DAY.set_value(300)
256 settings.REP_GAIN_BY_UPVOTED.set_value(15)
257 settings.REP_LOST_BY_DOWNVOTED.set_value(1)
258 settings.REP_LOST_BY_DOWNVOTING.set_value(0)
259 settings.REP_GAIN_BY_ACCEPTED.set_value(25)
260 settings.REP_GAIN_BY_ACCEPTING.set_value(5)
261 settings.REP_LOST_BY_FLAGGED.set_value(2)
262 settings.REP_LOST_BY_FLAGGED_3_TIMES.set_value(30)
263 settings.REP_LOST_BY_FLAGGED_5_TIMES.set_value(100)
265 settings.SETTINGS_PACK.set_value("bootstrap")
267 request.user.message_set.create(message=_('Bootstrap mode enabled'))
268 return HttpResponseRedirect(reverse('admin_index'))
271 def go_defaults(request):
272 for setting in Setting.sets['badges']:
274 for setting in Setting.sets['minrep']:
276 for setting in Setting.sets['repgain']:
279 settings.SETTINGS_PACK.set_value("default")
281 request.user.message_set.create(message=_('All values reverted to defaults'))
282 return HttpResponseRedirect(reverse('admin_index'))
286 def recalculate_denormalized(request):
287 for n in Node.objects.all():
289 n.score = n.votes.aggregate(score=models.Sum('value'))['score']
290 if not n.score: n.score = 0
293 for u in User.objects.all():
294 u.reputation = u.reputes.aggregate(reputation=models.Sum('value'))['reputation']
297 request.user.message_set.create(message=_('All values recalculated'))
298 return HttpResponseRedirect(reverse('admin_index'))
301 def maintenance(request):
303 if 'close' in request.POST or 'adjust' in request.POST:
304 form = MaintenanceModeForm(request.POST)
307 settings.MAINTAINANCE_MODE.set_value({
308 'allow_ips': form.cleaned_data['ips'],
309 'message': form.cleaned_data['message']})
311 if 'close' in request.POST:
312 message = _('Maintenance mode enabled')
314 message = _('Settings adjusted')
316 request.user.message_set.create(message=message)
318 return HttpResponseRedirect(reverse('admin_maintenance'))
319 elif 'open' in request.POST:
320 settings.MAINTAINANCE_MODE.set_value(None)
321 request.user.message_set.create(message=_("Your site is now running normally"))
322 return HttpResponseRedirect(reverse('admin_maintenance'))
324 form = MaintenanceModeForm(initial={'ips': request.META['REMOTE_ADDR'],
325 'message': _('Currently down for maintenance. We\'ll be back soon')})
327 return ('osqaadmin/maintenance.html', {'form': form, 'in_maintenance': settings.MAINTAINANCE_MODE.value is not None
332 def flagged_posts(request):
333 return ('osqaadmin/flagged_posts.html', {
334 'flagged_posts': get_flagged_posts(),
338 def static_pages(request):
339 pages = Page.objects.all()
341 return ('osqaadmin/static_pages.html', {
346 def edit_page(request, id=None):
348 page = get_object_or_404(Page, id=id)
353 form = PageForm(page, request.POST)
356 if form.has_changed():
358 page = NewPageAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=form.cleaned_data
361 EditPageAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save(
362 data=form.cleaned_data)
364 if ('publish' in request.POST) and (not page.published):
365 PublishAction(user=request.user, node=page, ip=request.META['REMOTE_ADDR']).save()
366 elif ('unpublish' in request.POST) and page.published:
367 page.nstate.published.cancel(ip=request.META['REMOTE_ADDR'])
369 return HttpResponseRedirect(reverse('admin_edit_page', kwargs={'id': page.id}))
372 form = PageForm(page)
375 published = page.published
379 return ('osqaadmin/edit_page.html', {
382 'published': published
385 @admin_tools_page(_('createuser'), _("Create new user"))
386 def create_user(request):
388 form = CreateUserForm(request.POST)
391 user_ = User(username=form.cleaned_data['username'], email=form.cleaned_data['email'])
392 user_.set_password(form.cleaned_data['password1'])
394 if not form.cleaned_data.get('validate_email', False):
395 user_.email_isvalid = True
398 UserJoinsAction(user=user_).save()
400 request.user.message_set.create(message=_("New user created sucessfully. %s.") % html.hyperlink(
401 user_.get_profile_url(), _("See %s profile") % user_.username, target="_blank"))
403 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'createuser'}))
405 form = CreateUserForm()
407 return ('osqaadmin/createuser.html', {
411 class NodeManagementPaginatorContext(pagination.PaginatorContext):
412 def __init__(self, id='QUESTIONS_LIST', prefix='', default_pagesize=100):
413 super (NodeManagementPaginatorContext, self).__init__(id, sort_methods=(
414 (_('added_at'), pagination.SimpleSort(_('added_at'), '-added_at', "")),
415 (_('added_at_asc'), pagination.SimpleSort(_('added_at_asc'), 'added_at', "")),
416 (_('author'), pagination.SimpleSort(_('author'), '-author__username', "")),
417 (_('author_asc'), pagination.SimpleSort(_('author_asc'), 'author__username', "")),
418 (_('score'), pagination.SimpleSort(_('score'), '-score', "")),
419 (_('score_asc'), pagination.SimpleSort(_('score_asc'), 'score', "")),
420 (_('act_at'), pagination.SimpleSort(_('act_at'), '-last_activity_at', "")),
421 (_('act_at_asc'), pagination.SimpleSort(_('act_at_asc'), 'last_activity_at', "")),
422 (_('act_by'), pagination.SimpleSort(_('act_by'), '-last_activity_by__username', "")),
423 (_('act_by_asc'), pagination.SimpleSort(_('act_by_asc'), 'last_activity_by__username', "")),
424 ), pagesizes=(default_pagesize,), force_sort='added_at', default_pagesize=default_pagesize, prefix=prefix)
426 @admin_tools_page(_("nodeman"), _("Bulk management"))
427 def node_management(request):
429 params = pagination.generate_uri(request.GET, ('page',))
431 if "save_filter" in request.POST:
432 filter_name = request.POST.get('filter_name', _('filter'))
433 params = pagination.generate_uri(request.GET, ('page',))
434 current_filters = settings.NODE_MAN_FILTERS.value
435 current_filters.append((filter_name, params))
436 settings.NODE_MAN_FILTERS.set_value(current_filters)
438 elif r"execute" in request.POST:
439 selected_nodes = request.POST.getlist('_selected_node')
441 if selected_nodes and request.POST.get('action', None):
442 action = request.POST['action']
443 selected_nodes = Node.objects.filter(id__in=selected_nodes)
445 message = _("No action performed")
447 if action == 'delete_selected':
448 for node in selected_nodes:
449 if node.node_type in ('question', 'answer', 'comment') and (not node.nis.deleted):
450 DeleteAction(user=request.user, node=node, ip=request.META['REMOTE_ADDR']).save()
452 message = _("All selected nodes marked as deleted")
454 if action == 'undelete_selected':
455 for node in selected_nodes:
456 if node.node_type in ('question', 'answer', 'comment') and (node.nis.deleted):
457 node.nstate.deleted.cancel(ip=request.META['REMOTE_ADDR'])
459 message = _("All selected nodes undeleted")
461 if action == "close_selected":
462 for node in selected_nodes:
463 if node.node_type == "question" and (not node.nis.closed):
464 CloseAction(node=node.leaf, user=request.user, extra=_("bulk close"), ip=request.META['REMOTE_ADDR']).save()
466 message = _("Selected questions were closed")
468 if action == "hard_delete_selected":
469 ids = [n.id for n in selected_nodes]
473 node = Node.objects.get(id=id)
478 message = _("All selected nodes deleted")
480 request.user.message_set.create(message=message)
482 params = pagination.generate_uri(request.GET, ('page',))
484 return HttpResponseRedirect(reverse("admin_tools", kwargs={'name': 'nodeman'}) + "?" + params)
487 nodes = Node.objects.all()
489 text = request.GET.get('text', '')
490 text_in = request.GET.get('text_in', 'body')
492 authors = request.GET.getlist('authors')
493 tags = request.GET.getlist('tags')
495 type_filter = request.GET.getlist('node_type')
496 state_filter = request.GET.getlist('state_type')
497 state_filter_type = request.GET.get('state_filter_type', 'any')
500 nodes = nodes.filter(node_type__in=type_filter)
502 state_types = NodeState.objects.filter(node__in=nodes).values_list('state_type', flat=True).distinct('state_type')
503 state_filter = [s for s in state_filter if s in state_types]
506 if state_filter_type == 'all':
507 nodes = nodes.all_states(*state_filter)
509 nodes = nodes.any_state(*state_filter)
512 nodes = nodes.filter(author__id__in=authors)
513 authors = User.objects.filter(id__in=authors)
516 nodes = nodes.filter(tags__id__in=tags)
517 tags = Tag.objects.filter(id__in=tags)
520 text_in = request.GET.get('text_in', 'body')
523 if text_in == 'title' or text_in == 'both':
524 filter = models.Q(title__icontains=text)
526 if text_in == 'body' or text_in == 'both':
527 sec_filter = models.Q(body__icontains=text)
529 filter = filter | sec_filter
534 nodes = nodes.filter(filter)
536 node_types = [(k, n.friendly_name) for k, n in NodeMetaClass.types.items()]
538 return ('osqaadmin/nodeman.html', pagination.paginated(request, ("nodes", NodeManagementPaginatorContext()), {
542 'type_filter': type_filter,
543 'state_filter': state_filter,
544 'state_filter_type': state_filter_type,
545 'node_types': node_types,
546 'state_types': state_types,
553 def test_email_settings(request):
556 send_template_email([user,], 'osqaadmin/mail_test.html', { 'user' : user })
558 return render_to_response(
559 'osqaadmin/test_email_settings.html',
561 RequestContext(request)