bgneal@425
|
1 """
|
bgneal@425
|
2 Views for the messages application.
|
bgneal@425
|
3
|
bgneal@425
|
4 """
|
bgneal@425
|
5 import datetime
|
bgneal@425
|
6
|
bgneal@425
|
7 from django.contrib.auth.decorators import login_required
|
bgneal@428
|
8 from django.contrib.auth.models import User
|
bgneal@436
|
9 from django.contrib import messages as django_messages
|
bgneal@425
|
10 from django.core.paginator import Paginator, EmptyPage, InvalidPage
|
bgneal@425
|
11 from django.core.urlresolvers import reverse
|
bgneal@425
|
12 from django.http import HttpResponse
|
bgneal@425
|
13 from django.http import HttpResponseForbidden
|
bgneal@425
|
14 from django.http import HttpResponseNotAllowed
|
bgneal@425
|
15 from django.shortcuts import get_object_or_404
|
bgneal@425
|
16 from django.shortcuts import render
|
bgneal@425
|
17 import django.utils.simplejson as json
|
bgneal@425
|
18
|
bgneal@425
|
19 from messages.models import Message, Options
|
bgneal@425
|
20 from messages.forms import OptionsForm, ComposeForm
|
bgneal@566
|
21 from messages.utils import reply_subject
|
bgneal@436
|
22 from messages import MSG_BOX_LIMIT
|
bgneal@566
|
23 from core.functions import quote_message
|
bgneal@425
|
24
|
bgneal@425
|
25
|
bgneal@436
|
26 MSGS_PER_PAGE = 20 # message pagination value
|
bgneal@425
|
27
|
bgneal@436
|
28 # This must match the jQuery UI tab control
|
bgneal@425
|
29 TAB_INDICES = {
|
bgneal@425
|
30 'inbox': 0,
|
bgneal@425
|
31 'compose': 1,
|
bgneal@425
|
32 'outbox': 2,
|
bgneal@425
|
33 'trash': 3,
|
bgneal@425
|
34 'options': 4,
|
bgneal@425
|
35 }
|
bgneal@425
|
36
|
bgneal@425
|
37
|
bgneal@425
|
38 def _get_page(request):
|
bgneal@425
|
39 try:
|
bgneal@425
|
40 n = int(request.GET.get('page', '1'))
|
bgneal@425
|
41 except ValueError:
|
bgneal@425
|
42 n = 1
|
bgneal@425
|
43 return n
|
bgneal@425
|
44
|
bgneal@425
|
45
|
bgneal@436
|
46 def _quota_check(box_name, count, request):
|
bgneal@436
|
47 """
|
bgneal@436
|
48 Checks the message box count against MSG_BOX_LIMIT.
|
bgneal@436
|
49 Emits a message to the user if the quota is exceeded.
|
bgneal@441
|
50
|
bgneal@568
|
51 Returns the percent used as an integer between 0-100.
|
bgneal@568
|
52
|
bgneal@436
|
53 """
|
bgneal@436
|
54 if count >= MSG_BOX_LIMIT:
|
bgneal@436
|
55 django_messages.warning(request,
|
bgneal@436
|
56 "Your %s is full. Please delete some messages." % box_name)
|
bgneal@436
|
57
|
bgneal@568
|
58 return 100 * count / MSG_BOX_LIMIT
|
bgneal@568
|
59
|
bgneal@436
|
60
|
bgneal@425
|
61 @login_required
|
bgneal@425
|
62 def index(request, tab=None):
|
bgneal@425
|
63 """
|
bgneal@425
|
64 This function displays the base tabbed private messages view.
|
bgneal@425
|
65
|
bgneal@425
|
66 """
|
bgneal@425
|
67 tab_index = TAB_INDICES[tab] if tab else 0
|
bgneal@425
|
68 return render(request, 'messages/tabbed_base.html', {
|
bgneal@425
|
69 'tab': tab_index,
|
bgneal@429
|
70 'unread_count': Message.objects.unread_count(request.user),
|
bgneal@425
|
71 })
|
bgneal@425
|
72
|
bgneal@425
|
73
|
bgneal@428
|
74 @login_required
|
bgneal@428
|
75 def compose_to(request, receiver):
|
bgneal@428
|
76 """
|
bgneal@428
|
77 This function displays the base tabbed private messages view,
|
bgneal@428
|
78 and configures it to display the compose PM tab for the given
|
bgneal@428
|
79 receiver.
|
bgneal@428
|
80
|
bgneal@428
|
81 """
|
bgneal@428
|
82 user = get_object_or_404(User, username=receiver)
|
bgneal@428
|
83 tab_index = TAB_INDICES['compose']
|
bgneal@428
|
84 return render(request, 'messages/tabbed_base.html', {
|
bgneal@428
|
85 'tab': tab_index,
|
bgneal@428
|
86 'receiver': receiver,
|
bgneal@429
|
87 'unread_count': Message.objects.unread_count(request.user),
|
bgneal@428
|
88 })
|
bgneal@428
|
89
|
bgneal@428
|
90
|
bgneal@425
|
91 def inbox(request):
|
bgneal@425
|
92 """
|
bgneal@425
|
93 Returns the inbox for the user.
|
bgneal@425
|
94
|
bgneal@425
|
95 """
|
bgneal@425
|
96 if not request.user.is_authenticated():
|
bgneal@425
|
97 return HttpResponseForbidden()
|
bgneal@425
|
98
|
bgneal@425
|
99 msg_list = Message.objects.inbox(request.user)
|
bgneal@568
|
100 msg_count = msg_list.count()
|
bgneal@568
|
101 pct_used = _quota_check('inbox', msg_count, request)
|
bgneal@436
|
102
|
bgneal@425
|
103 paginator = Paginator(msg_list, MSGS_PER_PAGE)
|
bgneal@425
|
104 try:
|
bgneal@425
|
105 msgs = paginator.page(_get_page(request))
|
bgneal@566
|
106 except (EmptyPage, InvalidPage):
|
bgneal@425
|
107 msgs = paginator.page(paginator.num_pages)
|
bgneal@425
|
108
|
bgneal@425
|
109 return render(request, 'messages/inbox_tab.html', {
|
bgneal@425
|
110 'msgs': msgs,
|
bgneal@429
|
111 'url': reverse('messages-inbox'),
|
bgneal@568
|
112 'pct_used': pct_used,
|
bgneal@425
|
113 })
|
bgneal@425
|
114
|
bgneal@425
|
115
|
bgneal@425
|
116 def outbox(request):
|
bgneal@425
|
117 """
|
bgneal@425
|
118 Returns the outbox for the user.
|
bgneal@425
|
119
|
bgneal@425
|
120 """
|
bgneal@425
|
121 if not request.user.is_authenticated():
|
bgneal@425
|
122 return HttpResponseForbidden()
|
bgneal@425
|
123
|
bgneal@425
|
124 msg_list = Message.objects.outbox(request.user)
|
bgneal@568
|
125 msg_count = msg_list.count()
|
bgneal@568
|
126 pct_used = _quota_check('outbox', msg_count, request)
|
bgneal@436
|
127
|
bgneal@425
|
128 paginator = Paginator(msg_list, MSGS_PER_PAGE)
|
bgneal@425
|
129 try:
|
bgneal@425
|
130 msgs = paginator.page(_get_page(request))
|
bgneal@566
|
131 except (EmptyPage, InvalidPage):
|
bgneal@425
|
132 msgs = paginator.page(paginator.num_pages)
|
bgneal@425
|
133
|
bgneal@425
|
134 return render(request, 'messages/outbox_tab.html', {
|
bgneal@425
|
135 'msgs': msgs,
|
bgneal@429
|
136 'url': reverse('messages-outbox'),
|
bgneal@568
|
137 'pct_used': pct_used,
|
bgneal@425
|
138 })
|
bgneal@425
|
139
|
bgneal@425
|
140
|
bgneal@425
|
141 def trash(request):
|
bgneal@425
|
142 """
|
bgneal@425
|
143 Returns the trash for the user.
|
bgneal@425
|
144
|
bgneal@425
|
145 """
|
bgneal@425
|
146 if not request.user.is_authenticated():
|
bgneal@425
|
147 return HttpResponseForbidden()
|
bgneal@425
|
148
|
bgneal@425
|
149 msg_list = Message.objects.trash(request.user)
|
bgneal@425
|
150 paginator = Paginator(msg_list, MSGS_PER_PAGE)
|
bgneal@425
|
151 try:
|
bgneal@425
|
152 msgs = paginator.page(_get_page(request))
|
bgneal@566
|
153 except (EmptyPage, InvalidPage):
|
bgneal@425
|
154 msgs = paginator.page(paginator.num_pages)
|
bgneal@425
|
155
|
bgneal@425
|
156 return render(request, 'messages/trash_tab.html', {
|
bgneal@425
|
157 'msgs': msgs,
|
bgneal@429
|
158 'url': reverse('messages-trash'),
|
bgneal@425
|
159 })
|
bgneal@425
|
160
|
bgneal@425
|
161
|
bgneal@425
|
162 def message(request):
|
bgneal@425
|
163 """
|
bgneal@425
|
164 This view function retrieves a message and returns it as a JSON object.
|
bgneal@425
|
165
|
bgneal@425
|
166 """
|
bgneal@425
|
167 if not request.user.is_authenticated():
|
bgneal@425
|
168 return HttpResponseForbidden()
|
bgneal@425
|
169 if request.method != 'POST':
|
bgneal@425
|
170 return HttpResponseNotAllowed(['POST'])
|
bgneal@425
|
171
|
bgneal@425
|
172 msg_id = request.POST.get('msg_id')
|
bgneal@425
|
173 msg = get_object_or_404(Message.objects.select_related(), pk=msg_id)
|
bgneal@425
|
174 if msg.sender != request.user and msg.receiver != request.user:
|
bgneal@425
|
175 return HttpResponseForbidden()
|
bgneal@425
|
176
|
bgneal@425
|
177 if msg.receiver == request.user and msg.read_date is None:
|
bgneal@425
|
178 msg.read_date = datetime.datetime.now()
|
bgneal@425
|
179 msg.save()
|
bgneal@425
|
180
|
bgneal@425
|
181 msg_dict = dict(subject=msg.subject,
|
bgneal@425
|
182 sender=msg.sender.username,
|
bgneal@425
|
183 receiver=msg.receiver.username,
|
bgneal@425
|
184 content=msg.html,
|
bgneal@425
|
185 re_subject=reply_subject(msg.subject),
|
bgneal@566
|
186 re_content=quote_message(msg.sender.username, msg.message))
|
bgneal@425
|
187
|
bgneal@425
|
188 result = json.dumps(msg_dict, ensure_ascii=False)
|
bgneal@425
|
189 return HttpResponse(result, content_type='application/json')
|
bgneal@425
|
190
|
bgneal@425
|
191
|
bgneal@425
|
192 def options(request):
|
bgneal@425
|
193 """
|
bgneal@425
|
194 This view handles the displaying and changing of private message options.
|
bgneal@425
|
195
|
bgneal@425
|
196 """
|
bgneal@425
|
197 if not request.user.is_authenticated():
|
bgneal@425
|
198 return HttpResponseForbidden()
|
bgneal@425
|
199
|
bgneal@425
|
200 if request.method == "POST":
|
bgneal@425
|
201 options = Options.objects.for_user(request.user)
|
bgneal@425
|
202 form = OptionsForm(request.POST, instance=options, prefix='opts')
|
bgneal@425
|
203 if form.is_valid():
|
bgneal@425
|
204 form.save()
|
bgneal@436
|
205 django_messages.success(request, 'Options saved.')
|
bgneal@425
|
206 else:
|
bgneal@425
|
207 options = Options.objects.for_user(request.user)
|
bgneal@425
|
208 form = OptionsForm(instance=options, prefix='opts')
|
bgneal@425
|
209
|
bgneal@425
|
210 return render(request, 'messages/options_tab.html', {
|
bgneal@425
|
211 'form': form,
|
bgneal@425
|
212 })
|
bgneal@425
|
213
|
bgneal@425
|
214
|
bgneal@425
|
215 def compose(request, receiver=None):
|
bgneal@425
|
216 """
|
bgneal@425
|
217 Process or prepare the compose form to create a new private message.
|
bgneal@425
|
218
|
bgneal@425
|
219 """
|
bgneal@425
|
220 if not request.user.is_authenticated():
|
bgneal@425
|
221 return HttpResponseForbidden()
|
bgneal@425
|
222
|
bgneal@425
|
223 if request.method == "POST":
|
bgneal@425
|
224 compose_form = ComposeForm(request.user, request.POST)
|
bgneal@430
|
225
|
bgneal@430
|
226 # Is this a reply to another message?
|
bgneal@430
|
227 parent_msg_id = request.POST.get('reply_to')
|
bgneal@430
|
228 if parent_msg_id:
|
bgneal@430
|
229 parent_msg = get_object_or_404(Message, id=parent_msg_id)
|
bgneal@430
|
230 if (request.user != parent_msg.receiver and
|
bgneal@430
|
231 request.user != parent_msg.sender):
|
bgneal@430
|
232 return HttpResponseForbidden()
|
bgneal@430
|
233 else:
|
bgneal@430
|
234 parent_msg = None
|
bgneal@430
|
235
|
bgneal@425
|
236 if compose_form.is_valid():
|
bgneal@430
|
237 compose_form.save(parent_msg=parent_msg)
|
bgneal@436
|
238 django_messages.success(request, 'Message sent.')
|
bgneal@431
|
239 compose_form = ComposeForm(request.user)
|
bgneal@425
|
240 else:
|
bgneal@425
|
241 if receiver is not None:
|
bgneal@425
|
242 form_data = {'receiver': receiver}
|
bgneal@425
|
243 compose_form = ComposeForm(request.user, initial=form_data)
|
bgneal@425
|
244 else:
|
bgneal@425
|
245 compose_form = ComposeForm(request.user)
|
bgneal@425
|
246
|
bgneal@436
|
247 _quota_check('outbox', Message.objects.outbox(request.user).count(), request)
|
bgneal@436
|
248
|
bgneal@425
|
249 return render(request, 'messages/compose_tab.html', {
|
bgneal@425
|
250 'compose_form': compose_form,
|
bgneal@425
|
251 })
|
bgneal@425
|
252
|
bgneal@425
|
253
|
bgneal@425
|
254 def _only_integers(slist):
|
bgneal@425
|
255 """
|
bgneal@425
|
256 Accepts a list of strings. Returns a list of integers consisting of only
|
bgneal@425
|
257 those elements from the original list that could be converted to integers
|
bgneal@425
|
258
|
bgneal@425
|
259 """
|
bgneal@425
|
260 result = []
|
bgneal@425
|
261 for s in slist:
|
bgneal@425
|
262 try:
|
bgneal@425
|
263 n = int(s)
|
bgneal@425
|
264 except ValueError:
|
bgneal@425
|
265 pass
|
bgneal@425
|
266 else:
|
bgneal@425
|
267 result.append(n)
|
bgneal@425
|
268 return result
|
bgneal@425
|
269
|
bgneal@425
|
270
|
bgneal@425
|
271 def _delete_msgs(user, msg_ids):
|
bgneal@425
|
272 """
|
bgneal@425
|
273 Deletes the messages given by the list of msg_ids. For this to succeed, the
|
bgneal@425
|
274 user has to be either the sender or receiver on each message.
|
bgneal@425
|
275
|
bgneal@425
|
276 """
|
bgneal@425
|
277 msg_ids = _only_integers(msg_ids)
|
bgneal@425
|
278 msgs = Message.objects.filter(id__in=msg_ids)
|
bgneal@425
|
279
|
bgneal@425
|
280 for msg in msgs:
|
bgneal@425
|
281 if msg.sender == user:
|
bgneal@425
|
282 if (msg.receiver_delete_date is not None or
|
bgneal@425
|
283 msg.read_date is None):
|
bgneal@425
|
284 # Both parties deleted the message or receiver hasn't read it
|
bgneal@425
|
285 # yet, we can delete it now
|
bgneal@425
|
286 msg.delete()
|
bgneal@425
|
287 else:
|
bgneal@425
|
288 # receiver still has it in inbox
|
bgneal@425
|
289 msg.sender_delete_date = datetime.datetime.now()
|
bgneal@425
|
290 msg.save()
|
bgneal@425
|
291
|
bgneal@425
|
292 elif msg.receiver == user:
|
bgneal@425
|
293 if msg.sender_delete_date is not None:
|
bgneal@425
|
294 # both parties deleted the message, we can delete it now
|
bgneal@425
|
295 msg.delete()
|
bgneal@425
|
296 else:
|
bgneal@425
|
297 # sender still has it in the outbox
|
bgneal@425
|
298 msg.receiver_delete_date = datetime.datetime.now()
|
bgneal@425
|
299 msg.save()
|
bgneal@425
|
300
|
bgneal@425
|
301
|
bgneal@425
|
302 def _undelete_msgs(user, msg_ids):
|
bgneal@425
|
303 """
|
bgneal@425
|
304 Attempts to "undelete" the messages given by the msg_ids list.
|
bgneal@425
|
305 This will only succeed if the user is either the sender or receiver.
|
bgneal@425
|
306
|
bgneal@425
|
307 """
|
bgneal@425
|
308 msg_ids = _only_integers(msg_ids)
|
bgneal@425
|
309 msgs = Message.objects.filter(id__in=msg_ids)
|
bgneal@425
|
310 for msg in msgs:
|
bgneal@425
|
311 if msg.sender == user:
|
bgneal@425
|
312 msg.sender_delete_date = None
|
bgneal@425
|
313 msg.save()
|
bgneal@425
|
314 elif msg.receiver == user:
|
bgneal@425
|
315 msg.receiver_delete_date = None
|
bgneal@425
|
316 msg.save()
|
bgneal@425
|
317
|
bgneal@425
|
318
|
bgneal@425
|
319 def bulk(request):
|
bgneal@425
|
320 """
|
bgneal@425
|
321 This view processes messages in bulk. Arrays of message ids are expected in
|
bgneal@425
|
322 the POST query dict: inbox_ids and outbox_ids will be deleted; trash_ids will
|
bgneal@425
|
323 be undeleted.
|
bgneal@425
|
324
|
bgneal@425
|
325 """
|
bgneal@425
|
326 if not request.user.is_authenticated():
|
bgneal@425
|
327 return HttpResponseForbidden()
|
bgneal@425
|
328 if request.method != 'POST':
|
bgneal@425
|
329 return HttpResponseNotAllowed(['POST'])
|
bgneal@425
|
330
|
bgneal@425
|
331 delete_ids = []
|
bgneal@425
|
332 if 'inbox_ids' in request.POST:
|
bgneal@425
|
333 delete_ids.extend(request.POST.getlist('inbox_ids'))
|
bgneal@425
|
334 if 'outbox_ids' in request.POST:
|
bgneal@425
|
335 delete_ids.extend(request.POST.getlist('outbox_ids'))
|
bgneal@425
|
336
|
bgneal@425
|
337 if len(delete_ids):
|
bgneal@425
|
338 _delete_msgs(request.user, delete_ids)
|
bgneal@425
|
339
|
bgneal@425
|
340 if 'trash_ids' in request.POST:
|
bgneal@425
|
341 _undelete_msgs(request.user, request.POST.getlist('trash_ids'))
|
bgneal@425
|
342
|
bgneal@425
|
343 return HttpResponse('');
|