views.py 19 KB
Newer Older
1 2 3 4
"""
go/views.py
"""

5
# Future Imports
David Haynes's avatar
David Haynes committed
6 7
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
8

David Haynes's avatar
David Haynes committed
9 10 11
# Python stdlib imports
from datetime import timedelta

12
# Django Imports
13
from django.conf import settings
14
from django.http import HttpResponseServerError  # Http404
15
from django.http import HttpResponseRedirect
16
from django.utils import timezone
17
from django.core.exceptions import PermissionDenied  # ValidationError
18
from django.core.mail import send_mail, EmailMessage
19 20
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import user_passes_test, login_required
Jean Michel Rouly's avatar
Jean Michel Rouly committed
21
from django.shortcuts import render, get_object_or_404, redirect
David Haynes's avatar
David Haynes committed
22 23

# Other imports
24
from ratelimit.decorators import ratelimit
25

26 27 28 29
# App Imports
from go.models import URL, RegisteredUser
from go.forms import URLForm, SignupForm

David Haynes's avatar
David Haynes committed
30
def index(request):
31
    """
David Haynes's avatar
David Haynes committed
32 33 34 35 36
    This view handles the homepage that the user is presented with when
    they request '/'. If they're not logged in, they're redirected to
    login. If they're logged in but not registered, they're given the
    not_registered error page. If they are logged in AND registered, they
    get the URL registration form.
37 38
    """

39
    # If the user is blocked, redirect them to the blocked page.
40
    # If the user is not authenticated, show them a public landing page.
41
    if not request.user.is_authenticated():
Zosman's avatar
Zosman committed
42
        return render(request, 'public_landing.html')
David Haynes's avatar
David Haynes committed
43
    # If the user isn't approved, then display the you're not approved page.
44
    elif not request.user.registereduser.approved:
45
        if request.user.registereduser.blocked:
Zosman's avatar
Zosman committed
46
            return render(request, 'banned.html')
47
        else:
Zosman's avatar
Zosman committed
48
            return render(request, 'not_registered.html')
49

50

David Haynes's avatar
David Haynes committed
51
    # Initialize a URL form
52
    url_form = URLForm(host=request.META.get('HTTP_HOST'))  # unbound form
53

David Haynes's avatar
David Haynes committed
54 55
    # If a POST request is received, then the user has submitted a form and it's
    # time to parse the form and create a new URL object
56
    if request.method == 'POST':
57 58
        # Now we initialize the form again but this time we have the POST
        # request
59
        url_form = URLForm(request.POST, host=request.META.get('HTTP_HOST'))
60 61 62

        # Django will check the form to make sure it's valid
        if url_form.is_valid():
63
            # Call our post method to assemble our new URL object
64
            res = post(request, url_form)
65

66 67
            # If there is a 500 error returned, handle it
            if res == 500:
68
                return HttpResponseServerError(render(request, '500.html'))
69

70
            # Redirect to the shiny new URL
71
            return redirect('view', res.short)
72

73 74 75 76 77
        # Else, there is an error, redisplay the form with the validation errors
        else:
            # Render index.html passing the form to the template
            return render(request, 'core/index.html', {
                'form': url_form,
78
            })
79

80

David Haynes's avatar
David Haynes committed
81
    # Render index.html passing the form to the template
82
    return render(request, 'core/index.html', {
83
        'form': url_form,
84
    })
Jean Michel Rouly's avatar
Jean Michel Rouly committed
85

David Haynes's avatar
David Haynes committed
86
# Rate limits are completely arbitrary
87 88
@ratelimit(key='user', rate='3/m', method='POST', block=True)
@ratelimit(key='user', rate='25/d', method='POST', block=True)
89
def post(request, url_form):
90
    """
91
    Helper function that handles POST requests for the URL creation
92 93
    """

94 95
    # We don't commit the url object yet because we need to add its
    # owner, and parse its date field.
96
    url = url_form.save(commit=False)
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
    url.owner = request.user.registereduser

    # If the user entered a short url, it's already been validated,
    # so accept it. If they did not, however, then generate a
    # random one and use that instead.
    short = url_form.cleaned_data.get('short').strip()

    # Check if a short URL was entered
    if len(short) > 0:
        url.short = short
    else:
        # If the user didn't enter a short url, generate a random
        # one. However, if a random one can't be generated, return
        # a 500 server error.
        random_short = URL.generate_valid_short()

        if random_short is None:
            return 500
