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@425
|
8 from django.contrib import messages
|
bgneal@425
|
9 from django.core.paginator import Paginator, EmptyPage, InvalidPage
|
bgneal@425
|
10 from django.core.urlresolvers import reverse
|
bgneal@425
|
11 from django.http import HttpResponse
|
bgneal@425
|
12 from django.http import HttpResponseForbidden
|
bgneal@425
|
13 from django.http import HttpResponseNotAllowed
|
bgneal@425
|
14 from django.http import HttpResponseRedirect
|
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@425
|
21 from messages.utils import reply_subject, quote_message
|
bgneal@425
|
22
|
bgneal@425
|
23
|
bgneal@425
|
24 MSGS_PER_PAGE = 20
|
bgneal@425
|
25
|
bgneal@425
|
26 TAB_INDICES = {
|
bgneal@425
|
27 'inbox': 0,
|
bgneal@425
|
28 'compose': 1,
|
bgneal@425
|
29 'outbox': 2,
|
bgneal@425
|
30 'trash': 3,
|
bgneal@425
|
31 'options': 4,
|
bgneal@425
|
32 }
|
bgneal@425
|
33
|
bgneal@425
|
34
|
bgneal@425
|
35 def _get_page(request):
|
bgneal@425
|
36 try:
|
bgneal@425
|
37 n = int(request.GET.get('page', '1'))
|
bgneal@425
|
38 except ValueError:
|
bgneal@425
|
39 n = 1
|
bgneal@425
|
40 return n
|
bgneal@425
|
41
|
bgneal@425
|
42
|
bgneal@425
|
43 @login_required
|
bgneal@425
|
44 def index(request, tab=None):
|
bgneal@425
|
45 """
|
bgneal@425
|
46 This function displays the base tabbed private messages view.
|
bgneal@425
|
47
|
bgneal@425
|
48 """
|
bgneal@425
|
49 tab_index = TAB_INDICES[tab] if tab else 0
|
bgneal@425
|
50 return render(request, 'messages/tabbed_base.html', {
|
bgneal@425
|
51 'tab': tab_index,
|
bgneal@425
|
52 })
|
bgneal@425
|
53
|
bgneal@425
|
54
|
bgneal@425
|
55 def inbox(request):
|
bgneal@425
|
56 """
|
bgneal@425
|
57 Returns the inbox for the user.
|
bgneal@425
|
58
|
bgneal@425
|
59 """
|
bgneal@425
|
60 if not request.user.is_authenticated():
|
bgneal@425
|
61 return HttpResponseForbidden()
|
bgneal@425
|
62
|
bgneal@425
|
63 msg_list = Message.objects.inbox(request.user)
|
bgneal@425
|
64 paginator = Paginator(msg_list, MSGS_PER_PAGE)
|
bgneal@425
|
65 try:
|
bgneal@425
|
66 msgs = paginator.page(_get_page(request))
|
bgneal@425
|
67 except EmptyPage, InvalidPage:
|
bgneal@425
|
68 msgs = paginator.page(paginator.num_pages)
|
bgneal@425
|
69
|
bgneal@425
|
70 return render(request, 'messages/inbox_tab.html', {
|
bgneal@425
|
71 'msgs': msgs,
|
bgneal@425
|
72 'url': reverse('messages-beta_inbox'),
|
bgneal@425
|
73 })
|
bgneal@425
|
74
|
bgneal@425
|
75
|
bgneal@425
|
76 def outbox(request):
|
bgneal@425
|
77 """
|
bgneal@425
|
78 Returns the outbox for the user.
|
bgneal@425
|
79
|
bgneal@425
|
80 """
|
bgneal@425
|
81 if not request.user.is_authenticated():
|
bgneal@425
|
82 return HttpResponseForbidden()
|
bgneal@425
|
83
|
bgneal@425
|
84 msg_list = Message.objects.outbox(request.user)
|
bgneal@425
|
85 paginator = Paginator(msg_list, MSGS_PER_PAGE)
|
bgneal@425
|
86 try:
|
bgneal@425
|
87 msgs = paginator.page(_get_page(request))
|
bgneal@425
|
88 except EmptyPage, InvalidPage:
|
bgneal@425
|
89 msgs = paginator.page(paginator.num_pages)
|
bgneal@425
|
90
|
bgneal@425
|
91 return render(request, 'messages/outbox_tab.html', {
|
bgneal@425
|
92 'msgs': msgs,
|
bgneal@425
|
93 'url': reverse('messages-beta_outbox'),
|
bgneal@425
|
94 })
|
bgneal@425
|
95
|
bgneal@425
|
96
|
bgneal@425
|
97 def trash(request):
|
bgneal@425
|
98 """
|
bgneal@425
|
99 Returns the trash for the user.
|
bgneal@425
|
100
|
bgneal@425
|
101 """
|
bgneal@425
|
102 if not request.user.is_authenticated():
|
bgneal@425
|
103 return HttpResponseForbidden()
|
bgneal@425
|
104
|
bgneal@425
|
105 msg_list = Message.objects.trash(request.user)
|
bgneal@425
|
106 paginator = Paginator(msg_list, MSGS_PER_PAGE)
|
bgneal@425
|
107 try:
|
bgneal@425
|
108 msgs = paginator.page(_get_page(request))
|
bgneal@425
|
109 except EmptyPage, InvalidPage:
|
bgneal@425
|
110 msgs = paginator.page(paginator.num_pages)
|
bgneal@425
|
111
|
bgneal@425
|
112 return render(request, 'messages/trash_tab.html', {
|
bgneal@425
|
113 'msgs': msgs,
|
bgneal@425
|
114 'url': reverse('messages-beta_trash'),
|
bgneal@425
|
115 })
|
bgneal@425
|
116
|
bgneal@425
|
117
|
bgneal@425
|
118 def message(request):
|
bgneal@425
|
119 """
|
bgneal@425
|
120 This view function retrieves a message and returns it as a JSON object.
|
bgneal@425
|
121
|
bgneal@425
|
122 """
|
bgneal@425
|
123 if not request.user.is_authenticated():
|
bgneal@425
|
124 return HttpResponseForbidden()
|
bgneal@425
|
125 if request.method != 'POST':
|
bgneal@425
|
126 return HttpResponseNotAllowed(['POST'])
|
bgneal@425
|
127
|
bgneal@425
|
128 msg_id = request.POST.get('msg_id')
|
bgneal@425
|
129 msg = get_object_or_404(Message.objects.select_related(), pk=msg_id)
|
bgneal@425
|
130 if msg.sender != request.user and msg.receiver != request.user:
|
bgneal@425
|
131 return HttpResponseForbidden()
|
bgneal@425
|
132
|
bgneal@425
|
133 if msg.receiver == request.user and msg.read_date is None:
|
bgneal@425
|
134 msg.read_date = datetime.datetime.now()
|
bgneal@425
|
135 msg.save()
|
bgneal@425
|
136
|
bgneal@425
|
137 msg_dict = dict(subject=msg.subject,
|
bgneal@425
|
138 sender=msg.sender.username,
|
bgneal@425
|
139 receiver=msg.receiver.username,
|
bgneal@425
|
140 content=msg.html,
|
bgneal@425
|
141 re_subject=reply_subject(msg.subject),
|
bgneal@425
|
142 re_content=quote_message(msg.sender.username, msg.send_date,
|
bgneal@425
|
143 msg.message))
|
bgneal@425
|
144
|
bgneal@425
|
145 result = json.dumps(msg_dict, ensure_ascii=False)
|
bgneal@425
|
146 return HttpResponse(result, content_type='application/json')
|
bgneal@425
|
147
|
bgneal@425
|
148
|
bgneal@425
|
149 def options(request):
|
bgneal@425
|
150 """
|
bgneal@425
|
151 This view handles the displaying and changing of private message options.
|
bgneal@425
|
152
|
bgneal@425
|
153 """
|
bgneal@425
|
154 if not request.user.is_authenticated():
|
bgneal@425
|
155 return HttpResponseForbidden()
|
bgneal@425
|
156
|
bgneal@425
|
157 if request.method == "POST":
|
bgneal@425
|
158 options = Options.objects.for_user(request.user)
|
bgneal@425
|
159 form = OptionsForm(request.POST, instance=options, prefix='opts')
|
bgneal@425
|
160 if form.is_valid():
|
bgneal@425
|
161 form.save()
|
bgneal@425
|
162 messages.success(request, 'Options saved.')
|
bgneal@425
|
163 else:
|
bgneal@425
|
164 options = Options.objects.for_user(request.user)
|
bgneal@425
|
165 form = OptionsForm(instance=options, prefix='opts')
|
bgneal@425
|
166
|
bgneal@425
|
167 return render(request, 'messages/options_tab.html', {
|
bgneal@425
|
168 'form': form,
|
bgneal@425
|
169 })
|
bgneal@425
|
170
|
bgneal@425
|
171
|
bgneal@425
|
172 def compose(request, receiver=None):
|
bgneal@425
|
173 """
|
bgneal@425
|
174 Process or prepare the compose form to create a new private message.
|
bgneal@425
|
175
|
bgneal@425
|
176 """
|
bgneal@425
|
177 if not request.user.is_authenticated():
|
bgneal@425
|
178 return HttpResponseForbidden()
|
bgneal@425
|
179
|
bgneal@425
|
180 if request.method == "POST":
|
bgneal@425
|
181 compose_form = ComposeForm(request.user, request.POST)
|
bgneal@425
|
182 if compose_form.is_valid():
|
bgneal@425
|
183 compose_form.save(sender=request.user)
|
bgneal@425
|
184 messages.success(request, 'Message sent.')
|
bgneal@425
|
185 return HttpResponseRedirect(reverse('messages-beta_index_named', args=['compose']))
|
bgneal@425
|
186 else:
|
bgneal@425
|
187 if receiver is not None:
|
bgneal@425
|
188 form_data = {'receiver': receiver}
|
bgneal@425
|
189 compose_form = ComposeForm(request.user, initial=form_data)
|
bgneal@425
|
190 else:
|
bgneal@425
|
191 compose_form = ComposeForm(request.user)
|
bgneal@425
|
192
|
bgneal@425
|
193 return render(request, 'messages/compose_tab.html', {
|
bgneal@425
|
194 'compose_form': compose_form,
|
bgneal@425
|
195 })
|
bgneal@425
|
196
|
bgneal@425
|
197
|
bgneal@425
|
198 def _only_integers(slist):
|
bgneal@425
|
199 """
|
bgneal@425
|
200 Accepts a list of strings. Returns a list of integers consisting of only
|
bgneal@425
|
201 those elements from the original list that could be converted to integers
|
bgneal@425
|
202
|
bgneal@425
|
203 """
|
bgneal@425
|
204 result = []
|
bgneal@425
|
205 for s in slist:
|
bgneal@425
|
206 try:
|
bgneal@425
|
207 n = int(s)
|
bgneal@425
|
208 except ValueError:
|
bgneal@425
|
209 pass
|
bgneal@425
|
210 else:
|
bgneal@425
|
211 result.append(n)
|
bgneal@425
|
212 return result
|
bgneal@425
|
213
|
bgneal@425
|
214
|
bgneal@425
|
215 def _delete_msgs(user, msg_ids):
|
bgneal@425
|
216 """
|
bgneal@425
|
217 Deletes the messages given by the list of msg_ids. For this to succeed, the
|
bgneal@425
|
218 user has to be either the sender or receiver on each message.
|
bgneal@425
|
219
|
bgneal@425
|
220 """
|
bgneal@425
|
221 msg_ids = _only_integers(msg_ids)
|
bgneal@425
|
222 msgs = Message.objects.filter(id__in=msg_ids)
|
bgneal@425
|
223
|
bgneal@425
|
224 for msg in msgs:
|
bgneal@425
|
225 if msg.sender == user:
|
bgneal@425
|
226 if (msg.receiver_delete_date is not None or
|
bgneal@425
|
227 msg.read_date is None):
|
bgneal@425
|
228 # Both parties deleted the message or receiver hasn't read it
|
bgneal@425
|
229 # yet, we can delete it now
|
bgneal@425
|
230 msg.delete()
|
bgneal@425
|
231 else:
|
bgneal@425
|
232 # receiver still has it in inbox
|
bgneal@425
|
233 msg.sender_delete_date = datetime.datetime.now()
|
bgneal@425
|
234 msg.save()
|
bgneal@425
|
235
|
bgneal@425
|
236 elif msg.receiver == user:
|
bgneal@425
|
237 if msg.sender_delete_date is not None:
|
bgneal@425
|
238 # both parties deleted the message, we can delete it now
|
bgneal@425
|
239 msg.delete()
|
bgneal@425
|
240 else:
|
bgneal@425
|
241 # sender still has it in the outbox
|
bgneal@425
|
242 msg.receiver_delete_date = datetime.datetime.now()
|
bgneal@425
|
243 msg.save()
|
bgneal@425
|
244
|
bgneal@425
|
245
|
bgneal@425
|
246 def _undelete_msgs(user, msg_ids):
|
bgneal@425
|
247 """
|
bgneal@425
|
248 Attempts to "undelete" the messages given by the msg_ids list.
|
bgneal@425
|
249 This will only succeed if the user is either the sender or receiver.
|
bgneal@425
|
250
|
bgneal@425
|
251 """
|
bgneal@425
|
252 msg_ids = _only_integers(msg_ids)
|
bgneal@425
|
253 msgs = Message.objects.filter(id__in=msg_ids)
|
bgneal@425
|
254 for msg in msgs:
|
bgneal@425
|
255 if msg.sender == user:
|
bgneal@425
|
256 msg.sender_delete_date = None
|
bgneal@425
|
257 msg.save()
|
bgneal@425
|
258 elif msg.receiver == user:
|
bgneal@425
|
259 msg.receiver_delete_date = None
|
bgneal@425
|
260 msg.save()
|
bgneal@425
|
261
|
bgneal@425
|
262
|
bgneal@425
|
263 def bulk(request):
|
bgneal@425
|
264 """
|
bgneal@425
|
265 This view processes messages in bulk. Arrays of message ids are expected in
|
bgneal@425
|
266 the POST query dict: inbox_ids and outbox_ids will be deleted; trash_ids will
|
bgneal@425
|
267 be undeleted.
|
bgneal@425
|
268
|
bgneal@425
|
269 """
|
bgneal@425
|
270 if not request.user.is_authenticated():
|
bgneal@425
|
271 return HttpResponseForbidden()
|
bgneal@425
|
272 if request.method != 'POST':
|
bgneal@425
|
273 return HttpResponseNotAllowed(['POST'])
|
bgneal@425
|
274
|
bgneal@425
|
275 delete_ids = []
|
bgneal@425
|
276 if 'inbox_ids' in request.POST:
|
bgneal@425
|
277 delete_ids.extend(request.POST.getlist('inbox_ids'))
|
bgneal@425
|
278 if 'outbox_ids' in request.POST:
|
bgneal@425
|
279 delete_ids.extend(request.POST.getlist('outbox_ids'))
|
bgneal@425
|
280
|
bgneal@425
|
281 if len(delete_ids):
|
bgneal@425
|
282 _delete_msgs(request.user, delete_ids)
|
bgneal@425
|
283
|
bgneal@425
|
284 if 'trash_ids' in request.POST:
|
bgneal@425
|
285 _undelete_msgs(request.user, request.POST.getlist('trash_ids'))
|
bgneal@425
|
286
|
bgneal@425
|
287 return HttpResponse('');
|