1 from forum.models import User
2 from django.db.models import Q, Count
3 from django.core.paginator import Paginator, EmptyPage, InvalidPage
4 from django.template.defaultfilters import slugify
5 from django.contrib.contenttypes.models import ContentType
6 from django.core.urlresolvers import reverse
7 from django.shortcuts import render_to_response, get_object_or_404
8 from django.template import RequestContext
9 from django.http import HttpResponse, HttpResponseRedirect, Http404
10 from forum.http_responses import HttpResponseUnauthorized
11 from django.utils.translation import ugettext as _
12 from django.utils.http import urlquote_plus
13 from django.utils.html import strip_tags
14 from django.utils.encoding import smart_unicode
15 from django.core.urlresolvers import reverse, NoReverseMatch
16 from forum.forms import *
17 from forum.utils.html import sanitize_html
18 from forum.modules import decorate, ReturnImediatelyException
19 from datetime import datetime, date
20 from forum.actions import EditProfileAction, FavoriteAction, BonusRepAction, SuspendAction, ReportAction
21 from forum.modules import ui
22 from forum.utils import pagination
23 from forum.views.readers import QuestionListPaginatorContext, AnswerPaginatorContext
24 from forum.settings import ONLINE_USERS
26 from django.contrib import messages
33 class UserReputationSort(pagination.SimpleSort):
34 def apply(self, objects):
35 return objects.order_by('-is_active', self.order_by)
37 class UserListPaginatorContext(pagination.PaginatorContext):
38 def __init__(self, pagesizes=(20, 35, 60), default_pagesize=35):
39 super (UserListPaginatorContext, self).__init__('USERS_LIST', sort_methods=(
40 (_('reputation'), UserReputationSort(_('reputation'), '-reputation', _("sorted by reputation"))),
41 (_('newest'), pagination.SimpleSort(_('recent'), '-date_joined', _("newest members"))),
42 (_('last'), pagination.SimpleSort(_('oldest'), 'date_joined', _("oldest members"))),
43 (_('name'), pagination.SimpleSort(_('by username'), 'username', _("sorted by username"))),
44 ), pagesizes=pagesizes, default_pagesize=default_pagesize)
46 class SubscriptionListPaginatorContext(pagination.PaginatorContext):
48 super (SubscriptionListPaginatorContext, self).__init__('SUBSCRIPTION_LIST', pagesizes=(5, 10, 20), default_pagesize=20)
50 class UserAnswersPaginatorContext(pagination.PaginatorContext):
52 super (UserAnswersPaginatorContext, self).__init__('USER_ANSWER_LIST', sort_methods=(
53 (_('oldest'), pagination.SimpleSort(_('oldest answers'), 'added_at', _("oldest answers will be shown first"))),
54 (_('newest'), pagination.SimpleSort(_('newest answers'), '-added_at', _("newest answers will be shown first"))),
55 (_('votes'), pagination.SimpleSort(_('popular answers'), '-score', _("most voted answers will be shown first"))),
56 ), default_sort=_('votes'), pagesizes=(5, 10, 20), default_pagesize=20, prefix=_('answers'))
58 USERS_PAGE_SIZE = 35# refactor - move to some constants file
60 @decorators.render('users/users.html', 'users', _('users'), weight=200)
62 suser = request.REQUEST.get('q', "")
63 users = User.objects.all()
66 users = users.filter(username__icontains=suser)
68 return pagination.paginated(request, ('users', UserListPaginatorContext()), {
74 @decorators.render('users/online_users.html', 'online_users', _('Online Users'), weight=200, tabbed=False)
75 def online_users(request):
76 suser = request.REQUEST.get('q', "")
79 if request.GET.get("sort", None):
81 sort = int(request.GET["sort"])
83 logging.error('Found invalid sort "%s", loading %s, refered by %s' % (
84 request.GET.get("sort", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
89 if request.GET.get("page", None):
91 page = int(request.GET["page"])
93 logging.error('Found invalid page "%s", loading %s, refered by %s' % (
94 request.GET.get("page", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
99 if request.GET.get("pagesize", None):
101 pagesize = int(request.GET["pagesize"])
103 logging.error('Found invalid pagesize "%s", loading %s, refered by %s' % (
104 request.GET.get("pagesize", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
110 if sort == "reputation":
111 users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.reputation)
112 elif sort == "newest" :
113 users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.newest)
115 users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.last)
117 users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.name)
118 elif sort == "oldest":
119 users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.oldest)
120 elif sort == "newest":
121 users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.newest)
122 elif sort == "votes":
123 users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.votes)
125 users = sorted(ONLINE_USERS.iteritems(), key=lambda x: x[1])
127 return render_to_response('users/online_users.html', {
132 "pageSize" : pagesize,
136 def edit_user(request, id, slug):
137 user = get_object_or_404(User, id=id)
138 if not (request.user.is_superuser or request.user == user):
139 return HttpResponseUnauthorized(request)
140 if request.method == "POST":
141 form = EditUserForm(user, request.POST)
143 new_email = sanitize_html(form.cleaned_data['email'])
145 if new_email != user.email:
146 user.email = new_email
147 user.email_isvalid = False
150 hash = ValidationHash.objects.get(user=request.user, type='email')
155 if settings.EDITABLE_SCREEN_NAME:
156 user.username = sanitize_html(form.cleaned_data['username'])
157 user.real_name = sanitize_html(form.cleaned_data['realname'])
158 user.website = sanitize_html(form.cleaned_data['website'])
159 user.location = sanitize_html(form.cleaned_data['city'])
160 user.date_of_birth = form.cleaned_data['birthday']
161 if user.date_of_birth == "None":
162 user.date_of_birth = datetime(1900, 1, 1, 0, 0)
163 user.about = sanitize_html(form.cleaned_data['about'])
166 EditProfileAction(user=user, ip=request.META['REMOTE_ADDR']).save()
168 messages.info(request, _("Profile updated."))
169 return HttpResponseRedirect(user.get_profile_url())
171 form = EditUserForm(user)
172 return render_to_response('users/edit.html', {
175 'gravatar_faq_url' : reverse('faq') + '#gravatar',
176 }, context_instance=RequestContext(request))
179 @decorate.withfn(decorators.command)
180 def user_powers(request, id, action, status):
181 if not request.user.is_superuser:
182 raise decorators.CommandException(_("Only superusers are allowed to alter other users permissions."))
184 if (action == 'remove' and 'status' == 'super') and not request.user.is_siteowner():
185 raise decorators.CommandException(_("Only the site owner can remove the super user status from other user."))
187 user = get_object_or_404(User, id=id)
188 new_state = action == 'grant'
190 if status == 'super':
191 user.is_superuser = new_state
192 elif status == 'staff':
193 user.is_staff = new_state
198 return decorators.RefreshPageCommand()
201 @decorate.withfn(decorators.command)
202 def award_points(request, id):
204 return render_to_response('users/karma_bonus.html')
206 if not request.user.is_superuser:
207 raise decorators.CommandException(_("Only superusers are allowed to award reputation points"))
210 points = int(request.POST['points'])
212 raise decorators.CommandException(_("Invalid number of points to award."))
214 user = get_object_or_404(User, id=id)
216 extra = dict(message=request.POST.get('message', ''), awarding_user=request.user.id, value=points)
218 BonusRepAction(user=request.user, extra=extra).save(data=dict(value=points, affected=user))
220 return {'commands': {
221 'update_profile_karma': [user.reputation]
225 @decorate.withfn(decorators.command)
226 def suspend(request, id):
227 user = get_object_or_404(User, id=id)
229 if not request.user.is_superuser:
230 raise decorators.CommandException(_("Only superusers can suspend other users"))
232 if not request.POST.get('bantype', None):
233 if user.is_suspended():
234 suspension = user.suspension
235 suspension.cancel(user=request.user, ip=request.META['REMOTE_ADDR'])
236 return decorators.RefreshPageCommand()
238 return render_to_response('users/suspend_user.html')
241 'bantype': request.POST.get('bantype', 'Indefinitely').strip(),
242 'publicmsg': request.POST.get('publicmsg', _('Bad behaviour')),
243 'privatemsg': request.POST.get('privatemsg', None) or request.POST.get('publicmsg', ''),
247 if data['bantype'] == 'forxdays':
249 data['forxdays'] = int(request.POST['forxdays'])
251 raise decorators.CommandException(_('Invalid numeric argument for the number of days.'))
253 SuspendAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)
255 return decorators.RefreshPageCommand()
257 @decorate.withfn(decorators.command)
258 def report_user(request, id):
259 user = get_object_or_404(User, id=id)
261 if not request.POST.get('publicmsg', None):
262 return render_to_response('users/report_user.html')
265 'publicmsg': request.POST.get('publicmsg', _('N/A')),
269 ReportAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)
272 return decorators.RefreshPageCommand()
276 def user_view(template, tab_name, tab_title, tab_description, private=False, tabbed=True, render_to=None, weight=500):
278 def params(request, id=None, slug=None):
279 # Get the user object by id if the id parameter has been passed
281 user = get_object_or_404(User, id=id)
282 # ...or by slug if the slug has been given
283 elif slug is not None:
285 user = User.objects.get(username__iexact=slug)
286 except User.DoesNotExist:
289 if private and not (user == request.user or request.user.is_superuser):
290 raise ReturnImediatelyException(HttpResponseUnauthorized(request))
292 if render_to and (not render_to(user)):
293 raise ReturnImediatelyException(HttpResponseRedirect(user.get_profile_url()))
295 return [request, user], { 'slug' : slug, }
297 decorated = decorate.params.withfn(params)(fn)
299 def result(context_or_response, request, user, **kwargs):
300 rev_page_title = smart_unicode(user.username) + " - " + tab_description
302 # Check whether the return type of the decorated function is a context or Http Response
303 if isinstance(context_or_response, HttpResponse):
304 response = context_or_response
306 # If it is a response -- show it
309 # ...if it is a context move forward, update it and render it to response
310 context = context_or_response
314 "active_tab" : tab_name,
315 "tab_description" : tab_description,
316 "page_title" : rev_page_title,
317 "can_view_private": (user == request.user) or request.user.is_superuser
319 return render_to_response(template, context, context_instance=RequestContext(request))
321 decorated = decorate.result.withfn(result, needs_params=True)(decorated)
326 return reverse(fn.__name__, kwargs={'id': vu.id, 'slug': slugify(smart_unicode(vu.username))})
327 except NoReverseMatch:
329 return reverse(fn.__name__, kwargs={'id': vu.id})
330 except NoReverseMatch:
331 return reverse(fn.__name__, kwargs={'slug': slugify(smart_unicode(vu.username))})
333 ui.register(ui.PROFILE_TABS, ui.ProfileTab(
334 tab_name, tab_title, tab_description,url_getter, private, render_to, weight
341 @user_view('users/stats.html', 'stats', _('overview'), _('user overview'))
342 def user_profile(request, user, **kwargs):
343 questions = Question.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')
344 answers = Answer.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')
346 # Check whether the passed slug matches the one for the user object
347 slug = kwargs['slug']
348 if slug != slugify(smart_unicode(user.username)):
349 return HttpResponseRedirect(user.get_absolute_url())
351 up_votes = user.vote_up_count
352 down_votes = user.vote_down_count
353 votes_today = user.get_vote_count_today()
354 votes_total = user.can_vote_count_today()
356 user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \
357 .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')
359 awards = [(Badge.objects.get(id=b['id']), b['count']) for b in
360 Badge.objects.filter(awards__user=user).values('id').annotate(count=Count('cls')).order_by('-count')]
362 return pagination.paginated(request, (
363 ('questions', QuestionListPaginatorContext('USER_QUESTION_LIST', _('questions'), default_pagesize=15)),
364 ('answers', UserAnswersPaginatorContext())), {
366 "questions" : questions,
368 "up_votes" : up_votes,
369 "down_votes" : down_votes,
370 "total_votes": up_votes + down_votes,
371 "votes_today_left": votes_total-votes_today,
372 "votes_total_per_day": votes_total,
373 "user_tags" : user_tags[:50],
375 "total_awards" : len(awards),
378 @user_view('users/recent.html', 'recent', _('recent activity'), _('recent user activity'))
379 def user_recent(request, user, **kwargs):
380 activities = user.actions.exclude(
381 action_type__in=("voteup", "votedown", "voteupcomment", "flag", "newpage", "editpage")).order_by(
382 '-action_date')[:USERS_PAGE_SIZE]
384 return {"view_user" : user, "activities" : activities}
387 @user_view('users/reputation.html', 'reputation', _('reputation history'), _('graph of user karma'))
388 def user_reputation(request, user, **kwargs):
389 rep = list(user.reputes.order_by('date'))
390 values = [r.value for r in rep]
391 redux = lambda x, y: x+y
393 graph_data = json.dumps([
394 (time.mktime(rep[i].date.timetuple()) * 1000, reduce(redux, values[:i+1], 0))
395 for i in range(len(values))
398 rep = user.reputes.filter(action__canceled=False).order_by('-date')[0:20]
400 return {"view_user": user, "reputation": rep, "graph_data": graph_data}
402 @user_view('users/votes.html', 'votes', _('votes'), _('user vote record'), True)
403 def user_votes(request, user, **kwargs):
404 votes = user.votes.exclude(node__state_string__contains="(deleted").filter(
405 node__node_type__in=("question", "answer")).order_by('-voted_at')[:USERS_PAGE_SIZE]
407 return {"view_user" : user, "votes" : votes}
409 @user_view('users/questions.html', 'favorites', _('favorites'), _('questions that user selected as his/her favorite'))
410 def user_favorites(request, user, **kwargs):
411 favorites = FavoriteAction.objects.filter(canceled=False, user=user)
413 return {"favorites" : favorites, "view_user" : user}
415 @user_view('users/subscriptions.html', 'subscriptions', _('subscription'), _('subscriptions'), True, tabbed=False)
416 def user_subscriptions(request, user, **kwargs):
417 return _user_subscriptions(request, user, **kwargs)
419 def _user_subscriptions(request, user, **kwargs):
422 tab = request.GET.get('tab', "settings")
428 auto = request.GET.get('auto', 'True')
431 subscriptions = QuestionSubscription.objects.filter(user=user).order_by('-last_view')
434 subscriptions = QuestionSubscription.objects.filter(user=user, auto_subscription=False).order_by('-last_view')
436 return pagination.paginated(request, ('subscriptions', SubscriptionListPaginatorContext()), {
437 'subscriptions':subscriptions,
440 'manage_open':manage_open,
442 # Settings Tab and everything else
445 if request.method == 'POST':
447 form = SubscriptionSettingsForm(data=request.POST, instance=user.subscription_settings)
451 message = _('New subscription settings are now saved')
453 user.subscription_settings.enable_notifications = enabled
454 user.subscription_settings.save()
456 messages.info(request, message)
458 form = SubscriptionSettingsForm(instance=user.subscription_settings)
462 'notificatons_on': enabled,
464 'manage_open':manage_open,
467 @user_view('users/preferences.html', 'preferences', _('preferences'), _('preferences'), True, tabbed=False)
468 def user_preferences(request, user, **kwargs):
470 form = UserPreferencesForm(request.POST)
473 user.prop.preferences = form.cleaned_data
474 messages.info(request, _('New preferences saved'))
477 preferences = user.prop.preferences
480 form = UserPreferencesForm(initial=preferences)
482 form = UserPreferencesForm()
484 return {'view_user': user, 'form': form}