Commit 2bfd80be authored by David Haynes's avatar David Haynes 🙆

Complete first pass on cleanup of models.py

- mostly involving the abstraction of validators int seperate file

Closes #176
parent 0241bf35
Pipeline #2514 failed with stage
in 1 minute and 31 seconds
...@@ -32,11 +32,11 @@ ...@@ -32,11 +32,11 @@
}, },
"django": { "django": {
"hashes": [ "hashes": [
"sha256:26b34f4417aa38d895b6b5307177b51bc3f4d53179d8696a5c19dcb50582523c", "sha256:3eb25c99df1523446ec2dc1b00e25eb2ecbdf42c9d8b0b8b32a204a8db9011f8",
"sha256:71d1a584bb4ad2b4f933d07d02c716755c1394feaac1ce61ce37843ac5401092" "sha256:69ff89fa3c3a8337015478a1a0744f52a9fef5d12c1efa01a01f99bcce9bf10c"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.0.5" "version": "==2.0.6"
}, },
"django-cas-client": { "django-cas-client": {
"hashes": [ "hashes": [
...@@ -127,10 +127,10 @@ ...@@ -127,10 +127,10 @@
"develop": { "develop": {
"astroid": { "astroid": {
"hashes": [ "hashes": [
"sha256:032f6e09161e96f417ea7fad46d3fac7a9019c775f202182c22df0e4f714cb1c", "sha256:0ef2bf9f07c3150929b25e8e61b5198c27b0dca195e156f0e4d5bdd89185ca1a",
"sha256:dea42ae6e0b789b543f728ddae7ddb6740ba33a49fb52c4a4d9cb7bb4aa6ec09" "sha256:fc9b582dba0366e63540982c3944a9230cbc6f303641c51483fa547dcc22393a"
], ],
"version": "==1.6.4" "version": "==1.6.5"
}, },
"coverage": { "coverage": {
"hashes": [ "hashes": [
...@@ -225,11 +225,11 @@ ...@@ -225,11 +225,11 @@
}, },
"pylint": { "pylint": {
"hashes": [ "hashes": [
"sha256:aa519865f8890a5905fa34924fed0f3bfc7d84fc9f9142c16dac52ffecd25a39", "sha256:a48070545c12430cfc4e865bf62f5ad367784765681b3db442d8230f0960aa3c",
"sha256:c353d8225195b37cc3aef18248b8f3fe94c5a6a95affaf885ae21a24ca31d8eb" "sha256:fff220bcb996b4f7e2b0f6812fd81507b72ca4d8c4d05daf2655c333800cb9b3"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.9.1" "version": "==1.9.2"
}, },
"pylint-django": { "pylint-django": {
"hashes": [ "hashes": [
......
...@@ -3,34 +3,31 @@ go/forms.py ...@@ -3,34 +3,31 @@ go/forms.py
Configure the layout and styling of the Go's forms. Configure the layout and styling of the Go's forms.
""" """
# Python stdlib Imports
from datetime import datetime, timedelta
# Django Imports # Django Imports
from django.core.exceptions import ValidationError
from django.forms import (BooleanField, CharField, ChoiceField, DateTimeField, from django.forms import (BooleanField, CharField, ChoiceField, DateTimeField,
ModelForm, RadioSelect, Textarea, TextInput, ModelForm, RadioSelect, Textarea, TextInput,
URLField, URLInput) URLField, URLInput)
from django.utils import timezone
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils import timezone
# App Imports # Third party imports
from .models import URL, RegisteredUser
# Other Imports
from crispy_forms.bootstrap import (Accordion, AccordionGroup, PrependedText, from crispy_forms.bootstrap import (Accordion, AccordionGroup, PrependedText,
StrictButton) StrictButton)
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import HTML, Div, Field, Fieldset, Layout 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): class URLForm(ModelForm):
""" """
The form that is used in URL creation. The form that is used in URL creation.
Define custom fields and then render them onto the template. Define custom fields and then render them onto the template.
""" """
# destination ------------------------------------------------------------------ # destination -------------------------------------------------------------
destination = URLField( destination = URLField(
required=True, required=True,
label='Long URL (Required)', label='Long URL (Required)',
...@@ -45,6 +42,7 @@ class URLForm(ModelForm): ...@@ -45,6 +42,7 @@ class URLForm(ModelForm):
required=False, required=False,
label='Short URL (Optional)', label='Short URL (Optional)',
widget=TextInput(), widget=TextInput(),
validators=[regex_short_validator],
max_length=20, max_length=20,
min_length=1, min_length=1,
) )
...@@ -73,29 +71,15 @@ class URLForm(ModelForm): ...@@ -73,29 +71,15 @@ class URLForm(ModelForm):
widget=RadioSelect(), 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( expires_custom = DateTimeField(
required=False, required=False,
label='Custom Date', label='Custom Date',
input_formats=['%m-%d-%Y'], input_formats=['%m-%d-%Y'],
validators=[valid_date], validators=[valid_date],
initial=lambda: datetime.now() + timedelta(days=1) initial=lambda: timezone.now() + timezone.timedelta(days=1)
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""
On initialization of the form, crispy forms renders this layout.
"""
# Grab that host info # Grab that host info
self.host = kwargs.pop('host', None) self.host = kwargs.pop('host', None)
super(URLForm, self).__init__(*args, **kwargs) super(URLForm, self).__init__(*args, **kwargs)
......
# Generated by Django 2.0.5 on 2018-06-03 23:40 # Generated by Django 2.0.5 on 2018-06-08 20:58
from django.db import migrations, models from django.db import migrations, models
import go.models import django.utils.timezone
import go.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
...@@ -11,14 +12,24 @@ class Migration(migrations.Migration): ...@@ -11,14 +12,24 @@ class Migration(migrations.Migration):
] ]
operations = [ 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( migrations.AlterField(
model_name='url', model_name='url',
name='destination', 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), 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( migrations.AlterField(
model_name='url', model_name='url',
name='short', name='short',
field=models.CharField(help_text='The shortcode that acts as the unique go link.', max_length=20, unique=True, validators=[go.models.URL.unique_short_validator, go.models.URL.regex_short_validator]), 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'),
),
]
...@@ -6,7 +6,6 @@ tables containing structured data in the database. ...@@ -6,7 +6,6 @@ tables containing structured data in the database.
""" """
# Python stdlib Imports # Python stdlib Imports
import string import string
import re
# Django Imports # Django Imports
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -15,18 +14,15 @@ from django.db import models ...@@ -15,18 +14,15 @@ from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone from django.utils import timezone
from django.core.exceptions import ValidationError
# Other Imports # Other Imports
from hashids import Hashids from hashids import Hashids
from .validators import regex_short_validator, unique_short_validator
""" # Generate the salt and initialize Hashids
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
Note: the Hashids library already implements several restrictions on character # characters adjacent to one another.
placement, including repeating or incrementing numbers, or placing curse word
characters adjacent to one another.
"""
SIMILAR_CHARS = set(['b', 'G', '6', 'g', 'q', 'l', SIMILAR_CHARS = set(['b', 'G', '6', 'g', 'q', 'l',
'1', 'I', 'S', '5', 'O', '0']) '1', 'I', 'S', '5', 'O', '0'])
ALPHANUMERICS = set(string.ascii_letters + string.digits) ALPHANUMERICS = set(string.ascii_letters + string.digits)
...@@ -36,7 +32,6 @@ HASHIDS = Hashids( ...@@ -36,7 +32,6 @@ HASHIDS = Hashids(
salt="srct.gmu.edu", alphabet=(LINK_CHARS) salt="srct.gmu.edu", alphabet=(LINK_CHARS)
) )
class RegisteredUser(models.Model): class RegisteredUser(models.Model):
""" """
Wrapper model for the built in User model which stores data pertaining to Wrapper model for the built in User model which stores data pertaining to
...@@ -49,49 +44,40 @@ class RegisteredUser(models.Model): ...@@ -49,49 +44,40 @@ class RegisteredUser(models.Model):
) )
full_name = models.CharField( full_name = models.CharField(
"verbose name", "Full Name",
max_length=100, max_length=100,
default="", default="",
help_text=""
) )
organization = models.CharField( organization = models.CharField(
"verbose name", "Organization",
max_length=100, max_length=100,
default="", default="",
help_text=""
) )
description = models.TextField( description = models.TextField(
"verbose name", "Signup Description",
blank=True, blank=True,
default="", default="",
help_text=""
) )
registered = models.BooleanField( registered = models.BooleanField(
"verbose name", "Registration Status",
default=False, default=False,
help_text=""
) )
approved = models.BooleanField( approved = models.BooleanField(
"verbose name", "Approval Status",
default=False, default=False,
help_text=""
) )
blocked = models.BooleanField( blocked = models.BooleanField(
"verbose name", "Blocked Status",
default=False, default=False,
help_text=""
) )
def __str__(self): def __str__(self):
return "<Registered User: {0} - Approval Status: {1}>".format( return f"<RegisteredUser: {self.user} - Approval Status: {self.approved}>"
self.user, self.approved
)
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def handle_reguser_creation(sender, instance, created, **kwargs): def handle_reguser_creation(sender, instance, created, **kwargs):
...@@ -102,82 +88,40 @@ def handle_reguser_creation(sender, instance, created, **kwargs): ...@@ -102,82 +88,40 @@ def handle_reguser_creation(sender, instance, created, **kwargs):
if created: if created:
RegisteredUser.objects.create(user=instance) RegisteredUser.objects.create(user=instance)
class URL(models.Model): class URL(models.Model):
""" """
The representation of a stored URL redirection rule. Each URL has The representation of a stored URL redirection rule. Each URL has
attributes that are used for analytic purposes. 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( owner = models.ForeignKey(
RegisteredUser, RegisteredUser,
on_delete="cascade", on_delete="cascade",
verbose_name="verbose name" verbose_name="RegisteredUser Owner"
) )
date_created = models.DateTimeField( date_created = models.DateTimeField(
"verbose name", "Go Link Creation Date",
default=timezone.now, default=timezone.now,
help_text=""
) )
date_expires = models.DateTimeField( date_expires = models.DateTimeField(
"verbose name", "Go Link Expiry Date",
blank=True, blank=True,
null=True, null=True,
# choices=EXPIRATION_CHOICES, TODO
# default=NEVER, TODO
help_text=""
) )
destination = models.URLField( destination = models.URLField(
"Go Link Destination URL",
max_length=1000, max_length=1000,
default="https://go.gmu.edu", default="https://go.gmu.edu",
help_text="The URL to be redirected to when visiting the shortlink."
) )
def unique_short_validator(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)
raise ValidationError(f"Short url already exists.")
except URL.DoesNotExist as ex:
print(ex)
return
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(f"Short url fails regex check.")
# Note: min_length cannot exist on a model so it is enforced in forms.py # Note: min_length cannot exist on a model so it is enforced in forms.py
short = models.CharField( short = models.CharField(
"Go Shortcode",
max_length=20, max_length=20,
unique=True, unique=True,
validators=[unique_short_validator, regex_short_validator], validators=[unique_short_validator, regex_short_validator],
help_text="The shortcode that acts as the unique go link."
) )
# TODO Abstract analytics into their own model # TODO Abstract analytics into their own model
...@@ -186,7 +130,7 @@ class URL(models.Model): ...@@ -186,7 +130,7 @@ class URL(models.Model):
socialclicks = models.IntegerField(default=0, help_text="") socialclicks = models.IntegerField(default=0, help_text="")
def __str__(self): def __str__(self):
return f"<Owner: {self.owner.user} - destination URL: {self.destination}>" return f"<Owner: {self.owner.user} - Destination URL: {self.destination}>"
class Meta: class Meta:
ordering = ['short'] ordering = ['short']
......
"""
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): ...@@ -138,8 +138,6 @@ def my_links(request):
return index(request) return index(request)
# Rate limits are completely arbitrary # Rate limits are completely arbitrary
@ratelimit(key='user', rate='3/m', method='POST', block=True) @ratelimit(key='user', rate='3/m', method='POST', block=True)
@ratelimit(key='user', rate='25/d', method='POST', block=True) @ratelimit(key='user', rate='25/d', method='POST', block=True)
def post(request, url_form): def post(request, url_form):
...@@ -475,7 +473,6 @@ def redirection(request, short): ...@@ -475,7 +473,6 @@ def redirection(request, short):
""" """
This view redirects a user based on the short URL they requested. This view redirects a user based on the short URL they requested.
""" """
# Get the current domain info # Get the current domain info
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/" domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"
...@@ -654,11 +651,9 @@ def useradmin(request): ...@@ -654,11 +651,9 @@ def useradmin(request):
return HttpResponseRedirect('manage') return HttpResponseRedirect('manage')
# Get a list of all RegisteredUsers that need to be approved # Get a list of all RegisteredUsers that need to be approved
need_approval = RegisteredUser.objects.filter( need_approval = RegisteredUser.objects.filter(registered=True).filter(approved=False).filter(blocked=False)
registered=True).filter(approved=False).filter(blocked=False)
# Get a list of all RegisteredUsers that are currently users # Get a list of all RegisteredUsers that are currently users
current_users = RegisteredUser.objects.filter( current_users = RegisteredUser.objects.filter(approved=True).filter(registered=True).filter(blocked=False)
approved=True).filter(registered=True).filter(blocked=False)
# Get a list of all RegisteredUsers that are blocked # Get a list of all RegisteredUsers that are blocked
blocked_users = RegisteredUser.objects.filter(blocked=True) 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