views.py 20.2 KB
Newer Older
1 2 3
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division

David Haynes's avatar
David Haynes committed
4 5 6
# Python stdlib imports
from datetime import timedelta

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

# Other imports
20
from ratelimit.decorators import ratelimit
21

22 23 24 25
# App Imports
from go.models import URL, RegisteredUser
from go.forms import URLForm, SignupForm

David Haynes's avatar
David Haynes committed
26
def index(request):
27 28 29 30 31 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)

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

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

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

68

David Haynes's avatar
David Haynes committed
69
    # Initialize a URL form
70
    url_form = URLForm(host=request.META.get('HTTP_HOST'))  # unbound form
71

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

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

84 85 86 87 88 89
            # If there is a 500 error returned, handle it
            if res == 500:
                return HttpResponseServerError(
                    render(request, 'admin/500.html', {})
                )

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

93 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
            return render(request, 'core/index.html', {
                'form': url_form,
98
            })
99

100

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

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

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

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

David Haynes's avatar
David Haynes committed
175
def view(request, short):
176
    """
David Haynes's avatar
David Haynes committed
177 178
    This view allows the user to view details about a URL. Note that they
    do not need to be logged in to view info.
179
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
180

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

376 377
    return user_passes_test(
        lambda u: u.is_active and u.is_staff,
David Haynes's avatar
David Haynes committed
378 379
        login_url = login_url,
        redirect_field_name = redirect_field_name
380 381
    )(view_func)

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

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

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

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

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

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

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

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

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