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 awarding_user = get_object_or_404(User, id=request.user.pk)
216 if points > awarding_user.reputation:
217 raise decorators.CommandException(_("Invalid number of points to award."))
219 user = get_object_or_404(User, id=id)
221 extra = dict(message=request.POST.get('message', ''), awarding_user=request.user.id, value=points)
223 BonusRepAction(user=user, extra=extra).save(data=dict(value=points, affected=user))
225 return {'commands': {
226 'update_profile_karma': [user.reputation]
230 @decorate.withfn(decorators.command)
231 def suspend(request, id):
232 user = get_object_or_404(User, id=id)
234 if not request.user.is_superuser:
235 raise decorators.CommandException(_("Only superusers can suspend other users"))
237 if not request.POST.get('bantype', None):
238 if user.is_suspended():
239 suspension = user.suspension
240 suspension.cancel(user=request.user, ip=request.META['REMOTE_ADDR'])
241 return decorators.RefreshPageCommand()
243 return render_to_response('users/suspend_user.html')
246 'bantype': request.POST.get('bantype', 'Indefinitely').strip(),
247 'publicmsg': request.POST.get('publicmsg', _('Bad behaviour')),
248 'privatemsg': request.POST.get('privatemsg', None) or request.POST.get('publicmsg', ''),
252 if data['bantype'] == 'forxdays':
254 data['forxdays'] = int(request.POST['forxdays'])
256 raise decorators.CommandException(_('Invalid numeric argument for the number of days.'))
258 SuspendAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)
260 return decorators.RefreshPageCommand()
262 @decorate.withfn(decorators.command)
263 def report_user(request, id):
264 user = get_object_or_404(User, id=id)
266 if not request.POST.get('publicmsg', None):
267 return render_to_response('users/report_user.html')
270 'publicmsg': request.POST.get('publicmsg', _('N/A')),
274 ReportAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)
277 return decorators.RefreshPageCommand()
281 def user_view(template, tab_name, tab_title, tab_description, private=False, tabbed=True, render_to=None, weight=500):
283 def params(request, id=None, slug=None):
284 # Get the user object by id if the id parameter has been passed
286 user = get_object_or_404(User, id=id)
287 # ...or by slug if the slug has been given
288 elif slug is not None:
290 user = User.objects.get(username__iexact=slug)
291 except User.DoesNotExist:
294 if private and not (user == request.user or request.user.is_superuser):
295 raise ReturnImediatelyException(HttpResponseUnauthorized(request))
297 if render_to and (not render_to(user)):
298 raise ReturnImediatelyException(HttpResponseRedirect(user.get_profile_url()))
300 return [request, user], { 'slug' : slug, }
302 decorated = decorate.params.withfn(params)(fn)
304 def result(context_or_response, request, user, **kwargs):
305 rev_page_title = smart_unicode(user.username) + " - " + tab_description
307 # Check whether the return type of the decorated function is a context or Http Response
308 if isinstance(context_or_response, HttpResponse):
309 response = context_or_response
311 # If it is a response -- show it
314 # ...if it is a context move forward, update it and render it to response
315 context = context_or_response
319 "active_tab" : tab_name,
320 "tab_description" : tab_description,
321 "page_title" : rev_page_title,
322 "can_view_private": (user == request.user) or request.user.is_superuser
324 return render_to_response(template, context, context_instance=RequestContext(request))
326 decorated = decorate.result.withfn(result, needs_params=True)(decorated)
331 return reverse(fn.__name__, kwargs={'id': vu.id, 'slug': slugify(smart_unicode(vu.username))})
332 except NoReverseMatch:
334 return reverse(fn.__name__, kwargs={'id': vu.id})
335 except NoReverseMatch:
336 return reverse(fn.__name__, kwargs={'slug': slugify(smart_unicode(vu.username))})
338 ui.register(ui.PROFILE_TABS, ui.ProfileTab(
339 tab_name, tab_title, tab_description,url_getter, private, render_to, weight
346 @user_view('users/stats.html', 'stats', _('overview'), _('user overview'))
347 def user_profile(request, user, **kwargs):
348 questions = Question.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')
349 answers = Answer.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')
351 # Check whether the passed slug matches the one for the user object
352 slug = kwargs['slug']
353 if slug != slugify(smart_unicode(user.username)):
354 return HttpResponseRedirect(user.get_absolute_url())
356 up_votes = user.vote_up_count
357 down_votes = user.vote_down_count
358 votes_today = user.get_vote_count_today()
359 votes_total = user.can_vote_count_today()
361 user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \
362 .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')
364 awards = [(Badge.objects.get(id=b['id']), b['count']) for b in
365 Badge.objects.filter(awards__user=user).values('id').annotate(count=Count('cls')).order_by('-count')]
367 return pagination.paginated(request, (
368 ('questions', QuestionListPaginatorContext('USER_QUESTION_LIST', _('questions'), default_pagesize=15)),
369 ('answers', UserAnswersPaginatorContext())), {
371 "questions" : questions,
373 "up_votes" : up_votes,
374 "down_votes" : down_votes,
375 "total_votes": up_votes + down_votes,
376 "votes_today_left": votes_total-votes_today,
377 "votes_total_per_day": votes_total,
378 "user_tags" : user_tags[:50],
380 "total_awards" : len(awards),
383 @user_view('users/recent.html', 'recent', _('recent activity'), _('recent user activity'))
384 def user_recent(request, user, **kwargs):
385 activities = user.actions.exclude(
386 action_type__in=("voteup", "votedown", "voteupcomment", "flag", "newpage", "editpage")).order_by(
387 '-action_date')[:USERS_PAGE_SIZE]
389 return {"view_user" : user, "activities" : activities}
392 @user_view('users/reputation.html', 'reputation', _('reputation history'), _('graph of user karma'))
393 def user_reputation(request, user, **kwargs):
394 rep = list(user.reputes.order_by('date'))
395 values = [r.value for r in rep]
396 redux = lambda x, y: x+y
398 graph_data = json.dumps([
399 (time.mktime(rep[i].date.timetuple()) * 1000, reduce(redux, values[:i+1], 0))
400 for i in range(len(values))
403 rep = user.reputes.filter(action__canceled=False).order_by('-date')[0:20]
405 return {"view_user": user, "reputation": rep, "graph_data": graph_data}
407 @user_view('users/votes.html', 'votes', _('votes'), _('user vote record'), True)
408 def user_votes(request, user, **kwargs):
409 votes = user.votes.exclude(node__state_string__contains="(deleted").filter(
410 node__node_type__in=("question", "answer")).order_by('-voted_at')[:USERS_PAGE_SIZE]
412 return {"view_user" : user, "votes" : votes}
414 @user_view('users/questions.html', 'favorites', _('favorites'), _('questions that user selected as his/her favorite'))
415 def user_favorites(request, user, **kwargs):
416 favorites = FavoriteAction.objects.filter(canceled=False, user=user)
418 return {"favorites" : favorites, "view_user" : user}
420 @user_view('users/subscriptions.html', 'subscriptions', _('subscription'), _('subscriptions'), True, tabbed=False)
421 def user_subscriptions(request, user, **kwargs):
422 return _user_subscriptions(request, user, **kwargs)
424 def _user_subscriptions(request, user, **kwargs):
427 tab = request.GET.get('tab', "settings")
433 auto = request.GET.get('auto', 'True')
436 subscriptions = QuestionSubscription.objects.filter(user=user).order_by('-last_view')
439 subscriptions = QuestionSubscription.objects.filter(user=user, auto_subscription=False).order_by('-last_view')
441 return pagination.paginated(request, ('subscriptions', SubscriptionListPaginatorContext()), {
442 'subscriptions':subscriptions,
445 'manage_open':manage_open,
447 # Settings Tab and everything else
450 if request.method == 'POST':
452 form = SubscriptionSettingsForm(data=request.POST, instance=user.subscription_settings)
456 message = _('New subscription settings are now saved')
458 user.subscription_settings.enable_notifications = enabled
459 user.subscription_settings.save()
461 messages.info(request, message)
463 form = SubscriptionSettingsForm(instance=user.subscription_settings)
467 'notificatons_on': enabled,
469 'manage_open':manage_open,
472 @user_view('users/preferences.html', 'preferences', _('preferences'), _('preferences'), True, tabbed=False)
473 def user_preferences(request, user, **kwargs):
475 form = UserPreferencesForm(request.POST)
478 user.prop.preferences = form.cleaned_data
479 messages.info(request, _('New preferences saved'))
482 preferences = user.prop.preferences
485 form = UserPreferencesForm(initial=preferences)
487 form = UserPreferencesForm()
489 return {'view_user': user, 'form': form}