]> git.openstreetmap.org Git - osqa.git/blob - forum/views/users.py
df38159d9482907e0bad9c80e2a7d8889ba2fe7f
[osqa.git] / forum / views / users.py
1 from forum.models import User\r
2 from django.db.models import Q, Count\r
3 from django.core.paginator import Paginator, EmptyPage, InvalidPage\r
4 from django.template.defaultfilters import slugify\r
5 from django.contrib.contenttypes.models import ContentType\r
6 from django.core.urlresolvers import reverse\r
7 from django.shortcuts import render_to_response, get_object_or_404\r
8 from django.template import RequestContext\r
9 from django.http import HttpResponse, HttpResponseRedirect, Http404\r
10 from forum.http_responses import HttpResponseUnauthorized\r
11 from django.utils.translation import ugettext as _\r
12 from django.utils.http import urlquote_plus\r
13 from django.utils.html import strip_tags\r
14 from django.utils.encoding import smart_unicode\r
15 from django.utils import simplejson\r
16 from django.core.urlresolvers import reverse, NoReverseMatch\r
17 from forum.forms import *\r
18 from forum.utils.html import sanitize_html\r
19 from forum.modules import decorate, ReturnImediatelyException\r
20 from datetime import datetime, date\r
21 from forum.actions import EditProfileAction, FavoriteAction, BonusRepAction, SuspendAction\r
22 from forum.modules import ui\r
23 from forum.utils import pagination\r
24 from forum.views.readers import QuestionListPaginatorContext, AnswerPaginatorContext\r
25 from forum.settings import ONLINE_USERS\r
26  \r
27 import time\r
28 import datetime\r
29 import decorators\r
30 \r
31 class UserReputationSort(pagination.SimpleSort):\r
32     def apply(self, objects):\r
33         return objects.order_by('-is_active', self.order_by)\r
34 \r
35 class UserListPaginatorContext(pagination.PaginatorContext):\r
36     def __init__(self, pagesizes=(20, 35, 60)):\r
37         super (UserListPaginatorContext, self).__init__('USERS_LIST', sort_methods=(\r
38             (_('reputation'), UserReputationSort(_('reputation'), '-reputation', _("sorted by reputation"))),\r
39             (_('newest'), pagination.SimpleSort(_('recent'), '-date_joined', _("newest members"))),\r
40             (_('last'), pagination.SimpleSort(_('oldest'), 'date_joined', _("oldest members"))),\r
41             (_('name'), pagination.SimpleSort(_('by username'), 'username', _("sorted by username"))),\r
42         ), pagesizes=pagesizes)\r
43 \r
44 class SubscriptionListPaginatorContext(pagination.PaginatorContext):\r
45     def __init__(self):\r
46         super (SubscriptionListPaginatorContext, self).__init__('SUBSCRIPTION_LIST', pagesizes=(5, 10, 20), default_pagesize=20)\r
47 \r
48 class UserAnswersPaginatorContext(pagination.PaginatorContext):\r
49     def __init__(self):\r
50         super (UserAnswersPaginatorContext, self).__init__('USER_ANSWER_LIST', sort_methods=(\r
51             (_('oldest'), pagination.SimpleSort(_('oldest answers'), 'added_at', _("oldest answers will be shown first"))),\r
52             (_('newest'), pagination.SimpleSort(_('newest answers'), '-added_at', _("newest answers will be shown first"))),\r
53             (_('votes'), pagination.SimpleSort(_('popular answers'), '-score', _("most voted answers will be shown first"))),\r
54         ), default_sort=_('votes'), pagesizes=(5, 10, 20), default_pagesize=20, prefix=_('answers'))\r
55 \r
56 USERS_PAGE_SIZE = 35# refactor - move to some constants file\r
57 \r
58 @decorators.render('users/users.html', 'users', _('users'), weight=200)\r
59 def users(request):\r
60     suser = request.REQUEST.get('q', "")\r
61     users = User.objects.all()\r
62 \r
63     if suser != "":\r
64         users = users.filter(username__icontains=suser)\r
65 \r
66     return pagination.paginated(request, ('users', UserListPaginatorContext()), {\r
67         "users" : users,\r
68         "suser" : suser,\r
69     })\r
70 \r
71 \r
72 @decorators.render('users/online_users.html', 'online_users', _('Online Users'), weight=200, tabbed=False)\r
73 def online_users(request):\r
74     suser = request.REQUEST.get('q', "")\r
75 \r
76     sort = ""\r
77     if request.GET.get("sort", None):\r
78         try:\r
79             sort = int(request.GET["sort"])\r
80         except ValueError:\r
81             logging.error('Found invalid sort "%s", loading %s, refered by %s' % (\r
82                 request.GET.get("sort", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')\r
83             ))\r
84             raise Http404()\r
85 \r
86     page = 0\r
87     if request.GET.get("page", None):\r
88         try:\r
89             page = int(request.GET["page"])\r
90         except ValueError:\r
91             logging.error('Found invalid page "%s", loading %s, refered by %s' % (\r
92                 request.GET.get("page", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')\r
93             ))\r
94             raise Http404()\r
95 \r
96     pagesize = 10\r
97     if request.GET.get("pagesize", None):\r
98         try:\r
99             pagesize = int(request.GET["pagesize"])\r
100         except ValueError:\r
101             logging.error('Found invalid pagesize "%s", loading %s, refered by %s' % (\r
102                 request.GET.get("pagesize", ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')\r
103             ))\r
104             raise Http404()\r
105 \r
106 \r
107     users = None\r
108     if sort == "reputation":\r
109         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.reputation)\r
110     elif sort == "newest" :\r
111         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.newest)\r
112     elif sort == "last":\r
113         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.last)\r
114     elif sort == "name":\r
115         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.name)\r
116     elif sort == "oldest":\r
117         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.oldest)\r
118     elif sort == "newest":\r
119         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.newest)\r
120     elif sort == "votes":\r
121         users = sorted(ONLINE_USERS.sets.keys(), key=lambda user: user.votes)\r
122     else:\r
123         users = sorted(ONLINE_USERS.iteritems(), key=lambda x: x[1])\r
124 \r
125     return render_to_response('users/online_users.html', {\r
126         "users" : users,\r
127         "suser" : suser,\r
128         "sort" : sort,\r
129         "page" : page,\r
130         "pageSize" : pagesize,\r
131     })\r
132 \r
133 \r
134 def edit_user(request, id, slug):\r
135     user = get_object_or_404(User, id=id)\r
136     if not (request.user.is_superuser or request.user == user):\r
137         return HttpResponseUnauthorized(request)\r
138     if request.method == "POST":\r
139         form = EditUserForm(user, request.POST)\r
140         if form.is_valid():\r
141             new_email = sanitize_html(form.cleaned_data['email'])\r
142 \r
143             if new_email != user.email:\r
144                 user.email = new_email\r
145                 user.email_isvalid = False\r
146 \r
147                 try:\r
148                     hash = ValidationHash.objects.get(user=request.user, type='email')\r
149                     hash.delete()\r
150                 except:\r
151                     pass\r
152 \r
153             if settings.EDITABLE_SCREEN_NAME:\r
154                 user.username = sanitize_html(form.cleaned_data['username'])\r
155             user.real_name = sanitize_html(form.cleaned_data['realname'])\r
156             user.website = sanitize_html(form.cleaned_data['website'])\r
157             user.location = sanitize_html(form.cleaned_data['city'])\r
158             user.date_of_birth = form.cleaned_data['birthday']\r
159             if user.date_of_birth == "None":\r
160                 user.date_of_birth = datetime(1900, 1, 1, 0, 0)\r
161             user.about = sanitize_html(form.cleaned_data['about'])\r
162 \r
163             user.save()\r
164             EditProfileAction(user=user, ip=request.META['REMOTE_ADDR']).save()\r
165 \r
166             request.user.message_set.create(message=_("Profile updated."))\r
167             return HttpResponseRedirect(user.get_profile_url())\r
168     else:\r
169         form = EditUserForm(user)\r
170     return render_to_response('users/edit.html', {\r
171     'user': user,\r
172     'form' : form,\r
173     'gravatar_faq_url' : reverse('faq') + '#gravatar',\r
174     }, context_instance=RequestContext(request))\r
175 \r
176 \r
177 @decorate.withfn(decorators.command)\r
178 def user_powers(request, id, action, status):\r
179     if not request.user.is_superuser:\r
180         raise decorators.CommandException(_("Only superusers are allowed to alter other users permissions."))\r
181 \r
182     if (action == 'remove' and 'status' == 'super') and not request.user.is_siteowner():\r
183         raise decorators.CommandException(_("Only the site owner can remove the super user status from other user."))\r
184 \r
185     user = get_object_or_404(User, id=id)\r
186     new_state = action == 'grant'\r
187 \r
188     if status == 'super':\r
189         user.is_superuser = new_state\r
190     elif status == 'staff':\r
191         user.is_staff = new_state\r
192     else:\r
193         raise Http404()\r
194 \r
195     user.save()\r
196     return decorators.RefreshPageCommand()\r
197 \r
198 \r
199 @decorate.withfn(decorators.command)\r
200 def award_points(request, id):\r
201     if not request.POST:\r
202         return render_to_response('users/karma_bonus.html')\r
203 \r
204     if not request.user.is_superuser:\r
205         raise decorators.CommandException(_("Only superusers are allowed to award reputation points"))\r
206 \r
207     try:\r
208         points = int(request.POST['points'])\r
209     except:\r
210         raise decorators.CommandException(_("Invalid number of points to award."))\r
211 \r
212     user = get_object_or_404(User, id=id)\r
213 \r
214     extra = dict(message=request.POST.get('message', ''), awarding_user=request.user.id, value=points)\r
215 \r
216     BonusRepAction(user=request.user, extra=extra).save(data=dict(value=points, affected=user))\r
217 \r
218     return {'commands': {\r
219             'update_profile_karma': [user.reputation]\r
220         }}\r
221     \r
222 \r
223 @decorate.withfn(decorators.command)\r
224 def suspend(request, id):\r
225     user = get_object_or_404(User, id=id)\r
226 \r
227     if not request.user.is_superuser:\r
228         raise decorators.CommandException(_("Only superusers can suspend other users"))\r
229 \r
230     if not request.POST.get('bantype', None):\r
231         if user.is_suspended():\r
232             suspension = user.suspension\r
233             suspension.cancel(user=request.user, ip=request.META['REMOTE_ADDR'])\r
234             return decorators.RefreshPageCommand()\r
235         else:\r
236             return render_to_response('users/suspend_user.html')\r
237 \r
238     data = {\r
239         'bantype': request.POST.get('bantype', 'Indefinitely').strip(),\r
240         'publicmsg': request.POST.get('publicmsg', _('Bad behaviour')),\r
241         'privatemsg': request.POST.get('privatemsg', None) or request.POST.get('publicmsg', ''),\r
242         'suspended': user\r
243     }\r
244 \r
245     if data['bantype'] == 'forxdays':\r
246         try:\r
247             data['forxdays'] = int(request.POST['forxdays'])\r
248         except:\r
249             raise decorators.CommandException(_('Invalid numeric argument for the number of days.'))\r
250 \r
251     SuspendAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)\r
252 \r
253     return decorators.RefreshPageCommand()\r
254 \r
255 \r
256 def user_view(template, tab_name, tab_title, tab_description, private=False, tabbed=True, render_to=None, weight=500):\r
257     def decorator(fn):\r
258         def params(request, id=None, slug=None):\r
259             # Get the user object by id if the id parameter has been passed\r
260             if id is not None:\r
261                 user = get_object_or_404(User, id=id)\r
262             # ...or by slug if the slug has been given\r
263             elif slug is not None:\r
264                 try:\r
265                     user = User.objects.get(username__iexact=slug)\r
266                 except User.DoesNotExist:\r
267                     raise Http404\r
268 \r
269             if private and not (user == request.user or request.user.is_superuser):\r
270                 raise ReturnImediatelyException(HttpResponseUnauthorized(request))\r
271 \r
272             if render_to and (not render_to(user)):\r
273                 raise ReturnImediatelyException(HttpResponseRedirect(user.get_profile_url()))\r
274 \r
275             return [request, user], { 'slug' : slug, }\r
276 \r
277         decorated = decorate.params.withfn(params)(fn)\r
278 \r
279         def result(context_or_response, request, user, **kwargs):\r
280             rev_page_title = smart_unicode(user.username) + " - " + tab_description\r
281 \r
282             # Check whether the return type of the decorated function is a context or Http Response\r
283             if isinstance(context_or_response, HttpResponse):\r
284                 response = context_or_response\r
285 \r
286                 # If it is a response -- show it\r
287                 return response\r
288             else:\r
289                 # ...if it is a context move forward, update it and render it to response\r
290                 context = context_or_response\r
291 \r
292             context.update({\r
293                 "tab": "users",\r
294                 "active_tab" : tab_name,\r
295                 "tab_description" : tab_description,\r
296                 "page_title" : rev_page_title,\r
297                 "can_view_private": (user == request.user) or request.user.is_superuser\r
298             })\r
299             return render_to_response(template, context, context_instance=RequestContext(request))\r
300 \r
301         decorated = decorate.result.withfn(result, needs_params=True)(decorated)\r
302 \r
303         if tabbed:\r
304             def url_getter(vu):\r
305                 try:\r
306                     return reverse(fn.__name__, kwargs={'id': vu.id, 'slug': slugify(smart_unicode(vu.username))})\r
307                 except NoReverseMatch:\r
308                     try:\r
309                         return reverse(fn.__name__, kwargs={'id': vu.id})\r
310                     except NoReverseMatch:\r
311                         return reverse(fn.__name__, kwargs={'slug': slugify(smart_unicode(vu.username))})\r
312 \r
313             ui.register(ui.PROFILE_TABS, ui.ProfileTab(\r
314                 tab_name, tab_title, tab_description,url_getter, private, render_to, weight\r
315             ))\r
316 \r
317         return decorated\r
318     return decorator\r
319 \r
320 \r
321 @user_view('users/stats.html', 'stats', _('overview'), _('user overview'))\r
322 def user_profile(request, user, **kwargs):\r
323     questions = Question.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')\r
324     answers = Answer.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')\r
325 \r
326     # Check whether the passed slug matches the one for the user object\r
327     slug = kwargs['slug']\r
328     if slug != slugify(smart_unicode(user.username)):\r
329         return HttpResponseRedirect(user.get_absolute_url())\r
330 \r
331     up_votes = user.vote_up_count\r
332     down_votes = user.vote_down_count\r
333     votes_today = user.get_vote_count_today()\r
334     votes_total = user.can_vote_count_today()\r
335 \r
336     user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \\r
337         .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')\r
338 \r
339     awards = [(Badge.objects.get(id=b['id']), b['count']) for b in\r
340               Badge.objects.filter(awards__user=user).values('id').annotate(count=Count('cls')).order_by('-count')]\r
341 \r
342     return pagination.paginated(request, (\r
343     ('questions', QuestionListPaginatorContext('USER_QUESTION_LIST', _('questions'), default_pagesize=15)),\r
344     ('answers', UserAnswersPaginatorContext())), {\r
345     "view_user" : user,\r
346     "questions" : questions,\r
347     "answers" : answers,\r
348     "up_votes" : up_votes,\r
349     "down_votes" : down_votes,\r
350     "total_votes": up_votes + down_votes,\r
351     "votes_today_left": votes_total-votes_today,\r
352     "votes_total_per_day": votes_total,\r
353     "user_tags" : user_tags[:50],\r
354     "awards": awards,\r
355     "total_awards" : len(awards),\r
356     })\r
357     \r
358 @user_view('users/recent.html', 'recent', _('recent activity'), _('recent user activity'))\r
359 def user_recent(request, user, **kwargs):\r
360     activities = user.actions.exclude(\r
361             action_type__in=("voteup", "votedown", "voteupcomment", "flag", "newpage", "editpage")).order_by(\r
362             '-action_date')[:USERS_PAGE_SIZE]\r
363 \r
364     return {"view_user" : user, "activities" : activities}\r
365 \r
366 \r
367 @user_view('users/reputation.html', 'reputation', _('reputation history'), _('graph of user karma'))\r
368 def user_reputation(request, user, **kwargs):\r
369     rep = list(user.reputes.order_by('date'))\r
370     values = [r.value for r in rep]\r
371     redux = lambda x, y: x+y\r
372 \r
373     graph_data = simplejson.dumps([\r
374     (time.mktime(rep[i].date.timetuple()) * 1000, reduce(redux, values[:i+1], 0))\r
375     for i in range(len(values))\r
376     ])\r
377 \r
378     rep = user.reputes.filter(action__canceled=False).order_by('-date')[0:20]\r
379 \r
380     return {"view_user": user, "reputation": rep, "graph_data": graph_data}\r
381 \r
382 @user_view('users/votes.html', 'votes', _('votes'), _('user vote record'), True)\r
383 def user_votes(request, user, **kwargs):\r
384     votes = user.votes.exclude(node__state_string__contains="(deleted").filter(\r
385             node__node_type__in=("question", "answer")).order_by('-voted_at')[:USERS_PAGE_SIZE]\r
386 \r
387     return {"view_user" : user, "votes" : votes}\r
388 \r
389 @user_view('users/questions.html', 'favorites', _('favorites'), _('questions that user selected as his/her favorite'))\r
390 def user_favorites(request, user, **kwargs):\r
391     favorites = FavoriteAction.objects.filter(canceled=False, user=user)\r
392 \r
393     return {"favorites" : favorites, "view_user" : user}\r
394 \r
395 @user_view('users/subscriptions.html', 'subscriptions', _('subscription'), _('subscriptions'), True, tabbed=False)\r
396 def user_subscriptions(request, user, **kwargs):\r
397     enabled = True\r
398 \r
399     tab = request.GET.get('tab', "settings")\r
400 \r
401     # Manage tab\r
402     if tab == 'manage':\r
403         manage_open = True\r
404 \r
405         auto = request.GET.get('auto', 'True')\r
406         if auto == 'True':\r
407             show_auto = True\r
408             subscriptions = QuestionSubscription.objects.filter(user=user).order_by('-last_view')\r
409         else:\r
410             show_auto = False\r
411             subscriptions = QuestionSubscription.objects.filter(user=user, auto_subscription=False).order_by('-last_view')\r
412 \r
413         return pagination.paginated(request, ('subscriptions', SubscriptionListPaginatorContext()), {\r
414             'subscriptions':subscriptions,\r
415             'view_user':user,\r
416             "auto":show_auto,\r
417             'manage_open':manage_open,\r
418         })\r
419     # Settings Tab and everything else\r
420     else:\r
421         manage_open = False\r
422         if request.method == 'POST':\r
423             manage_open = False\r
424             form = SubscriptionSettingsForm(data=request.POST, instance=user.subscription_settings)\r
425 \r
426             if form.is_valid():\r
427                 form.save()\r
428                 message = _('New subscription settings are now saved')\r
429 \r
430                 user.subscription_settings.enable_notifications = enabled\r
431                 user.subscription_settings.save()\r
432 \r
433                 request.user.message_set.create(message=message)\r
434         else:\r
435             form = SubscriptionSettingsForm(instance=user.subscription_settings)\r
436 \r
437         return {\r
438             'view_user':user,\r
439             'notificatons_on': enabled,\r
440             'form':form,\r
441             'manage_open':manage_open,\r
442         }\r
443 \r
444 @user_view('users/preferences.html', 'preferences', _('preferences'), _('preferences'), True, tabbed=False)\r
445 def user_preferences(request, user, **kwargs):\r
446     if request.POST:\r
447         form = UserPreferencesForm(request.POST)\r
448 \r
449         if form.is_valid():\r
450             user.prop.preferences = form.cleaned_data\r
451             request.user.message_set.create(message=_('New preferences saved'))\r
452 \r
453     else:\r
454         preferences = user.prop.preferences\r
455 \r
456         if preferences:\r
457             form = UserPreferencesForm(initial=preferences)\r
458         else:\r
459             form = UserPreferencesForm()\r
460             \r
461     return {'view_user': user, 'form': form}\r
462 \r
463 \r