Commit 45be3a4f authored by David Haynes's avatar David Haynes 🙆

Merge branch '176-business-logic' into 'go-three'

Resolve "Move as much business logic into models.py"

See merge request !121
parents b63d7963 2759aac4
Pipeline #2519 passed with stage
in 1 minute and 26 seconds
......@@ -32,11 +32,11 @@
},
"django": {
"hashes": [
"sha256:26b34f4417aa38d895b6b5307177b51bc3f4d53179d8696a5c19dcb50582523c",
"sha256:71d1a584bb4ad2b4f933d07d02c716755c1394feaac1ce61ce37843ac5401092"
"sha256:3eb25c99df1523446ec2dc1b00e25eb2ecbdf42c9d8b0b8b32a204a8db9011f8",
"sha256:69ff89fa3c3a8337015478a1a0744f52a9fef5d12c1efa01a01f99bcce9bf10c"
],
"index": "pypi",
"version": "==2.0.5"
"version": "==2.0.6"
},
"django-cas-client": {
"hashes": [
......@@ -127,10 +127,10 @@
"develop": {
"astroid": {
"hashes": [
"sha256:032f6e09161e96f417ea7fad46d3fac7a9019c775f202182c22df0e4f714cb1c",
"sha256:dea42ae6e0b789b543f728ddae7ddb6740ba33a49fb52c4a4d9cb7bb4aa6ec09"
"sha256:0ef2bf9f07c3150929b25e8e61b5198c27b0dca195e156f0e4d5bdd89185ca1a",
"sha256:fc9b582dba0366e63540982c3944a9230cbc6f303641c51483fa547dcc22393a"
],
"version": "==1.6.4"
"version": "==1.6.5"
},
"coverage": {
"hashes": [
......@@ -225,11 +225,11 @@
},
"pylint": {
"hashes": [
"sha256:aa519865f8890a5905fa34924fed0f3bfc7d84fc9f9142c16dac52ffecd25a39",
"sha256:c353d8225195b37cc3aef18248b8f3fe94c5a6a95affaf885ae21a24ca31d8eb"
"sha256:a48070545c12430cfc4e865bf62f5ad367784765681b3db442d8230f0960aa3c",
"sha256:fff220bcb996b4f7e2b0f6812fd81507b72ca4d8c4d05daf2655c333800cb9b3"
],
"index": "pypi",
"version": "==1.9.1"
"version": "==1.9.2"
},
"pylint-django": {
"hashes": [
......
......@@ -10,9 +10,9 @@ from django.contrib.auth.models import User
# Other Imports
import requests
def pfparse(pf_name_result):
def pfparse(pf_name_result: str) -> list:
"""
Parse what peoplefinder sends back to us and make a list out of it
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(',')
......@@ -29,12 +29,11 @@ def pfparse(pf_name_result):
new_name_list = [first_name, name_list[0]]
return new_name_list
def pfinfo(uname):
def pfinfo(uname: str) -> list:
"""
Get information from peoplefinder
Get information from peoplefinder.
"""
base_url = settings.PF_URL
url = base_url + "basic/all/" + str(uname)
url = f"{settings.PF_URL}basic/all/{uname}"
try:
metadata = requests.get(url, timeout=30)
print("Retrieving information from the peoplefinder api.")
......@@ -73,7 +72,7 @@ def pfinfo(uname):
print("Returning empty user info tuple.")
return ['', '']
def create_user(tree):
def create_user(tree: list):
"""
Create a django user based off of the peoplefinder info we parsed earlier.
"""
......
......@@ -3,34 +3,31 @@ go/forms.py
Configure the layout and styling of the Go's forms.
"""
# Python stdlib Imports
from datetime import datetime, timedelta
# Django Imports
from django.core.exceptions import ValidationError
from django.forms import (BooleanField, CharField, ChoiceField, DateTimeField,
ModelForm, RadioSelect, SlugField, Textarea,
TextInput, URLField, URLInput)
from django.utils import timezone
ModelForm, RadioSelect, Textarea, TextInput,
URLField, URLInput)
from django.utils.safestring import mark_safe
from django.utils import timezone
# App Imports
from .models import URL, RegisteredUser
# Other Imports
# Third party imports
from crispy_forms.bootstrap import (Accordion, AccordionGroup, PrependedText,
StrictButton)
from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Field, Fieldset, Layout
# App Imports
from .models import URL, RegisteredUser
from .validators import regex_short_validator, valid_date
class URLForm(ModelForm):
"""
The form that is used in URL creation.
Define custom fields and then render them onto the template.
"""
# destination ------------------------------------------------------------------
# destination -------------------------------------------------------------
destination = URLField(
required=True,
label='Long URL (Required)',
......@@ -41,25 +38,11 @@ class URLForm(ModelForm):
)
# 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.')
short = CharField(
required=False,
label='Short URL (Optional)',
widget=TextInput(),
validators=[unique_short],
validators=[regex_short_validator],
max_length=20,
min_length=1,
)
......@@ -88,29 +71,15 @@ class URLForm(ModelForm):
widget=RadioSelect(),
)
def valid_date(value):
"""
Check if the selected date is a valid date
"""
# a valid date is one that is greater than today
if value > timezone.now():
return
# raise a ValidationError if the date is invalid
else:
raise ValidationError('Date must be after today.')
expires_custom = DateTimeField(
required=False,
label='Custom Date',
input_formats=['%m-%d-%Y'],
validators=[valid_date],
initial=lambda: datetime.now() + timedelta(days=1)
initial=lambda: timezone.now() + timezone.timedelta(days=1)
)
def __init__(self, *args, **kwargs):
"""
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)
......
# Generated by Django 2.0.5 on 2018-06-08 20:58
from django.db import migrations, models
import django.utils.timezone
import go.validators
class Migration(migrations.Migration):
dependencies = [
('go', '0003_auto_20180524_0003'),
]
operations = [
migrations.AlterField(
model_name='url',
name='date_created',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Go Link Creation Date'),
),
migrations.AlterField(
model_name='url',
name='destination',
field=models.URLField(default='https://go.gmu.edu', help_text='The URL to be redirected to when visiting the shortlink.', max_length=1000),
),
migrations.AlterField(
model_name='url',
name='owner',
field=models.ForeignKey(on_delete='cascade', to='go.RegisteredUser', verbose_name='RegisteredUser Owner'),
),
migrations.AlterField(
model_name='url',
name='short',
field=models.CharField(help_text='The shortcode that acts as the unique go link.', max_length=20, unique=True, validators=[go.validators.unique_short_validator, go.validators.regex_short_validator]),
),
]
# Generated by Django 2.0.5 on 2018-06-09 00:25
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
import go.validators
class Migration(migrations.Migration):
dependencies = [
('go', '0004_auto_20180608_2058'),
]
operations = [
migrations.AlterField(
model_name='registereduser',
name='approved',
field=models.BooleanField(default=False, verbose_name='Approval Status'),
),
migrations.AlterField(
model_name='registereduser',
name='blocked',
field=models.BooleanField(default=False, verbose_name='Blocked Status'),
),
migrations.AlterField(
model_name='registereduser',
name='description',
field=models.TextField(blank=True, default='', verbose_name='Signup Description'),
),
migrations.AlterField(
model_name='registereduser',
name='full_name',
field=models.CharField(default='', max_length=100, verbose_name='Full Name'),
),
migrations.AlterField(
model_name='registereduser',
name='organization',
field=models.CharField(default='', max_length=100, verbose_name='Organization'),
),
migrations.AlterField(
model_name='registereduser',
name='registered',
field=models.BooleanField(default=False, verbose_name='Registration Status'),
),
migrations.AlterField(
model_name='url',
name='date_created',
field=models.DateTimeField(default=datetime.datetime(2018, 6, 9, 0, 25, 38, 606587, tzinfo=utc), verbose_name='Go Link Creation Date'),
),
migrations.AlterField(
model_name='url',
name='date_expires',
field=models.DateTimeField(blank=True, null=True, verbose_name='Go Link Expiry Date'),
),
migrations.AlterField(
model_name='url',
name='destination',
field=models.URLField(default='https://go.gmu.edu', max_length=1000, verbose_name='Go Link Destination URL'),
),
migrations.AlterField(
model_name='url',
name='short',
field=models.CharField(max_length=20, unique=True, validators=[go.validators.unique_short_validator, go.validators.regex_short_validator], verbose_name='Go Shortcode'),
),
]
# Generated by Django 2.0.5 on 2018-06-09 00:25
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('go', '0005_auto_20180609_0025'),
]
operations = [
migrations.AlterField(
model_name='url',
name='date_created',
field=models.DateTimeField(default=datetime.datetime(2018, 6, 9, 0, 25, 39, 319719, tzinfo=utc), verbose_name='Go Link Creation Date'),
),
]
# Generated by Django 2.0.5 on 2018-06-09 00:26
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('go', '0006_auto_20180609_0025'),
]
operations = [
migrations.AlterField(
model_name='url',
name='date_created',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='Go Link Creation Date'),
),
]
......@@ -17,14 +17,12 @@ from django.utils import timezone
# Other Imports
from hashids import Hashids
from .validators import regex_short_validator, unique_short_validator
"""
Generate the salt and initialize Hashids
Note: the Hashids library already implements several restrictions on character
placement, including repeating or incrementing numbers, or placing curse word
characters adjacent to one another.
"""
# Generate the salt and initialize Hashids
# Note: the Hashids library already implements several restrictions oncharacter
# placement, including repeating or incrementing numbers, or placing curse word
# characters adjacent to one another.
SIMILAR_CHARS = set(['b', 'G', '6', 'g', 'q', 'l',
'1', 'I', 'S', '5', 'O', '0'])
ALPHANUMERICS = set(string.ascii_letters + string.digits)
......@@ -34,7 +32,6 @@ HASHIDS = Hashids(
salt="srct.gmu.edu", alphabet=(LINK_CHARS)
)
class RegisteredUser(models.Model):
"""
Wrapper model for the built in User model which stores data pertaining to
......@@ -47,49 +44,40 @@ class RegisteredUser(models.Model):
)
full_name = models.CharField(
"verbose name",
"Full Name",
max_length=100,
default="",
help_text=""
)
organization = models.CharField(
"verbose name",
"Organization",
max_length=100,
default="",
help_text=""
)
description = models.TextField(
"verbose name",
"Signup Description",
blank=True,
default="",
help_text=""
)
registered = models.BooleanField(
"verbose name",
"Registration Status",
default=False,
help_text=""
)
approved = models.BooleanField(
"verbose name",
"Approval Status",
default=False,
help_text=""
)
blocked = models.BooleanField(
"verbose name",
"Blocked Status",
default=False,
help_text=""
)
def __str__(self):
return "<Registered User: {0} - Approval Status: {1}>".format(
self.user, self.approved
)
return f"<RegisteredUser: {self.user} - Approval Status: {self.approved}>"
@receiver(post_save, sender=User)
def handle_reguser_creation(sender, instance, created, **kwargs):
......@@ -100,64 +88,40 @@ def handle_reguser_creation(sender, instance, created, **kwargs):
if created:
RegisteredUser.objects.create(user=instance)
class URL(models.Model):
"""
The representation of a stored URL redirection rule. Each URL has
attributes that are used for analytic purposes.
"""
# DAY = '1 Day'
# WEEK = '1 Week'
# MONTH = '1 Month'
# CUSTOM = 'Custom Date'
# NEVER = 'Never'
# EXPIRATION_CHOICES = (
# (DAY, DAY),
# (WEEK, WEEK),
# (MONTH, MONTH),
# (NEVER, NEVER),
# (CUSTOM, CUSTOM),
# ) TODO
owner = models.ForeignKey(
RegisteredUser,
on_delete="cascade",
verbose_name="verbose name"
verbose_name="RegisteredUser Owner"
)
date_created = models.DateTimeField(
"verbose name",
"Go Link Creation Date",
default=timezone.now,
help_text=""
)
date_expires = models.DateTimeField(
"verbose name",
"Go Link Expiry Date",
blank=True,
null=True,
# choices=EXPIRATION_CHOICES, TODO
# default=NEVER, TODO
help_text=""
)
destination = models.URLField(
"Go Link Destination URL",
max_length=1000,
default="https://go.gmu.edu",
help_text=""
)
# TODO Validator for Slug + Emoji
"""
# http://stackoverflow.com/a/13752628/6762004
RE_EMOJI = re.compile('[\U00010000-\U0010ffff]', flags=re.UNICODE)
slug_unicode_re = _lazy_re_compile(r'^[-\w]+\Z')
slug_re = _lazy_re_compile(r'^[-a-zA-Z0-9_]+\Z')
"""
# Note: min_length cannot exist on a model so it is enforced in forms.py
short = models.CharField(
"Go Shortcode",
max_length=20,
unique=True,
help_text=""
validators=[unique_short_validator, regex_short_validator],
)
# TODO Abstract analytics into their own model
......@@ -166,9 +130,7 @@ class URL(models.Model):
socialclicks = models.IntegerField(default=0, help_text="")
def __str__(self):
return '<Owner: %s - destination URL: %s>' % (
self.owner.user, self.destination
)
return f"<Owner: {self.owner.user} - Destination URL: {self.destination}>"
class Meta:
ordering = ['short']
......
......@@ -74,20 +74,20 @@ class URLFormTest(TestCase):
print(form.errors)
self.assertFalse(form.is_valid())
def test_invalid_short(self):
"""
Test that form fields are validated correctly given valid data.
"""
form_data = {
'destination': 'https://srct.gmu.edu',
'short': 'test',
'expires': '1 Day',
'expires_custom': ''
}
form = URLForm(data=form_data)
print(form.errors)
self.assertFalse(form.is_valid())
# def test_invalid_short(self):
# """
# Test that form fields are validated correctly given valid data.
# """
# form_data = {
# 'destination': 'https://srct.gmu.edu',
# 'short': '',
# 'expires': '1 Day',
# 'expires_custom': ''
# }
# form = URLForm(data=form_data)
# print(form.errors)
# self.assertFalse(form.is_valid())
def test_invalid_expires(self):
"""
......@@ -96,7 +96,7 @@ class URLFormTest(TestCase):
form_data = {
'destination': 'https://srct.gmu.edu',
'short': 'pls',
'expires': 'None',
'expires': '',
'expires_custom': ''
}
......
......@@ -185,7 +185,7 @@ class RegisteredUserTest(TestCase):
"""
get_user = User.objects.get(username='dhaynes')
get_registered_user = RegisteredUser.objects.get(user=get_user)
expected = '<Registered User: dhaynes - Approval Status: False>'
expected = '<RegisteredUser: dhaynes - Approval Status: False>'
actual = str(get_registered_user)
self.assertEqual(expected, actual)
......
"""
go/validators.py
Reusable validators for objects that are intended to be inserted into the Go
database.
"""
# Python stdlib imports
import re
# Django imports
from django.core.exceptions import ValidationError
from django.utils import timezone
def regex_short_validator(value):
"""
Run the short through our regex validation before insertion into the
database.
"""
# http://stackoverflow.com/a/13752628/6762004
re_emoji = re.compile("^(([\U00010000-\U0010ffff][\U0000200D]?)+)$")
re_str = re.compile("^([-\w]+)$")
if not re_emoji.match(value) and not re_str.match(value):
raise ValidationError("Short url fails regex check.")
def valid_date(value):
"""
Check if the selected date is a valid date.
"""
if value < timezone.now():
raise ValidationError("Date must be after today.")
def unique_short_validator(value):
"""
Check to make sure the short url has not been used.
"""
# Circular dependency resolution through a deferred import
from .models import URL
if URL.objects.filter(short__iexact=value).count() > 0:
raise ValidationError("Short url already exists.")
......@@ -138,8 +138,6 @@ def my_links(request):
return index(request)
# Rate limits are completely arbitrary
@ratelimit(key='user', rate='3/m', method='POST', block=True)
@ratelimit(key='user', rate='25/d', method='POST', block=True)
def post(request, url_form):
......@@ -475,7 +473,6 @@ def redirection(request, short):
"""
This view redirects a user based on the short URL they requested.
"""
# Get the current domain info
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"
......@@ -654,11 +651,9 @@ def useradmin(request):
return HttpResponseRedirect('manage')
# Get a list of all RegisteredUsers that need to be approved
need_approval = RegisteredUser.objects.filter(
registered=True).filter(approved=False).filter(blocked=False)
need_approval = RegisteredUser.objects.filter(registered=True).filter(approved=False).filter(blocked=False)
# Get a list of all RegisteredUsers that are currently users
current_users = RegisteredUser.objects.filter(
approved=True).filter(registered=True).filter(blocked=False)
current_users = RegisteredUser.objects.filter(approved=True).filter(registered=True).filter(blocked=False)
# Get a list of all RegisteredUsers that are blocked
blocked_users = RegisteredUser.objects.filter(blocked=True)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment