views.py 20.4 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
David Haynes's avatar
David Haynes committed
14 15 16 17
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import login_required, user_passes_test
from django.core.exceptions import PermissionDenied  # ValidationError
from django.core.mail import EmailMessage, send_mail
18
from django.http import HttpResponseServerError  # Http404
19
from django.http import HttpResponseRedirect
David Haynes's avatar
David Haynes committed
20
from django.shortcuts import get_object_or_404, redirect, render
21
from django.utils import timezone
David Haynes's avatar
David Haynes committed
22 23

# Other imports
24
from ratelimit.decorators import ratelimit
25

26
# App Imports
David Haynes's avatar
David Haynes committed
27
from go.forms import SignupForm, URLForm
28
from go.models import URL, RegisteredUser
David Haynes's avatar
David Haynes committed
29

30

David Haynes's avatar
David Haynes committed
31
def index(request):
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
    """
    If a user is logged in, this view displays all the information about all
    of their URLs. Otherwise, it will show the public landing page
    """

    # If the user is not authenticated, show them a public landing page.
    if not request.user.is_authenticated():
        return render(request, 'public_landing.html')
    # Do not display this page to unapproved users
    if not request.user.registereduser.approved:
        return render(request, 'not_registered.html')

    # Get the current domain info
    domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"

    # Grab a list of all the URL's that are currently owned by the user
    urls = URL.objects.filter(owner=request.user.registereduser)

50
    # Render my_links passing the list of URL's and Domain to the template
51 52 53 54
    return render(request, 'core/index.html', {
        'urls': urls,
        'domain': domain,
    })
Jean Michel Rouly's avatar
Jean Michel Rouly committed
55

56
@login_required
Zach Knox's avatar
Zach Knox committed
57
def new_link(request):
58
    """
David Haynes's avatar
David Haynes committed
59
    This view handles the homepage that the user is presented with when
60
    they request '/newLink'. If they're not logged in, they're redirected to
David Haynes's avatar
David Haynes committed
61 62 63
    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.
64 65
    """

David Haynes's avatar
David Haynes committed
66
    # If the user isn't approved, then display the you're not approved page.
67
    if not request.user.registereduser.approved:
68
        if request.user.registereduser.blocked:
Zosman's avatar
Zosman committed
69
            return render(request, 'banned.html')
70
        else:
Zosman's avatar
Zosman committed
71
            return render(request, 'not_registered.html')
72

73

David Haynes's avatar
David Haynes committed
74
    # Initialize a URL form
75
    url_form = URLForm(host=request.META.get('HTTP_HOST'))  # unbound form
76

David Haynes's avatar
David Haynes committed
77 78
    # 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
79
    if request.method == 'POST':
80 81
        # Now we initialize the form again but this time we have the POST
        # request
82
        url_form = URLForm(request.POST, host=request.META.get('HTTP_HOST'))
83 84 85

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

89 90
            # If there is a 500 error returned, handle it
            if res == 500:
91
                return HttpResponseServerError(render(request, '500.html'))
92

93
            # Redirect to the shiny new URL
94
            return redirect('view', res.short)
95

96 97 98
        # Else, there is an error, redisplay the form with the validation errors
        else:
            # Render index.html passing the form to the template
99
            return render(request, 'core/new_link.html', {
100
                'form': url_form,
101
            })
102

103

David Haynes's avatar
David Haynes committed
104
    # Render index.html passing the form to the template
Zach Knox's avatar
Zach Knox committed
105
    return render(request, 'core/new_link.html', {
106
        'form': url_form,
107
    })
Jean Michel Rouly's avatar
Jean Michel Rouly committed
108

109
@login_required
Zach Knox's avatar
Zach Knox committed
110
def my_links(request):
111 112 113 114 115 116 117 118 119 120
    """
    for compatibility, just in case
    shows the same thing as /, but requires login to be consistent with
    /newLink
    """
    if not request.user.registereduser.approved:
        if request.user.registereduser.blocked:
            return render(request, 'banned.html')
        else:
            return render(request, 'not_registered.html')
Zach Knox's avatar
Zach Knox committed
121 122
    return index(request)

David Haynes's avatar
David Haynes committed
123
# Rate limits are completely arbitrary
124 125
@ratelimit(key='user', rate='3/m', method='POST', block=True)
@ratelimit(key='user', rate='25/d', method='POST', block=True)
126
def post(request, url_form):
127
    """
128
    Helper function that handles POST requests for the URL creation
129 130
    """

