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 @@
},
"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": [
......
......@@ -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, Textarea, TextInput,
URLField, URLInput)
from django.utils import timezone
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)',
......@@ -45,6 +42,7 @@ class URLForm(ModelForm):
required=False,
label='Short URL (Optional)',
widget=TextInput(),
validators=[regex_short_validator],
max_length=20,
min_length=1,
)
......@@ -73,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-03 23:40
# Generated by Django 2.0.5 on 2018-06-08 20:58
from django.db import migrations, models
import go.models
import django.utils.timezone
import go.validators
class Migration(migrations.Migration):
......@@ -11,14 +12,24 @@ class Migration(migrations.Migration):
]
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.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.
"""
# Python stdlib Imports
import string
import re
# Django Imports
from django.contrib.auth.models import User
......@@ -15,18 +14,15 @@ 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.core.exceptions import ValidationError
# 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)
......@@ -36,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
......@@ -49,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):
......@@ -102,82 +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="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
short = models.CharField(
"Go Shortcode",
max_length=20,
unique=True,
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
......@@ -186,7 +130,7 @@ class URL(models.Model):
socialclicks = models.IntegerField(default=0, help_text="")
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:
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):
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