views.py 20.2 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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
    """
    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)

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

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

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

72

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

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

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

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

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

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

102

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

108
@login_required
Zach Knox's avatar
Zach Knox committed
109
def my_links(request):
110 111 112 113 114 115 116 117 118 119
    """
    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
120 121
    return index(request)

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

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

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

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

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

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

David Haynes's avatar
David Haynes committed
195 196
@login_required
def my_links(request):
197
    """
David Haynes's avatar
David Haynes committed
198 199
    This view displays all the information about all of your URLs. You
    obviously need to be logged in to view your URLs.
200
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
201

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

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

David Haynes's avatar
David Haynes committed
209
    # Grab a list of all the URL's that are currently owned by the user
210
    urls = URL.objects.filter(owner=request.user.registereduser)
David Haynes's avatar
David Haynes committed
211 212

    # Render my_links.html passing the list of URL's and Domain to the template
213
    return render(request, 'my_links.html', {
214 215
        'urls': urls,
        'domain': domain,
216
    })
217

David Haynes's avatar
David Haynes committed
218 219
@login_required
def delete(request, short):
220
    """
David Haynes's avatar
David Haynes committed
221 222
    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.
223
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
224

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

David Haynes's avatar
David Haynes committed
229
    # Get the URL that is going to be deleted
230
    url = get_object_or_404(URL, short__iexact=short)
David Haynes's avatar
David Haynes committed
231 232

    # If the RegisteredUser is the owner of the URL
233
    if url.owner == request.user.registereduser:
David Haynes's avatar
David Haynes committed
234
        # remove the URL
235
        url.delete()
David Haynes's avatar
David Haynes committed
236
        # rediret to my_links
237 238
        return redirect('my_links')
    else:
David Haynes's avatar
David Haynes committed
239
        # do not allow them to delete
240
        raise PermissionDenied()
241

242
@login_required
Jean Michel Rouly's avatar
Jean Michel Rouly committed
243
def signup(request):
244
    """
245 246
    This view presents the user with a registration form. You can register
    yourself.
247 248
    """

249
    # Do not display signup page to registered or approved users
250
    if request.user.registereduser.blocked:
251
        return render(request, 'banned.html')
252
    elif request.user.registereduser.approved:
David Haynes's avatar
David Haynes committed
253
        return redirect('/')
254
    elif request.user.registereduser.registered:
David Haynes's avatar
David Haynes committed
255
        return redirect('registered')
256

David Haynes's avatar
David Haynes committed
257
    # Initialize our signup form
258 259 260 261 262 263
    signup_form = SignupForm(
        request,
        initial={
            'full_name': request.user.first_name + " " + request.user.last_name
        }
    )
David Haynes's avatar
David Haynes committed
264 265

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

David Haynes's avatar
David Haynes committed
268 269
    # 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
270
    if request.method == 'POST':
David Haynes's avatar
David Haynes committed
271 272
        # Now we initialize the form again but this time we have the POST
        # request
273 274 275 276 277 278
        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
279 280

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

David Haynes's avatar
David Haynes committed
283
        # Django will check the form to make sure it's valid
284
        if signup_form.is_valid():
David Haynes's avatar
David Haynes committed
285
            # Grab data from the form and store into variables
286
            description = signup_form.cleaned_data.get('description')
287
            full_name = signup_form.cleaned_data.get('full_name')
288
            organization = signup_form.cleaned_data.get('organization')
289

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

David Haynes's avatar
David Haynes committed
332 333
            # Make sure that our new RegisteredUser object is clean, then save
            # it and let's redirect to tell the user they have registered.
334
            signup_form.save()
335
            return redirect('registered')
Jean Michel Rouly's avatar
Jean Michel Rouly committed
336

David Haynes's avatar
David Haynes committed
337 338
    # render signup.html passing along the form and the current registered
    # status
339
    return render(request, 'core/signup.html', {
340
        'form': signup_form,
341
        'registered': False,
342
    })
Jean Michel Rouly's avatar
Jean Michel Rouly committed
343

David Haynes's avatar
David Haynes committed
344
def redirection(request, short):
345
    """
David Haynes's avatar
David Haynes committed
346
    This view redirects a user based on the short URL they requested.
347
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
348

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

David Haynes's avatar
David Haynes committed
352
    # Get the URL object that relates to the requested Go link
353
    url = get_object_or_404(URL, short__iexact=short)
David Haynes's avatar
David Haynes committed
354 355
    # Increment our clicks by one
    url.clicks += 1
356

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

David Haynes's avatar
David Haynes committed
361
    # If the user is coming from a QR request then increment qrclicks
362 363 364
    if 'qr' in request.GET:
        url.qrclicks += 1

David Haynes's avatar
David Haynes committed
365
    # If the user is coming from a social media request then increment qrclicks
366 367 368
    if 'social' in request.GET:
        url.socialclicks += 1

David Haynes's avatar
David Haynes committed
369
    # Save our data and redirect the user towards thier destination
Jean Michel Rouly's avatar
Jean Michel Rouly committed
370
    url.save()
371
    return redirect(url.target)
Jean Michel Rouly's avatar
Jean Michel Rouly committed
372

373 374
def staff_member_required(view_func, redirect_field_name=REDIRECT_FIELD_NAME, login_url='/'):
    """
David Haynes's avatar
David Haynes committed
375 376
    Decorator function for views that checks that the user is logged in and is
    a staff member, displaying the login page if necessary.
377 378
    """

379 380
    return user_passes_test(
        lambda u: u.is_active and u.is_staff,
381 382
        login_url=login_url,
        redirect_field_name=redirect_field_name
383 384
    )(view_func)

David Haynes's avatar
David Haynes committed
385 386
@staff_member_required
def useradmin(request):
387
    """
David Haynes's avatar
David Haynes committed
388 389
    This view is a simplified admin panel, so that staff don't need to log in
    to approve links
390
    """
David Haynes's avatar
David Haynes committed
391 392

    # If we receive a POST request
393
    if request.POST:
David Haynes's avatar
David Haynes committed
394
        # Get a list of the potential victims (users)
395
        userlist = request.POST.getlist('username')
David Haynes's avatar
David Haynes committed
396
        # If we're approving users
397
        if '_approve' in request.POST:
398
            for name in userlist:
399 400 401
                to_approve = RegisteredUser.objects.get(user__username__exact=name)
                to_approve.approved = True
                to_approve.save()
David Haynes's avatar
David Haynes committed
402 403

                # Send an email letting them know they are approved
404
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
405
                    user_mail = to_approve.user.username + settings.EMAIL_DOMAIN
406 407 408 409 410 411 412 413
                    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'
414
                        % (str(to_approve.full_name)),
415 416 417 418
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
Zosman's avatar
Zosman committed
419

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

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

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

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

516
    # Get a list of all RegisteredUsers that need to be approved
517 518
    need_approval = RegisteredUser.objects.filter(registered=True).filter(
        approved=False).filter(blocked=False)
Zosman's avatar
Zosman committed
519
    # Get a list of all RegisteredUsers that are currently users
520 521
    current_users = RegisteredUser.objects.filter(approved=True).filter(
        registered=True).filter(blocked=False)
Zosman's avatar
Zosman committed
522
    # Get a list of all RegisteredUsers that are blocked
523
    blocked_users = RegisteredUser.objects.filter(blocked=True)
524

David Haynes's avatar
David Haynes committed
525
    # Pass that list to the template
526
    return render(request, 'admin/useradmin.html', {
527 528 529
        'need_approval': need_approval,
        'current_users': current_users,
        'blocked_users': blocked_users
David Haynes's avatar
David Haynes committed
530
    })