views.py 19 KB
Newer Older
1 2 3 4
"""
go/views.py
"""

5 6 7
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division

David Haynes's avatar
David Haynes committed
8 9 10
# Python stdlib imports
from datetime import timedelta

11
# Django Imports
12
from django.conf import settings
13
from django.http import HttpResponseServerError  # Http404
14
from django.http import HttpResponseRedirect
15
from django.utils import timezone
16
from django.core.exceptions import PermissionDenied  # ValidationError
17
from django.core.mail import send_mail, EmailMessage
18 19
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
20
from django.shortcuts import render, get_object_or_404, redirect
David Haynes's avatar
David Haynes committed
21 22

# Other imports
23
from ratelimit.decorators import ratelimit
24

25 26 27 28
# App Imports
from go.models import URL, RegisteredUser
from go.forms import URLForm, SignupForm

David Haynes's avatar
David Haynes committed
29
def index(request):
30
    """
David Haynes's avatar
David Haynes committed
31 32 33 34 35
    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.
36 37
    """

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

49

David Haynes's avatar
David Haynes committed
50
    # Initialize a URL form
51
    url_form = URLForm(host=request.META.get('HTTP_HOST'))  # unbound form
52

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

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

65 66
            # If there is a 500 error returned, handle it
            if res == 500:
67
                return HttpResponseServerError(render(request, '500.html'))
68

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

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

79

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

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

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

David Haynes's avatar
David Haynes committed
140
def view(request, short):
141
    """
142 143
    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.
144
    """
Jean Michel Rouly's avatar
Jean Michel Rouly committed
145

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

342 343
    return user_passes_test(
        lambda u: u.is_active and u.is_staff,
344 345
        login_url=login_url,
        redirect_field_name=redirect_field_name
346 347
    )(view_func)

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

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

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

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

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

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

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

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

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