115
        else:
116 117 118 119 120 121 122 123 124
            url.short = random_short

    # Grab the expiration field value. It's currently an unsable
    # string value, so we need to parse it into a datetime object
    # relative to right now.
    expires = url_form.cleaned_data.get('expires')

    # Determine what the expiration date is
    if expires == URLForm.DAY:
125
        url.expires = timezone.now() + timedelta(days=1)
126
    elif expires == URLForm.WEEK:
127
        url.expires = timezone.now() + timedelta(weeks=1)
128
    elif expires == URLForm.MONTH:
129
        url.expires = timezone.now() + timedelta(weeks=3)
130 131 132 133 134 135 136 137 138 139
    elif expires == URLForm.CUSTOM:
        url.expires = url_form.cleaned_data.get('expires_custom')
    else:
        pass  # leave the field NULL

    # Make sure that our new URL object is clean, then save it and
    # let's redirect to view this baby.
    url.full_clean()
    url.save()
    return url
140

David Haynes's avatar
David Haynes committed
141
def view(request, short):
142
    """
143 144
    This view allows the user to "view details" about a URL. Note that they
    do not need to be logged in to view this information.
145
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
146

David Haynes's avatar
David Haynes committed
147
    # Get the current domain info
Nicholas Anderson's avatar
Nicholas Anderson committed
148
    domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"
149

David Haynes's avatar
David Haynes committed
150
    # Get the URL that is being requested
151
    url = get_object_or_404(URL, short__iexact=short)
152

David Haynes's avatar
David Haynes committed
153
    # Render view.html passing the specified URL and Domain to the template
Jean Michel Rouly's avatar
Jean Michel Rouly committed
154
    return render(request, 'view.html', {
155
        'url': url,
156
        'domain': domain,
157
    })
158

David Haynes's avatar
David Haynes committed
159 160
@login_required
def my_links(request):
161
    """
David Haynes's avatar
David Haynes committed
162 163
    This view displays all the information about all of your URLs. You
    obviously need to be logged in to view your URLs.
164
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
165

David Haynes's avatar
David Haynes committed
166
    # Do not display this page to unapproved users
167
    if not request.user.registereduser.approved:
168
        return render(request, 'not_registered.html')
Jean Michel Rouly's avatar
Jean Michel Rouly committed
169

David Haynes's avatar
David Haynes committed
170
    # Get the current domain info
171
    domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"
172

David Haynes's avatar
David Haynes committed
173
    # Grab a list of all the URL's that are currently owned by the user
174
    urls = URL.objects.filter(owner=request.user.registereduser)
David Haynes's avatar
David Haynes committed
175 176

    # Render my_links.html passing the list of URL's and Domain to the template
177
    return render(request, 'my_links.html', {
178 179
        'urls': urls,
        'domain': domain,
180
    })
181

David Haynes's avatar
David Haynes committed
182 183
@login_required
def delete(request, short):
184
    """
David Haynes's avatar
David Haynes committed
185 186
    This view deletes a URL if you have the permission to. User must be
    logged in and registered, and must also be the owner of the URL.
187
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
188

David Haynes's avatar
David Haynes committed
189
    # Do not allow unapproved users to delete links
190
    if not request.user.registereduser.approved:
191
        return render(request, 'not_registered.html')
Jean Michel Rouly's avatar
Jean Michel Rouly committed
192

David Haynes's avatar
David Haynes committed
193
    # Get the URL that is going to be deleted
194
    url = get_object_or_404(URL, short__iexact=short)
David Haynes's avatar
David Haynes committed
195 196

    # If the RegisteredUser is the owner of the URL
197
    if url.owner == request.user.registereduser:
David Haynes's avatar
David Haynes committed
198
        # remove the URL
199
        url.delete()
David Haynes's avatar
David Haynes committed
200
        # rediret to my_links
201 202
        return redirect('my_links')
    else:
David Haynes's avatar
David Haynes committed
203
        # do not allow them to delete
204
        raise PermissionDenied()
205

206
@login_required
Jean Michel Rouly's avatar
Jean Michel Rouly committed
207
def signup(request):
208
    """
