-
Notifications
You must be signed in to change notification settings - Fork 2
/
views.py
313 lines (250 loc) · 13.1 KB
/
views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
"""'Global' view functions for the gtphipsi package.
This module exports the following view functions:
- home (request)
- sign_in (request)
- sign_out (request)
- forbidden (request)
- register (request)
- register_success (request)
- calendar (request)
- contact (request)
- contact_thanks (request)
- forgot_password (request)
- reset_password (request, id)
- reset_password_success (request)
"""
from datetime import datetime, timedelta
import logging
from django.conf import settings
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User
from django.core.mail import get_connection
from django.core.mail.message import EmailMessage
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponseRedirect, QueryDict
from django.shortcuts import get_object_or_404, render
from django.template import RequestContext
from gtphipsi.brothers.forms import UserForm
from gtphipsi.brothers.models import UserProfile, STATUS_BITS
from gtphipsi.chapter.forms import ContactForm
from gtphipsi.chapter.models import Announcement, InformationCard
from gtphipsi.common import create_user_and_profile, log_page_view, REFERRER
from gtphipsi.messages import get_message
log = logging.getLogger('django.request')
## ============================================= ##
## ##
## Public Views ##
## ##
## ============================================= ##
def home(request):
"""Render a home page - a page of text for visitors and a sort of 'dashboard' view for authenticated members."""
log_page_view(request, 'Home')
if request.user.is_anonymous():
template = 'index.html'
context = {} # main index doesn't require any context
else:
template = 'index_bros_only.html'
user = request.user
profile = user.get_profile()
threads = profile.subscriptions.order_by('-updated')
if len(threads) > 5:
threads = threads[:5]
announcements = Announcement.most_recent(False)
two_months_ago = datetime.now() - timedelta(days=60)
info_cards = InformationCard.objects.filter(created__gte=two_months_ago).order_by('-created')
if len(info_cards) > 5:
info_cards = info_cards[:5]
accounts = UserProfile.objects.filter(user__date_joined__gte=two_months_ago).order_by('badge')
context = {'subscriptions': threads, 'announcements': announcements, 'info_cards': info_cards, 'accounts': accounts}
return render(request, template, context, context_instance=RequestContext(request))
def sign_in(request):
"""Render and process a form for users to sign into their accounts."""
log_page_view(request, 'Sign In')
if request.user.is_authenticated():
return HttpResponseRedirect(reverse('home')) # you can't sign in once you're signed in ...
if 'login_attempts' not in request.session:
request.session['login_attempts'] = 1
if 'username' not in request.session:
request.session['username'] = ''
error = None
if request.method == 'POST':
username = request.POST.get('username', '')
if username != request.session['username']:
request.session['username'] = username
request.session['login_attempts'] = 1
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
error = get_message('login.account.invalid')
else:
profile = user.get_profile()
if profile.has_bit(STATUS_BITS['LOCKED_OUT']):
error = get_message('login.account.locked')
elif request.session['login_attempts'] > settings.MAX_LOGIN_ATTEMPTS:
error = get_message('login.account.locked')
profile.set_bit(STATUS_BITS['LOCKED_OUT'])
profile.save()
log.info('User %s (%s) is now locked out', profile.user.username, profile.common_name())
else:
user = authenticate(username=username, password=request.POST.get('password'))
if user is None:
error = get_message('login.account.invalid')
request.session['login_attempts'] += 1 # invalid password; increment login attempts
elif not user.is_active:
error = get_message('login.account.disabled')
else:
log.info('User %s (%s) signed in - last login was %s', user.username, user.get_full_name(),
user.last_login.strftime('%m/%d/%Y %I:%M %p'))
login(request, user)
del request.session['login_attempts'], request.session['username'], request.session['group_perms']
return HttpResponseRedirect(_get_redirect_destination(request.META[REFERRER], user.get_profile()))
else:
username = ''
return render(request, 'public/login.html', {'username': username, 'error': error},
context_instance=RequestContext(request))
def sign_out(request):
"""Sign out the currently authenticated user (if there is one), then redirect to the home page."""
log_page_view(request, 'Sign Out')
log.info('User %s (%s) signed out', request.user.username, request.user.get_full_name())
logout(request)
if 'group_perms' in request.session:
del request.session['group_perms']
return HttpResponseRedirect(reverse('home'))
def forbidden(request):
"""Render a 'forbidden' page when a user tries to go to a page that he is not allowed to view."""
log_page_view(request, 'Forbidden')
log.info('Forbidden action attempted!')
return render(request, 'forbidden.html', context_instance=RequestContext(request))
def register(request):
"""Render and process a form for members to register with the site."""
log_page_view(request, 'Register')
if request.user.is_authenticated():
return HttpResponseRedirect(reverse('home'))
if request.method == 'POST':
form = UserForm(request.POST)
if form.is_valid():
create_user_and_profile(form.cleaned_data)
log.info('Created new user and profile (badge = %d)', form.cleaned_data.get('badge'))
return HttpResponseRedirect(reverse('register_success'))
else:
form = UserForm()
return render(request, 'brothers/register.html', {'form': form}, context_instance=RequestContext(request))
def register_success(request):
"""Render a 'success' page after a member has registered successfully."""
return render(request, 'brothers/register_success.html', context_instance=RequestContext(request))
def calendar(request):
"""Display the chapter's calendar of events (currently displays a Google calendar inline)."""
log_page_view(request, 'Calendar')
return render(request, 'public/calendar.html', context_instance=RequestContext(request))
def contact(request):
"""Render and process a form for visitors to contact the chapter."""
log_page_view(request, 'Contact Us')
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
contact = form.save()
_send_contact_emails(contact)
request.META[REFERRER] = reverse('contact') # set the HTTP_REFERER header, expected by the 'thanks' view
return HttpResponseRedirect(reverse('contact_thanks'))
else:
form = ContactForm(initial={'message': ''})
return render(request, 'public/contact.html', {'form': form}, context_instance=RequestContext(request))
def contact_thanks(request):
"""Render a 'thanks' view after processing a new contact form."""
if REFERRER not in request.META or not request.META[REFERRER].endswith(reverse('contact')):
raise Http404
return render(request, 'public/contact_thanks.html', context_instance=RequestContext(request))
def forgot_password(request):
"""Render and process a form to allow users to reset their passwords."""
log_page_view(request, 'Forgot Password')
if request.user.is_authenticated() and not request.user.get_profile().is_admin():
return HttpResponseRedirect(reverse('forbidden')) # if you're logged in but not admin, you shouldn't be here
username = None
if request.method == 'POST':
username = request.POST.get('username', '')
elif 'username' in request.session and request.session['username'] != '':
username = request.session['username']
if username is None:
error = None
else:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
error = 'That username is invalid.'
else:
return reset_password(request, user.id)
return render(request, 'public/forgot_password.html', {'error': error}, context_instance=RequestContext(request))
def reset_password(request, id):
"""Reset a user's password to a random string and email the user the new password.
Required parameters:
- id => the unique ID of the user whose password should be reset (as an integer)
"""
log_page_view(request, 'Reset Password')
if request.user.is_authenticated() and not request.user.get_profile().is_admin():
return HttpResponseRedirect(reverse('forbidden')) # non-admins should use the 'change password' form
user = get_object_or_404(User, id=id)
anonymous = request.user.is_anonymous()
if request.method == 'POST':
password = User.objects.make_random_password(length=8, allowed_chars=settings.PASSWORD_RESET_CHARS)
user.set_password(password)
user.save()
profile = user.get_profile()
profile.set_bit(STATUS_BITS['PASSWORD_RESET'])
profile.save()
reset_message = get_message('email.password.reset' if anonymous else 'email.password.admin.reset')
message = get_message('email.password.body', args=(profile.preferred_name(), reset_message, password))
user.email_user(get_message('email.password.subject'), message)
if anonymous:
log.info('Password reset for %s (%s) - temporary password emailed to %s', user.username, user.get_full_name(), user.email)
redirect = reverse('reset_password_success')
else:
log.info('Admin %s (#%d) reset password for %s (%s) - temporary password emailed to %s',
request.user.get_full_name(), request.user.get_profile().badge, user.username,
user.get_full_name(), user.email)
redirect = reverse('manage_users')
return HttpResponseRedirect(redirect)
return render(request, 'public/reset_password.html', {'anon': anonymous, 'username': user.username, 'user_id': id},
context_instance=RequestContext(request))
def reset_password_success(request):
"""Render a 'success' page after a user's password has been reset successfully."""
return render(request, 'public/reset_password_success.html', context_instance=RequestContext(request))
## ============================================= ##
## ##
## Private Functions ##
## ##
## ============================================= ##
def _get_redirect_destination(referrer, profile):
"""Find and return the 'next' parameter from the HTTP Referrer header, if present.
Required parameters:
- referrer => the content of the HTTP Referrer header, as a string
- profile => the profile of the user who just logged in
"""
index = referrer.find('?')
redirect = reverse('home')
if profile is not None and profile.has_bit(STATUS_BITS['PASSWORD_RESET']):
redirect = reverse('change_password')
elif index > -1:
params = QueryDict(referrer[index+1:])
if 'next' in params:
redirect = params['next']
return redirect
def _send_contact_emails(contact):
"""Send an email to a visitor who submitted a contact form; email brothers who want to be notified.
Required parameters:
- contact => the contact record for which to email
"""
date = contact.created.strftime('%B %d, %Y at %I:%M %p')
# message to the person who submitted the information card
message = EmailMessage(get_message('email.contact.subject'),
get_message('email.contact.body', args=(contact.name, date, contact.to_string())),
to=[contact.email])
# message to the webmaster and everyone who has selected to be notified about new contact forms
notification = EmailMessage(get_message('notify.contact.subject'),
get_message('notify.contact.body', args=(date, contact.to_string())),
to=['webmaster@gtphipsi.org'],
bcc=UserProfile.all_emails_with_bit(STATUS_BITS['EMAIL_NEW_CONTACT']))
# open a connection to the SMTP backend so we can send two messages over the same connection
conn = get_connection()
conn.open()
conn.send_messages([message, notification])
conn.close()