Commit adcc5c7d authored by David Haynes's avatar David Haynes 🙆

Merge branch '2.2-dev' into 'master'

2.2 Release

Closes #130, #68, #140, #143, #113, #114, #111, #55, #54, #76, #122, #43, #119, #108, #39, #124, #131, and #57

See merge request !100
parents ddce4b23 57eeb7c1
Pipeline #1346 failed with stages
in 2 minutes and 11 seconds
......@@ -14,3 +14,6 @@ venv
.coverage
htmlcov/
.idea
__pycache__/
.vscode
go/sourceme.sh
image: ubuntu:14.04
services:
- mysql:latest
types:
stages:
- test
- lint
variables:
MYSQL_DATABASE: go
MYSQL_ROOT_PASSWORD: root
test_Go:
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
before_script:
- apt-get update -qy
- apt-get install -y mysql-client libmysqlclient-dev python-mysqldb redis-server
- pip install -r requirements/ci.txt
- nohup redis-server &
- cd go/
- export GO_SECRET_KEY=$(dd if=/dev/urandom count=100 | tr -dc "A-Za-z0-9" | fold -w 60 | head -n1 2>/dev/null)
- export GO_DB_NAME="go"
- export GO_DB_USER="root"
- export GO_DB_PASSWORD="root"
- export GO_DB_HOST="mysql"
- export GO_DB_PORT=3306
- export GO_ALLOWED_HOSTS="*"
- export GO_EMAIL_DOMAIN="@masonlive.gmu.edu"
- export GO_CAS_URL="https://cas.srct.gmu.edu/"
- export GO_EMAIL_HOST=
- export GO_EMAIL_PORT=
- export GO_EMAIL_HOST_USER=
- export GO_EMAIL_HOST_PASSWORD=
- export GO_EMAIL_FROM=
- export GO_EMAIL_TO=
- 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
Go-py2.7:
image: library/python:2.7
stage: test
script:
- python manage.py test
Go-py3.4:
image: library/python:3.4
stage: test
script:
- python manage.py test
Go-py3.5:
image: library/python:3.5
stage: test
script:
- python manage.py test
Go-py3.6:
image: library/python:3.6
stage: test
script:
- if pip list --outdated | grep "Latest" | wc -l > 0; then pip list --outdated && exit 1; else exit 0; fi
- coverage run --source=go --omit=*migrations/*,*admin.py,*manage.py,*wsgi.py,*settings.py,*secret.py,*__init__.py,*.pyc,*templates/*,*static/* manage.py test
- coverage html -i && grep pc_cov htmlcov/index.html | egrep -o "[0-9]+\%" | awk '{ print "covered " $1;}'
Go-flake8:
image: library/python:3.5
stage: lint
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;}'
- pip install flake8
- flake8 go/ --statistics --exit-zero
\ No newline at end of file
## Summary
# Summary
Here you should include two to three sentences explaining the thought process
about the current issue. Maybe a picture? Some details that could best help someone,
especially someone new, understand the goal of the issue and how they should best
approach the problem.
## Helpful Links
# Helpful Links
Here you should include a bullet point list of links to documentation, stack overflow,
whatever, that could help guide someone on what it is they are trying to do.
Essentially, a list of links to point them in the right direction.
# Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
All notable changes to this project will be documented in this file. This
project adheres (to the best of our ability) to [Semantic Versioning](http://semver.org/).
## [2.2.0] - 2017-26-04
### Added
- LibreJS Compatible
- Current version of Go displayed in footer
- Warning model when interacting with /useradmin
- Can block users
- Blocked users may log in but cannot interact with the site
- Blocked users may be managed from /useradmin
- " " may be unblocked
- "New Link" button added to my_links view
- Python 3.4-3.6 support
- Django 1.11 upgrade
- Unit tests for all major Django components
- models
- views
- URLs
- forms
- random python files (cas_callbacks)
- Search bar in /useradmin
- Windows instructions in README
- Flake8 run on CI time
- Help text on the signup form
- Twitter card metadata
- Facebook/Opengraph metadata
### Changed
- Design consistency across error pages
- Project requirements follow two scoops style
- Go links cannot be rendered to itself
- Settings files inherit from one another
- Enviornment variables used instead of .template
- secret.py dropped
- local settings and prod settings
- Cache static pages
- Rate limit POST requests
- Homepage now defaults to my_links
- Imports are now explicit
- IE Compatible
- CSRF Protection for /delete
### Fixed
- Error pages are rendered correctly now
- HTTPS Go links displayed everywhere
- All content is served with HTTPS
- Dependecies are checked at CI time for updates
- Standardization of fonts
### Removed
- .template files
- secret.py
- LDAP support
## [2.1.1] - 2017-01-07
### Added
......
# On Contributing
## git
First take a look at [github flow](https://guides.github.com/introduction/flow/)
as this page gives a good starting point on understanding how to work with `git`
in an open source repo.
**Note:**
You will need to be a member before making any contributions. Join the slack #go channel and ask nicely.
### Branches
Each branch off of the development branch serves one and only one purpose: to
......@@ -73,7 +78,13 @@ Example commit description:
### Merging to the current development branch
Once you've finished work in a branch open a [pull request](https://git.gmu.edu/srct/go/merge_requests/new)
Once you've finished work in a branch you will need to push your commits to gitlab.
git push origin ##-branchname
`Origin` is gitlab.
Open a [merge request](https://git.gmu.edu/srct/go/merge_requests/new)
to start the process of getting your code into the repo. Your code wil be reviewed
by another member before being merged. Your code must pass our tests and include
in the description:
......
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 pip install -r requirements.txt
RUN mkdir /go
WORKDIR /go
ADD /requirements/ /go/
RUN pip install -r base.txt
ADD . /go/
Copyright 2016 George Mason Student-Run Computing and Technology
Copyright 2017 George Mason Student-Run Computing and Technology
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
......
# 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).
#### A project of [GMU SRCT](https://srct.gmu.edu).
Go is a drop-in URL shortening service. This project aims to provide an easy to use
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
......@@ -66,16 +67,36 @@ Finally we can install git with:
### On Windows
Instructions on how to setup git on Windows goes here.
We recommend that if you are on Windows 10 AE (Anniversary Edition) or above to make use of the
Windows Subsystem for Linux (WSL). The following link should get you up and running:
[https://msdn.microsoft.com/en-us/commandline/wsl/install_guide](https://msdn.microsoft.com/en-us/commandline/wsl/install_guide)
#### Contributing with Windows
After that is setup, you should be able to follow the Linux instructions for _manual setup_ to
contribute to the project.
If you are not on Windows 10 or would rather prefer to not use the WSL you may download Git for
Windows here:
[https://git-scm.com/download/win](https://git-scm.com/download/win)
You'll want to follow the Vagrant setup method as it is designed to run on all platforms
including Windows.
I have also successfully ran the project with Docker, though you need
access to Hyper-V which is only available on "Professional" versions of Windows.
## 2) Clone the Go codebase.
Now, we're going to clone down a copy of the Go codebase from [git.gmu.edu](http://git.gmu.edu/srct/go),
Now, we're going to clone down a copy of the Go codebase from [git.gmu.edu](https://git.gmu.edu/srct/go),
the SRCT code respository with SSH.
**a)** Configure your ssh keys by following the directions at:
[git.gmu.edu/help/ssh/README](http://git.gmu.edu/help/ssh/README).
[git.gmu.edu/help/ssh/README](https://git.gmu.edu/help/ssh/README).
**b)** Now, on your computer, navigate to the directory in which you want to download the project (ie. perhaps one called `development/SRCT`), and run
......@@ -176,6 +197,40 @@ https://git.gmu.edu/srct/go/wikis/manual-setup
# Some words about contributing to Go.
## Testing
You are _very strongly_ encouraged to write test cases where applicible for
code that you contribute to the repo. This is not a rule at the moment but rather
a strong suggestion. It's good practice for corporate land and will also ensure
your code works. Additionally, there are quite a few example ones to look at in
the repo and on Google.
### Running Unit Tests
Unit tests are run on every commit sent to gitlab though that can be a pain to
rely on. Here's how to run them locally:
#### Docker
Docker is not supported currently for running unit tests. If you're able to get
it set up, open a merge request and I'll merge it in.
#### Vagrant
vagrant up
vagrant ssh
cd /vagrant
source venv/bin/activate
cd go
source sourceme.sh
python manage.py test
#### Manual Setup
Assuming you are within your virtualenv:
python manage.py test
## CONTRIBUTING.md
This document goes into detail about how to contribute to the repo, plus some
......@@ -197,6 +252,7 @@ Once in the admin page go to "registered users", and create a new registered use
use the same username and Full Name as your main account and select "approved" in the bottom row.
## Coding style
You should adhere to the style of the repo code. Consistancy is key! PEP8 guidelines
are strongly reccomended but not enforced at the time. Please comment your code,
I will not accept commits that contain uncommented code.
......
......@@ -95,7 +95,7 @@ Vagrant.configure(2) do |config|
venv_path: "/vagrant/venv",
cas_url: "https://cas.srct.gmu.edu/",
app_path: "/vagrant/go",
settings_path: "/vagrant/go/settings",
sourceme_dest: "/vagrant/go/",
superuser: "dhaynes3"
}
}
......
......@@ -11,22 +11,22 @@ services:
depends_on:
- db
environment:
- debug=True
- host=*
- email_domain=@masonlive.gmu.edu
- cas_url=https://cas.srct.gmu.edu/
- GO_ALLOWED_HOSTS=*
- GO_EMAIL_DOMAIN=@masonlive.gmu.edu
- GO_CAS_URL=https://cas.srct.gmu.edu/
- GO_DB_NAME=go
- GO_DB_USER=go
- GO_DB_PASSWORD=go
- GO_DB_HOST=db
- GO_DB_PORT=3306
- GO_EMAIL_HOST=
- GO_EMAIL_PORT=
- GO_EMAIL_HOST_USER=
- GO_EMAIL_HOST_PASSWORD=
- GO_EMAIL_FROM=
- GO_EMAIL_TO=
- 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=
- EMAIL_HOST_USER=
- EMAIL_HOST_PASSWORD=
db:
image: mysql
environment:
......
"""
go/admin.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
# Django Imports
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User
# App Imports
from go.models import URL, RegisteredUser
from .models import URL, RegisteredUser
"""
Define what attributes display in the URL Admin
"""
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)
"""
Define an inline admin descriptor for User model
"""
class RegisteredUserInline(admin.StackedInline):
"""
Define an inline admin descriptor for User model
"""
model = RegisteredUser
can_delete = False
"""
Define a new User admin
"""
class UserAdmin(UserAdmin):
"""
Define a new User admin
"""
# see above class that we defined
inlines = (RegisteredUserInline, )
......
from __future__ import absolute_import, print_function
# python 3 imports ^^^
"""
go/cas_callbacks.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
# Django Imports
from django.contrib.auth.models import User
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import User
# third party imports
# Other Imports
import requests
"""
parse what peoplefinder sends back to us and make a list out of it
"""
def pfparse(pf_name_result):
"""
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(',')
# there's random whitespace with the first name
......@@ -28,20 +33,21 @@ def pfparse(pf_name_result):
new_name_list = [first_name, name_list[0]]
return new_name_list
"""
get information from peoplefinder
"""
def pfinfo(uname):
"""
Get information from peoplefinder
"""
base_url = settings.PF_URL
url = base_url + "basic/all/" + str(uname)
try:
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,25 +70,25 @@ 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
"""
def create_user(tree):
"""
Create a django user based off of the peoplefinder info we parsed earlier
"""
print("Parsing CAS information.")
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 +117,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)
This diff is collapsed.
"""
go/commands/expirelinks.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
# Django Imports
from django.core.management.base import BaseCommand
from django.utils import timezone
......@@ -5,13 +13,21 @@ from django.utils import timezone
# App Imports
from go.models import URL
# Define a new custom django-admin command
class Command(BaseCommand):
"""
Define a new custom django-admin command to remove expired links from the
database
"""
# Define help text for this command
help = 'Removes expired links from the database'
# The handle function handles the main component of the django-admin command
def handle(self, *args, **options):
"""
The handle function handles the main component of the django-admin
command.
"""
# Loop through a list of all URL objects that have expired
# (expires field is less than or equal to today's date)
for toexpire in URL.objects.filter(expires__lte=timezone.now()):
......
"""
go/commands/test_expirelinks.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
# Python stdlib Imports
from datetime import timedelta
# Django Imports
from django.contrib.auth.models import User
from django.core.management import call_command
from django.test import TestCase
from django.utils import timezone
# App Imports
from go.models import URL, RegisteredUser
class ExpireLinksTest(TestCase):
"""
Test cases for the functions in expirelinks
"""
def setUp(self):
"""
Set up any variables such as dummy objects that will be utilised in
testing methods
"""
# Setup a blank URL object with an owner
User.objects.create(username='dhaynes', password='password')
get_user = User.objects.get(username='dhaynes')
get_registered_user = RegisteredUser.objects.get(user=get_user)
URL.objects.create(owner=get_registered_user, short='test')
URL.objects.create(owner=get_registered_user, short='test-2')
# Get some dates
yesterday = timezone.now() - timedelta(days=1)
tomorrow = timezone.now() + timedelta(days=1)
# Get the URL to apply it to
current_url = URL.objects.get(short='test')
second_url = URL.objects.get(short='test-2')
# Apply the dates
current_url.expires = yesterday
second_url.expires = tomorrow
current_url.save()
second_url.save()
def test_expirelinks(self):
"""
Test that the expirelinks django admin command functions as intentioned.
"""
call_command('expirelinks')
self.assertTrue(len(URL.objects.all()) == 1)
"""
go/models.py
"""
# Future Imports
from __future__ import (absolute_import, division, print_function,
unicode_literals)
# Python stdlib Imports
import string
# Django Imports
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.core.cache import cache
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.utils.encoding import python_2_unicode_compatible
# Other Imports
import string
# http://hashids.org/python/
from hashids import Hashids
from hashids import Hashids # http://hashids.org/python/
# generate the salt and initialize Hashids
hashids = Hashids(salt="srct.gmu.edu", alphabet=(string.ascii_lowercase + string.digits))
HASHIDS = Hashids(
salt="srct.gmu.edu", alphabet=(string.ascii_lowercase + string.digits)
)
"""
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)
"""
This is simply a wrapper model for the user object which, if an object
exists, indicates that that user is registered.
"""
# Let's associate a User to this RegisteredUser
user = models.OneToOneField(User)
......@@ -47,25 +57,37 @@ class RegisteredUser(models.Model):
# Are you approved to use Go?
approved = models.BooleanField(default=False)
# print(RegisteredUser)
def __unicode__(self):
return '<Registered User: %s - Approval Status: %s>' % (self.user, self.approved)
# Is this User Blocked?
blocked = models.BooleanField(default=False)
def __str__(self):
"""
str(RegisteredUser)
"""
return '<Registered User: %s - Approval Status: %s>' % (
self.user, self.approved
)
# When a post_save is called on a User object (and it is newly created), this is