209 210
    This view presents the user with a registration form. You can register
    yourself.
211 212
    """

213
    # Do not display signup page to registered or approved users
214
    if request.user.registereduser.blocked:
215
        return render(request, 'banned.html')
216
    elif request.user.registereduser.approved:
David Haynes's avatar
David Haynes committed
217
        return redirect('/')
218
    elif request.user.registereduser.registered:
David Haynes's avatar
David Haynes committed
219
        return redirect('registered')
220

David Haynes's avatar
David Haynes committed
221
    # Initialize our signup form
222 223 224 225 226 227
    signup_form = SignupForm(
        request,
        initial={
            'full_name': request.user.first_name + " " + request.user.last_name
        }
    )
David Haynes's avatar
David Haynes committed
228 229

    # Set the full_name field to readonly since CAS will fill that in for them
230
    signup_form.fields['full_name'].widget.attrs['readonly'] = 'readonly'
Jean Michel Rouly's avatar
Jean Michel Rouly committed
231

David Haynes's avatar
David Haynes committed
232 233
    # If a POST request is received, then the user has submitted a form and it's
    # time to parse the form and create a new RegisteredUser
Jean Michel Rouly's avatar
Jean Michel Rouly committed
234
    if request.method == 'POST':
David Haynes's avatar
David Haynes committed
235 236
        # Now we initialize the form again but this time we have the POST
        # request
237 238 239 240 241 242
        signup_form = SignupForm(
            request, request.POST, instance=request.user.registereduser,
            initial={
                'full_name': request.user.first_name + " " + request.user.last_name
            }
        )
David Haynes's avatar
David Haynes committed
243 244

        # set the readonly flag again for good measure
245
        signup_form.fields['full_name'].widget.attrs['readonly'] = 'readonly'
246

David Haynes's avatar
David Haynes committed
247
        # Django will check the form to make sure it's valid
248
        if signup_form.is_valid():
David Haynes's avatar
David Haynes committed
249
            # Grab data from the form and store into variables
250
            description = signup_form.cleaned_data.get('description')
251
            full_name = signup_form.cleaned_data.get('full_name')
252
            organization = signup_form.cleaned_data.get('organization')
253

254 255
            # Only send mail if we've defined the mailserver
            if settings.EMAIL_HOST and settings.EMAIL_PORT:
root's avatar
root committed
256
                user_mail = request.user.username + settings.EMAIL_DOMAIN
257
                # Email sent to notify Admins
258
                to_admin = EmailMessage(
259
                    'Signup from %s' % (request.user.registereduser.user),
260 261 262 263 264
                    ######################
                    '%s signed up at %s\n\n'
                    'Username: %s\n'
                    'Organization: %s\n\n'
                    'Message: %s\n\n'
265 266
                    'You can contact the user directly by replying to this email or '
                    'reply all to contact the user and notfiy the mailing list.\n'
267 268
                    'Please head to go.gmu.edu/useradmin to approve or '
                    'deny this application.'
269 270 271 272 273
                    %(
                        str(full_name), str(timezone.now()).strip(),
                        str(request.user.registereduser.user), str(organization),
                        str(description)
                    ),
274 275
                    ######################
                    settings.EMAIL_FROM,
276
                    [settings.EMAIL_TO],
277 278 279
                    reply_to=[user_mail]
                )
                to_admin.send()
280
                # Confirmation email sent to Users
281
                send_mail(
282 283 284 285 286 287 288 289 290 291 292 293 294
                    'We have received your Go application!',
                    ######################
                    'Hey there %s,\n\n'
                    'The Go admins have received your application and are '
                    'currently in the process of reviewing it.\n\n'
                    'You will receive another email when you have been '
                    'approved.\n\n'
                    '- Go Admins'
                    % (str(full_name)),
                    ######################
                    settings.EMAIL_FROM,
                    [user_mail]
                )
295

David Haynes's avatar
David Haynes committed
296 297
            # Make sure that our new RegisteredUser object is clean, then save
            # it and let's redirect to tell the user they have registered.
298
            signup_form.save()
299
            return redirect('registered')
Jean Michel Rouly's avatar
Jean Michel Rouly committed
300

David Haynes's avatar
David Haynes committed
301 302
    # render signup.html passing along the form and the current registered
    # status
303
    return render(request, 'core/signup.html', {
304
        'form': signup_form,
305
        'registered': False,
306
    })
Jean Michel Rouly's avatar
Jean Michel Rouly committed
307

David Haynes's avatar
David Haynes committed
308
def redirection(request, short):
309
    """
