Commit 9214318d authored by David Haynes's avatar David Haynes 🙆

Merge branch '185-go-forward' into 'go-three'

Resolve "Outline the project structure for "Go Forward""

See merge request !124
parents 267806e3 238ac8b5
Pipeline #2881 passed with stage
in 1 minute and 12 seconds
......@@ -15,3 +15,6 @@ htmlcov/
__pycache__/
.vscode
go/sourceme.sh
.DS_STORE
node_modules/
go/static/
......@@ -32,7 +32,8 @@ before_script:
- export GO_EMAIL_FROM=
- export GO_EMAIL_TO=
- python manage.py makemigrations
- python manage.py makemigrations go
- python manage.py makemigrations go_ahead
- python manage.py makemigrations go_back
- 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
......
......@@ -32,11 +32,11 @@
},
"django": {
"hashes": [
"sha256:97886b8a13bbc33bfeba2ff133035d3eca014e2309dff2b6da0bdfc0b8656613",
"sha256:e900b73beee8977c7b887d90c6c57d68af10066b9dac898e1eaf0f82313de334"
"sha256:0c5b65847d00845ee404bbc0b4a85686f15eb3001ffddda3db4e9baa265bf136",
"sha256:68aeea369a8130259354b6ba1fa9babe0c5ee6bced505dea4afcd00f765ae38b"
],
"index": "pypi",
"version": "==2.0.7"
"version": "==2.0.8"
},
"django-cas-client": {
"hashes": [
......@@ -132,10 +132,10 @@
"develop": {
"astroid": {
"hashes": [
"sha256:0a0c484279a5f08c9bcedd6fa9b42e378866a7dcc695206b92d59dc9f2d9760d",
"sha256:218e36cf8d98a42f16214e8670819ce307fa707d1dcf7f9af84c7aede1febc7f"
"sha256:a48b57ede295c3188ef5c84273bc2a8eadc46e4cbb001eae0d49fb5d1fabbb19",
"sha256:d066cdeec5faeb51a4be5010da612680653d844b57afd86a5c8315f2f801b4cc"
],
"version": "==2.0.1"
"version": "==2.0.2"
},
"coverage": {
"hashes": [
......@@ -226,19 +226,19 @@
},
"pylint": {
"hashes": [
"sha256:248a7b19138b22e6390cba71adc0cb03ac6dd75a25d3544f03ea1728fa20e8f4",
"sha256:9cd70527ef3b099543eeabeb5c80ff325d86b477aa2b3d49e264e12d12153bc8"
"sha256:0edfec21270725c5aa8e8d8d06ef5666f766e0e748ed2f1ab23624727303b935",
"sha256:4cadcaa4f1fb19123d4baa758d9fbe6286c5b3aa513af6ea42a2d51d405db205"
],
"index": "pypi",
"version": "==2.0.0"
"version": "==2.1.0"
},
"pylint-django": {
"hashes": [
"sha256:70a723115a9649d6082f6ef09209ff299ce5b90c19005e351f7eb62889888694",
"sha256:ff10fbf6e0ab207fbb8ec26f5b6a3f4fa604233b38182ccda038a08ba1b8ba80"
"sha256:5c5a20c443b4e70fdc8c47e42cff8ce79c953954e918f8e559f6e1d05a971585",
"sha256:70f2b5397aa2468373fcf87d64a700b359050e905e56e2dbaf954e6edb04c593"
],
"index": "pypi",
"version": "==0.11.1"
"version": "==2.0"
},
"pylint-plugin-utils": {
"hashes": [
......@@ -253,6 +253,42 @@
],
"version": "==1.11.0"
},
"typed-ast": {
"hashes": [
"sha256:0948004fa228ae071054f5208840a1e88747a357ec1101c17217bfe99b299d58",
"sha256:10703d3cec8dcd9eef5a630a04056bbc898abc19bac5691612acba7d1325b66d",
"sha256:1f6c4bd0bdc0f14246fd41262df7dfc018d65bb05f6e16390b7ea26ca454a291",
"sha256:25d8feefe27eb0303b73545416b13d108c6067b846b543738a25ff304824ed9a",
"sha256:29464a177d56e4e055b5f7b629935af7f49c196be47528cc94e0a7bf83fbc2b9",
"sha256:2e214b72168ea0275efd6c884b114ab42e316de3ffa125b267e732ed2abda892",
"sha256:3e0d5e48e3a23e9a4d1a9f698e32a542a4a288c871d33ed8df1b092a40f3a0f9",
"sha256:519425deca5c2b2bdac49f77b2c5625781abbaf9a809d727d3a5596b30bb4ded",
"sha256:57fe287f0cdd9ceaf69e7b71a2e94a24b5d268b35df251a88fef5cc241bf73aa",
"sha256:668d0cec391d9aed1c6a388b0d5b97cd22e6073eaa5fbaa6d2946603b4871efe",
"sha256:68ba70684990f59497680ff90d18e756a47bf4863c604098f10de9716b2c0bdd",
"sha256:6de012d2b166fe7a4cdf505eee3aaa12192f7ba365beeefaca4ec10e31241a85",
"sha256:79b91ebe5a28d349b6d0d323023350133e927b4de5b651a8aa2db69c761420c6",
"sha256:8550177fa5d4c1f09b5e5f524411c44633c80ec69b24e0e98906dd761941ca46",
"sha256:898f818399cafcdb93cbbe15fc83a33d05f18e29fb498ddc09b0214cdfc7cd51",
"sha256:94b091dc0f19291adcb279a108f5d38de2430411068b219f41b343c03b28fb1f",
"sha256:a26863198902cda15ab4503991e8cf1ca874219e0118cbf07c126bce7c4db129",
"sha256:a8034021801bc0440f2e027c354b4eafd95891b573e12ff0418dec385c76785c",
"sha256:bc978ac17468fe868ee589c795d06777f75496b1ed576d308002c8a5756fb9ea",
"sha256:c05b41bc1deade9f90ddc5d988fe506208019ebba9f2578c622516fd201f5863",
"sha256:c9b060bd1e5a26ab6e8267fd46fc9e02b54eb15fffb16d112d4c7b1c12987559",
"sha256:edb04bdd45bfd76c8292c4d9654568efaedf76fe78eb246dde69bdb13b2dad87",
"sha256:f19f2a4f547505fe9072e15f6f4ae714af51b5a681a97f187971f50c283193b6"
],
"version": "==1.1.0"
},
"typing": {
"hashes": [
"sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf",
"sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8",
"sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2"
],
"version": "==3.6.4"
},
"wrapt": {
"hashes": [
"sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6"
......
# Go 3
# Go 3.0 - Go Forward 🚀
Go is at its core a URL shortening service built for the GMU community. It was
the first big SRCT project that was taken from development to production by the
founders. Originally a PHP app, it was translated into Django as a 1.0 release
and sat unmaintained for a period of time. Development on 2.0 started in 2016
with the intention of modernizing the application as well as designing for long
term maintenance. Additionally, since the core of the project is fairly simple,
2.0 functioned as a good introduction to open source development for new
members.
A project of [GMU SRCT](https://srct.gmu.edu).
## DISCLAIMER
## Architecture of the project
### `go_back`
`go_back` is the API backend of the project. It is built with the Django REST
Framework (python). It supports all CRUD (Create, Read, Update, Delete)
operations on Go links as well as RegisteredUser account management.
### `go_ahead`
## Getting started with contributing
There's a workflow involved with getting started contributing but once you do
it once or twice it'll seem a lot less daunting.
1. React / Webpack
```sh
yarn
yarn dev
```
2. Docker
In another terminal tab:
```sh
docker-compose up
```
Go 3 is a major refactor of the project with an emphasis on extensibility.
3. Actually coding
Things might not work quite right.
All JS changes will require a refresh (Webpack rebuilds the app in the background).
Docs may not exist.
All Python changes will require a refresh.
I wouldn't go so far as to say we are hitting the reset button.
But it's close.
4. Deployment of changes
```sh
pipenv install
pipenv shell
code .
docker-compose up
```
See me.
\ No newline at end of file
......@@ -3,11 +3,13 @@ version: "3"
services:
db:
image: mysql:5.7
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
command:
mysqld --character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
ports:
- "3306:3306"
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
MYSQL_DATABASE: go
MYSQL_USER: go
MYSQL_PASSWORD: go
......@@ -15,7 +17,7 @@ services:
web:
build: .
ports:
- '8000:8000'
- "8000:8000"
command: ./docker-startup.sh
volumes:
- .:/go
......
......@@ -10,7 +10,8 @@ export GO_CREATE_SUPERUSER
GO_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 makemigrations
python go/manage.py makemigrations go
python go/manage.py makemigrations go_back
python go/manage.py makemigrations go_ahead
python go/manage.py migrate
python go/manage.py runserver 0.0.0.0:8000
\ No newline at end of file
# Generated by Django 2.0.5 on 2018-05-23 23:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('go', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='url',
name='short',
field=models.SlugField(allow_unicode=True, max_length=20, unique=True),
),
]
# Generated by Django 2.0.5 on 2018-05-24 00:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('go', '0002_auto_20180523_2351'),
]
operations = [
migrations.AlterField(
model_name='url',
name='short',
field=models.CharField(max_length=20, unique=True),
),
]
# Generated by Django 2.0.5 on 2018-06-08 20:58
from django.db import migrations, models
import django.utils.timezone
import go.validators
class Migration(migrations.Migration):
dependencies = [
('go', '0003_auto_20180524_0003'),
]
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.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'),
),
]
from django.apps import AppConfig
class GoAheadConfig(AppConfig):
name = 'go_ahead'
import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(
<div>
<p>Hello Go 3 with React!</p>
</div>,
document.getElementById("root")
);
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#006633" />
<meta name="description" content="University-branded URL shortening" />
<title>Welcome &bull; SRCT Go</title>
</head>
<body>
<!-- React injects itself here -->
<div id="root"></div>
</body>
<script src="static/main.js"></script>
</html>
from django.urls import path
from . import views
urlpatterns = [
path('', views.index),
]
from django.shortcuts import render
def index(request):
return render(request, 'index.html')
......@@ -8,7 +8,7 @@ from django.core.management.base import BaseCommand
from django.utils import timezone
# App Imports
from go.models import URL
from go_back.models import URL
class Command(BaseCommand):
"""
......
......@@ -13,7 +13,7 @@ from django.test import TestCase
from django.utils import timezone
# App Imports
from go.models import URL, RegisteredUser
from go_back.models import URL, RegisteredUser
# class ExpireLinksTest(TestCase):
# def setUp(self):
......
# Generated by Django 2.0.5 on 2018-05-23 23:29
# Generated by Django 2.0.8 on 2018-08-02 01:05
from django.conf import settings
from django.db import migrations, models
import django.utils.timezone
import go_back.validators
class Migration(migrations.Migration):
......@@ -18,12 +19,12 @@ class Migration(migrations.Migration):
name='RegisteredUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('full_name', models.CharField(default='', max_length=100, verbose_name='verbose name')),
('organization', models.CharField(default='', max_length=100, verbose_name='verbose name')),
('description', models.TextField(blank=True, default='', verbose_name='verbose name')),
('registered', models.BooleanField(default=False, verbose_name='verbose name')),
('approved', models.BooleanField(default=False, verbose_name='verbose name')),
('blocked', models.BooleanField(default=False, verbose_name='verbose name')),
('full_name', models.CharField(default='', max_length=100, verbose_name='Full Name')),
('organization', models.CharField(default='', max_length=100, verbose_name='Organization')),
('description', models.TextField(blank=True, default='', verbose_name='Signup Description')),
('registered', models.BooleanField(default=False, verbose_name='Registration Status')),
('approved', models.BooleanField(default=False, verbose_name='Approval Status')),
('blocked', models.BooleanField(default=False, verbose_name='Blocked Status')),
('user', models.OneToOneField(on_delete='cascade', to=settings.AUTH_USER_MODEL, verbose_name='Django User Object')),
],
),
......@@ -31,14 +32,14 @@ class Migration(migrations.Migration):
name='URL',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_created', models.DateTimeField(default=django.utils.timezone.now, verbose_name='verbose name')),
('date_expires', models.DateTimeField(blank=True, null=True, verbose_name='verbose name')),
('destination', models.URLField(default='https://go.gmu.edu', max_length=1000)),
('short', models.SlugField(max_length=20, unique=True)),
('date_created', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Go Link Creation Date')),
('date_expires', models.DateTimeField(blank=True, null=True, verbose_name='Go Link Expiry Date')),
('destination', models.URLField(default='https://go.gmu.edu', max_length=1000, verbose_name='Go Link Destination URL')),
('short', models.CharField(max_length=20, unique=True, validators=[go_back.validators.unique_short_validator, go_back.validators.regex_short_validator], verbose_name='Go Shortcode')),
('clicks', models.IntegerField(default=0)),
('qrclicks', models.IntegerField(default=0)),
('socialclicks', models.IntegerField(default=0)),
('owner', models.ForeignKey(on_delete='cascade', to='go.RegisteredUser', verbose_name='verbose name')),
('owner', models.ForeignKey(on_delete='cascade', to='go_back.RegisteredUser', verbose_name='RegisteredUser Owner')),
],
options={
'ordering': ['short'],
......
......@@ -8,7 +8,7 @@ data based on the status of the user.
from django import template
# App Imports
from go.models import RegisteredUser
from go_back.models import RegisteredUser
# To be a valid tag library, the module must contain a module-level variable
# named register that is a template.Library instance, in which all the tags and
......
......@@ -8,7 +8,7 @@ from django.contrib.auth.models import User
from django.test import TestCase
# App Imports
from go.models import RegisteredUser
from go_back.models import RegisteredUser
from .go_extras import is_approved, is_registered
class GoExtrasTest(TestCase):
......
"""
settings/urls.py
The URLs of the project and their associated view that requests are routed to.
"""
# Django Imports
from django.urls import path, re_path, include
from django.contrib import admin
from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView
# App Imports
from . import views
from cas import views as cas_views
# Third Party
from rest_framework import routers
router = routers.DefaultRouter()
router.register(r'golinks', views.URLViewSet, base_name="golinks")
# This function attempts to import an admin module in each installed
# application. Such modules are expected to register models with the admin.
admin.autodiscover()
urlpatterns = [
# Root API URL
path("api/", include(router.urls)),
# Authentication URLs
path('auth/login/', cas_views.login, name='cas_login'),
path('auth/logout/', cas_views.logout, {'next_page': '/'}, name='cas_logout'),
# /admin - Administrator interface.
path('admin/', admin.site.urls, name='go_admin'),
path('auth/', include('rest_framework.urls'))
# # /view/<short> - View URL data. Cached for 15 minutes
# re_path(r'^view/(?P<short>([\U00010000-\U0010ffff][\U0000200D]?)+)$',
# cache_page(60 * 15)(go.views.view), name='view'),
# re_path(r'^view/(?P<short>[-\w]+)$',
# cache_page(60 * 15)(go.views.view), name='view'),
# # Redirection regex.
# re_path(r'^(?P<short>([\U00010000-\U0010ffff][\U0000200D]?)+)$',
# go.views.redirection, name='redirection'),
# re_path(r'^(?P<short>[-\w]+)$',
# go.views.redirection, name='redirection'),
]
......@@ -26,6 +26,8 @@ else:
}
# STANDALONE VARS
# /go/go
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# You can generate a secret key from the following link:
......@@ -62,6 +64,7 @@ STATIC_URL = '/static/'
STATIC_ROOT = ''
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static/'),
'./go/static/'
)
STATICFILES_FINDERS = (
......@@ -74,7 +77,8 @@ TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'templates')
os.path.join(BASE_DIR, 'templates'),
'./go/go_ahead/templates/'
],
'APP_DIRS': True,
'OPTIONS': {
......@@ -123,7 +127,8 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'go',
'go_back',
'go_ahead',
# Third party
'crispy_forms',
'cas',
......@@ -183,7 +188,7 @@ CAS_LOGOUT_COMPLETELY = True
CAS_PROVIDE_URL_TO_LOGOUT = True
CAS_RESPONSE_CALLBACKS = (
'go.cas_callbacks.create_user',
'go_back.cas_callbacks.create_user',
)
CAS_SERVER_URL = "https://login.gmu.edu"
......
"""
settings/urls.py
The URLs of the project and their associated view that requests are routed to.
"""
# Django Imports
import django.contrib.auth.views
from django.urls import path, re_path, include