views.py 19.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
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 delete(request, short):
197
    """
David Haynes's avatar
David Haynes committed
198 199
    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.
200
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
201

David Haynes's avatar
David Haynes committed
202
    # Do not allow unapproved users to delete links
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 URL that is going to be deleted
207
    url = get_object_or_404(URL, short__iexact=short)
David Haynes's avatar
David Haynes committed
208 209

    # If the RegisteredUser is the owner of the URL
210
    if url.owner == request.user.registereduser:
David Haynes's avatar
David Haynes committed
211
        # remove the URL
212
        url.delete()
David Haynes's avatar
David Haynes committed
213
        # rediret to my_links
214 215
        return redirect('my_links')
    else:
David Haynes's avatar
David Haynes committed
216
        # do not allow them to delete
217
        raise PermissionDenied()
218

219
@login_required
Jean Michel Rouly's avatar
Jean Michel Rouly committed
220
def signup(request):
221
    """
222 223
    This view presents the user with a registration form. You can register
    yourself.
224 225
    """

226
    # Do not display signup page to registered or approved users
227
    if request.user.registereduser.blocked:
228
        return render(request, 'banned.html')
229
    elif request.user.registereduser.approved:
David Haynes's avatar
David Haynes committed
230
        return redirect('/')
231
    elif request.user.registereduser.registered:
David Haynes's avatar
David Haynes committed
232
        return redirect('registered')
233

David Haynes's avatar
David Haynes committed
234
    # Initialize our signup form
235 236 237 238 239 240
    signup_form = SignupForm(
        request,
        initial={
            'full_name': request.user.first_name + " " + request.user.last_name
        }
    )
David Haynes's avatar
David Haynes committed
241 242

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

David Haynes's avatar
David Haynes committed
245 246
    # 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
247
    if request.method == 'POST':
David Haynes's avatar
David Haynes committed
248 249
        # Now we initialize the form again but this time we have the POST
        # request
250 251 252 253 254 255
        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
256 257

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

David Haynes's avatar
David Haynes committed
260
        # Django will check the form to make sure it's valid
261
        if signup_form.is_valid():
David Haynes's avatar
David Haynes committed
262
            # Grab data from the form and store into variables
263
            description = signup_form.cleaned_data.get('description')
264
            full_name = signup_form.cleaned_data.get('full_name')
265
            organization = signup_form.cleaned_data.get('organization')
266

267 268
            # Only send mail if we've defined the mailserver
            if settings.EMAIL_HOST and settings.EMAIL_PORT:
root's avatar
root committed
269
                user_mail = request.user.username + settings.EMAIL_DOMAIN
270
                # Email sent to notify Admins
271
                to_admin = EmailMessage(
272
                    'Signup from %s' % (request.user.registereduser.user),
273 274 275 276 277
                    ######################
                    '%s signed up at %s\n\n'
                    'Username: %s\n'
                    'Organization: %s\n\n'
                    'Message: %s\n\n'
278 279
                    'You can contact the user directly by replying to this email or '
                    'reply all to contact the user and notfiy the mailing list.\n'
280 281
                    'Please head to go.gmu.edu/useradmin to approve or '
                    'deny this application.'
282 283 284 285 286
                    %(
                        str(full_name), str(timezone.now()).strip(),
                        str(request.user.registereduser.user), str(organization),
                        str(description)
                    ),
287 288
                    ######################
                    settings.EMAIL_FROM,
289
                    [settings.EMAIL_TO],
290 291 292
                    reply_to=[user_mail]
                )
                to_admin.send()
293
                # Confirmation email sent to Users
294
                send_mail(
295 296 297 298 299 300 301 302 303 304 305 306 307
                    '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]
                )
308

David Haynes's avatar
David Haynes committed
309 310
            # Make sure that our new RegisteredUser object is clean, then save
            # it and let's redirect to tell the user they have registered.
311
            signup_form.save()
312
            return redirect('registered')
Jean Michel Rouly's avatar
Jean Michel Rouly committed
313

David Haynes's avatar
David Haynes committed
314 315
    # render signup.html passing along the form and the current registered
    # status
316
    return render(request, 'core/signup.html', {
317
        'form': signup_form,
318
        'registered': False,
319
    })
Jean Michel Rouly's avatar
Jean Michel Rouly committed
320

David Haynes's avatar
David Haynes committed
321
def redirection(request, short):
322
    """
David Haynes's avatar
David Haynes committed
323
    This view redirects a user based on the short URL they requested.
324
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
325

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

David Haynes's avatar
David Haynes committed
329
    # Get the URL object that relates to the requested Go link
330
    url = get_object_or_404(URL, short__iexact=short)
David Haynes's avatar
David Haynes committed
331 332
    # Increment our clicks by one
    url.clicks += 1
333

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

David Haynes's avatar
David Haynes committed
338
    # If the user is coming from a QR request then increment qrclicks
339 340 341
    if 'qr' in request.GET:
        url.qrclicks += 1

David Haynes's avatar
David Haynes committed
342
    # If the user is coming from a social media request then increment qrclicks
343 344 345
    if 'social' in request.GET:
        url.socialclicks += 1

David Haynes's avatar
David Haynes committed
346
    # Save our data and redirect the user towards thier destination
Jean Michel Rouly's avatar
Jean Michel Rouly committed
347
    url.save()
348
    return redirect(url.target)
Jean Michel Rouly's avatar
Jean Michel Rouly committed
349