David Haynes's avatar
David Haynes committed
310
    This view redirects a user based on the short URL they requested.
311
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
312

David Haynes's avatar
David Haynes committed
313 314
    # Get the current domain info
    domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"
Jean Michel Rouly's avatar
Jean Michel Rouly committed
315

David Haynes's avatar
David Haynes committed
316
    # Get the URL object that relates to the requested Go link
317
    url = get_object_or_404(URL, short__iexact=short)
David Haynes's avatar
David Haynes committed
318 319
    # Increment our clicks by one
    url.clicks += 1
320

David Haynes's avatar
David Haynes committed
321
    # If the user is trying to make a Go link to itself, we 404 them
322
    if url.target == domain + short:
323
        return redirect('404.html')
324

David Haynes's avatar
David Haynes committed
325
    # If the user is coming from a QR request then increment qrclicks
326 327 328
    if 'qr' in request.GET:
        url.qrclicks += 1

David Haynes's avatar
David Haynes committed
329
    # If the user is coming from a social media request then increment qrclicks
330 331 332
    if 'social' in request.GET:
        url.socialclicks += 1

David Haynes's avatar
David Haynes committed
333
    # Save our data and redirect the user towards thier destination
Jean Michel Rouly's avatar
Jean Michel Rouly committed
334
    url.save()
335
    return redirect(url.target)
Jean Michel Rouly's avatar
Jean Michel Rouly committed
336

337 338
def staff_member_required(view_func, redirect_field_name=REDIRECT_FIELD_NAME, login_url='/'):
    """
David Haynes's avatar
David Haynes committed
339 340
    Decorator function for views that checks that the user is logged in and is
    a staff member, displaying the login page if necessary.
341 342
    """

343 344
    return user_passes_test(
        lambda u: u.is_active and u.is_staff,
345 346
        login_url=login_url,
        redirect_field_name=redirect_field_name
347 348
    )(view_func)

David Haynes's avatar
David Haynes committed
349 350
@staff_member_required
def useradmin(request):
351
    """
David Haynes's avatar
David Haynes committed
352 353
    This view is a simplified admin panel, so that staff don't need to log in
    to approve links
354
    """
David Haynes's avatar
David Haynes committed
355 356

    # If we receive a POST request
357
    if request.POST:
David Haynes's avatar
David Haynes committed
358
        # Get a list of the potential victims (users)
359
        userlist = request.POST.getlist('username')
David Haynes's avatar
David Haynes committed
360
        # If we're approving users
361
        if '_approve' in request.POST:
362
            for name in userlist:
363 364 365
                to_approve = RegisteredUser.objects.get(user__username__exact=name)
                to_approve.approved = True
                to_approve.save()
David Haynes's avatar
David Haynes committed
366 367

                # Send an email letting them know they are approved
368
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
369
                    user_mail = to_approve.user.username + settings.EMAIL_DOMAIN
