Release 1.13

This commit is contained in:
Aaron Louie 2021-04-08 10:37:17 -04:00
parent b84a977e2c
commit c58e504cfe
21 changed files with 165 additions and 167 deletions

View File

@ -16,7 +16,16 @@ sudo apt-get install python3 python3-dev
sudo apt-get install -y libssl-dev libffi-dev
```
#### PostgreSQL
#### Using Docker
You can use Docker to run the two primary dependencies for this project: Postgres, and ElasticSearch
1. create a directory at ~/docker/volumes/stardrive
2. chmod 777 ~/docker/volumes/stardrive/elasticsearch
3. spin it all up, with docker-compose up
* Optionally set up a run configuration in PyCharm
#### Not Using Docker
###### PostgreSQL
* MacOS:
[Download and install Postgres.app](https://postgresapp.com). This will install `postgres`, along with the command-line tools, including `psql`, `pg_ctl`, and others. Then update your `PATH` variable to look in the Postgres.app `bin` directory for the relevant Postgres CLI tools.
```BASH
@ -27,28 +36,8 @@ export PATH="/Applications/Postgres.app/Contents/Versions/latest/bin:$PATH"
```BASH
apt-get install postgresql postgresql-client libpq-dev
```
#### ElasticSearch
We are currently using version 6, and should look at upgrading this in the future when my hair isn't on fire.
* Debian/Ubuntu: https://medium.com/@pierangelo1982/how-to-install-elasticsearch-6-on-ubuntu-64316dc2de1c
#### Angular
```BASH
npm install -g @angular/cli
```
### Project Setup
* Please use Python 3's virtual environment setup, and install the dependencies in requirements.txt
```bash
cd backend
python3 -m venv python-env
source python-env/bin/activate
pip3 install -r requirements.txt
```
## Database Setup
### Create a Database
###### Database Setup
*NOTE:* Docker will do this automatically, only necissary if you are doing it locally.
*NOTE:* The configuration is currently set up to use "ed_pass" as a password. You will be promoted to enter a password when you connect.
* MacOS:
```BASH
@ -68,6 +57,34 @@ exit
```
If you are using Ubuntu you will likely need to [enable PSQL](https://help.ubuntu.com/community/PostgreSQL#Managing_users_and_rights) to manage its own users.
###### ElasticSearch
We are currently using version 6, and should look at upgrading this in the future when my hair isn't on fire.
* Debian/Ubuntu: https://medium.com/@pierangelo1982/how-to-install-elasticsearch-6-on-ubuntu-64316dc2de1c
#### Angular
You will need the angular command line utilities to run the front end.
```BASH
npm install -g @angular/cli
```
### Project Setup
* Please use Python 3's virtual environment setup, and install the dependencies in requirements.txt
```bash
cd backend
python3 -m venv python-env
source python-env/bin/activate
pip3 install -r requirements.txt
```
## Add a config file
In the `backend` directory, execute the following command:
```BASH
mkdir instance && cp -r config instance/config && cp instance/config/default.py instance/config.py
```
### Update the Database
You will need to update your database each time you return to do a pull to make sure all the migrations are run. In the `backend` directory, execute the following command:
```BASH
@ -95,41 +112,9 @@ This will pull in initial values into the database.
flask initdb
```
## Add a config file
In the `backend` directory, execute the following command:
```BASH
mkdir instance && cp -r config instance/config && cp instance/config/default.py instance/config.py
```
## Run the app
Execute the following at the top level of the repository to start PostgreSQL, flask, and Angular all in one command:
```BASH
./start.sh
```
Or you can run these 3 commands separately:
Database:
```BASH
./start-db.sh
```
Backend:
```BASH
./start-backend.sh
```
Frontend:
```BASH
./start-frontend.sh
```
Alternatively, you could start each of the services individually, using the commands below.
### Start PostgreSQL
```BASH
pg_ctl -D /usr/local/var/postgres start
```
* Strongly suggest you set up run configurations for this in PyCharm and WebStorm
### Start the backend app
In the `backend` directory, execute the following command:
@ -144,12 +129,6 @@ npm install
ng serve
```
### Stopping the app
Execute the following at the top level of the repository to stop all running services:
```BASH
./stop.sh
```
### Setting up Mailtrap
To test email integration set up an account with [Mailtrap](https://mailtrap.io/)
In your instance config, set your sname and password.

View File

@ -40,6 +40,10 @@ if "MIRRORING" in os.environ and os.environ["MIRRORING"] == "true":
# Database Configuration
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
""":type: sqlalchemy.orm.SQLAlchemy"""
session = db.session
""":type: sqlalchemy.orm.Session"""
# Enable CORS
if app.config["CORS_ENABLED"]:
@ -49,6 +53,7 @@ if app.config["CORS_ENABLED"]:
# Flask-Marshmallow provides HATEOAS links
ma = Marshmallow(app)
""":type: flask_marshmallow.Marshmallow"""
# Database Migrations
migrate = Migrate(app, db, compare_type=True)
@ -210,6 +215,24 @@ def loadstudies():
data_loader.load_studies()
@app.cli.command()
def loadusers():
"""Used for loading new users into the database"""
click.echo('Loading users, not clearing out existing ones')
from app import data_loader
data_loader = data_loader.DataLoader()
data_loader.load_users()
@app.cli.command()
def loadparticipants():
"""Used for loading new participants into the database"""
click.echo('Loading participants, not clearing out existing ones')
from app import data_loader
data_loader = data_loader.DataLoader()
data_loader.load_participants()
@app.cli.command()
def run_full_export():
"""Remove all data and recreate it from the example data files"""

View File

@ -3,9 +3,9 @@ from sqlalchemy import func
from sqlalchemy.ext.declarative import declared_attr
from app import db, ma
from app.export_service import ExportService
from app.model.questionnaires.chain_session import ChainSessionSchema, ChainSession
from app.schema.model_schema import ModelSchema
from app.export_service import ExportService
class ChainQuestionnaire(db.Model):
@ -37,18 +37,18 @@ class ChainQuestionnaire(db.Model):
def get_field_groups(self):
field_groups = {
"sessions": {
"type": "repeat",
"display_order": 3,
"wrappers": ["card"],
"repeat_class": ChainSession,
"template_options": {
"label": "Chain Session",
"description": "Add a session",
},
"expression_properties": {},
"sessions": {
"type": "repeat",
"display_order": 3,
"wrappers": ["card"],
"repeat_class": ChainSession,
"template_options": {
"label": "Chain Session",
"description": "Add a session",
},
}
"expression_properties": {},
},
}
return field_groups
@ -68,4 +68,5 @@ class ChainQuestionnaireSchema(ModelSchema):
"time_on_task_ms",
"sessions",
)
sessions = ma.Nested(ChainSessionSchema, many=True)

View File

@ -28,9 +28,7 @@ class ChainSession(db.Model):
db.ForeignKey('chain_questionnaire.id')
)
@declared_attr
def date(cls):
return db.Column(
date = db.Column(
db.DateTime(timezone=True),
info={
"display_order": 1,
@ -42,9 +40,7 @@ class ChainSession(db.Model):
},
)
@declared_attr
def completed(cls):
return db.Column(
completed = db.Column(
db.Boolean,
info={
"display_order": 2,
@ -60,9 +56,7 @@ class ChainSession(db.Model):
},
)
@declared_attr
def session_type(cls):
return db.Column(
session_type = db.Column(
db.String,
info={
"display_order": 2,
@ -79,11 +73,9 @@ class ChainSession(db.Model):
},
)
@declared_attr
def step_attempts(cls):
return db.relationship(
step_attempts = db.relationship(
"ChainSessionStep",
backref=db.backref(cls.__tablename__, lazy=True),
backref='chain_session',
cascade="all, delete-orphan",
passive_deletes=True
)
@ -104,6 +96,7 @@ class ChainSession(db.Model):
}
return field_groups
class ChainSessionSchema(ModelSchema):
@pre_load
def set_field_session(self, data, **kwargs):
@ -115,12 +108,28 @@ class ChainSessionSchema(ModelSchema):
fields = (
"id",
"last_updated",
"participant_id",
"user_id",
"time_on_task_ms",
"date",
"completed",
"session_type",
"chain_questionnaire_id",
"step_attempts",
"chain_questionnaire_id"
)
step_attempts = ma.Nested(ChainSessionStepSchema, many=True)
step_attempts = fields.Nested(ChainSessionStepSchema, many=True)
participant_id = fields.Method('get_participant_id', dump_only=True)
user_id = fields.Method('get_user_id', dump_only=True)
def get_participant_id(self, obj):
if obj is None:
return missing
return obj.chain_questionnaire.participant_id
def get_user_id(self, obj):
if obj is None:
return missing
return obj.chain_questionnaire.user_id

View File

@ -1,13 +1,13 @@
from marshmallow import missing, pre_load
from marshmallow import fields, missing
from sqlalchemy import func
from sqlalchemy.ext.declarative import declared_attr
from app import db, ma
from app.schema.model_schema import ModelSchema
from app import db
from app.export_service import ExportService
from app.model.chain_step import ChainStep
from app.model.questionnaires.challenging_behavior import ChallengingBehavior, ChallengingBehaviorSchema
from app.schema.chain_step_schema import ChainStepSchema
from app.schema.model_schema import ModelSchema
class ChainSessionStep(db.Model):
@ -37,7 +37,10 @@ class ChainSessionStep(db.Model):
except:
pass
return db.Column("chain_step_id", db.Integer, db.ForeignKey('chain_step.id'), info={
return db.Column(
"chain_step_id",
db.Integer,
db.ForeignKey('chain_step.id'), info={
"display_order": 1,
"type": "select",
"template_options": {
@ -46,7 +49,7 @@ class ChainSessionStep(db.Model):
"options": options,
},
}
)
)
date = db.Column(
db.DateTime(timezone=True),
@ -290,8 +293,10 @@ class ChainSessionStepSchema(ModelSchema):
fields = (
"id",
"last_updated",
"participant_id",
"user_id",
"chain_session_id",
"chain_step_id",
"chain_step",
"date",
"session_type",
"was_focus_step",
@ -303,11 +308,14 @@ class ChainSessionStepSchema(ModelSchema):
"had_challenging_behavior",
"reason_step_incomplete",
"challenging_behaviors",
"chain_session_id"
"chain_step",
)
challenging_behaviors = ma.Nested(ChallengingBehaviorSchema, many=True)
chain_step = ma.Method('get_chain_step', dump_only=True)
session_type = ma.Method('get_session_type', dump_only=True)
participant_id = fields.Method('get_participant_id', dump_only=True)
user_id = fields.Method('get_user_id', dump_only=True)
challenging_behaviors = fields.Nested(ChallengingBehaviorSchema, many=True)
chain_step = fields.Method('get_chain_step', dump_only=True)
session_type = fields.Method('get_session_type', dump_only=True)
def get_chain_step(self, obj):
if obj is None:
@ -320,5 +328,16 @@ class ChainSessionStepSchema(ModelSchema):
if obj is None:
return missing
if hasattr(obj, 'chain_session'):
return obj.chain_session.session_type
return obj.chain_session.session_type
def get_participant_id(self, obj):
if obj is None:
return missing
return obj.chain_session.chain_questionnaire.participant_id
def get_user_id(self, obj):
if obj is None:
return missing
return obj.chain_session.chain_questionnaire.user_id

View File

@ -5,7 +5,7 @@ import datetime
from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
from sqlalchemy import func
from app import app, RestException, db, auth, email_service
from app import app, RestException, db, auth, email_service, session
from app.model.email_log import EmailLog
from app.model.user import User
from flask import g, request, Blueprint, jsonify
@ -24,7 +24,7 @@ def confirm_email(email_token):
except:
raise RestException(RestException.EMAIL_TOKEN_INVALID)
user = User.query.filter_by(email=email).first_or_404()
user = session.query(User).filter_by(email=email).first_or_404()
user.email_verified = True
db.session.add(user)
db.session.commit()
@ -42,7 +42,7 @@ def login_password():
raise RestException(RestException.INVALID_INPUT)
email = request_data['email']
user = User.query.filter(func.lower(User.email) == email.lower()).first()
user = session.query(User).filter(func.lower(User.email) == email.lower()).first()
schema = UserSchema(many=False)
if user is None:
@ -71,7 +71,7 @@ def login_password():
def forgot_password():
request_data = request.get_json()
email = request_data['email']
user = User.query.filter(func.lower(User.email) == email.lower()).first()
user = session.query(User).filter(func.lower(User.email) == func.lower(email)).first()
if user:
tracking_code = email_service.reset_email(user)
@ -101,7 +101,7 @@ def reset_password():
except BadSignature:
raise RestException(RestException.TOKEN_INVALID)
user = User.query.filter(func.lower(User.email) == email.lower()).first_or_404()
user = session.query(User).filter(func.lower(User.email) == email.lower()).first_or_404()
user.token_url = ''
user.email_verified = True
user.password = password
@ -118,7 +118,7 @@ def verify_token(token):
try:
resp = User.decode_auth_token(token)
if resp:
g.user = User.query.filter_by(id=resp).first()
g.user = session.query(User).filter_by(id=resp).first()
except:
g.user = None

View File

@ -4,3 +4,4 @@ id,email,password,role
3,ajlouie@gmail.com,Total Perspective Vortex 56,admin
4,aaron@sartography.com,Zarquon Disaster Area 78,user
5,kenna@sartography.com,Example Password Runner 2019,admin
6,alicia@sartography.com,Example Password billybob 55,admin

1 id email password role
4 3 ajlouie@gmail.com Total Perspective Vortex 56 admin
5 4 aaron@sartography.com Zarquon Disaster Area 78 user
6 5 kenna@sartography.com Example Password Runner 2019 admin
7 6 alicia@sartography.com Example Password billybob 55 admin

View File

@ -16,6 +16,7 @@ googlemaps==3.0.2
marshmallow-enum==1.5.1
marshmallow==3.8.0
marshmallow_sqlalchemy==0.23.1
sqlalchemy==1.3.18
mod-wsgi==4.7.1
nose2==0.6.0
nose==1.3.7

View File

@ -80,7 +80,7 @@ MIT
MIT
The MIT License
Copyright (c) 2020 Google LLC.
Copyright (c) 2021 Google LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -139,7 +139,7 @@ MIT
MIT
The MIT License
Copyright (c) 2020 Google LLC.
Copyright (c) 2021 Google LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -202,7 +202,7 @@ MIT
core-js
MIT
Copyright (c) 2014-2020 Denis Pushkarev
Copyright (c) 2014-2021 Denis Pushkarev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -223,30 +223,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
css-loader
MIT
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
geolib
MIT
MIT License
@ -489,6 +465,7 @@ all code is your original work. `</legalese>`
## Marked
Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/)
Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
Permission is hereby granted, free of charge, to any person obtaining a copy
@ -511,8 +488,8 @@ THE SOFTWARE.
## Markdown
Copyright © 2004, John Gruber
http://daringfireball.net/
Copyright © 2004, John Gruber
http://daringfireball.net/
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
@ -555,6 +532,7 @@ SOFTWARE.
ngx-progressbar
MIT
regenerator-runtime
MIT
@ -825,7 +803,7 @@ zone.js
MIT
The MIT License
Copyright (c) 2010-2020 Google LLC. http://angular.io/license
Copyright (c) 2010-2020 Google LLC. https://angular.io/license
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -6,29 +6,16 @@
<title>Autism DRIVE</title>
<base href="/">
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
<meta property="og:site_name" content="Autism DRIVE" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Transform Outcomes. Together." />
<meta property="og:description" content="Transform Outcomes. Together. A centralized system for autism research & resources for individuals, families & professionals. " />
<meta name="twitter:text:title" content="Transform Outcomes. Together." />
<link
rel="icon"
type="image/x-icon"
href="/assets/favicon.ico"
>
<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
>
<link
rel="stylesheet"
href="https://use.typekit.net/ato2zta.css"
>
<link rel="stylesheet" href="styles.253f70d6ea3ee022e42d.css"></head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:site_name" content="Autism DRIVE"/>
<meta property="og:type" content="website"/>
<meta property="og:title" content="Transform Outcomes. Together."/>
<meta property="og:description" content="Transform Outcomes. Together. A centralized system for autism research &amp; resources for individuals, families &amp; professionals. "/>
<meta name="twitter:text:title" content="Transform Outcomes. Together."/>
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
<style type="text/css">@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/materialicons/v83/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.woff) format('woff');}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;font-feature-settings:'liga';}@font-face{font-family:'Material Icons';font-style:normal;font-weight:400;src:url(https://fonts.gstatic.com/s/materialicons/v83/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2');}.material-icons{font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-feature-settings:'liga';-webkit-font-smoothing:antialiased;}</style>
<link rel="stylesheet" href="https://use.typekit.net/ato2zta.css">
<link rel="stylesheet" href="styles.418eadfe1eb1ee0b2bd8.css"></head>
<body>
<app-root></app-root>
@ -39,6 +26,6 @@
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
</script>
<script src="runtime-es2015.66c79b9d36e7169e27b0.js" type="module"></script><script src="runtime-es5.66c79b9d36e7169e27b0.js" nomodule defer></script><script src="polyfills-es5.5f25760a6e7790d968b4.js" nomodule defer></script><script src="polyfills-es2015.f4693cff282ac90e4596.js" type="module"></script><script src="main-es2015.bf887d12e63744995793.js" type="module"></script><script src="main-es5.bf887d12e63744995793.js" nomodule defer></script></body>
<script src="runtime-es2015.a4dadbc03350107420a4.js" type="module"></script><script src="runtime-es5.a4dadbc03350107420a4.js" nomodule="" defer=""></script><script src="polyfills-es5.e6fc9488c015b7e85901.js" nomodule="" defer=""></script><script src="polyfills-es2015.2f61e63415c6de3ac93d.js" type="module"></script><script src="main-es2015.68cedc8815d9c8207bb5.js" type="module"></script><script src="main-es5.68cedc8815d9c8207bb5.js" nomodule="" defer=""></script></body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long