350 351
def staff_member_required(view_func, redirect_field_name=REDIRECT_FIELD_NAME, login_url='/'):
    """
David Haynes's avatar
David Haynes committed
352 353
    Decorator function for views that checks that the user is logged in and is
    a staff member, displaying the login page if necessary.
354 355
    """

356 357
    return user_passes_test(
        lambda u: u.is_active and u.is_staff,
358 359
        login_url=login_url,
        redirect_field_name=redirect_field_name
360 361
    )(view_func)

David Haynes's avatar
David Haynes committed
362 363
@staff_member_required
def useradmin(request):
364
    """
David Haynes's avatar
David Haynes committed
365 366
    This view is a simplified admin panel, so that staff don't need to log in
    to approve links
367
    """
David Haynes's avatar
David Haynes committed
368 369

    # If we receive a POST request
370
    if request.POST:
David Haynes's avatar
David Haynes committed
371
        # Get a list of the potential victims (users)
372
        userlist = request.POST.getlist('username')
David Haynes's avatar
David Haynes committed
373
        # If we're approving users
374
        if '_approve' in request.POST:
375
            for name in userlist:
376 377 378
                to_approve = RegisteredUser.objects.get(user__username__exact=name)
                to_approve.approved = True
                to_approve.save()
David Haynes's avatar
David Haynes committed
379 380

                # Send an email letting them know they are approved
381
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
382
                    user_mail = to_approve.user.username + settings.EMAIL_DOMAIN
383 384 385 386 387 388 389 390
                    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'
391
                        % (str(to_approve.full_name)),
392 393 394 395
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
Zosman's avatar
Zosman committed
396

David Haynes's avatar
David Haynes committed
397
        # If we're denying users
398
        elif '_deny' in request.POST:
399
            for name in userlist:
400
                to_deny = RegisteredUser.objects.get(user__username__exact=name)
401
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
402
                    user_mail = to_deny.user.username + settings.EMAIL_DOMAIN
David Haynes's avatar
David Haynes committed
403
                    # Send an email letting them know they are denied
404 405 406 407 408 409 410 411 412
                    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'
413
                        % (str(to_deny.full_name)),
414 415 416 417
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
David Haynes's avatar
David Haynes committed
418
                # Delete their associated RegisteredUsers
419
                to_deny.user.delete()
420
                return HttpResponseRedirect('useradmin')
Zosman's avatar
Zosman committed
421

422
        # If we're blocking users
Zosman's avatar
draft 1  
Zosman committed
423 424
        elif '_block' in request.POST:
            for name in userlist:
425
                to_block = RegisteredUser.objects.get(user__username__exact=name)
Zosman's avatar
draft 1  
Zosman committed
426
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
427
                    user_mail = to_block.user.username + settings.EMAIL_DOMAIN
Zosman's avatar
draft 1  
Zosman committed
428 429 430 431 432 433 434 435 436
                    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'
437
                        % (str(to_block.full_name)),
Zosman's avatar
draft 1  
Zosman committed
438 439 440 441
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
442 443 444 445
                to_block.blocked = True
                to_block.approved = False
                to_block.registered = False
                to_block.save()
Zosman's avatar
Zosman committed
446

447
        # If we're un-blocking users
448 449
        elif '_unblock' in request.POST:
            for name in userlist:
450
                to_un_block = RegisteredUser.objects.get(user__username__exact=name)
451
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
452
                    user_mail = to_un_block.user.username + settings.EMAIL_DOMAIN
453
                    send_mail(
454
                        'Your Account has been Un-Blocked!',
455 456 457
                        ######################
                        'Hey there %s,\n\n'
                        'The Go admins have reviewed your application and have '
458
                        'Un-Blocked you from using Go.\n\n'
459
                        'If you wish to continue Go use please register again. \n\n'
460 461
                        'Congratulations! '
                        '- Go Admins'
462
                        % (str(to_un_block.full_name)),
463 464 465 466
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
467 468
                to_un_block.blocked = False
                to_un_block.save()
469
                return HttpResponseRedirect('useradmin')
470

471
        # If we're removing existing users
472 473
        elif '_remove' in request.POST:
            for name in userlist:
474
                to_remove = RegisteredUser.objects.get(user__username__exact=name)
475
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
476
                    user_mail = to_remove.user.username + settings.EMAIL_DOMAIN
477 478 479 480 481 482 483 484
                    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'
485
                        % (str(to_remove.full_name)),
486 487 488 489
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
490
                to_remove.user.delete()
491
                return HttpResponseRedirect('useradmin')
492

493
    # Get a list of all RegisteredUsers that need to be approved
494 495
    need_approval = RegisteredUser.objects.filter(registered=True).filter(
        approved=False).filter(blocked=False)
Zosman's avatar
Zosman committed
496
    # Get a list of all RegisteredUsers that are currently users
497 498
    current_users = RegisteredUser.objects.filter(approved=True).filter(
        registered=True).filter(blocked=False)
Zosman's avatar
Zosman committed
499
    # Get a list of all RegisteredUsers that are blocked
500
    blocked_users = RegisteredUser.objects.filter(blocked=True)
501

David Haynes's avatar
David Haynes committed
502
    # Pass that list to the template
503
    return render(request, 'admin/useradmin.html', {
504 505 506
        'need_approval': need_approval,
        'current_users': current_users,
        'blocked_users': blocked_users
David Haynes's avatar
David Haynes committed
507
    })