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