Commit ac819e83 authored by Zac Wood's avatar Zac Wood

Merge branch 'v2.3' into 199-remove-signup

parents 35945f04 249aaa01
Pipeline #4811 passed with stages
in 1 minute and 58 seconds
GO_ENV=development
GO_ALLOWED_HOSTS=*
GO_EMAIL_DOMAIN=@masonlive.gmu.edu
GO_CAS_URL=https://login.gmu.edu/
GO_DB_NAME=go
GO_DB_USER=go
GO_DB_PASSWORD=go
GO_DB_HOST=localhost
GO_DB_PORT=3306
GO_EMAIL_HOST=
GO_EMAIL_PORT=
GO_EMAIL_HOST_USER=
GO_EMAIL_HOST_PASSWORD=
GO_EMAIL_FROM=
GO_EMAIL_TO=
GO_SECRET_KEY=spookyspecret
superuser=zwood2
FROM python:3.6
FROM python:3.6.9
ENV PYTHONUNBUFFERED 1
RUN apt-get update
RUN apt-get install netcat python3-dev default-libmysqlclient-dev -y
# downgrade openssl security for login.gmu.edu compatibility
RUN sed -i -e 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf
RUN mkdir /go
WORKDIR /go
ADD /requirements/ /go/
......
FROM python:3.7
ENV PYTHONUNBUFFERED 1
RUN apt-get update
RUN apt-get install netcat python3-dev default-libmysqlclient-dev -y
RUN mkdir /go
WORKDIR /go
ADD /requirements/ /go/
RUN pip install -r prod.txt
ADD . /go/
RUN mkdir /static
# Go 2
[![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.4+-blue.svg)]() [![Django version](https://img.shields.io/badge/Django-1.11-brightgreen.svg)]() [![SemVer version](https://img.shields.io/badge/SemVer Version-2.2.3-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-3.4+-blue.svg)]() [![Django version](https://img.shields.io/badge/Django-2.2-brightgreen.svg)]()
#### A project of [GMU SRCT](https://srct.gmu.edu).
......@@ -65,29 +65,13 @@ Finally we can install git with:
brew install git
### On Windows
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:
### Windows
[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:
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.
......@@ -145,49 +129,20 @@ Cons:
interact with the database.
There are instructions on how to setup/develop with Docker at the [docker-configuration page in the Go project wiki](https://git.gmu.edu/srct/go/wikis/docker-configuration).
### Vagrant + Ansible
Vagrant boots up a full virtual machine (VM) through VirtualBox that then runs Go. A
script written with Ansible will then run on that VM to automate the setup process
for you. It is similar in a way to running Go on a legitimate server.
Pros:
- Very similar to a production environment
- Can use `vagrant ssh` to "ssh" into the VM to debug things such as the
database.
- Relatively straightforward and easy setup.
- One Command.
- Can easily destroy and rebuild the VM.
- Loads in changes to code on the fly.
- Fast-ish (Initial provision takes a bit).
Cons:
- Heavier on resources.
- It's literally a full VM.
- Occasional issues/hiccups.
- Documented fixes are in the wiki.
There are instructions on how to setup with Vagrant at the [vagrant-configuration
page in the Go project wiki](https://git.gmu.edu/srct/go/wikis/vagrant-configuration).
Additionally, there is documentation about developing with Vagrant at
the [vagrant-usage page in the Go project wiki](https://git.gmu.edu/srct/go/wikis/vagrant-usage).
**NOTE**: On Windows, Docker only works if you have Windows 10 Eductaion or Pro,
as it requires HyperV.
### Manual Setup
Manual setup (or: the old fashioned way) is where you install all dependecies on
your system and run Go as a local server with Django. Granted you are technically
doing that with Vagrant and Docker except those platforms automate the steps that
doing that with Docker except those platforms automate the steps that
are laid out in this section.
Pros:
- Experience setting up a Django project for local development
Cons:
- Greater potential for things to go wrong
- Way more steps
Head to:
......@@ -215,15 +170,6 @@ rely on. Here's how to run them locally:
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
......@@ -241,16 +187,6 @@ opinions about using `git`.
There is a template for issue descriptions located on the new issue page. I will
close issues with poor descriptions or who do not follow the standard.
## Authentication
The authentication service used for Go is CAS. In local development however we
utilize a test server. You can log in with just your CAS username to simulate logging
in. By default, the Django superuser is set to `dhaynes3`.
In order to approve yourself to be an 'approved user' you must navigate to 127.0.0.1:8000/admin and log in.
Once in the admin page go to "registered users", and create a new registered user in the top right. Be sure to
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
......
......@@ -5,12 +5,13 @@ services:
restart: always
ports:
- '8000:8000'
command: /bin/bash ./startup.sh -python go/manage.py runserver 0.0.0.0:8000
command: /bin/bash ./startup.sh
volumes:
- .:/go
depends_on:
- db
environment:
- DOCKER=true
- GO_ENV=development
- GO_ALLOWED_HOSTS=*
- GO_EMAIL_DOMAIN=@masonlive.gmu.edu
......
......@@ -271,7 +271,7 @@ class EditForm(URLForm):
HTML("""
<br />"""),
StrictButton('Submit Changes', css_class="btn btn-primary btn-md col-md-4", type='submit')))
class Meta(URLForm.Meta):
# what attributes are included
fields = URLForm.Meta.fields
......@@ -98,7 +98,7 @@
<!-- define the table of current users -->
<div class="row">
<div class="col-md-12">
<input class="inputfilter" type="text" id="currentInput"
<input class="inputfilter" type="text" id="currentInput"
placeholder="Search Usernames or Full Names">
<h3>Current Users</h3>
<form method="post" action="useradmin">
......
......@@ -9,6 +9,8 @@ SRCT Go &bull; My Links
<!-- define the content block for the page -->
{% block content %}
{% load staticfiles %}
<!-- define the page header div -->
<div class="page-header" id="banner">
<div class="row">
......
......@@ -5,7 +5,7 @@
<!DOCTYPE html>
<!-- Start the HTML page for Go -->
<html>
<html lang="en-us">
<!-- load in our header content for every page -->
<head>
<!-- load in the title block defined on each html page -->
......@@ -51,6 +51,22 @@
<script src="{% static "js/jquery.min.js" %}"></script>
<script src="{% static "js/bootstrap.min.js" %}"></script>
<script src="{% static "js/clipboard.min.js" %}"></script>
<!-- Matomo -->
<script type="text/javascript">
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//matomo.srct.gmu.edu/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '4']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
</head>
<!-- Load in the body of a Go page -->
......
......@@ -2,7 +2,7 @@
{% load go_extras %}
<!-- define our navbar -->
<div class="navbar navbar-default">
<nav class="navbar navbar-default">
<!-- define the main "header" of the navbar -->
<div class="navbar-header" style="margin-left:5%;">
<!-- responsive hamburger menu -->
......@@ -62,4 +62,4 @@
{% endif %}
</ul>
</div>
</div>
</nav>
......@@ -66,32 +66,35 @@ def new_link(request):
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'))
return _new_link_post(request)
# Django will check the form to make sure it's valid
if url_form.is_valid():
# Call our post method to assemble our new URL object
res = post(request, url_form)
# Render index.html passing the form to the template
return render(request, 'core/new_link.html', {
'form': url_form,
})
# If there is a 500 error returned, handle it
if res == 500:
return HttpResponseServerError(render(request, '500.html'))
def _new_link_post(request):
"""
This view handles when a POST is received from the new_link form.
"""
url_form = URLForm(request.POST, host=request.META.get('HTTP_HOST'))
# Redirect to the shiny new URL
return redirect('view', res.short)
if not url_form.is_valid():
# there is an error, redisplay the form with the validation errors
# Render index.html passing the form to the template
return render(request, 'core/new_link.html', {
'form': url_form,
})
# Else, there is an error, redisplay the form with the validation errors
else:
# Render index.html passing the form to the template
return render(request, 'core/new_link.html', {
'form': url_form,
})
# Call our post method to assemble our new URL object
res = post(request, url_form)
# If there is a 500 error returned, handle it
if res == 500:
return HttpResponseServerError(render(request, '500.html'))
# Render index.html passing the form to the template
return render(request, 'core/new_link.html', {
'form': url_form,
})
# Redirect to the shiny new URL
return redirect('view', res.short)
@login_required
def my_links(request):
......@@ -133,8 +136,8 @@ def post(request, url_form):
if random_short is None:
return 500
else:
url.short = random_short
url.short = random_short
# Grab the expiration field value. It's currently an unsable
# string value, so we need to parse it into a datetime object
......@@ -193,107 +196,104 @@ def edit(request, 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:
# If a POST request is received, then the user has submitted a form and it's
# time to parse the form and edit that URL object
if request.method == 'POST':
# Now we initialize the form again but this time we have the POST
# request
url_form = EditForm(request.POST, host=request.META.get('HTTP_HOST'))
# Make a copy of the old URL
copy = url
# Remove the old one
url.delete()
# Django will check the form to make sure it's valid
if url_form.is_valid():
# If the short changed then we need to create a new object and
# migrate some data over
if url_form.cleaned_data.get('short').strip() != copy.short:
# Parse the form and create a new URL object
res = post(request, url_form)
# If there is a 500 error returned, handle it
if res == 500:
return HttpResponseServerError(render(request, '500.html'))
# We can procede with the editing process
else:
# Migrate clicks data
res.clicks = copy.clicks
res.qrclicks = copy.qrclicks
res.socialclicks = copy.socialclicks
# Save the new URL
res.save()
# Redirect to the shiny new *edited URL
return redirect('view', res.short)
# The short was not edited and thus, we can directly edit the url
else:
if url_form.cleaned_data.get('target').strip() != copy.target:
copy.target = url_form.cleaned_data.get('target').strip()
copy.save()
# Grab the expiration field value. It's currently an unsable
# string value, so we need to parse it into a datetime object
# relative to right now.
expires = url_form.cleaned_data.get('expires')
# Determine what the expiration date is
if expires == URLForm.DAY:
edited_expires = timezone.now() + timedelta(days=1)
elif expires == URLForm.WEEK:
edited_expires = timezone.now() + timedelta(weeks=1)
elif expires == URLForm.MONTH:
edited_expires = timezone.now() + timedelta(weeks=3)
elif expires == URLForm.CUSTOM:
edited_expires = url_form.cleaned_data.get('expires_custom')
else:
pass # leave the field NULL
if edited_expires != copy.expires:
copy.expires = edited_expires
copy.save()
# Redirect to the shiny new *edited URL
return redirect('view', copy.short)
# Else, there is an error, redisplay the form with the validation errors
else:
# Render index.html passing the form to the template
return render(request, 'core/edit_link.html', {
'form': url_form
})
else:
# Initial data set here
if url.expires != None:
# Initialize a URL form with an expire date
url_form = EditForm(host=request.META.get('HTTP_HOST'), initial={
'target': url.target,
'short': url.short,
'expires': 'Custom Date',
'expires_custom': url.expires
}) # unbound form
else:
# Initialize a URL form without an expire date
url_form = EditForm(host=request.META.get('HTTP_HOST'), initial={
'target': url.target,
'short': url.short,
'expires': 'Never',
}) # unbound form
# Render index.html passing the form to the template
return render(request, 'core/edit_link.html', {
'form': url_form
})
else:
if url.owner != request.user.registereduser:
# do not allow them to edit
raise PermissionDenied()
# If a POST request is received, then the user has submitted a form and it's
# time to parse the form and edit that URL object
if request.method == 'POST':
return _edit_post(request, url)
# Initial data set here
if url.expires != None:
# Initialize a URL form with an expire date
url_form = EditForm(host=request.META.get('HTTP_HOST'), initial={
'target': url.target,
'short': url.short,
'expires': 'Custom Date',
'expires_custom': url.expires
}) # unbound form
else:
# Initialize a URL form without an expire date
url_form = EditForm(host=request.META.get('HTTP_HOST'), initial={
'target': url.target,
'short': url.short,
'expires': 'Never',
}) # unbound form
# Render index.html passing the form to the template
return render(request, 'core/edit_link.html', {
'form': url_form
})
def _edit_post(request, url):
# Now we initialize the form again but this time we have the POST
# request
url_form = EditForm(request.POST, host=request.META.get('HTTP_HOST'))
# Make a copy of the old URL
copy = url
# Remove the old one
url.delete()
# Django will check the form to make sure it's valid
if not url_form.is_valid():
# Render index.html passing the form to the template
return render(request, 'core/edit_link.html', {
'form': url_form
})
# If the short changed then we need to create a new object and
# migrate some data over
if url_form.cleaned_data.get('short').strip() != copy.short:
# Parse the form and create a new URL object
res = post(request, url_form)
# If there is a 500 error returned, handle it
if res == 500:
return HttpResponseServerError(render(request, '500.html'))
# Else we can procede with the editing process
# Migrate clicks data
res.clicks = copy.clicks
res.qrclicks = copy.qrclicks
res.socialclicks = copy.socialclicks
# Save the new URL
res.save()
# Redirect to the shiny new *edited URL
return redirect('view', res.short)
# The short was not edited and thus, we can directly edit the url
if url_form.cleaned_data.get('target').strip() != copy.target:
copy.target = url_form.cleaned_data.get('target').strip()
copy.save()
# Grab the expiration field value. It's currently an unsable
# string value, so we need to parse it into a datetime object
# relative to right now.
expires = url_form.cleaned_data.get('expires')
# Determine what the expiration date is
if expires == URLForm.DAY:
edited_expires = timezone.now() + timedelta(days=1)
elif expires == URLForm.WEEK:
edited_expires = timezone.now() + timedelta(weeks=1)
elif expires == URLForm.MONTH:
edited_expires = timezone.now() + timedelta(weeks=3)
elif expires == URLForm.CUSTOM:
edited_expires = url_form.cleaned_data.get('expires_custom')
else:
pass # leave the field NULL
if edited_expires != copy.expires:
copy.expires = edited_expires
copy.save()
# Redirect to the shiny new *edited URL
return redirect('view', copy.short)
@login_required
def delete(request, short):
......@@ -301,20 +301,16 @@ def delete(request, short):
This view deletes a URL if you have the permission to. User must be
logged in and registered, and must also be the owner of the URL.
"""
# Get the URL that is going to be deleted
url = get_object_or_404(URL, short__iexact=short)
# If the RegisteredUser is the owner of the URL
if url.owner == request.user.registereduser:
# remove the URL
url.delete()
# redirect to my_links
return redirect('my_links')
else:
if url.owner != request.user.registereduser:
# do not allow them to delete
raise PermissionDenied()
url.delete()
return redirect('my_links')
def redirection(request, short):
"""
......@@ -323,7 +319,7 @@ def redirection(request, short):
# Get the current domain info
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)
# Increment our clicks by one
......@@ -370,137 +366,65 @@ def useradmin(request):
# If we receive a POST request
if request.POST:
# Get a list of the potential victims (users)
userlist = request.POST.getlist('username')
# If we're approving users
if '_approve' in request.POST:
for name in userlist:
to_approve = RegisteredUser.objects.get(user__username__exact=name)
to_approve.approved = True
to_approve.save()
# Send an email letting them know they are approved
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = to_approve.user.username + settings.EMAIL_DOMAIN
send_mail(
'Your Account has been Approved!',
######################
'Hey there %s,\n\n'
'The Go admins have reviewed your application and have '
'approved you to use Go!\n\n'
'Head over to go.gmu.edu to create your first address.\n\n'
'- Go Admins'
% (str(to_approve.full_name)),
######################
settings.EMAIL_FROM,
[user_mail]
)
# If we're denying users
elif '_deny' in request.POST:
for name in userlist:
to_deny = RegisteredUser.objects.get(user__username__exact=name)
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = to_deny.user.username + settings.EMAIL_DOMAIN
# Send an email letting them know they are denied
send_mail(
'Your Account has been Denied!',
######################
'Hey there %s,\n\n'
'The Go admins have reviewed your application and have '
'decided to not approve you to use Go.\n\n'
'Please reach out to srct@gmu.edu to appeal '
'this decision.\n\n'
'- Go Admins'
% (str(to_deny.full_name)),
######################
settings.EMAIL_FROM,
[user_mail]
)
# Delete their associated RegisteredUsers
to_deny.user.delete()
return HttpResponseRedirect('useradmin')
# If we're blocking users
elif '_block' in request.POST:
for name in userlist:
to_block = RegisteredUser.objects.get(user__username__exact=name)
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = to_block.user.username + settings.EMAIL_DOMAIN
send_mail(
'Your Account has been Blocked!',
######################
'Hey there %s,\n\n'
'The Go admins have reviewed your application and have '
'blocked you from using Go.\n\n'
'Please reach out to srct@gmu.edu to appeal '
'this decision.\n\n'
'- Go Admins'
% (str(to_block.full_name)),
######################
settings.EMAIL_FROM,
[user_mail]
)
to_block.blocked = True
to_block.save()
# If we're un-blocking users
elif '_unblock' in request.POST:
for name in userlist:
to_un_block = RegisteredUser.objects.get(user__username__exact=name)
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = to_un_block.user.username + settings.EMAIL_DOMAIN
send_mail(
'Your Account has been Un-Blocked!',
######################
'Hey there %s,\n\n'
'The Go admins have reviewed your application and have '
'Un-Blocked you from using Go.\n\n'
'Congratulations! '
'- Go Admins'
% (str(to_un_block.full_name)),
######################
settings.EMAIL_FROM,
[user_mail]
)
to_un_block.blocked = False
to_un_block.save()
return HttpResponseRedirect('useradmin')
# If we're removing existing users
elif '_remove' in request.POST:
for name in userlist:
to_remove = RegisteredUser.objects.get(user__username__exact=name)
if settings.EMAIL_HOST and settings.EMAIL_PORT:
user_mail = to_remove.user.username + settings.EMAIL_DOMAIN
send_mail(
'Your Account has been Deleted!',
######################
'Hey there %s,\n\n'
'The Go admins have decided to remove you from Go. \n\n'
'Please reach out to srct@gmu.edu to appeal '
'this decision.\n\n'
'- Go Admins'
% (str(to_remove.full_name)),
######################
settings.EMAIL_FROM,
[user_mail]
)
to_remove.user.delete()
return HttpResponseRedirect('useradmin')
return _useradmin_post(request)
# Get a list of all RegisteredUsers that need to be approved
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(blocked=False)