Verified Commit a8469013 authored by David Haynes's avatar David Haynes 🙆

Django 2.0 + Python 3 adoption

- style guide: 2018 edition
- thankfully nothing big breaks
- man this is a bigger project than I remember
parent 76d358c7
Pipeline #2111 failed with stages
in 1 minute and 24 seconds
"""
go/admin.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
Configure the Django admin pages and apply optional formatting.
"""
# Django Imports
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
......@@ -18,7 +15,6 @@ class URLAdmin(admin.ModelAdmin):
"""
Define what attributes display in the URL Admin
"""
list_display = ("target", "short", "owner", "clicks", "date_created", "expires")
# Register URLAdmin
......@@ -28,7 +24,6 @@ class RegisteredUserInline(admin.StackedInline):
"""
Define an inline admin descriptor for User model
"""
model = RegisteredUser
can_delete = False
......@@ -36,10 +31,7 @@ class UserAdmin(UserAdmin):
"""
Define a new User admin
"""
# see above class that we defined
inlines = (RegisteredUserInline, )
# and modify User to use our new UserAdmin
admin.site.unregister(User)
admin.site.register(User, UserAdmin)
"""
go/cas_callbacks.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
Parse the CAS/PF responses and create users in the database.
"""
# Django Imports
from django.conf import settings
from django.contrib.auth.models import User
......@@ -17,7 +14,6 @@ def pfparse(pf_name_result):
"""
Parse what peoplefinder sends back to us and make a list out of it
"""
# name comes in format of Anderson, Nicholas J
name_list = pf_name_result.split(',')
# there's random whitespace with the first name
......@@ -37,7 +33,6 @@ def pfinfo(uname):
"""
Get information from peoplefinder
"""
base_url = settings.PF_URL
url = base_url + "basic/all/" + str(uname)
try:
......@@ -80,9 +75,8 @@ def pfinfo(uname):
def create_user(tree):
"""
Create a django user based off of the peoplefinder info we parsed earlier
Create a django user based off of the peoplefinder info we parsed earlier.
"""
print("Parsing CAS information.")
try:
username = tree[0][0].text
......@@ -112,10 +106,8 @@ def create_user(tree):
print("Added user's name, %s %s." % (info_name[0], info_name[1]))
print("User object creation process completed.")
else:
print("User object already exists.")
print("CAS callback successful.")
except Exception as ex:
print("Unhandled user creation error:", ex)
"""
go/forms.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
Configure the layout and styling of the Go's forms.
"""
# Python stdlib Imports
from datetime import datetime, timedelta
......@@ -30,29 +28,10 @@ from crispy_forms.layout import HTML, Div, Field, Fieldset, Layout
class URLForm(ModelForm):
"""
The form that is used in URL creation.
"""
# target -------------------------------------------------------------------
def clean_target(self):
"""
Prevent redirect loop links
"""
# get the entered target link
target = self.cleaned_data.get('target')
# Commented out as this check cannont properly be tested since we cannot
# dynamically generate request.META.get('HTTP_HOST')
# # if the host (go.gmu.edu) is in the entered target link or where it
# # redirects
# if self.host in final_url or self.host in target:
# raise ValidationError("You can't make a Go link to Go silly!")
# else:
# return target
return target
# Custom target URL field
Define custom fields and then render them onto the template.
"""
# target ------------------------------------------------------------------
target = URLField(
required=True,
label='Long URL (Required)',
......@@ -62,23 +41,21 @@ class URLForm(ModelForm):
})
)
# short --------------------------------------------------------------------
# short -------------------------------------------------------------------
def unique_short(value):
"""
Check to make sure the short url has not been used
"""
try:
# if we're able to get a URL with the same short url
URL.objects.get(short__iexact=value)
except URL.DoesNotExist as ex:
print(ex)
return
# then raise a ValidationError
raise ValidationError('Short url already exists.')
# Custom short-url field with validators.
short = SlugField(
required=False,
label='Short URL (Optional)',
......@@ -88,9 +65,7 @@ class URLForm(ModelForm):
min_length=3,
)
# expires ------------------------------------------------------------------
# Define some string date standards
# expires -----------------------------------------------------------------
DAY = '1 Day'
WEEK = '1 Week'
MONTH = '1 Month'
......@@ -106,7 +81,6 @@ class URLForm(ModelForm):
(CUSTOM, CUSTOM),
)
# Add preset expiration choices.
expires = ChoiceField(
required=True,
label='Expiration (Required)',
......@@ -119,7 +93,6 @@ class URLForm(ModelForm):
"""
Check if the selected date is a valid date
"""
# a valid date is one that is greater than today
if value > timezone.now():
return
......@@ -127,30 +100,18 @@ class URLForm(ModelForm):
else:
raise ValidationError('Date must be after today.')
# Add a custom expiration choice.
expires_custom = DateTimeField(
required=False,
label='Custom Date',
input_formats=['%m-%d-%Y'],
validators=[valid_date],
initial=lambda: datetime.now() + timedelta(days=1),
widget=DateTimePicker(
options={
"format": "MM-DD-YYYY",
"pickTime": False,
},
icon_attrs={
"class": "fa fa-calendar",
},
)
initial=lambda: datetime.now() + timedelta(days=1)
)
def __init__(self, *args, **kwargs):
"""
On initialization of the form, crispy forms renders this layout
On initialization of the form, crispy forms renders this layout.
"""
# Grab that host info
self.host = kwargs.pop('host', None)
super(URLForm, self).__init__(*args, **kwargs)
......@@ -158,7 +119,7 @@ class URLForm(ModelForm):
self.helper = FormHelper()
self.helper.form_method = 'POST'
# Some xtra vars for form css purposes
# Some extra vars for form css purposes
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-md-1'
self.helper.field_class = 'col-md-6'
......@@ -198,7 +159,7 @@ class URLForm(ModelForm):
<h4>Set when you would like your Go address to expire:</h4>
<br />"""),
'expires',
Field('expires_custom', template="crispy/customDateField.html"),
Field('expires_custom'),
style="background: rgb(#F6F6F6);"),
active=True,
template='crispy/accordian-group.html'),
......@@ -214,19 +175,22 @@ class URLForm(ModelForm):
"""
Metadata about this ModelForm
"""
# what model this form is for
model = URL
# what attributes are included
fields = ['target']
class EditForm(URLForm):
"""
The form that is used in editing URLs.
A modification of the URL creation form... now for editing URLs. Inherit
custom form fields for DRY purposes.
"""
def __init__(self, *args, **kwargs):
"""
On initialization of the form, crispy forms renders this layout
On initialization of the form, crispy forms renders this layout.
"""
# Grab that host info
self.host = kwargs.pop('host', None)
super(URLForm, self).__init__(*args, **kwargs)
......@@ -285,8 +249,11 @@ class EditForm(URLForm):
HTML("""
<br />"""),
StrictButton('Submit Changes', css_class="btn btn-primary btn-md col-md-4", type='submit')))
class Meta(URLForm.Meta):
"""
Metadata about this ModelForm
"""
# what attributes are included
fields = URLForm.Meta.fields
......@@ -294,8 +261,6 @@ class SignupForm(ModelForm):
"""
The form that is used when a user is signing up to be a RegisteredUser
"""
# The full name of the RegisteredUser
full_name = CharField(
required=True,
label='Full Name (Required)',
......@@ -304,7 +269,6 @@ class SignupForm(ModelForm):
help_text="We can fill in this field based on information provided by https://peoplefinder.gmu.edu.",
)
# The RegisteredUser's chosen organization
organization = CharField(
required=True,
label='Organization (Required)',
......@@ -313,7 +277,6 @@ class SignupForm(ModelForm):
help_text="Or whatever \"group\" you would associate with on campus.",
)
# The RegisteredUser's reason for signing up to us Go
description = CharField(
required=False,
label='Description (Optional)',
......@@ -328,16 +291,15 @@ class SignupForm(ModelForm):
# ***Need to replace lower url with production URL***
# ie. go.gmu.edu/about#terms
label=mark_safe(
'Do you accept the <a href="http://127.0.0.1:8000/about#terms">Terms of Service</a>?'
'Do you accept the <a href="about">Terms of Service</a>?'
),
help_text="Esssentially the GMU Responsible Use of Computing policies.",
)
def __init__(self, request, *args, **kwargs):
"""
On initialization of the form, crispy forms renders this layout
On initialization of the form, crispy forms renders this layout.
"""
# Necessary to call request in forms.py, is otherwise restricted to
# views.py and models.py
self.request = request
......@@ -366,7 +328,6 @@ class SignupForm(ModelForm):
"""
Metadata about this ModelForm
"""
# what model this form is for
model = RegisteredUser
# what attributes are included
......
......@@ -3,10 +3,6 @@ go/commands/expirelinks.py
Remove expired links from the database.
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
# Django Imports
from django.core.management.base import BaseCommand
from django.utils import timezone
......
......@@ -3,10 +3,6 @@ go/commands/test_expirelinks.py
Test that the function to expire Go links actually works.
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
# Python stdlib Imports
from datetime import timedelta
......
"""
go/models.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
The core of Go: define the business logic through classes that represent
tables containing structured data in the database.
"""
# Python stdlib Imports
import string
......@@ -16,7 +14,6 @@ from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible
# Other Imports
from hashids import Hashids # http://hashids.org/python/
......@@ -26,15 +23,13 @@ HASHIDS = Hashids(
salt="srct.gmu.edu", alphabet=(string.ascii_lowercase + string.digits)
)
@python_2_unicode_compatible
class RegisteredUser(models.Model):
"""
This is simply a wrapper model for the user object which, if an object
This is simply a wrapper model for the User model which, if an object
exists, indicates that that user is registered.
"""
# Let's associate a User to this RegisteredUser
user = models.OneToOneField(User)
user = models.OneToOneField(User, on_delete="cascade")
# What is your name?
full_name = models.CharField(
......@@ -62,35 +57,29 @@ class RegisteredUser(models.Model):
def __str__(self):
"""
str(RegisteredUser)
String representation of this object.
"""
return '<Registered User: %s - Approval Status: %s>' % (
self.user, self.approved
)
@receiver(post_save, sender=User)
def handle_regUser_creation(sender, instance, created, **kwargs):
"""
When a post_save is called on a User object (and it is newly created), this
is called to create an associated RegisteredUser
is called to create an associated RegisteredUser.
"""
if created:
RegisteredUser.objects.create(user=instance)
@python_2_unicode_compatible
class URL(models.Model):
"""
This model represents a stored URL redirection rule. Each URL has an
owner, target url, short identifier, click counter, and expiration
date.
"""
# Who is the owner of this Go link
owner = models.ForeignKey(RegisteredUser)
owner = models.ForeignKey(RegisteredUser, on_delete="cascade")
# When was this link created?
date_created = models.DateTimeField(default=timezone.now)
......@@ -111,18 +100,16 @@ class URL(models.Model):
def __str__(self):
"""
print(URL)
String representation of this object.
"""
return '<Owner: %s - Target URL: %s>' % (
self.owner.user, self.target
)
class Meta:
"""
metadata for URLs
Meta information for this object.
"""
# they should be ordered by their short links
ordering = ['short']
......
"""
go/templatetags/go_extras.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
Template functions that can be included to help out with rendering correct
data based on the status of the user.
"""
# Django Imports
from django import template
......@@ -20,12 +18,11 @@ register = template.Library()
@register.filter
def is_registered(given_user):
"""
Helper template function to check if a user is registered.
Check if a user is registered.
given_user: The User object that we are checking to see if they are
registered or not.
"""
# try getting the RegisteredUser of the current user
try:
getRegisteredUser = RegisteredUser.objects.get(user=given_user)
......@@ -40,12 +37,11 @@ def is_registered(given_user):
@register.filter
def is_approved(given_user):
"""
Helper template function to check if a user is approved.
Check if a user is approved.
given_user: The User object that we are checking to see if they are approved
or not.
given_user: The User object that we are checking to see if they are
approved or not.
"""
# try getting the RegisteredUser of the current user
try:
get_registered_user = RegisteredUser.objects.get(user=given_user)
......
"""
go/templatetags/test_go_extras.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
Test the helper functions.
"""
# Django Imports
from django.contrib.auth.models import User
from django.test import TestCase
......@@ -15,25 +12,18 @@ from go.models import RegisteredUser
from .go_extras import is_approved, is_registered
class GoExtrasTest(TestCase):
"""
Test cases for the template helper functions in go_extras.py
"""
def setUp(self):
"""
Create a dummy user to be tested against.
"""
User.objects.create(username='dhaynes', password='password')
# is_registered ------------------------------------------------------------
# is_registered -----------------------------------------------------------
def test_is_registered_false(self):
"""
Test the is_registered function to see if it gives correct false
answers
"""
getUser = User.objects.get(username='dhaynes')
getRegisteredUser = RegisteredUser.objects.get(user=getUser)
......@@ -46,7 +36,6 @@ class GoExtrasTest(TestCase):
"""
Test the is_registered function to see if it gives correct true answers
"""
getUser = User.objects.get(username='dhaynes')
getRegisteredUser = RegisteredUser.objects.get(user=getUser)
......@@ -55,13 +44,12 @@ class GoExtrasTest(TestCase):
self.assertTrue(is_registered(getUser))
# is_approved --------------------------------------------------------------
# is_approved -------------------------------------------------------------
def test_is_approved_false(self):
"""
Test the is_registered function to see if it gives correct false answers
Test the is_registered function to see if it gives correct false
answers
"""
getUser = User.objects.get(username='dhaynes')
getRegisteredUser = RegisteredUser.objects.get(user=getUser)
......@@ -70,12 +58,10 @@ class GoExtrasTest(TestCase):
self.assertFalse(is_approved(getUser))
def test_is_approved_true(self):
"""
Test the is_registered function to see if it gives correct true answers
"""
getUser = User.objects.get(username='dhaynes')
getRegisteredUser = RegisteredUser.objects.get(user=getUser)
......
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
"""
go/test_cas_callbacks.py
Test the CAS/PF response parsing functions.
"""
# Django Imports
from django.test import TestCase
......@@ -9,25 +10,19 @@ from django.test import TestCase
from .cas_callbacks import pfparse, pfinfo
class CasCallbacksTest(TestCase):
"""
Test cases for the functions in call_callbacks.
"""
def test_pf_peoplefinder_method(self):
"""
Presently enrolled student who has been added to peoplefinder
"""
actual = pfinfo('dhaynes3')
expected = ['David', 'Haynes']
self.assertEqual(expected, actual)
def test_pfparse_peoplefinder_method(self):
"""
Test the parsing method to ensure that first and last names are seperated
accordingly and correctly.
Test the parsing method to ensure that first and last names are
seperated accordingly and correctly.
"""
actual = pfparse("Haynes, David M")
expected = ['David', 'Haynes']
self.assertEqual(expected, actual)
......@@ -36,25 +31,24 @@ class CasCallbacksTest(TestCase):
"""
student no longer in peoplefinder, or who hasn't yet been added
"""
actual = pfinfo('lfaraone')
expected = ['Luke W', 'Faraone']
self.assertEqual(expected, actual)
def test_pfinfo_employee_method(self):
"""
student employees will have their staff info return before their student info
student employees will have their staff info return before their
student info
"""
actual = pfinfo('nander13')
expected = ['Nicholas', 'Anderson']
self.assertEqual(expected, actual)
def test_pfinfo_dne(self):
"""
a name not found for either (should never happen, but gracefully handle anyway)
a name not found for either (should never happen, but gracefully
handle anyway)
"""
actual = pfinfo('bobama')
expected = ['', '']
self.assertEqual(expected, actual)
"""
go/test_forms.py
Unit test the Go forms.
References:
- http://stackoverflow.com/a/7304658
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
# Python stdlib Imports
from datetime import datetime, timedelta
......@@ -21,28 +18,21 @@ from .forms import SignupForm, URLForm, EditForm
from .models import URL, RegisteredUser
class URLFormTest(TestCase):
"""
Test cases for the URL form
"""
def setUp(self):
"""
Set up any variables such as dummy objects that will be utilised in
testing methods
"""
# Setup a blank URL object with an owner
User.objects.create(username='dhaynes', password='password')
get_user = User.objects.get(username='dhaynes')
get_registered_user = RegisteredUser.objects.get(user=get_user)
URL.objects.create(owner=get_registered_user, short='test')