views.py 19.1 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
    """
David Haynes's avatar
David Haynes committed
28 29 30 31 32
    This view handles the homepage that the user is presented with when
    they request '/'. If they're not logged in, they're redirected to
    login. If they're logged in but not registered, they're given the
    not_registered error page. If they are logged in AND registered, they
    get the URL registration form.
33 34
    """

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

46

David Haynes's avatar
David Haynes committed
47
    # Initialize a URL form
48
    url_form = URLForm(host=request.META.get('HTTP_HOST'))  # unbound form
49

David Haynes's avatar
David Haynes committed
50 51
    # 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
52
    if request.method == 'POST':
53 54
        # Now we initialize the form again but this time we have the POST
        # request
55
        url_form = URLForm(request.POST, host=request.META.get('HTTP_HOST'))
56 57 58

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

62 63 64 65 66 67
            # If there is a 500 error returned, handle it
            if res == 500:
                return HttpResponseServerError(
                    render(request, 'admin/500.html', {})
                )

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

71 72 73 74 75
        # 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,
76
            })
77

78

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

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

92 93
    # We don't commit the url object yet because we need to add its
    # owner, and parse its date field.
94
    url = url_form.save(commit=False)
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    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
113
        else:
114 115 116 117 118 119 120 121 122
            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:
123
        url.expires = timezone.now() + timedelta(days=1)
124
    elif expires == URLForm.WEEK:
125
        url.expires = timezone.now() + timedelta(weeks=1)
126
    elif expires == URLForm.MONTH:
127
        url.expires = timezone.now() + timedelta(weeks=3)
128 129 130 131 132 133 134 135 136 137
    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
138

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

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

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

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

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

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

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

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

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

David Haynes's avatar
David Haynes committed
180 181
@login_required
def delete(request, short):
182
    """
David Haynes's avatar
David Haynes committed
183 184
    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.
185
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
186

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

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

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

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

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

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

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

David Haynes's avatar
David Haynes committed
229 230
    # 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
231
    if request.method == 'POST':
David Haynes's avatar
David Haynes committed
232 233
        # Now we initialize the form again but this time we have the POST
        # request
234 235 236 237 238 239
        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
240 241

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

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

251 252
            # Only send mail if we've defined the mailserver
            if settings.EMAIL_HOST and settings.EMAIL_PORT:
root's avatar
root committed
253
                user_mail = request.user.username + settings.EMAIL_DOMAIN
254
                # Email sent to notify Admins
255
                to_admin = EmailMessage(
256
                    'Signup from %s' % (request.user.registereduser.user),
257 258 259 260 261
                    ######################
                    '%s signed up at %s\n\n'
                    'Username: %s\n'
                    'Organization: %s\n\n'
                    'Message: %s\n\n'
262 263
                    'You can contact the user directly by replying to this email or '
                    'reply all to contact the user and notfiy the mailing list.\n'
264 265
                    'Please head to go.gmu.edu/useradmin to approve or '
                    'deny this application.'
266 267 268 269 270
                    %(
                        str(full_name), str(timezone.now()).strip(),
                        str(request.user.registereduser.user), str(organization),
                        str(description)
                    ),
271 272
                    ######################
                    settings.EMAIL_FROM,
273
                    [settings.EMAIL_TO],
274 275 276
                    reply_to=[user_mail]
                )
                to_admin.send()
277
                # Confirmation email sent to Users
278
                send_mail(
279 280 281 282 283 284 285 286 287 288 289 290 291
                    '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]
                )
292

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

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

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

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

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

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

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

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

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

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

340 341
    return user_passes_test(
        lambda u: u.is_active and u.is_staff,
David Haynes's avatar
David Haynes committed
342 343
        login_url = login_url,
        redirect_field_name = redirect_field_name
344 345
    )(view_func)

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

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

                # Send an email letting them know they are approved
365
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
366
                    user_mail = to_approve.user.username + settings.EMAIL_DOMAIN
367 368 369 370 371 372 373 374
                    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'
375
                        % (str(to_approve.full_name)),
376 377 378 379
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
Zosman's avatar
Zosman committed
380

David Haynes's avatar
David Haynes committed
381
        # If we're denying users
382
        elif '_deny' in request.POST:
383
            for name in userlist:
384
                to_deny = RegisteredUser.objects.get(user__username__exact=name)
385
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
386
                    user_mail = to_deny.user.username + settings.EMAIL_DOMAIN
David Haynes's avatar
David Haynes committed
387
                    # Send an email letting them know they are denied
388 389 390 391 392 393 394 395 396
                    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'
397
                        % (str(to_deny.full_name)),
398 399 400 401
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
David Haynes's avatar
David Haynes committed
402
                # Delete their associated RegisteredUsers
403
                to_deny.user.delete()
404
                return HttpResponseRedirect('useradmin')
Zosman's avatar
Zosman committed
405

406
        # If we're blocking users
Zosman's avatar
draft 1  
Zosman committed
407 408
        elif '_block' in request.POST:
            for name in userlist:
409
                to_block = RegisteredUser.objects.get(user__username__exact=name)
Zosman's avatar
draft 1  
Zosman committed
410
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
411
                    user_mail = to_block.user.username + settings.EMAIL_DOMAIN
Zosman's avatar
draft 1  
Zosman committed
412 413 414 415 416 417 418 419 420
                    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'
421
                        % (str(to_block.full_name)),
Zosman's avatar
draft 1  
Zosman committed
422 423 424 425
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
426 427 428 429
                to_block.blocked = True
                to_block.approved = False
                to_block.registered = False
                to_block.save()
Zosman's avatar
Zosman committed
430

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

455
        # If we're removing existing users
456 457
        elif '_remove' in request.POST:
            for name in userlist:
458
                to_remove = RegisteredUser.objects.get(user__username__exact=name)
459
                if settings.EMAIL_HOST and settings.EMAIL_PORT:
460
                    user_mail = to_remove.user.username + settings.EMAIL_DOMAIN
461 462 463 464 465 466 467 468
                    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'
469
                        % (str(to_remove.full_name)),
470 471 472 473
                        ######################
                        settings.EMAIL_FROM,
                        [user_mail]
                    )
474
                to_remove.user.delete()
475
                return HttpResponseRedirect('useradmin')
476

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

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