370 371 372 373 374 375 376 377
                    send_mail(
                        'Your Account has been Approved!',
                        ######################
                        'Hey there %s,\n\n'
                        'The Go admins have reviewed your application and have '
                        'approved you to use Go!\n\n'
                        'Head over to go.gmu.edu to create your first address.\n\n'
                        '- Go Admins'
378
                        % (str(to_approve.full_name)),
379 380 381 382
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
Zosman's avatar
Zosman committed
383

David Haynes's avatar
David Haynes committed
384
        # If we're denying users
385
        elif '_deny' in request.POST:
386
            for name in userlist:
387
                to_deny = RegisteredUser.objects.get(user__username__exact=name)
388
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
389
                    user_mail = to_deny.user.username + settings.EMAIL_DOMAIN
David Haynes's avatar
David Haynes committed
390
                    # Send an email letting them know they are denied
391 392 393 394 395 396 397 398 399
                    send_mail(
                        'Your Account has been Denied!',
                        ######################
                        'Hey there %s,\n\n'
                        'The Go admins have reviewed your application and have '
                        'decided to not approve you to use Go.\n\n'
                        'Please reach out to srct@gmu.edu to appeal '
                        'this decision.\n\n'
                        '- Go Admins'
400
                        % (str(to_deny.full_name)),
401 402 403 404
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
David Haynes's avatar
David Haynes committed
405
                # Delete their associated RegisteredUsers
406
                to_deny.user.delete()
407
                return HttpResponseRedirect('useradmin')
Zosman's avatar
Zosman committed
408

409
        # If we're blocking users
Zosman's avatar
draft 1  
Zosman committed
410 411
        elif '_block' in request.POST:
            for name in userlist:
412
                to_block = RegisteredUser.objects.get(user__username__exact=name)
Zosman's avatar
draft 1  
Zosman committed
413
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
414
                    user_mail = to_block.user.username + settings.EMAIL_DOMAIN
Zosman's avatar
draft 1  
Zosman committed
415 416 417 418 419 420 421 422 423
                    send_mail(
                        'Your Account has been Blocked!',
                        ######################
                        'Hey there %s,\n\n'
                        'The Go admins have reviewed your application and have '
                        'blocked you from using Go.\n\n'
                        'Please reach out to srct@gmu.edu to appeal '
                        'this decision.\n\n'
                        '- Go Admins'
424
                        % (str(to_block.full_name)),
Zosman's avatar
draft 1  
Zosman committed
425 426 427 428
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
429 430 431 432
                to_block.blocked = True
                to_block.approved = False
                to_block.registered = False
                to_block.save()
Zosman's avatar
Zosman committed
433

434
        # If we're un-blocking users
435 436
        elif '_unblock' in request.POST:
            for name in userlist:
437
                to_un_block = RegisteredUser.objects.get(user__username__exact=name)
438
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
439
                    user_mail = to_un_block.user.username + settings.EMAIL_DOMAIN
440
                    send_mail(
441
                        'Your Account has been Un-Blocked!',
442 443 444
                        ######################
                        'Hey there %s,\n\n'
                        'The Go admins have reviewed your application and have '
445
                        'Un-Blocked you from using Go.\n\n'
446
                        'If you wish to continue Go use please register again. \n\n'
447 448
                        'Congratulations! '
                        '- Go Admins'
449
                        % (str(to_un_block.full_name)),
450 451 452 453
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
454 455
                to_un_block.blocked = False
                to_un_block.save()
456
                return HttpResponseRedirect('useradmin')
457

458
        # If we're removing existing users
459 460
        elif '_remove' in request.POST:
            for name in userlist:
461
                to_remove = RegisteredUser.objects.get(user__username__exact=name)
462
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
463
                    user_mail = to_remove.user.username + settings.EMAIL_DOMAIN
464 465 466 467 468 469 470 471
                    send_mail(
                        'Your Account has been Deleted!',
                        ######################
                        'Hey there %s,\n\n'
                        'The Go admins have decided to remove you from Go. \n\n'
                        'Please reach out to srct@gmu.edu to appeal '
                        'this decision.\n\n'
                        '- Go Admins'
472
                        % (str(to_remove.full_name)),
473 474 475 476
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
477
                to_remove.user.delete()
478
                return HttpResponseRedirect('useradmin')
479

480
    # Get a list of all RegisteredUsers that need to be approved
481 482
    need_approval = RegisteredUser.objects.filter(registered=True).filter(
        approved=False).filter(blocked=False)
Zosman's avatar
Zosman committed
483
    # Get a list of all RegisteredUsers that are currently users
484 485
    current_users = RegisteredUser.objects.filter(approved=True).filter(
        registered=True).filter(blocked=False)
Zosman's avatar
Zosman committed
486
    # Get a list of all RegisteredUsers that are blocked
487
    blocked_users = RegisteredUser.objects.filter(blocked=True)
488

David Haynes's avatar
David Haynes committed
489
    # Pass that list to the template
490
    return render(request, 'admin/useradmin.html', {
491 492 493
        'need_approval': need_approval,
        'current_users': current_users,
        'blocked_users': blocked_users
David Haynes's avatar
David Haynes committed
494
    })