Mercurial > public > sg101
comparison gpp/messages/views.py @ 429:d0f0800eef0c
Making the jquery tabbed version of the messages app the current version and removing the old. Also figured out how to dynamically update the base template's count of unread messages when messages are read.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Tue, 03 May 2011 02:56:58 +0000 |
parents | gpp/messages/views2.py@77b3b01843b5 |
children | 9df368d9775d |
comparison
equal
deleted
inserted
replaced
428:77b3b01843b5 | 429:d0f0800eef0c |
---|---|
1 """Views for the messages application""" | 1 """ |
2 | 2 Views for the messages application. |
3 import collections | 3 |
4 """ | |
4 import datetime | 5 import datetime |
5 | 6 |
6 from django.shortcuts import render_to_response | 7 from django.contrib.auth.decorators import login_required |
7 from django.template import RequestContext | 8 from django.contrib.auth.models import User |
9 from django.contrib import messages | |
10 from django.core.paginator import Paginator, EmptyPage, InvalidPage | |
11 from django.core.urlresolvers import reverse | |
12 from django.http import HttpResponse | |
13 from django.http import HttpResponseForbidden | |
14 from django.http import HttpResponseNotAllowed | |
8 from django.http import HttpResponseRedirect | 15 from django.http import HttpResponseRedirect |
9 from django.contrib.auth.decorators import login_required | |
10 from django.contrib import messages | |
11 from django.shortcuts import get_object_or_404 | 16 from django.shortcuts import get_object_or_404 |
12 from django.core.urlresolvers import reverse | 17 from django.shortcuts import render |
13 from django.http import Http404 | 18 import django.utils.simplejson as json |
14 from django.views.decorators.http import require_POST | 19 |
15 | 20 from messages.models import Message, Options |
16 from messages.models import Message | 21 from messages.forms import OptionsForm, ComposeForm |
17 from messages.models import Options | 22 from messages.utils import reply_subject, quote_message |
18 from messages.forms import ComposeForm | 23 |
19 from messages.forms import OptionsForm | 24 |
20 from messages.utils import reply_subject | 25 MSGS_PER_PAGE = 20 |
21 from messages.utils import quote_message | 26 |
22 | 27 TAB_INDICES = { |
23 | 28 'inbox': 0, |
24 BOX_MAP = { | 29 'compose': 1, |
25 'inbox': 'messages-inbox', | 30 'outbox': 2, |
26 'outbox': 'messages-outbox', | 31 'trash': 3, |
27 'trash': 'messages-trash', | 32 'options': 4, |
28 } | 33 } |
29 | 34 |
30 MSG_DELETED, MSG_TRASHED, MSG_ERROR = range(3) | 35 |
31 | 36 def _get_page(request): |
32 | 37 try: |
33 def box_redirect(request): | 38 n = int(request.GET.get('page', '1')) |
34 """ | 39 except ValueError: |
35 Determines which box to redirect to by looking for a GET or | 40 n = 1 |
36 POST parameter. | 41 return n |
37 """ | |
38 if request.method == 'GET': | |
39 box = request.GET.get('box', 'inbox') | |
40 else: | |
41 box = request.POST.get('box', 'inbox') | |
42 if BOX_MAP.has_key(box): | |
43 url = reverse(BOX_MAP[box]) | |
44 else: | |
45 url = reverse(BOX_MAP['inbox']) | |
46 return HttpResponseRedirect(url) | |
47 | 42 |
48 | 43 |
49 @login_required | 44 @login_required |
45 def index(request, tab=None): | |
46 """ | |
47 This function displays the base tabbed private messages view. | |
48 | |
49 """ | |
50 tab_index = TAB_INDICES[tab] if tab else 0 | |
51 return render(request, 'messages/tabbed_base.html', { | |
52 'tab': tab_index, | |
53 'unread_count': Message.objects.unread_count(request.user), | |
54 }) | |
55 | |
56 | |
57 @login_required | |
58 def compose_to(request, receiver): | |
59 """ | |
60 This function displays the base tabbed private messages view, | |
61 and configures it to display the compose PM tab for the given | |
62 receiver. | |
63 | |
64 """ | |
65 user = get_object_or_404(User, username=receiver) | |
66 tab_index = TAB_INDICES['compose'] | |
67 return render(request, 'messages/tabbed_base.html', { | |
68 'tab': tab_index, | |
69 'receiver': receiver, | |
70 'unread_count': Message.objects.unread_count(request.user), | |
71 }) | |
72 | |
73 | |
50 def inbox(request): | 74 def inbox(request): |
51 """Displays the inbox for the user making the request.""" | 75 """ |
52 msgs = Message.objects.inbox(request.user) | 76 Returns the inbox for the user. |
53 return render_to_response('messages/inbox.html', { | 77 |
78 """ | |
79 if not request.user.is_authenticated(): | |
80 return HttpResponseForbidden() | |
81 | |
82 msg_list = Message.objects.inbox(request.user) | |
83 paginator = Paginator(msg_list, MSGS_PER_PAGE) | |
84 try: | |
85 msgs = paginator.page(_get_page(request)) | |
86 except EmptyPage, InvalidPage: | |
87 msgs = paginator.page(paginator.num_pages) | |
88 | |
89 return render(request, 'messages/inbox_tab.html', { | |
54 'msgs': msgs, | 90 'msgs': msgs, |
55 }, | 91 'url': reverse('messages-inbox'), |
56 context_instance = RequestContext(request)) | 92 }) |
57 | 93 |
58 | 94 |
59 @login_required | |
60 def outbox(request): | 95 def outbox(request): |
61 """Displays the outbox for the user making the request.""" | 96 """ |
62 msgs = Message.objects.outbox(request.user) | 97 Returns the outbox for the user. |
63 return render_to_response('messages/outbox.html', { | 98 |
99 """ | |
100 if not request.user.is_authenticated(): | |
101 return HttpResponseForbidden() | |
102 | |
103 msg_list = Message.objects.outbox(request.user) | |
104 paginator = Paginator(msg_list, MSGS_PER_PAGE) | |
105 try: | |
106 msgs = paginator.page(_get_page(request)) | |
107 except EmptyPage, InvalidPage: | |
108 msgs = paginator.page(paginator.num_pages) | |
109 | |
110 return render(request, 'messages/outbox_tab.html', { | |
64 'msgs': msgs, | 111 'msgs': msgs, |
65 }, | 112 'url': reverse('messages-outbox'), |
66 context_instance = RequestContext(request)) | 113 }) |
67 | 114 |
68 | 115 |
69 @login_required | |
70 def trash(request): | 116 def trash(request): |
71 """Displays the trash for the user making the request.""" | 117 """ |
72 msgs = Message.objects.trash(request.user) | 118 Returns the trash for the user. |
73 return render_to_response('messages/trash.html', { | 119 |
120 """ | |
121 if not request.user.is_authenticated(): | |
122 return HttpResponseForbidden() | |
123 | |
124 msg_list = Message.objects.trash(request.user) | |
125 paginator = Paginator(msg_list, MSGS_PER_PAGE) | |
126 try: | |
127 msgs = paginator.page(_get_page(request)) | |
128 except EmptyPage, InvalidPage: | |
129 msgs = paginator.page(paginator.num_pages) | |
130 | |
131 return render(request, 'messages/trash_tab.html', { | |
74 'msgs': msgs, | 132 'msgs': msgs, |
75 }, | 133 'url': reverse('messages-trash'), |
76 context_instance = RequestContext(request)) | 134 }) |
77 | 135 |
78 | 136 |
79 @login_required | 137 def message(request): |
80 def view(request, msg_id): | 138 """ |
81 """ | 139 This view function retrieves a message and returns it as a JSON object. |
82 View a given message. Only the sender or receiver can see | 140 |
83 the message. | 141 """ |
84 """ | 142 if not request.user.is_authenticated(): |
85 msg = get_object_or_404(Message, pk=msg_id) | 143 return HttpResponseForbidden() |
144 if request.method != 'POST': | |
145 return HttpResponseNotAllowed(['POST']) | |
146 | |
147 msg_id = request.POST.get('msg_id') | |
148 msg = get_object_or_404(Message.objects.select_related(), pk=msg_id) | |
86 if msg.sender != request.user and msg.receiver != request.user: | 149 if msg.sender != request.user and msg.receiver != request.user: |
87 raise Http404 | 150 return HttpResponseForbidden() |
88 | 151 |
89 if msg.receiver == request.user and msg.read_date is None: | 152 if msg.receiver == request.user and msg.read_date is None: |
90 msg.read_date = datetime.datetime.now() | 153 msg.read_date = datetime.datetime.now() |
91 msg.save() | 154 msg.save() |
92 | 155 |
93 box = request.GET.get('box', None) | 156 msg_dict = dict(subject=msg.subject, |
94 | 157 sender=msg.sender.username, |
95 return render_to_response('messages/view.html', { | 158 receiver=msg.receiver.username, |
96 'box': box, | 159 content=msg.html, |
97 'msg': msg, | 160 re_subject=reply_subject(msg.subject), |
98 'is_deleted': msg.is_deleted(request.user), | 161 re_content=quote_message(msg.sender.username, msg.send_date, |
99 }, | 162 msg.message)) |
100 context_instance = RequestContext(request)) | 163 |
101 | 164 result = json.dumps(msg_dict, ensure_ascii=False) |
102 | 165 return HttpResponse(result, content_type='application/json') |
103 @login_required | 166 |
104 def reply(request, msg_id): | 167 |
105 """ | 168 def options(request): |
106 Process or prepare the compose form in order to reply | 169 """ |
107 to a given message. | 170 This view handles the displaying and changing of private message options. |
108 """ | 171 |
109 msg = get_object_or_404(Message, pk=msg_id) | 172 """ |
173 if not request.user.is_authenticated(): | |
174 return HttpResponseForbidden() | |
110 | 175 |
111 if request.method == "POST": | 176 if request.method == "POST": |
112 if request.POST.get('submit_button', 'Cancel') == 'Cancel': | 177 options = Options.objects.for_user(request.user) |
113 return box_redirect(request) | 178 form = OptionsForm(request.POST, instance=options, prefix='opts') |
114 compose_form = ComposeForm(request.user, request.POST) | 179 if form.is_valid(): |
115 if compose_form.is_valid(): | 180 form.save() |
116 compose_form.save(sender=request.user, parent_msg=msg) | 181 messages.success(request, 'Options saved.') |
117 messages.success(request, 'Reply sent.') | |
118 return box_redirect(request) | |
119 else: | 182 else: |
120 if msg.receiver == request.user: | 183 options = Options.objects.for_user(request.user) |
121 receiver_name = msg.sender.username | 184 form = OptionsForm(instance=options, prefix='opts') |
122 else: | 185 |
123 # replying to message in outbox | 186 return render(request, 'messages/options_tab.html', { |
124 receiver_name = msg.receiver.username | 187 'form': form, |
125 | 188 }) |
126 form_data = { | 189 |
127 'receiver': receiver_name, | 190 |
128 'subject': reply_subject(msg.subject), | |
129 'message': quote_message(msg.sender, msg.send_date, msg.message), | |
130 'box': request.GET.get('box', 'inbox'), | |
131 } | |
132 | |
133 compose_form = ComposeForm(request.user, initial=form_data) | |
134 | |
135 return render_to_response('messages/compose.html', { | |
136 'compose_form': compose_form, | |
137 }, | |
138 context_instance = RequestContext(request)) | |
139 | |
140 | |
141 @login_required | |
142 def compose(request, receiver=None): | 191 def compose(request, receiver=None): |
143 """ | 192 """ |
144 Process or prepare the compose form in order to create | 193 Process or prepare the compose form to create a new private message. |
145 a new message. | 194 |
146 """ | 195 """ |
196 if not request.user.is_authenticated(): | |
197 return HttpResponseForbidden() | |
198 | |
147 if request.method == "POST": | 199 if request.method == "POST": |
148 if request.POST.get('submit_button', 'Cancel') == 'Cancel': | |
149 return HttpResponseRedirect(reverse('messages-inbox')) | |
150 compose_form = ComposeForm(request.user, request.POST) | 200 compose_form = ComposeForm(request.user, request.POST) |
151 if compose_form.is_valid(): | 201 if compose_form.is_valid(): |
152 compose_form.save(sender=request.user) | 202 compose_form.save(sender=request.user) |
153 messages.success(request, 'Message sent.') | 203 messages.success(request, 'Message sent.') |
154 return HttpResponseRedirect(reverse('messages-inbox')) | 204 return HttpResponseRedirect(reverse('messages-index_named', args=['compose'])) |
155 else: | 205 else: |
156 if receiver is not None: | 206 if receiver is not None: |
157 form_data = { | 207 form_data = {'receiver': receiver} |
158 'receiver': receiver, | |
159 } | |
160 compose_form = ComposeForm(request.user, initial=form_data) | 208 compose_form = ComposeForm(request.user, initial=form_data) |
161 else: | 209 else: |
162 compose_form = ComposeForm(request.user) | 210 compose_form = ComposeForm(request.user) |
163 | 211 |
164 return render_to_response('messages/compose.html', { | 212 return render(request, 'messages/compose_tab.html', { |
165 'compose_form': compose_form, | 213 'compose_form': compose_form, |
166 }, | 214 }) |
167 context_instance = RequestContext(request)) | 215 |
168 | 216 |
169 | 217 def _only_integers(slist): |
170 def _delete_message(user, msg): | 218 """ |
171 """ | 219 Accepts a list of strings. Returns a list of integers consisting of only |
172 Deletes a given message. The user must be either the sender or | 220 those elements from the original list that could be converted to integers |
173 receiver for this to succeed. | 221 |
174 If both parties have deleted the message, it is deleted from the | 222 """ |
175 database. If only one party has deleted the message, the message | 223 result = [] |
176 is just sent to the trash. | 224 for s in slist: |
177 | 225 try: |
178 Returns MSG_DELETED, MSG_TRASHED, or MSG_ERROR to indicate what | 226 n = int(s) |
179 action was performed. | 227 except ValueError: |
180 | 228 pass |
181 """ | |
182 if msg.sender == user: | |
183 if (msg.receiver_delete_date is not None or | |
184 msg.read_date is None): | |
185 # Both parties deleted the message or receiver hasn't read it yet, | |
186 # we can delete it now | |
187 msg.delete() | |
188 return MSG_DELETED | |
189 else: | 229 else: |
190 # receiver still has it in inbox | 230 result.append(n) |
191 msg.sender_delete_date = datetime.datetime.now() | 231 return result |
232 | |
233 | |
234 def _delete_msgs(user, msg_ids): | |
235 """ | |
236 Deletes the messages given by the list of msg_ids. For this to succeed, the | |
237 user has to be either the sender or receiver on each message. | |
238 | |
239 """ | |
240 msg_ids = _only_integers(msg_ids) | |
241 msgs = Message.objects.filter(id__in=msg_ids) | |
242 | |
243 for msg in msgs: | |
244 if msg.sender == user: | |
245 if (msg.receiver_delete_date is not None or | |
246 msg.read_date is None): | |
247 # Both parties deleted the message or receiver hasn't read it | |
248 # yet, we can delete it now | |
249 msg.delete() | |
250 else: | |
251 # receiver still has it in inbox | |
252 msg.sender_delete_date = datetime.datetime.now() | |
253 msg.save() | |
254 | |
255 elif msg.receiver == user: | |
256 if msg.sender_delete_date is not None: | |
257 # both parties deleted the message, we can delete it now | |
258 msg.delete() | |
259 else: | |
260 # sender still has it in the outbox | |
261 msg.receiver_delete_date = datetime.datetime.now() | |
262 msg.save() | |
263 | |
264 | |
265 def _undelete_msgs(user, msg_ids): | |
266 """ | |
267 Attempts to "undelete" the messages given by the msg_ids list. | |
268 This will only succeed if the user is either the sender or receiver. | |
269 | |
270 """ | |
271 msg_ids = _only_integers(msg_ids) | |
272 msgs = Message.objects.filter(id__in=msg_ids) | |
273 for msg in msgs: | |
274 if msg.sender == user: | |
275 msg.sender_delete_date = None | |
192 msg.save() | 276 msg.save() |
193 return MSG_TRASHED | 277 elif msg.receiver == user: |
194 | 278 msg.receiver_delete_date = None |
195 elif msg.receiver == user: | |
196 if msg.sender_delete_date is not None: | |
197 # both parties deleted the message, we can delete it now | |
198 msg.delete() | |
199 return MSG_DELETED | |
200 else: | |
201 # sender still has it in the outbox | |
202 msg.receiver_delete_date = datetime.datetime.now() | |
203 msg.save() | 279 msg.save() |
204 return MSG_TRASHED | 280 |
205 | 281 |
206 return MSG_ERROR | 282 def bulk(request): |
207 | 283 """ |
208 | 284 This view processes messages in bulk. Arrays of message ids are expected in |
209 @login_required | 285 the POST query dict: inbox_ids and outbox_ids will be deleted; trash_ids will |
210 @require_POST | 286 be undeleted. |
211 def delete(request, msg_id): | 287 |
212 """ | 288 """ |
213 Deletes a given message. The user must be either the sender or | 289 if not request.user.is_authenticated(): |
214 receiver for this to succeed. | 290 return HttpResponseForbidden() |
215 """ | 291 if request.method != 'POST': |
216 msg = get_object_or_404(Message, pk=msg_id) | 292 return HttpResponseNotAllowed(['POST']) |
217 result = _delete_message(request.user, msg) | 293 |
218 if result == MSG_DELETED: | 294 delete_ids = [] |
219 messages.success(request, 'Message deleted.') | 295 if 'inbox_ids' in request.POST: |
220 elif result == MSG_TRASHED: | 296 delete_ids.extend(request.POST.getlist('inbox_ids')) |
221 messages.success(request, 'Message sent to trash.') | 297 if 'outbox_ids' in request.POST: |
222 else: | 298 delete_ids.extend(request.POST.getlist('outbox_ids')) |
223 messages.error(request, 'Error deleting message.') | 299 |
224 | 300 if len(delete_ids): |
225 return box_redirect(request) | 301 _delete_msgs(request.user, delete_ids) |
226 | 302 |
227 | 303 if 'trash_ids' in request.POST: |
228 @login_required | 304 _undelete_msgs(request.user, request.POST.getlist('trash_ids')) |
229 def delete_bulk(request): | 305 |
230 """ | 306 return HttpResponse(''); |
231 Deletes messages in bulk. The message ID's to be deleted are expected | |
232 to be in the delete POST array. The user must be either the sender | |
233 or receiver for this to succeed. | |
234 """ | |
235 if request.method == "POST": | |
236 delete_ids = request.POST.getlist('delete_ids') | |
237 try: | |
238 delete_ids = [int(id) for id in delete_ids] | |
239 except ValueError: | |
240 raise Http404 | |
241 | |
242 msgs = Message.objects.filter(id__in=delete_ids) | |
243 | |
244 counts = collections.defaultdict(int) | |
245 for msg in msgs: | |
246 result = _delete_message(request.user, msg) | |
247 counts[result] += 1 | |
248 | |
249 if counts[MSG_DELETED]: | |
250 messages.success(request, 'Messages deleted: %d' % counts[MSG_DELETED]) | |
251 if counts[MSG_TRASHED]: | |
252 messages.success(request, 'Messages sent to trash: %d' % counts[MSG_TRASHED]) | |
253 if counts[MSG_ERROR]: | |
254 messages.error(request, 'Message errors: %d' % counts[MSG_ERROR]) | |
255 | |
256 return box_redirect(request) | |
257 | |
258 | |
259 @login_required | |
260 @require_POST | |
261 def undelete(request, msg_id): | |
262 """ | |
263 Undeletes a given message. The user must be either the sender or | |
264 receiver for this to succeed. | |
265 """ | |
266 msg = get_object_or_404(Message, pk=msg_id) | |
267 if msg.sender == request.user: | |
268 msg.sender_delete_date = None | |
269 elif msg.receiver == request.user: | |
270 msg.receiver_delete_date = None | |
271 else: | |
272 raise Http404 | |
273 msg.save() | |
274 messages.success(request, 'Message retrieved from the trash.') | |
275 | |
276 return box_redirect(request) | |
277 | |
278 | |
279 @login_required | |
280 def undelete_bulk(request): | |
281 """ | |
282 Undeletes messages in bulk. The message ID's to be deleted are expected | |
283 to be in the delete POST array. The user must be either the sender | |
284 or receiver for this to succeed. | |
285 """ | |
286 if request.method == "POST": | |
287 undelete_ids = request.POST.getlist('undelete_ids') | |
288 try: | |
289 undelete_ids = [int(id) for id in undelete_ids] | |
290 except ValueError: | |
291 raise Http404 | |
292 msgs = Message.objects.filter(id__in = undelete_ids) | |
293 for msg in msgs: | |
294 if msg.sender == request.user: | |
295 msg.sender_delete_date = None | |
296 msg.save() | |
297 elif msg.receiver == request.user: | |
298 msg.receiver_delete_date = None | |
299 msg.save() | |
300 messages.success(request, 'Messages retrieved from the trash.') | |
301 | |
302 return box_redirect(request) | |
303 | |
304 | |
305 @login_required | |
306 def options(request): | |
307 """ | |
308 View to display/change user options. | |
309 """ | |
310 if request.method == "POST": | |
311 if request.POST.get('submit_button', 'Cancel') == 'Cancel': | |
312 return HttpResponseRedirect(reverse('messages-inbox')) | |
313 options = Options.objects.for_user(request.user) | |
314 form = OptionsForm(request.POST, instance=options) | |
315 if form.is_valid(): | |
316 form.save() | |
317 messages.success(request, 'Options saved.') | |
318 return HttpResponseRedirect(reverse('messages-inbox')) | |
319 else: | |
320 try: | |
321 options = Options.objects.for_user(request.user) | |
322 except: | |
323 options = Options() | |
324 options.user = request.user | |
325 options.save() | |
326 | |
327 form = OptionsForm(instance=options) | |
328 | |
329 return render_to_response('messages/options.html', { | |
330 'form': form, | |
331 }, | |
332 context_instance = RequestContext(request)) | |
333 |