Commit 7b84fb10 authored by David Haynes's avatar David Haynes 🙆

Begin the cleanup of model fields

- more semantic
- also ensure that dev flow is good to go re: docker
parent a54fc20a
Pipeline #2457 failed with stages
in 1 minute and 10 seconds
...@@ -13,11 +13,10 @@ django = "<2.1,>=2.0" ...@@ -13,11 +13,10 @@ django = "<2.1,>=2.0"
django-crispy-forms = "==1.7.0" django-crispy-forms = "==1.7.0"
django-ratelimit = "==1.0.1" django-ratelimit = "==1.0.1"
django-redis-cache = "==1.7.1" django-redis-cache = "==1.7.1"
django-qrcode = {git = "https://github.com/dhaynespls/django-qrcode.git", editable = true}
django-cas-client = {git = "https://github.com/kstateome/django-cas.git", editable = true}
"django-bootstrap3-datetimepicker" = {git = "https://github.com/dhaynespls/django-bootstrap3-datetimepicker.git", editable = true}
hashids = "==1.2.0" hashids = "==1.2.0"
mysqlclient = "*" mysqlclient = "*"
django-cas-client = "*"
requests = "*"
[requires] [requires]
python_version = "3.6" python_version = "3.6"
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "00e93a32d56376e61c103f31302646aa9b2aaac10a9c1c442507aaa42643522c" "sha256": "c9aa141f890933ad50c8c0f52627649fad85140d56fa35ec350bc052d748b99a"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
...@@ -16,6 +16,20 @@ ...@@ -16,6 +16,20 @@
] ]
}, },
"default": { "default": {
"certifi": {
"hashes": [
"sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
"sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
],
"version": "==2018.4.16"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"django": { "django": {
"hashes": [ "hashes": [
"sha256:26b34f4417aa38d895b6b5307177b51bc3f4d53179d8696a5c19dcb50582523c", "sha256:26b34f4417aa38d895b6b5307177b51bc3f4d53179d8696a5c19dcb50582523c",
...@@ -24,13 +38,13 @@ ...@@ -24,13 +38,13 @@
"index": "pypi", "index": "pypi",
"version": "==2.0.5" "version": "==2.0.5"
}, },
"django-bootstrap3-datetimepicker": {
"editable": true,
"git": "https://github.com/dhaynespls/django-bootstrap3-datetimepicker.git"
},
"django-cas-client": { "django-cas-client": {
"editable": true, "hashes": [
"git": "https://github.com/kstateome/django-cas.git" "sha256:2a190c9e651df3a65840206b38a9fc1c2c404696fcaf66fc69a684591f56d978",
"sha256:4d941d58769437e56656464c91461e61eee27ff2dac3ed53766e0042bc33169a"
],
"index": "pypi",
"version": "==1.4.0"
}, },
"django-crispy-forms": { "django-crispy-forms": {
"hashes": [ "hashes": [
...@@ -40,10 +54,6 @@ ...@@ -40,10 +54,6 @@
"index": "pypi", "index": "pypi",
"version": "==1.7.0" "version": "==1.7.0"
}, },
"django-qrcode": {
"editable": true,
"git": "https://github.com/dhaynespls/django-qrcode.git"
},
"django-ratelimit": { "django-ratelimit": {
"hashes": [ "hashes": [
"sha256:a74f23069291441792f960b6ac662579560a7c959e2e8444ecf140bf1a9041c4", "sha256:a74f23069291441792f960b6ac662579560a7c959e2e8444ecf140bf1a9041c4",
...@@ -66,6 +76,13 @@ ...@@ -66,6 +76,13 @@
"index": "pypi", "index": "pypi",
"version": "==1.2.0" "version": "==1.2.0"
}, },
"idna": {
"hashes": [
"sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f",
"sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4"
],
"version": "==2.6"
},
"mysqlclient": { "mysqlclient": {
"hashes": [ "hashes": [
"sha256:1e85e48b167e2af3bb08f273fdbd1ad6401cbe75057fa6513f97387dc7b282dc", "sha256:1e85e48b167e2af3bb08f273fdbd1ad6401cbe75057fa6513f97387dc7b282dc",
...@@ -90,6 +107,21 @@ ...@@ -90,6 +107,21 @@
"sha256:a22ca993cea2962dbb588f9f30d0015ac4afcc45bee27d3978c0dbe9e97c6c0f" "sha256:a22ca993cea2962dbb588f9f30d0015ac4afcc45bee27d3978c0dbe9e97c6c0f"
], ],
"version": "==2.10.6" "version": "==2.10.6"
},
"requests": {
"hashes": [
"sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b",
"sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e"
],
"index": "pypi",
"version": "==2.18.4"
},
"urllib3": {
"hashes": [
"sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b",
"sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"
],
"version": "==1.22"
} }
}, },
"develop": { "develop": {
......
...@@ -2,7 +2,7 @@ version: "3" ...@@ -2,7 +2,7 @@ version: "3"
services: services:
db: db:
image: mysql image: mysql:5.7
ports: ports:
- "3306:3306" - "3306:3306"
environment: environment:
...@@ -12,7 +12,7 @@ services: ...@@ -12,7 +12,7 @@ services:
MYSQL_PASSWORD: go MYSQL_PASSWORD: go
web: web:
image: go_web build: .
ports: ports:
- '8000:8000' - '8000:8000'
command: ./docker-startup.sh command: ./docker-startup.sh
......
#! /bin/bash #! /bin/bash
until nc -z db 3306; do
echo "waiting for database to start..."
sleep 1
done
export GO_SECRET_KEY export GO_SECRET_KEY
export GO_CREATE_SUPERUSER export GO_CREATE_SUPERUSER
......
...@@ -11,27 +11,25 @@ from django.contrib.auth.models import User ...@@ -11,27 +11,25 @@ from django.contrib.auth.models import User
# App Imports # App Imports
from .models import URL, RegisteredUser from .models import URL, RegisteredUser
class URLAdmin(admin.ModelAdmin):
"""
Define what attributes display in the URL Admin
"""
list_display = ("target", "short", "owner", "clicks", "date_created", "expires")
# Register URLAdmin
admin.site.register(URL, URLAdmin)
class RegisteredUserInline(admin.StackedInline): class RegisteredUserInline(admin.StackedInline):
""" """
Define an inline admin descriptor for User model Allow for RegisteredUsers to be displayed alongside their Django user
objects.
""" """
model = RegisteredUser model = RegisteredUser
can_delete = False can_delete = False
class UserAdmin(UserAdmin):
class RegUserAdmin(UserAdmin):
""" """
Define a new User admin Stick information about RegisteredUsers into its own Admin panel.
""" """
inlines = (RegisteredUserInline, ) inlines = (RegisteredUserInline, )
# Default ModelAdmin
admin.site.register(URL)
# Define a new User admin
admin.site.unregister(User) admin.site.unregister(User)
admin.site.register(User, UserAdmin) admin.site.register(User, RegUserAdmin)
...@@ -18,7 +18,6 @@ from django.utils.safestring import mark_safe ...@@ -18,7 +18,6 @@ from django.utils.safestring import mark_safe
from .models import URL, RegisteredUser from .models import URL, RegisteredUser
# Other Imports # Other Imports
from bootstrap3_datetime.widgets import DateTimePicker
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
......
...@@ -16,70 +16,83 @@ from django.dispatch import receiver ...@@ -16,70 +16,83 @@ from django.dispatch import receiver
from django.utils import timezone from django.utils import timezone
# Other Imports # Other Imports
from hashids import Hashids # http://hashids.org/python/ from hashids import Hashids
# generate the salt and initialize Hashids """
# note: the Hashids library already implements several restrictions Generate the salt and initialize Hashids
# on character 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)
link_chars = ''.join(alphanumerics - similar_chars) 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.
"""
SIMILAR_CHARS = set(['b', 'G', '6', 'g', 'q', 'l',
'1', 'I', 'S', '5', 'O', '0'])
ALPHANUMERICS = set(string.ascii_letters + string.digits)
LINK_CHARS = ''.join(ALPHANUMERICS - SIMILAR_CHARS)
HASHIDS = Hashids( HASHIDS = Hashids(
salt="srct.gmu.edu", alphabet=(link_chars) salt="srct.gmu.edu", alphabet=(LINK_CHARS)
) )
class RegisteredUser(models.Model): class RegisteredUser(models.Model):
""" """
This is simply a wrapper model for the User model which, if an object Wrapper model for the built in User model which stores data pertaining to
exists, indicates that that user is registered. the registration / approval / blocked status of a django user.
""" """
# Let's associate a User to this RegisteredUser user = models.OneToOneField(
user = models.OneToOneField(User, on_delete="cascade") User,
on_delete="cascade",
verbose_name="Django User Object"
)
# What is your name?
full_name = models.CharField( full_name = models.CharField(
blank=False, "verbose name",
max_length=100, max_length=100,
default="",
help_text=""
) )
# What organization are you associated with?
organization = models.CharField( organization = models.CharField(
blank=False, "verbose name",
max_length=100, max_length=100,
default="",
help_text=""
) )
# Why do you want to use Go? description = models.TextField(
description = models.TextField(blank=True) "verbose name",
blank=True,
default="",
help_text=""
)
# Have you filled out the registration form? registered = models.BooleanField(
registered = models.BooleanField(default=False) "verbose name",
default=False,
help_text=""
)
# Are you approved to use Go? approved = models.BooleanField(
approved = models.BooleanField(default=False) "verbose name",
default=False,
help_text=""
)
# Is this User Blocked? blocked = models.BooleanField(
blocked = models.BooleanField(default=False) "verbose name",
default=False,
help_text=""
)
def __str__(self): def __str__(self):
""" return "<Registered User: {0} - Approval Status: {1}>".format(
String representation of this object.
"""
return '<Registered User: %s - Approval Status: %s>' % (
self.user, 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):
""" """
When a post_save is called on a User object (and it is newly created), this 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.
...@@ -90,61 +103,82 @@ def handle_regUser_creation(sender, instance, created, **kwargs): ...@@ -90,61 +103,82 @@ def handle_regUser_creation(sender, instance, created, **kwargs):
class URL(models.Model): class URL(models.Model):
""" """
This model represents a stored URL redirection rule. Each URL has an The representation of a stored URL redirection rule. Each URL has
owner, target url, short identifier, click counter, and expiration attributes that are used for analytic purposes.
date.
""" """
# Who is the owner of this Go link # DAY = '1 Day'
owner = models.ForeignKey(RegisteredUser, on_delete="cascade") # WEEK = '1 Week'
# When was this link created? # MONTH = '1 Month'
date_created = models.DateTimeField(default=timezone.now) # CUSTOM = 'Custom Date'
# NEVER = 'Never'
# What is the target URL for this Go link
target = models.URLField(max_length=1000) # EXPIRATION_CHOICES = (
# What is the actual go link (short url) for this URL # (DAY, DAY),
short = models.SlugField(max_length=20, primary_key=True) # (WEEK, WEEK),
# (MONTH, MONTH),
# how many people have visited this Go link # (NEVER, NEVER),
clicks = models.IntegerField(default=0) # (CUSTOM, CUSTOM),
# how many people have visited this Go link through the qr code # ) TODO
qrclicks = models.IntegerField(default=0)
# how many people have visited the go link through social media owner = models.ForeignKey(
socialclicks = models.IntegerField(default=0) RegisteredUser,
on_delete="cascade",
# does this Go link expire on a certain date verbose_name="verbose name"
expires = models.DateTimeField(blank=True, null=True) )
date_created = models.DateTimeField(
"verbose name",
default=timezone.now,
help_text=""
)
date_expires = models.DateTimeField(
"verbose name",
blank=True,
null=True,
# choices=EXPIRATION_CHOICES, TODO
# default=NEVER, TODO
help_text=""
)
destination = models.URLField(
max_length=1000,
default="https://go.gmu.edu",
help_text=""
)
short = models.SlugField(
max_length=20,
unique=True,
help_text=""
)
# TODO Abstract analytics into their own model
clicks = models.IntegerField(default=0, help_text="")
qrclicks = models.IntegerField(default=0, help_text="")
socialclicks = models.IntegerField(default=0, help_text="")
def __str__(self): def __str__(self):
"""
String representation of this object.
"""
return '<Owner: %s - Target URL: %s>' % ( return '<Owner: %s - Target URL: %s>' % (
self.owner.user, self.target self.owner.user, self.target
) )
class Meta: class Meta:
"""
Meta information for this object.
"""
# they should be ordered by their short links
ordering = ['short'] ordering = ['short']
@staticmethod @staticmethod
def generate_valid_short(): def generate_valid_short():
""" """
legacy method to ensure that generated short URL's are valid Generate a short to be used as a default go link if the user does not
should be updated to be simpler provide a custom one.
""" """
if cache.get("hashids_counter") is None: if cache.get("hashids_counter") is None:
cache.set("hashids_counter", URL.objects.count()) cache.set("hashids_counter", URL.objects.count())
tries = 1
while tries < 100: short = HASHIDS.encrypt(cache.get("hashids_counter"))
try:
short = HASHIDS.encrypt(cache.get("hashids_counter")) # Continually generate new shorts until there are no conflicts
tries += 1 while URL.objects.filter(short__iexact=short).count() > 0:
cache.incr("hashids_counter") short = HASHIDS.encrypt(cache.get("hashids_counter"))
URL.objects.get(short__iexact=short)
except URL.DoesNotExist as ex: return short
print(ex)
return short
return None
...@@ -24,10 +24,11 @@ ALLOWED_HOSTS = [os.environ['GO_ALLOWED_HOSTS']] ...@@ -24,10 +24,11 @@ ALLOWED_HOSTS = [os.environ['GO_ALLOWED_HOSTS']]
ADMINS = () ADMINS = ()
MANAGERS = ADMINS MANAGERS = ADMINS
# TIME # Internationalization
TIME_ZONE = 'America/New_York' # https://docs.djangoproject.com/en/2.0/topics/i18n/
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
SITE_ID = 1 TIME_ZONE = 'UTC'
USE_I18N = True USE_I18N = True
USE_L10N = True USE_L10N = True
USE_TZ = True USE_TZ = True
...@@ -57,15 +58,15 @@ TEMPLATES = [ ...@@ -57,15 +58,15 @@ TEMPLATES = [
'DIRS': [ 'DIRS': [
os.path.join(BASE_DIR, 'templates') os.path.join(BASE_DIR, 'templates')
], ],
'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.template.context_processors.request' 'django.contrib.messages.context_processors.messages',
],
'loaders': [
'django.template.loaders.app_directories.Loader'
], ],
} },
} }
] ]
...@@ -105,9 +106,7 @@ INSTALLED_APPS = ( ...@@ -105,9 +106,7 @@ INSTALLED_APPS = (
'django.contrib.admin', 'django.contrib.admin',
'go', 'go',
# Third party # Third party
'qrcode',
'crispy_forms', 'crispy_forms',
'bootstrap3_datetime',
'cas', 'cas',
) )
......
...@@ -8,7 +8,8 @@ https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ ...@@ -8,7 +8,8 @@ https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
""" """
import os import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.production") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.production")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application() application = get_wsgi_application()
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