]> git.openstreetmap.org Git - osqa.git/blob - forum/views/users.py
Fixes OSQA-406, Validation hash is not reset when email address is changed (enabling...
[osqa.git] / forum / views / users.py
1 from django.contrib.auth.decorators import login_required\r
2 from forum.models import User\r
3 from django.db.models import Q, Count\r
4 from django.core.paginator import Paginator, EmptyPage, InvalidPage\r
5 from django.template.defaultfilters import slugify\r
6 from django.contrib.contenttypes.models import ContentType\r
7 from django.core.urlresolvers import reverse\r
8 from django.shortcuts import render_to_response, get_object_or_404\r
9 from django.template import RequestContext\r
10 from django.http import HttpResponse, HttpResponseRedirect, Http404\r
11 from forum.http_responses import HttpResponseUnauthorized\r
12 from django.utils.translation import ugettext as _\r
13 from django.utils.http import urlquote_plus\r
14 from django.utils.html import strip_tags\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\r
20 from datetime import datetime, date\r
21 import decorators\r
22 from forum.actions import EditProfileAction, FavoriteAction, BonusRepAction, SuspendAction\r
23 from forum.modules import ui\r
24 from forum.utils import pagination\r
25 from forum.views.readers import QuestionListPaginatorContext, AnswerPaginatorContext\r
26 \r
27 import time\r
28 import decorators\r
29 \r
30 class UserReputationSort(pagination.SimpleSort):\r
31     def apply(self, objects):\r
32         return objects.order_by('-is_active', self.order_by)\r
33 \r
34 class UserListPaginatorContext(pagination.PaginatorContext):\r
35     def __init__(self):\r
36         super (UserListPaginatorContext, self).__init__('USERS_LIST', sort_methods=(\r
37             (_('reputation'), UserReputationSort(_('reputation'), '-reputation', _("sorted by reputation"))),\r
38             (_('newest'), pagination.SimpleSort(_('recent'), '-date_joined', _("newest members"))),\r
39             (_('last'), pagination.SimpleSort(_('oldest'), 'date_joined', _("oldest members"))),\r
40             (_('name'), pagination.SimpleSort(_('by username'), 'username', _("sorted by username"))),\r
41         ), pagesizes=(20, 35, 60))\r
42 \r
43 class UserAnswersPaginatorContext(pagination.PaginatorContext):\r
44     def __init__(self):\r
45         super (UserAnswersPaginatorContext, self).__init__('USER_ANSWER_LIST', sort_methods=(\r
46             (_('oldest'), pagination.SimpleSort(_('oldest answers'), 'added_at', _("oldest answers will be shown first"))),\r
47             (_('newest'), pagination.SimpleSort(_('newest answers'), '-added_at', _("newest answers will be shown first"))),\r
48             (_('votes'), pagination.SimpleSort(_('popular answers'), '-score', _("most voted answers will be shown first"))),\r
49         ), default_sort=_('votes'), pagesizes=(5, 10, 20), default_pagesize=20, prefix=_('answers'))\r
50 \r
51 USERS_PAGE_SIZE = 35# refactor - move to some constants file\r
52 \r
53 @decorators.render('users/users.html', 'users', _('users'), weight=200)\r
54 def users(request):\r
55     suser = request.REQUEST.get('q', "")\r
56     users = User.objects.all()\r
57 \r
58     if suser != "":\r
59         users = users.filter(username__icontains=suser)\r
60 \r
61     return pagination.paginated(request, ('users', UserListPaginatorContext()), {\r
62         "users" : users,\r
63         "suser" : suser,\r
64     })\r
65 \r
66 \r
67 @login_required\r
68 def edit_user(request, id):\r
69     user = get_object_or_404(User, id=id)\r
70     if not (request.user.is_superuser or request.user == user):\r
71         return HttpResponseUnauthorized(request)\r
72     if request.method == "POST":\r
73         form = EditUserForm(user, request.POST)\r
74         if form.is_valid():\r
75             new_email = sanitize_html(form.cleaned_data['email'])\r
76 \r
77             if new_email != user.email:\r
78                 user.email = new_email\r
79                 user.email_isvalid = False\r
80 \r
81                 try:\r
82                     hash = ValidationHash.objects.get(user=request.user, type='email')\r
83                     hash.delete()\r
84                 except:\r
85                     pass\r
86 \r
87             if settings.EDITABLE_SCREEN_NAME:\r
88                 user.username = sanitize_html(form.cleaned_data['username'])\r
89             user.real_name = sanitize_html(form.cleaned_data['realname'])\r
90             user.website = sanitize_html(form.cleaned_data['website'])\r
91             user.location = sanitize_html(form.cleaned_data['city'])\r
92             user.date_of_birth = form.cleaned_data['birthday']\r
93             if user.date_of_birth == "None":\r
94                 user.date_of_birth = datetime(1900, 1, 1, 0, 0)\r
95             user.about = sanitize_html(form.cleaned_data['about'])\r
96 \r
97             user.save()\r
98             EditProfileAction(user=user, ip=request.META['REMOTE_ADDR']).save()\r
99 \r
100             request.user.message_set.create(message=_("Profile updated."))\r
101             return HttpResponseRedirect(user.get_profile_url())\r
102     else:\r
103         form = EditUserForm(user)\r
104     return render_to_response('users/edit.html', {\r
105     'user': user,\r
106     'form' : form,\r
107     'gravatar_faq_url' : reverse('faq') + '#gravatar',\r
108     }, context_instance=RequestContext(request))\r
109 \r
110 \r
111 @decorate.withfn(decorators.command)\r
112 def user_powers(request, id, action, status):\r
113     if not request.user.is_superuser:\r
114         raise decorators.CommandException(_("Only superusers are allowed to alter other users permissions."))\r
115 \r
116     if (action == 'remove' and 'status' == 'super') and not request.user.is_siteowner():\r
117         raise decorators.CommandException(_("Only the site owner can remove the super user status from other user."))\r
118 \r
119     user = get_object_or_404(User, id=id)\r
120     new_state = action == 'grant'\r
121 \r
122     if status == 'super':\r
123         user.is_superuser = new_state\r
124     elif status == 'staff':\r
125         user.is_staff = new_state\r
126     else:\r
127         raise Http404()\r
128 \r
129     user.save()\r
130     return decorators.RefreshPageCommand()\r
131 \r
132 \r
133 @decorate.withfn(decorators.command)\r
134 def award_points(request, id):\r
135     if not request.POST:\r
136         return render_to_response('users/karma_bonus.html')\r
137 \r
138     if not request.user.is_superuser:\r
139         raise decorators.CommandException(_("Only superusers are allowed to award reputation points"))\r
140 \r
141     try:\r
142         points = int(request.POST['points'])\r
143     except:\r
144         raise decorators.CommandException(_("Invalid number of points to award."))\r
145 \r
146     user = get_object_or_404(User, id=id)\r
147 \r
148     extra = dict(message=request.POST.get('message', ''), awarding_user=request.user.id, value=points)\r
149 \r
150     BonusRepAction(user=request.user, extra=extra).save(data=dict(value=points, affected=user))\r
151 \r
152     return {'commands': {\r
153             'update_profile_karma': [user.reputation]\r
154         }}\r
155     \r
156 \r
157 @decorate.withfn(decorators.command)\r
158 def suspend(request, id):\r
159     user = get_object_or_404(User, id=id)\r
160 \r
161     if not request.user.is_superuser:\r
162         raise decorators.CommandException(_("Only superusers can suspend other users"))\r
163 \r
164     if not request.POST.get('bantype', None):\r
165         if user.is_suspended():\r
166             suspension = user.suspension\r
167             suspension.cancel(user=request.user, ip=request.META['REMOTE_ADDR'])\r
168             return decorators.RefreshPageCommand()\r
169         else:\r
170             return render_to_response('users/suspend_user.html')\r
171 \r
172     data = {\r
173     'bantype': request.POST.get('bantype', 'indefinetly').strip(),\r
174     'publicmsg': request.POST.get('publicmsg', _('Bad behaviour')),\r
175     'privatemsg': request.POST.get('privatemsg', None) or request.POST.get('publicmsg', ''),\r
176     'suspended': user\r
177     }\r
178 \r
179     if data['bantype'] == 'forxdays':\r
180         try:\r
181             data['forxdays'] = int(request.POST['forxdays'])\r
182         except:\r
183             raise decorators.CommandException(_('Invalid numeric argument for the number of days.'))\r
184 \r
185     SuspendAction(user=request.user, ip=request.META['REMOTE_ADDR']).save(data=data)\r
186 \r
187     return decorators.RefreshPageCommand()\r
188 \r
189 \r
190 def user_view(template, tab_name, tab_title, tab_description, private=False, tabbed=True, render_to=None, weight=500):\r
191     def decorator(fn):\r
192         def params(request, id, slug=None):\r
193             user = get_object_or_404(User, id=id)\r
194             if private and not (user == request.user or request.user.is_superuser):\r
195                 return HttpResponseUnauthorized(request)\r
196 \r
197             if render_to and (not render_to(user)):\r
198                 return HttpResponseRedirect(user.get_profile_url())\r
199 \r
200             return [request, user], {}\r
201 \r
202         decorated = decorate.params.withfn(params)(fn)\r
203 \r
204         def result(context, request, user):\r
205             rev_page_title = user.username + " - " + tab_description\r
206 \r
207             context.update({\r
208                 "tab": "users",\r
209                 "active_tab" : tab_name,\r
210                 "tab_description" : tab_description,\r
211                 "page_title" : rev_page_title,\r
212                 "can_view_private": (user == request.user) or request.user.is_superuser\r
213             })\r
214             return render_to_response(template, context, context_instance=RequestContext(request))\r
215 \r
216         decorated = decorate.result.withfn(result, needs_params=True)(decorated)\r
217 \r
218         if tabbed:\r
219             def url_getter(vu):\r
220                 try:\r
221                     return reverse(fn.__name__, kwargs={'id': vu.id, 'slug': slugify(vu.username)})\r
222                 except NoReverseMatch:\r
223                     return reverse(fn.__name__, kwargs={'id': vu.id})\r
224 \r
225             ui.register(ui.PROFILE_TABS, ui.ProfileTab(\r
226                 tab_name, tab_title, tab_description,url_getter, private, render_to, weight\r
227             ))\r
228 \r
229         return decorated\r
230     return decorator\r
231 \r
232 \r
233 @user_view('users/stats.html', 'stats', _('overview'), _('user overview'))\r
234 def user_profile(request, user):\r
235     questions = Question.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')\r
236     answers = Answer.objects.filter_state(deleted=False).filter(author=user).order_by('-added_at')\r
237 \r
238     up_votes = user.vote_up_count\r
239     down_votes = user.vote_down_count\r
240     votes_today = user.get_vote_count_today()\r
241     votes_total = int(settings.MAX_VOTES_PER_DAY)\r
242 \r
243     user_tags = Tag.objects.filter(Q(nodes__author=user) | Q(nodes__children__author=user)) \\r
244         .annotate(user_tag_usage_count=Count('name')).order_by('-user_tag_usage_count')\r
245 \r
246     awards = [(Badge.objects.get(id=b['id']), b['count']) for b in\r
247               Badge.objects.filter(awards__user=user).values('id').annotate(count=Count('cls')).order_by('-count')]\r
248 \r
249     return pagination.paginated(request, (\r
250     ('questions', QuestionListPaginatorContext('USER_QUESTION_LIST', _('questions'), 15)),\r
251     ('answers', UserAnswersPaginatorContext())), {\r
252     "view_user" : user,\r
253     "questions" : questions,\r
254     "answers" : answers,\r
255     "up_votes" : up_votes,\r
256     "down_votes" : down_votes,\r
257     "total_votes": up_votes + down_votes,\r
258     "votes_today_left": votes_total-votes_today,\r
259     "votes_total_per_day": votes_total,\r
260     "user_tags" : user_tags[:50],\r
261     "awards": awards,\r
262     "total_awards" : len(awards),\r
263     })\r
264     \r
265 @user_view('users/recent.html', 'recent', _('recent activity'), _('recent user activity'))\r
266 def user_recent(request, user):\r
267     activities = user.actions.exclude(\r
268             action_type__in=("voteup", "votedown", "voteupcomment", "flag", "newpage", "editpage")).order_by(\r
269             '-action_date')[:USERS_PAGE_SIZE]\r
270 \r
271     return {"view_user" : user, "activities" : activities}\r
272 \r
273 \r
274 @user_view('users/reputation.html', 'reputation', _('karma history'), _('graph of user karma'))\r
275 def user_reputation(request, user):\r
276     rep = list(user.reputes.order_by('date'))\r
277     values = [r.value for r in rep]\r
278     redux = lambda x, y: x+y\r
279 \r
280     graph_data = simplejson.dumps([\r
281     (time.mktime(rep[i].date.timetuple()) * 1000, reduce(redux, values[:i], 0))\r
282     for i in range(len(values))\r
283     ])\r
284 \r
285     rep = user.reputes.filter(action__canceled=False).order_by('-date')[0:20]\r
286 \r
287     return {"view_user": user, "reputation": rep, "graph_data": graph_data}\r
288 \r
289 @user_view('users/votes.html', 'votes', _('votes'), _('user vote record'), True)\r
290 def user_votes(request, user):\r
291     votes = user.votes.exclude(node__state_string__contains="(deleted").filter(\r
292             node__node_type__in=("question", "answer")).order_by('-voted_at')[:USERS_PAGE_SIZE]\r
293 \r
294     return {"view_user" : user, "votes" : votes}\r
295 \r
296 @user_view('users/questions.html', 'favorites', _('favorites'), _('questions that user selected as his/her favorite'))\r
297 def user_favorites(request, user):\r
298     favorites = FavoriteAction.objects.filter(canceled=False, user=user)\r
299 \r
300     return {"favorites" : favorites, "view_user" : user}\r
301 \r
302 @user_view('users/subscriptions.html', 'subscriptions', _('subscription settings'), _('subscriptions'), True, tabbed=False)\r
303 def user_subscriptions(request, user):\r
304     enabled = user.subscription_settings.enable_notifications\r
305 \r
306     if request.method == 'POST':        \r
307         form = SubscriptionSettingsForm(data=request.POST, instance=user.subscription_settings)\r
308 \r
309         if form.is_valid():\r
310             form.save()\r
311             message = _('New subscription settings are now saved')\r
312 \r
313             if 'notswitch' in request.POST:\r
314                 enabled = not enabled\r
315 \r
316                 if enabled:\r
317                     message = _('Notifications are now enabled')\r
318                 else:\r
319                     message = _('Notifications are now disabled')\r
320 \r
321             user.subscription_settings.enable_notifications = enabled\r
322             user.subscription_settings.save()\r
323 \r
324             request.user.message_set.create(message=message)\r
325     else:\r
326         form = SubscriptionSettingsForm(instance=user.subscription_settings)\r
327 \r
328     return {'view_user':user, 'notificatons_on': enabled, 'form':form}\r
329 \r
330 @user_view('users/preferences.html', 'preferences', _('preferences'), _('preferences'), True, tabbed=False)\r
331 def user_preferences(request, user):\r
332     if request.POST:\r
333         form = UserPreferencesForm(request.POST)\r
334 \r
335         if form.is_valid():\r
336             user.prop.preferences = form.cleaned_data\r
337             request.user.message_set.create(message=_('New preferences saved'))\r
338 \r
339     else:\r
340         preferences = user.prop.preferences\r
341 \r
342         if preferences:\r
343             form = UserPreferencesForm(initial=preferences)\r
344         else:\r
345             form = UserPreferencesForm()\r
346             \r
347     return {'view_user': user, 'form': form}\r
348 \r
349 @login_required\r
350 def account_settings(request):\r
351     logging.debug('')\r
352     msg = request.GET.get('msg', '')\r
353     is_openid = False\r
354 \r
355     return render_to_response('account_settings.html', {\r
356     'msg': msg,\r
357     'is_openid': is_openid\r
358     }, context_instance=RequestContext(request))\r
359 \r