131 132
    # We don't commit the url object yet because we need to add its
    # owner, and parse its date field.
133
    url = url_form.save(commit=False)
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
    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
152
        else:
153 154 155 156 157 158 159 160 161
            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:
162
        url.expires = timezone.now() + timedelta(days=1)
163
    elif expires == URLForm.WEEK:
164
        url.expires = timezone.now() + timedelta(weeks=1)
165
    elif expires == URLForm.MONTH:
166
        url.expires = timezone.now() + timedelta(weeks=3)
167 168 169 170 171 172 173 174 175 176
    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
177

David Haynes's avatar
David Haynes committed
178
def view(request, short):
179
    """
180 181
    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.
182
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
183

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

David Haynes's avatar
David Haynes committed
187
    # Get the URL that is being requested
188
    url = get_object_or_404(URL, short__iexact=short)
189

David Haynes's avatar
David Haynes committed
190
    # Render view.html passing the specified URL and Domain to the template
Jean Michel Rouly's avatar
Jean Michel Rouly committed
191
    return render(request, 'view.html', {
192
        'url': url,
193
        'domain': domain,
194
    })
195

David Haynes's avatar
David Haynes committed
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
@login_required
def edit(request, short):
    """
    This view allows a logged in user to edit the details of a Go link that they
    own. They can modify any value that they wish. If `short` is modified then
    we will need to create a new link and copy over stats from the previous.
    """

    # Do not allow unapproved users to edit links
    if not request.user.registereduser.approved:
        return render(request, 'not_registered.html')

    # Get the URL that is going to be edited
    url = get_object_or_404(URL, short__iexact=short)

    # If the RegisteredUser is the owner of the URL
    if url.owner == request.user.registereduser:
        # render the edit URL form
214 215 216 217 218 219

        # Render index.html passing the form to the template
        return render(request, 'core/edit_link.html', {
        })


David Haynes's avatar
David Haynes committed
220
        # redirect to my_links
221
        # return redirect('my_links')
David Haynes's avatar
David Haynes committed
222 223 224 225
    else:
        # do not allow them to edit
        raise PermissionDenied()

David Haynes's avatar
David Haynes committed
226 227
@login_required
def delete(request, short):
228
    """
David Haynes's avatar
David Haynes committed
229 230
    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.
231
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
232

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

David Haynes's avatar
David Haynes committed
237
    # Get the URL that is going to be deleted
238
    url = get_object_or_404(URL, short__iexact=short)
David Haynes's avatar
David Haynes committed
239 240

    # If the RegisteredUser is the owner of the URL
241
    if url.owner == request.user.registereduser:
David Haynes's avatar
David Haynes committed
242
        # remove the URL
243
        url.delete()
David Haynes's avatar
David Haynes committed
244
        # redirect to my_links
245 246
        return redirect('my_links')
    else:
David Haynes's avatar
David Haynes committed
247
        # do not allow them to delete
248
        raise PermissionDenied()
249

250
@login_required
Jean Michel Rouly's avatar
Jean Michel Rouly committed
251
def signup(request):
252
    """
253 254
    This view presents the user with a registration form. You can register
    yourself.
255 256
    """

257
    # Do not display signup page to registered or approved users
258
    if request.user.registereduser.blocked:
259
        return render(request, 'banned.html')
260
    elif request.user.registereduser.approved:
David Haynes's avatar
David Haynes committed
261
        return redirect('/')
262
    elif request.user.registereduser.registered:
David Haynes's avatar
David Haynes committed
263
        return redirect('registered')
264

David Haynes's avatar
David Haynes committed
265
    # Initialize our signup form
266 267 268 269 270 271
    signup_form = SignupForm(
        request,
        initial={
            'full_name': request.user.first_name + " " + request.user.last_name
        }
    )
David Haynes's avatar
David Haynes committed
272 273

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

David Haynes's avatar
David Haynes committed
276 277
    # 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
278
    if request.method == 'POST':
David Haynes's avatar
David Haynes committed
279 280
        # Now we initialize the form again but this time we have the POST
        # request
281 282 283 284 285 286
        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
287 288

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

David Haynes's avatar
David Haynes committed
291
        # Django will check the form to make sure it's valid
292
        if signup_form.is_valid():
