]> git.openstreetmap.org Git - osqa.git/blob - forum/views/users.py
cf9b3732a363bc4f857e57f80820b67d860d6a88
[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):\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', 'indefinetly').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                     return reverse(fn.__name__, kwargs={'id': vu.id})\r
309 \r
310             ui.register(ui.PROFILE_TABS, ui.ProfileTab(\r
311                 tab_name, tab_title, tab_description,url_getter, private, render_to, weight\r
312             ))\r
313 \r
314         return decorated\r
315     return decorator\r
316 \r
317 \r
318 @user_view('users/stats.html', 'stats', _('overview'), _('user overview'))\r
319 def user_profile(request, user, **kwargs):\r
320     questions = Question.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')\r
321     answers = Answer.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')\r
322 \r
323     # Check whether the passed slug matches the one for the user object\r
324     slug = kwargs['slug']\r
325     if slug != slugify(smart_unicode(user.username)):\r
326         return HttpResponseRedirect(user.get_absolute_url())\r
327 \r
328     up_votes = user.vote_up_count\r
329     down_votes = user.vote_down_count\r
330     votes_today = user.get_vote_count_today()\r
331     votes_total = user.can_vote_count_today()\r
332 \r
333     user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \\r
334         .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')\r
335 \r
336     awards = [(Badge.objects.get(id=b['id']), b['count']) for b in\r
337               Badge.objects.filter(awards__user=user).values('id').annotate(count=Count('cls')).order_by('-count')]\r
338 \r
339     return pagination.paginated(request, (\r
340     ('questions', QuestionListPaginatorContext('USER_QUESTION_LIST', _('questions'), default_pagesize=15)),\r
341     ('answers', UserAnswersPaginatorContext())), {\r
342     "view_user" : user,\r
343     "questions" : questions,\r
344     "answers" : answers,\r
345     "up_votes" : up_votes,\r
346     "down_votes" : down_votes,\r
347     "total_votes": up_votes + down_votes,\r
348     "votes_today_left": votes_total-votes_today,\r
349     "votes_total_per_day": votes_total,\r
350     "user_tags" : user_tags[:50],\r
351     "awards": awards,\r
352     "total_awards" : len(awards),\r
353     })\r
354     \r
355 @user_view('users/recent.html', 'recent', _('recent activity'), _('recent user activity'))\r
356 def user_recent(request, user, **kwargs):\r
357     activities = user.actions.exclude(\r
358             action_type__in=("voteup", "votedown", "voteupcomment", "flag", "newpage", "editpage")).order_by(\r
359             '-action_date')[:USERS_PAGE_SIZE]\r
360 \r
361     return {"view_user" : user, "activities" : activities}\r
362 \r
363 \r
364 @user_view('users/reputation.html', 'reputation', _('reputation history'), _('graph of user karma'))\r
365 def user_reputation(request, user, **kwargs):\r
366     rep = list(user.reputes.order_by('date'))\r
367     values = [r.value for r in rep]\r
368     redux = lambda x, y: x+y\r
369 \r
370     graph_data = simplejson.dumps([\r
371     (time.mktime(rep[i].date.timetuple()) * 1000, reduce(redux, values[:i], 0))\r
372     for i in range(len(values))\r
373     ])\r
374 \r
375     rep = user.reputes.filter(action__canceled=False).order_by('-date')[0:20]\r
376 \r
377     return {"view_user": user, "reputation": rep, "graph_data": graph_data}\r
378 \r
379 @user_view('users/votes.html', 'votes', _('votes'), _('user vote record'), True)\r
380 def user_votes(request, user, **kwargs):\r
381     votes = user.votes.exclude(node__state_string__contains="(deleted").filter(\r
382             node__node_type__in=("question", "answer")).order_by('-voted_at')[:USERS_PAGE_SIZE]\r
383 \r
384     return {"view_user" : user, "votes" : votes}\r
385 \r
386 @user_view('users/questions.html', 'favorites', _('favorites'), _('questions that user selected as his/her favorite'))\r
387 def user_favorites(request, user, **kwargs):\r
388     favorites = FavoriteAction.objects.filter(canceled=False, user=user)\r
389 \r
390     return {"favorites" : favorites, "view_user" : user}\r
391 \r
392 @user_view('users/subscriptions.html', 'subscriptions', _('subscription'), _('subscriptions'), True, tabbed=False)\r
393 def user_subscriptions(request, user, **kwargs):\r
394     enabled = True\r
395 \r
396     tab = request.GET.get('tab', "settings")\r
397 \r
398     if tab == 'settings':\r
399         manage_open = False\r
400         if request.method == 'POST':\r
401             manage_open = False\r
402             form = SubscriptionSettingsForm(data=request.POST, instance=user.subscription_settings)\r
403 \r
404             if form.is_valid():\r
405                 form.save()\r
406                 message = _('New subscription settings are now saved')\r
407 \r
408                 user.subscription_settings.enable_notifications = enabled\r
409                 user.subscription_settings.save()\r
410 \r
411                 request.user.message_set.create(message=message)\r
412         else:\r
413             form = SubscriptionSettingsForm(instance=user.subscription_settings)\r
414 \r
415         return {\r
416             'view_user':user,\r
417             'notificatons_on': enabled,\r
418             'form':form,\r
419             'manage_open':manage_open,\r
420         }\r
421 \r
422     elif tab == 'manage':\r
423         manage_open = True\r
424 \r
425         auto = request.GET.get('auto', 'True')\r
426         if auto == 'True':\r
427             show_auto = True\r
428             subscriptions = QuestionSubscription.objects.filter(user=user).order_by('-last_view')\r
429         else:\r
430             show_auto = False\r
431             subscriptions = QuestionSubscription.objects.filter(user=user, auto_subscription=False).order_by('-last_view')\r
432 \r
433         return pagination.paginated(request, ('subscriptions', SubscriptionListPaginatorContext()), {\r
434             'subscriptions':subscriptions,\r
435             'view_user':user,\r
436             "auto":show_auto,\r
437             'manage_open':manage_open,\r
438         })\r
439 \r
440     # else:\r
441         # todo: probably want to throw an error\r
442         # error = "error to throw"\r
443 \r
444 \r
445 \r
446 \r
447 \r
448 \r
449 \r
450 @user_view('users/preferences.html', 'preferences', _('preferences'), _('preferences'), True, tabbed=False)\r
451 def user_preferences(request, user, **kwargs):\r
452     if request.POST:\r
453         form = UserPreferencesForm(request.POST)\r
454 \r
455         if form.is_valid():\r
456             user.prop.preferences = form.cleaned_data\r
457             request.user.message_set.create(message=_('New preferences saved'))\r
458 \r
459     else:\r
460         preferences = user.prop.preferences\r
461 \r
462         if preferences:\r
463             form = UserPreferencesForm(initial=preferences)\r
464         else:\r
465             form = UserPreferencesForm()\r
466             \r
467     return {'view_user': user, 'form': form}\r
468 \r
469 \r