Commit 62674ce1 authored by David Haynes's avatar David Haynes

Merge branch '2.2-dev' into versionFooter

parents b5a2a502 5bc056f5
Pipeline #782 passed with stage
in 2 minutes and 10 seconds
image: ubuntu:14.04
services:
- mysql:latest
......@@ -10,29 +8,36 @@ variables:
MYSQL_DATABASE: go
MYSQL_ROOT_PASSWORD: root
test_Go:
before_script:
- apt-get update -qy
- apt-get install -y libldap2-dev libsasl2-dev mysql-client libmysqlclient-dev python-mysqldb
- pip install -r requirements.txt
- cd go/
- cp settings/settings.py.template settings/settings.py
- cp settings/secret.py.template settings/secret.py
- export SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9" | fold -w 60 | head -n1 2>/dev/null)
- export DJANGO_DEBUG="True"
- sed -i settings/secret.py -e 's/DB_NAME.*/DB_NAME = \"go\"/'
- sed -i settings/secret.py -e 's/DB_USER.*/DB_USER = \"root\"/'
- sed -i settings/secret.py -e 's/DB_PASSWORD.*/DB_PASSWORD = \"root\"/'
- sed -i settings/secret.py -e 's/DB_HOST.*/DB_HOST = \"mysql\"/'
- sed -i settings/secret.py -e 's/SECRET_KEY.*/SECRET_KEY = \"${SECRET_KEY}\"/'
- python manage.py makemigrations
- python manage.py makemigrations go
- python manage.py migrate
- echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('root', 'root@srct.gmu.edu', 'root') " | python manage.py shell
- pip install tblib
Go-py2.7:
image: library/python:2.7
type: test
before_script:
- apt-get update -qy
- apt-get install -y python-dev python-pip python-pip libldap2-dev mysql-client libmysqlclient-dev python-mysqldb libsasl2-dev libjpeg-dev git
- pip install -r requirements.txt
- pip install coverage
- cp go/settings/settings.py.template go/settings/settings.py
- cp go/settings/secret.py.template go/settings/secret.py
- export SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9" | fold -w 60 | head -n1 2>/dev/null)
- sed -i go/settings/secret.py -e 's/DB_NAME.*/DB_NAME = \"go\"/'
- sed -i go/settings/secret.py -e 's/DB_USER.*/DB_USER = \"root\"/'
- sed -i go/settings/secret.py -e 's/DB_PASSWORD.*/DB_PASSWORD = \"root\"/'
- sed -i go/settings/secret.py -e 's/DB_HOST.*/DB_HOST = \"mysql\"/'
- sed -i go/settings/secret.py -e 's/SECRET_KEY.*/SECRET_KEY = \"${SECRET_KEY}\"/'
- cd go
- export DJANGO_DEBUG="True"
- python manage.py makemigrations
- python manage.py makemigrations go
- python manage.py migrate
- echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('root', 'root@srct.gmu.edu', 'root') " | python ./manage.py shell
script:
- cd ..
- coverage run --source=go ./go/manage.py test
- coverage html
- grep pc_cov htmlcov/index.html | egrep -o "[0-9]+\%" | awk '{ print "covered " $1;}'
- python manage.py test --parallel
Go-py3.6:
image: library/python:3.6
type: test
script:
- pip install coverage
- coverage run --source=go --omit=*migrations/* manage.py test --parallel
- coverage html -i && grep pc_cov htmlcov/index.html | egrep -o "[0-9]+\%" | awk '{ print "covered " $1;}'
FROM python:2.7
FROM python:3.6
ENV PYTHONUNBUFFERED 1
# HEALTHCHECK CMD curl --fail http://localhost:8000/ || exit 1
RUN mkdir /go
WORKDIR /go
ADD requirements.txt /go/
RUN apt-get update
RUN apt-get install git-all -y
RUN apt-get install python2.7-dev -y
RUN apt-get install libsasl2-dev -y
RUN apt-get install libldap2-dev -y
RUN apt-get install netcat -y
RUN apt-get install
RUN mkdir /go
WORKDIR /go
ADD requirements.txt /go/
RUN pip install -r requirements.txt
ADD . /go/
# Go
[![build status](https://git.gmu.edu/srct/go/badges/master/build.svg)](https://git.gmu.edu/srct/go/commits/master) [![coverage report](https://git.gmu.edu/srct/go/badges/master/coverage.svg)](https://git.gmu.edu/srct/go/commits/master) [![python version](https://img.shields.io/badge/python-2.7-blue.svg)]() [![Django version](https://img.shields.io/badge/Django-1.10-brightgreen.svg)]() [![SemVer version](https://img.shields.io/badge/SemVer Version-2.1.1-yellowgreen.svg)]()
[![build status](https://git.gmu.edu/srct/go/badges/master/build.svg)](https://git.gmu.edu/srct/go/commits/master) [![coverage report](https://git.gmu.edu/srct/go/badges/master/coverage.svg)](https://git.gmu.edu/srct/go/commits/master) [![python version](https://img.shields.io/badge/python-2.7,3.6-blue.svg)]() [![Django version](https://img.shields.io/badge/Django-1.10-brightgreen.svg)]() [![SemVer version](https://img.shields.io/badge/SemVer Version-2.1.1-yellowgreen.svg)]()
#### A project of [GMU SRCT](http://srct.gmu.edu).
......@@ -8,7 +8,8 @@ Go is a drop-in URL shortening service. This project aims to provide an easy to
URL branding service for institutions that wish to widely disseminate information
without unnecessarily outsourcing branding.
Go is currently a `Python 2.7` project written in the `Django` web framework, with
Go is currently a `Python 3` (with backwards compatability foor `Python 2.7` until
Django 2.0 in December 2017) project written in the `Django` web framework, with
`MySQL` as our backend database.
# Setup instructions for local development
......
......@@ -16,12 +16,10 @@ services:
- email_domain=@masonlive.gmu.edu
- cas_url=https://cas.srct.gmu.edu/
- superuser=dhaynes3
# - SECRET_KEY=much-secret
- DB_NAME=go
- DB_USER=go
- DB_PASSWORD=go
- DB_HOST=db
- PIWIK_SITE_ID=
- PIWIK_URL=
- EMAIL_HOST=
- EMAIL_PORT=
......
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division
# Django Imports
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
......
from __future__ import absolute_import, print_function
# python 3 imports ^^^
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division
# Django Imports
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib import messages
# third party imports
import requests
......@@ -38,10 +37,10 @@ def pfinfo(uname):
metadata = requests.get(url, timeout=5)
print("Retrieving information from the peoplefinder api.")
metadata.raise_for_status()
except requests.exceptions.RequestException as e:
print("Cannot resolve to peoplefinder api:", e)
except requests.exceptions.RequestException as ex:
print("Cannot resolve to peoplefinder api:", ex)
print("Returning empty user info tuple.")
return [u'', u'']
return ['', '']
else:
pfjson = metadata.json()
try:
......@@ -64,13 +63,13 @@ def pfinfo(uname):
name = pfjson['results'][0]['name']
return name
# if the name is not in peoplefinder, return empty first and last name
except IndexError:
except IndexError as ex:
print("Name not found in peoplefinder.")
return [u'',u'']
except Exception as e:
print("Unknown peoplefinder error:", e)
return ['','']
except Exception as ex:
print("Unknown peoplefinder error:", ex)
print("Returning empty user info tuple.")
return [u'', u'']
return ['', '']
"""
create a django user based off of the peoplefinder info we parsed earlier
......@@ -81,8 +80,8 @@ def create_user(tree):
try:
username = tree[0][0].text
user, user_created = User.objects.get_or_create(username=username)
except Exception as e:
print("CAS callback unsuccessful:", e)
except Exception as ex:
print("CAS callback unsuccessful:", ex)
# error handling in pfinfo function
info_name = pfinfo(username)
......@@ -111,5 +110,5 @@ def create_user(tree):
print("User object already exists.")
print("CAS callback successful.")
except Exception as e:
print("Unhandled user creation error:", e)
except Exception as ex:
print("Unhandled user creation error:", ex)
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division
# Django Imports
from django import forms
from django.core.exceptions import ValidationError
......@@ -44,7 +47,7 @@ class URLForm(forms.ModelForm):
try:
# if we're able to get a URL with the same short url
URL.objects.get(short__iexact=value)
except URL.DoesNotExist:
except URL.DoesNotExist as ex:
return
# then raise a ValidationError
raise ValidationError('Short url already exists.')
......
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division
# Django Imports
from django.core.management.base import BaseCommand
from django.utils import timezone
......
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division
# Django Imports
from django.db import models
from django.contrib.auth.models import User
......@@ -5,11 +8,11 @@ from django.utils import timezone
from django.core.cache import cache
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.encoding import python_2_unicode_compatible
# Other Imports
from hashids import Hashids # http://hashids.org/python/
import string
# http://hashids.org/python/
from hashids import Hashids
# generate the salt and initialize Hashids
hashids = Hashids(salt="srct.gmu.edu", alphabet=(string.ascii_lowercase + string.digits))
......@@ -18,37 +21,38 @@ hashids = Hashids(salt="srct.gmu.edu", alphabet=(string.ascii_lowercase + string
This is simply a wrapper model for the user object which, if an object
exists, indicates that that user is registered.
"""
@python_2_unicode_compatible
class RegisteredUser(models.Model):
# Is this User Blocked?
blocked = models.BooleanField(default=False)
blocked = models.BooleanField(default = False)
# Let's associate a User to this RegisteredUser
user = models.OneToOneField(User)
# What is your name?
full_name = models.CharField(
blank=False,
max_length=100,
blank = False,
max_length = 100,
)
# What organization are you associated with?
organization = models.CharField(
blank=False,
max_length=100,
blank = False,
max_length = 100,
)
# Why do you want to use Go?
description = models.TextField(blank=True)
description = models.TextField(blank = True)
# Have you filled out the registration form?
registered = models.BooleanField(default=False)
registered = models.BooleanField(default = False)
# Are you approved to use Go?
approved = models.BooleanField(default=False)
approved = models.BooleanField(default = False)
# print(RegisteredUser)
def __unicode__(self):
def __str__(self):
return '<Registered User: %s - Approval Status: %s>' % (self.user, self.approved)
......@@ -65,30 +69,31 @@ def handle_regUser_creation(sender, instance, created, **kwargs):
owner, target url, short identifier, click counter, and expiration
date.
"""
@python_2_unicode_compatible
class URL(models.Model):
# Who is the owner of this Go link
owner = models.ForeignKey(RegisteredUser)
# When was this link created?
date_created = models.DateTimeField(default=timezone.now)
date_created = models.DateTimeField(default = timezone.now)
# What is the target URL for this Go link
target = models.URLField(max_length=1000)
target = models.URLField(max_length = 1000)
# What is the actual go link (short url) for this URL
short = models.SlugField(max_length=20, primary_key=True)
short = models.SlugField(max_length = 20, primary_key = True)
# how many people have visited this Go link
clicks = models.IntegerField(default=0)
clicks = models.IntegerField(default = 0)
# how many people have visited this Go link through the qr code
qrclicks = models.IntegerField(default=0)
qrclicks = models.IntegerField(default = 0)
# how many people have visited the go link through social media
socialclicks = models.IntegerField(default=0)
socialclicks = models.IntegerField(default = 0)
# does this Go link expire on a certain date
expires = models.DateTimeField(blank=True, null=True)
expires = models.DateTimeField(blank = True, null = True)
# print(URL)
def __unicode__(self):
def __str__(self):
return '<%s : %s>' % (self.owner.user, self.target)
# metadata for URL's
......@@ -107,9 +112,9 @@ class URL(models.Model):
tries = 1
while tries < 100:
try:
URL.objects.get(short__iexact=short)
URL.objects.get(short__iexact = short)
tries += 1
cache.incr("hashids_counter")
except URL.DoesNotExist:
except URL.DoesNotExist as ex:
return short
return None
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division
# Django Imports
from django import template
......@@ -19,7 +22,7 @@ def is_registered(user):
registered = RegisteredUser.objects.get(username=user.username)
# if it works then the user is registered
return True
except RegisteredUser.DoesNotExist:
except RegisteredUser.DoesNotExist as ex:
# if they don't exist then they are not registered
return False
......@@ -33,6 +36,6 @@ def is_approved(user):
registered = RegisteredUser.objects.get(username=user.username)
# if they exist, return whether or not they are approved (boolean)
return registered.approved
except RegisteredUser.DoesNotExist:
except RegisteredUser.DoesNotExist as ex:
# if they don't exist then they are not approved
return False
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division
# Django Imports
from django.test import TestCase
from django.contrib.auth.models import User
# App Imports
from go.models import URL, RegisteredUser
......@@ -7,10 +11,23 @@ from go.models import URL, RegisteredUser
"""
Test cases for the URL Model
"""
class URLTestCase(TestCase):
class URLTest(TestCase):
"""
Default test case, does not actually test anything
"""
def test_Django_Test(self):
self.assertEqual("Hello World!", "Hello World!")
"""
Test cases for the RegisteredUser Model
"""
class RegisteredUserTest(TestCase):
def setUp(self):
User.objects.create(username='dhaynes', password='password')
def test_RegisteredUserCreation(self):
getUser = User.objects.get(username='dhaynes')
getRegisteredUser = RegisteredUser.objects.get(user=getUser)
self.assertTrue(getRegisteredUser)
# Future Imports
from __future__ import unicode_literals, absolute_import, print_function, division
# Django Imports
from django.conf import settings
from django.http import HttpResponseServerError # Http404
......@@ -38,21 +41,21 @@ def index(request):
# Initialize a URL form
url_form = URLForm(host=request.META.get('HTTP_HOST')) # unbound form
url_form = URLForm(host = request.META.get('HTTP_HOST')) # unbound form
# If a POST request is received, then the user has submitted a form and it's
# time to parse the form and create a new URL object
if request.method == 'POST':
# Now we initialize the form again but this time we have the POST
# request
url_form = URLForm(request.POST, host=request.META.get('HTTP_HOST'))
url_form = URLForm(request.POST, host = request.META.get('HTTP_HOST'))
# Django will check the form to make sure it's valid
if url_form.is_valid():
# We don't commit the url object yet because we need to add its
# owner, and parse its date field.
url = url_form.save(commit=False)
url = url_form.save(commit = False)
url.owner = request.user.registereduser
# If the user entered a short url, it's already been validated,
......@@ -82,11 +85,11 @@ def index(request):
# Determine what the expiration date is
if expires == URLForm.DAY:
url.expires = timezone.now() + timedelta(days=1)
url.expires = timezone.now() + timedelta(days = 1)
elif expires == URLForm.WEEK:
url.expires = timezone.now() + timedelta(weeks=1)
url.expires = timezone.now() + timedelta(weeks = 1)
elif expires == URLForm.MONTH:
url.expires = timezone.now() + timedelta(weeks=3)
url.expires = timezone.now() + timedelta(weeks = 3)
elif expires == URLForm.CUSTOM:
url.expires = url_form.cleaned_data.get('expires_custom')
else:
......@@ -115,7 +118,7 @@ def view(request, short):
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"
# Get the URL that is being requested
url = get_object_or_404(URL, short__iexact=short)
url = get_object_or_404(URL, short__iexact = short)
# Render view.html passing the specified URL and Domain to the template
return render(request, 'view.html', {
......@@ -139,7 +142,7 @@ def my_links(request):
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"
# Grab a list of all the URL's that are currently owned by the user
urls = URL.objects.filter(owner=request.user.registereduser)
urls = URL.objects.filter(owner = request.user.registereduser)
# Render my_links.html passing the list of URL's and Domain to the template
return render(request, 'my_links.html', {
......@@ -161,7 +164,7 @@ def delete(request, short):
return render(request, 'not_registered.html')
# Get the URL that is going to be deleted
url = get_object_or_404(URL, short__iexact=short)
url = get_object_or_404(URL, short__iexact = short)
# If the RegisteredUser is the owner of the URL
if url.owner == request.user.registereduser:
......@@ -198,8 +201,8 @@ def signup(request):
if request.method == 'POST':
# Now we initialize the form again but this time we have the POST
# request
signup_form = SignupForm(request, request.POST, instance=request.user.registereduser,
initial={'full_name': request.user.first_name + " " + request.user.last_name})
signup_form = SignupForm(request, request.POST, instance = request.user.registereduser,
initial = {'full_name': request.user.first_name + " " + request.user.last_name})
# set the readonly flag again for good measure
signup_form.fields['full_name'].widget.attrs['readonly'] = 'readonly'
......@@ -232,7 +235,7 @@ def signup(request):
######################
settings.EMAIL_FROM,
[settings.EMAIL_TO],
reply_to=[user_mail]
reply_to = [user_mail]
).send()
# Confirmation email sent to Users
send_mail(
......@@ -272,7 +275,7 @@ def redirection(request, short):
domain = "%s://%s" % (request.scheme, request.META.get('HTTP_HOST')) + "/"
# Get the URL object that relates to the requested Go link
url = get_object_or_404(URL, short__iexact=short)
url = get_object_or_404(URL, short__iexact = short)
# Increment our clicks by one
url.clicks += 1
......@@ -296,11 +299,11 @@ def redirection(request, short):
Decorator function for views that checks that the user is logged in and is
a staff member, displaying the login page if necessary.
"""
def staff_member_required(view_func, redirect_field_name=REDIRECT_FIELD_NAME, login_url='/'):
def staff_member_required(view_func, redirect_field_name = REDIRECT_FIELD_NAME, login_url = '/'):
return user_passes_test(
lambda u: u.is_active and u.is_staff,
login_url=login_url,
redirect_field_name=redirect_field_name
login_url = login_url,
redirect_field_name = redirect_field_name
)(view_func)
"""
......@@ -317,7 +320,7 @@ def useradmin(request):
# If we're approving users
if '_approve' in request.POST:
for name in userlist:
toApprove = RegisteredUser.objects.get(user__username__exact=name)
toApprove = RegisteredUser.objects.get(user__username__exact = name)
toApprove.approved = True
toApprove.save()
......@@ -341,7 +344,7 @@ def useradmin(request):
# If we're denying users
elif '_deny' in request.POST:
for name in userlist:
toDeny = RegisteredUser.objects.get(user__username__exact=name)
toDeny = RegisteredUser.objects.get(user__username__exact = name)
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = toDeny.user.username + settings.EMAIL_DOMAIN
# Send an email letting them know they are denied
......@@ -366,7 +369,7 @@ def useradmin(request):
# If we're blocking users
elif '_block' in request.POST:
for name in userlist:
toBlock = RegisteredUser.objects.get(user__username__exact=name)
toBlock = RegisteredUser.objects.get(user__username__exact = name)
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = toBlock.user.username + settings.EMAIL_DOMAIN
send_mail(
......@@ -392,7 +395,7 @@ def useradmin(request):
# If we're un-blocking users
elif '_unblock' in request.POST:
for name in userlist:
toUnBlock = RegisteredUser.objects.get(user__username__exact=name)
toUnBlock = RegisteredUser.objects.get(user__username__exact = name)
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = toUnBlock.user.username + settings.EMAIL_DOMAIN
send_mail(
......@@ -417,7 +420,7 @@ def useradmin(request):
# If we're removing existing users
elif '_remove' in request.POST:
for name in userlist:
toRemove = RegisteredUser.objects.get(user__username__exact=name)
toRemove = RegisteredUser.objects.get(user__username__exact = name)
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = toRemove.user.username + settings.EMAIL_DOMAIN
send_mail(
......@@ -448,5 +451,4 @@ def useradmin(request):
'need_approval': need_approval,
'current_users': current_users,
'blocked_users': blocked_users
},
)
})
#Create a new file 'settings.py' and copy these contents into that file
import secret
from . import secret
import os
AUTH_MODE = "CAS"
......
# Create a new file 'settings.py' and copy these contents into that file
import secret
from . import secret
import os
import sys
......
......@@ -9,24 +9,46 @@
- hosts: all
tasks:
- name: install go packages
- name: Install Python 3.4.3 and related packages
apt:
name: "{{ item }}"
update_cache: yes
state: present
with_items:
- python3
- python3-dev
- python3-pip
- name: Upgrade pip
pip:
name: pip
state: latest
- name: Install virtualenv
pip:
name: virtualenv
- name: Create the virtualenv
command: virtualenv -p python3 /vagrant/venv
- name: install site packages to virtual env
pip:
requirements: "{{ django['requirements_path'] }}"
virtualenv: "{{ django['venv_path'] }}"
virtualenv_python: /usr/bin/python3
- name: Install go packages
apt:
name: "{{ item }}"
state: latest
update_cache: yes
with_items:
- python
- python-dev
- python-virtualenv
- python-pip
- git
- libldap2-dev
- libsasl2-dev
- mysql-server
- mysql-client
- libmysqlclient-dev
- python-mysqldb
- libsasl2-dev
- libjpeg-dev
- name: create mysql user
mysql_user:
......@@ -55,12 +77,6 @@
priv: test_{{ mysql['db'] }}.*:ALL
append_privs: yes
- name: install site packages to virtual env
pip:
requirements: "{{ django['requirements_path'] }}"
virtualenv: "{{ django['venv_path'] }}"
virtualenv_python: python2.7
- name: install django settings.py
template:
src: templates/settings.py.j2
......@@ -94,4 +110,4 @@
ignore_errors: true
- name: start django runserver (access via localhost:8000)
command: screen -dmS django bash -c "echo Starting on port {{ nginx['port'] }}; cd /vagrant/go; source ../venv/bin/activate; python manage.py runserver 0.0.0.0:8000;"
command: screen -dmS django bash -c "echo Starting on port {{ nginx['port'] }}; cd /vagrant/go; source ../venv/bin/activate; python3 manage.py runserver 0.0.0.0:8000;"
# Create a new file 'settings.py' and copy these contents into that file
import secret
from . import secret
import os
AUTH_MODE = "CAS"
......
......@@ -8,12 +8,11 @@ git+https://github.com/bruno207/django-bootstrap3-datetimepicker.git
gunicorn==19.6.0
hashids==1.1.0
mccabe==0.5.2
MySQL-python==1.2.5
mysqlclient
pep8==1.7.0
Pillow==3.3.0
pyflakes==1.2.3
python-ldap==2.4.27
pyldap
requests==2.11.0
simplejson==3.8.2
six==1.10.0
wheel==0.29.0
# export SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9" | fold -w 60 | head -n1 2>/dev/null)
until nc -z db 3306; do
echo "waiting for database to start..."
sleep 1
done
cp go/settings/settings.docker.py.template go/settings/settings.py
cp go/settings/secret.docker.py.template go/settings/secret.py
export SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9" | fold -w 60 | head -n1 2>/dev/null)
python go/manage.py flush --no-input
python go/manage.py makemigrations
python go/manage.py makemigrations go
python go/manage.py flush --no-input
python go/manage.py makemigrations
python go/manage.py makemigrations go
python go/manage.py migrate
python go/manage.py createsuperuser --noinput --username=$superuser --email=$superuser$email_domain
python go/manage.py runserver 0.0.0.0:8000
\ No newline at end of file
python go/manage.py runserver 0.0.0.0:8000
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