David Haynes's avatar
David Haynes committed
293
            # Grab data from the form and store into variables
294
            description = signup_form.cleaned_data.get('description')
295
            full_name = signup_form.cleaned_data.get('full_name')
296
            organization = signup_form.cleaned_data.get('organization')
297

298 299
            # Only send mail if we've defined the mailserver
            if settings.EMAIL_HOST and settings.EMAIL_PORT:
root's avatar
root committed
300
                user_mail = request.user.username + settings.EMAIL_DOMAIN
301
                # Email sent to notify Admins
302
                to_admin = EmailMessage(
303
                    'Signup from %s' % (request.user.registereduser.user),
304 305 306 307 308
                    ######################
                    '%s signed up at %s\n\n'
                    'Username: %s\n'
                    'Organization: %s\n\n'
                    'Message: %s\n\n'
309 310
                    'You can contact the user directly by replying to this email or '
                    'reply all to contact the user and notfiy the mailing list.\n'
311 312
                    'Please head to go.gmu.edu/useradmin to approve or '
                    'deny this application.'
313 314 315 316 317
                    %(
                        str(full_name), str(timezone.now()).strip(),
                        str(request.user.registereduser.user), str(organization),
                        str(description)
                    ),
318 319
                    ######################
                    settings.EMAIL_FROM,
320
                    [settings.EMAIL_TO],
321 322 323
                    reply_to=[user_mail]
                )
                to_admin.send()
324
                # Confirmation email sent to Users
325
                send_mail(
326 327 328 329 330 331 332 333 334 335 336 337 338
                    '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]
                )
339

David Haynes's avatar
David Haynes committed
340 341
            # Make sure that our new RegisteredUser object is clean, then save
            # it and let's redirect to tell the user they have registered.
342
            signup_form.save()
343
            return redirect('registered')
Jean Michel Rouly's avatar
Jean Michel Rouly committed
344

David Haynes's avatar
David Haynes committed
345 346
    # render signup.html passing along the form and the current registered
    # status
347
    return render(request, 'core/signup.html', {
348
        'form': signup_form,
349
        'registered': False,
350
    })
Jean Michel Rouly's avatar
Jean Michel Rouly committed
351

David Haynes's avatar
David Haynes committed
352
def redirection(request, short):
353
    """
David Haynes's avatar
David Haynes committed
354
    This view redirects a user based on the short URL they requested.
355
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
356

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

David Haynes's avatar
David Haynes committed
360
    # Get the URL object that relates to the requested Go link
361
    url = get_object_or_404(URL, short__iexact=short)
David Haynes's avatar
David Haynes committed
362 363
    # Increment our clicks by one
    url.clicks += 1
364

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

David Haynes's avatar
David Haynes committed
369
    # If the user is coming from a QR request then increment qrclicks
370 371 372
    if 'qr' in request.GET:
        url.qrclicks += 1

David Haynes's avatar
David Haynes committed
373
    # If the user is coming from a social media request then increment qrclicks
374 375 376
    if 'social' in request.GET:
        url.socialclicks += 1

David Haynes's avatar
David Haynes committed
377
    # Save our data and redirect the user towards thier destination
Jean Michel Rouly's avatar
Jean Michel Rouly committed
378
    url.save()
379
    return redirect(url.target)
Jean Michel Rouly's avatar
Jean Michel Rouly committed
380

381 382
def staff_member_required(view_func, redirect_field_name=REDIRECT_FIELD_NAME, login_url='/'):
    """
David Haynes's avatar
David Haynes committed
383 384
    Decorator function for views that checks that the user is logged in and is
    a staff member, displaying the login page if necessary.
385 386
    """

387 388
    return user_passes_test(
        lambda u: u.is_active and u.is_staff,
389 390
        login_url=login_url,
        redirect_field_name=redirect_field_name
391 392
    )(view_func)

David Haynes's avatar
David Haynes committed
393 394
@staff_member_required
def useradmin(request):
395
    """
David Haynes's avatar
David Haynes committed
396 397
    This view is a simplified admin panel, so that staff don't need to log in
    to approve links
398
    """
David Haynes's avatar
David Haynes committed
399 400

    # If we receive a POST request
401
    if request.POST:
David Haynes's avatar
David Haynes committed
402
        # Get a list of the potential victims (users)
403
        userlist = request.POST.getlist('username')
David Haynes's avatar
David Haynes committed
404
        # If we're approving users
405
        if '_approve' in request.POST:
406
            for name in userlist:
407 408 409
                to_approve = RegisteredUser.objects.get(user__username__exact=name)
                to_approve.approved = True
                to_approve.save()
David Haynes's avatar
David Haynes committed
410 411

                # Send an email letting them know they are approved
412
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
413
                    user_mail = to_approve.user.username + settings.EMAIL_DOMAIN
414 415 416 417 418 419 420 421
                    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'
422
                        % (str(to_approve.full_name)),
423 424 425 426
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
Zosman's avatar
Zosman committed
427

David Haynes's avatar
David Haynes committed
428
        # If we're denying users
429
        elif '_deny' in request.POST:
430
            for name in userlist:
431
                to_deny = RegisteredUser.objects.get(user__username__exact=name)
432
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
433
                    user_mail = to_deny.user.username + settings.EMAIL_DOMAIN
David Haynes's avatar
David Haynes committed
434
                    # Send an email letting them know they are denied
435 436 437 438 439 440 441 442 443
                    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'
444
                        % (str(to_deny.full_name)),
445 446 447 448
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
David Haynes's avatar
David Haynes committed
449
                # Delete their associated RegisteredUsers
450
                to_deny.user.delete()
451
                return HttpResponseRedirect('useradmin')
Zosman's avatar
Zosman committed
452

453
        # If we're blocking users
Zosman's avatar
draft 1  
Zosman committed
454 455
        elif '_block' in request.POST:
            for name in userlist:
456
                to_block = RegisteredUser.objects.get(user__username__exact=name)
Zosman's avatar
draft 1  
Zosman committed
457
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
458
                    user_mail = to_block.user.username + settings.EMAIL_DOMAIN
Zosman's avatar
draft 1  
Zosman committed
459 460 461 462 463 464 465 466 467
                    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'
468
                        % (str(to_block.full_name)),
Zosman's avatar
draft 1  
Zosman committed
469 470 471 472
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
473 474 475 476
                to_block.blocked = True
                to_block.approved = False
                to_block.registered = False
                to_block.save()
Zosman's avatar
Zosman committed
477

478
        # If we're un-blocking users
479 480
        elif '_unblock' in request.POST:
            for name in userlist:
481
                to_un_block = RegisteredUser.objects.get(user__username__exact=name)
482
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
483
                    user_mail = to_un_block.user.username + settings.EMAIL_DOMAIN
484
                    send_mail(
485
                        'Your Account has been Un-Blocked!',
486 487 488
                        ######################
                        'Hey there %s,\n\n'
                        'The Go admins have reviewed your application and have '
489
                        'Un-Blocked you from using Go.\n\n'
490
                        'If you wish to continue Go use please register again. \n\n'
491 492
                        'Congratulations! '
                        '- Go Admins'
493
                        % (str(to_un_block.full_name)),
494 495 496 497
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
498 499
                to_un_block.blocked = False
                to_un_block.save()
500
                return HttpResponseRedirect('useradmin')
501

502
        # If we're removing existing users
503 504
        elif '_remove' in request.POST:
            for name in userlist:
505
                to_remove = RegisteredUser.objects.get(user__username__exact=name)
506
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
507
                    user_mail = to_remove.user.username + settings.EMAIL_DOMAIN
508 509 510 511 512 513 514 515
                    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'
516
                        % (str(to_remove.full_name)),
517 518 519 520
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
521
                to_remove.user.delete()
522
                return HttpResponseRedirect('useradmin')
523

524
    # Get a list of all RegisteredUsers that need to be approved
525 526
    need_approval = RegisteredUser.objects.filter(registered=True).filter(
        approved=False).filter(blocked=False)
Zosman's avatar
Zosman committed
527
    # Get a list of all RegisteredUsers that are currently users
528 529
    current_users = RegisteredUser.objects.filter(approved=True).filter(
        registered=True).filter(blocked=False)
Zosman's avatar
Zosman committed
530
    # Get a list of all RegisteredUsers that are blocked
531
    blocked_users = RegisteredUser.objects.filter(blocked=True)
532

David Haynes's avatar
David Haynes committed
533
    # Pass that list to the template
534
    return render(request, 'admin/useradmin.html', {
535 536 537
        'need_approval': need_approval,
        'current_users': current_users,
        'blocked_users': blocked_users
David Haynes's avatar
David Haynes committed
538
    })