Release 1.9.0

This commit is contained in:
Aaron Louie 2020-09-30 09:37:44 -04:00
parent 9d8f8139c4
commit e3411f217b
43 changed files with 85 additions and 63 deletions

View File

@ -311,6 +311,12 @@ python3 -m venv python-env
```
When pushing to production please create a new 'Release' on gitHub describing the changes that were rolled out.
Currently we are using a separate repository for deployment: star-drive-dist. To prepare this for deployment, you should have a
copy of star-drive-dist in the same directory as your local copy of star-drive. Your local star-drive should be on master,
up to date with all the changes for the release. Once this is ready, run the ```prepare_for_deploy.sh``` script in
star-drive-dist to prepare the release. Commit these changes referencing the release number and push them up.

View File

@ -85,7 +85,7 @@ class DataLoader:
db.session.commit()
for i in range(17, 25):
if row[i] and row[i] is not '':
if row[i] and row[i] != '':
category = self.get_category_by_name(row[i].strip())
event_id = event.id
category_id = category.id
@ -125,7 +125,7 @@ class DataLoader:
db.session.commit()
for i in range(19, 28):
if row[i] and row[i] is not '':
if row[i] and row[i] != '':
category = self.get_category_by_name(row[i].strip())
location_id = location.id
category_id = category.id
@ -159,7 +159,7 @@ class DataLoader:
db.session.commit()
for i in range(7, 14):
if row[i] and row[i] is not '':
if row[i] and row[i] != '':
category = self.get_category_by_name(row[i].strip())
resource_id = resource.id
category_id = category.id
@ -199,7 +199,7 @@ class DataLoader:
self.__increment_id_sequence(Study)
for i in range(24, 31):
if row[i] and row[i] is not '':
if row[i] and row[i] != '':
category = self.get_category_by_name(row[i].strip())
study_id = study.id
category_id = category.id

View File

@ -157,7 +157,7 @@ class ElasticIndex:
for cat in document.categories:
doc.category.extend(cat.category.all_search_paths())
if document.__tablename__ is 'study':
if document.__tablename__ == 'study':
doc.title = document.short_title
doc.description = document.short_description
@ -211,7 +211,7 @@ class ElasticIndex:
elastic_search = elastic_search.filter('range', **{"date": {"gte": search.date}})
else:
elastic_search = elastic_search.filter('bool', **{"should": [
{"range": {"date": {"gte": datetime.datetime.now()}}}, # Future events OR
{"range": {"date": {"gte": datetime.datetime.utcnow()}}}, # Future events OR
{"bool": {"must_not": {"exists": {"field": "date"}}}} # Date field is empty
]})
@ -277,7 +277,7 @@ class ElasticIndex:
# Filter out past events
elastic_search = elastic_search.filter('bool', **{"should": [
{"range": {"date": {"gte": datetime.datetime.now()}}}, # Future events OR
{"range": {"date": {"gte": datetime.datetime.utcnow()}}}, # Future events OR
{"bool": {"must_not": {"exists": {"field": "date"}}}} # Date field is empty
]})

View File

@ -206,11 +206,13 @@ class ExportService:
return meta_relationed
@staticmethod
# this evil method recurses down through the metadata, removing items that have a
# RELATIONSHIP_REQUIRED, if the relationship isn't there and selecting the right
# content from a list, if RELATIONSHIP_SPECIFIC provides an array content for each
# possible type of relationship.
def _recursive_relationship_changes(meta, relationship):
"""
This evil method recurses down through the metadata, removing items that have a
RELATIONSHIP_REQUIRED, if the relationship isn't there and selecting the right
content from a list, if RELATIONSHIP_SPECIFIC provides an array content for each
possible type of relationship.
"""
meta_copy = {}
for k, v in meta.items():
if type(v) is dict:
@ -240,12 +242,13 @@ class ExportService:
@staticmethod
def send_alert_if_exports_not_running():
"""If more than 30 minutes pass without an export from the Public Mirror to the
Private Mirror, an email will be sent to an administrative email address.
Emails to this address will occur every two (2) hours for the first 24 hours
and every four hours after that until the fault is corrected or the system taken down.
After 24 hours, the PI will also be emailed notifications every 8 hours until
the fault is corrected or the system taken down."""
"""
If more than 30 minutes pass without an export from the Public Mirror to the Private Mirror, an email will be
sent to an administrative email address. Emails to this address will occur every two (2) hours for the first
24 hours and every four hours after that until the fault is corrected or the system taken down. After 24
hours, the PI will also be emailed notifications every 8 hours until the fault is corrected or the system
taken down.
"""
alert_principal_investigator = False
last_log = db.session.query(DataTransferLog).filter(DataTransferLog.type == 'export')\
.order_by(desc(DataTransferLog.last_updated)).limit(1).first()
@ -257,7 +260,8 @@ class ExportService:
else:
msg = None
subject = "Autism DRIVE: Error - "
time_difference = datetime.datetime.now(tz=UTC) - last_log.last_updated
now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc) # Make date timezone aware
time_difference = now - last_log.last_updated
hours = int(time_difference.total_seconds()/3600)
minutes = int(time_difference.total_seconds()/60)
if hours >= 24 and hours% 4 == 0 and last_log.alerts_sent < (hours / 4 + 12):

View File

@ -108,8 +108,8 @@ class ExportXlsService:
# Add output to response
response.data = output.read()
# Set filname
file_name = 'export_{}_{}.xlsx'.format(name, datetime.now())
# Set filename
file_name = 'export_{}_{}.xlsx'.format(name, datetime.utcnow())
# HTTP headers for forcing file download
response_headers = Headers({

View File

@ -31,7 +31,7 @@ class ImportService:
self.import_interval_minutes = app.config['IMPORT_INTERVAL_MINUTES']
def run_backup(self, load_admin=True, full_backup=False):
date_started = datetime.datetime.now()
date_started = datetime.datetime.utcnow()
exportables = self.get_export_list(full_backup)
# Note: We request data THEN create the next log. We depend on this order to get data since
# the last log was recorded, but besure and set the start date from the moment this was called.

View File

@ -1,6 +1,5 @@
import datetime
from dateutil.tz import tzutc
from sqlalchemy import func
from sqlalchemy.ext.declarative import declared_attr

View File

@ -1,6 +1,5 @@
import datetime
from dateutil.tz import tzutc
from sqlalchemy import func
from sqlalchemy.ext.declarative import declared_attr

View File

@ -1,6 +1,5 @@
import datetime
from dateutil.tz import tzutc
from sqlalchemy import func
from app import db

View File

@ -1,7 +1,6 @@
import datetime
import enum
from dateutil.tz import tzutc
from sqlalchemy import func
from app import db

View File

@ -1,6 +1,5 @@
import datetime
from dateutil.tz import tzutc
from sqlalchemy import func
from app import db

View File

@ -1,6 +1,5 @@
import datetime
from dateutil.tz import tzutc
from sqlalchemy import func
from app import db

View File

@ -44,7 +44,9 @@ class User(db.Model):
def get_self_participant(self):
if len(self.participants) > 0:
return next(p for p in self.participants if "self" in p.relationship.name)
for p in self.participants:
if "self" in p.relationship.name:
return p
def self_registration_complete(self):
if self.get_self_participant() is not None:
@ -113,3 +115,16 @@ class User(db.Model):
for p in self.participants:
if p.contact:
return {'name': p.get_name(), 'relationship': p.relationship.name, 'contact': p.contact}
def created_password(self):
return self.password is not None
def identity(self):
if len(self.participants) > 0:
return self.get_self_participant().relationship.name
else:
return 'Not set'
def percent_self_registration_complete(self):
if len(self.participants) > 0:
return self.get_self_participant().get_percent_complete()

View File

@ -34,7 +34,7 @@ class AdminNoteEndpoint(flask_restful.Resource):
instance = db.session.query(AdminNote).filter_by(id=id).first()
updated, errors = self.schema.load(request_data, instance=instance)
if errors: raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now()
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
return self.schema.dump(updated)

View File

@ -49,7 +49,7 @@ def login_password():
auth_token = user.encode_auth_token().decode()
g.user = user
user.token = auth_token
user.last_login = datetime.datetime.now()
user.last_login = datetime.datetime.utcnow()
db.session.add(user)
db.session.commit()
return schema.jsonify(user)
@ -101,7 +101,7 @@ def reset_password():
user.token_url = ''
user.email_verified = True
user.password = password
user.last_login = datetime.datetime.now()
user.last_login = datetime.datetime.utcnow()
db.session.add(user)
db.session.commit()
auth_token = user.encode_auth_token().decode()

View File

@ -52,7 +52,7 @@ class EventEndpoint(flask_restful.Resource):
request_data['longitude'] = geocode['lng']
updated, errors = self.schema.load(request_data, instance=instance)
if errors: raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now()
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
elastic_index.update_document(updated, 'Event', latitude=updated.latitude, longitude=updated.longitude)

View File

@ -19,6 +19,8 @@ def get_date_arg():
after_date = None
if date_arg:
after_date = datetime.datetime.strptime(date_arg, ExportService.DATE_FORMAT)
print('after_date', after_date)
return after_date
@ -48,7 +50,7 @@ class ExportListEndpoint(flask_restful.Resource):
@requires_roles(Role.admin)
def get(self):
date_started = datetime.datetime.now()
date_started = datetime.datetime.utcnow()
info_list = ExportService.get_table_info(get_date_arg())
# Remove items that are not exportable, or that are identifying
@ -71,7 +73,7 @@ class ExportListEndpoint(flask_restful.Resource):
log = db.session.query(DataTransferLog).filter(DataTransferLog.type == 'export')\
.order_by(desc(DataTransferLog.last_updated)).limit(1).first()
if log is None: log = DataTransferLog(type="export", total_records=0)
log.last_updated = datetime.datetime.now()
log.last_updated = datetime.datetime.utcnow()
db.session.add(log)
db.session.commit()

View File

@ -79,7 +79,7 @@ class FlowQuestionnaireEndpoint(flask_restful.Resource):
flow=flow.name,
participant_id=questionnaire.participant_id,
user_id=g.user.id,
date_completed=datetime.datetime.now(),
date_completed=datetime.datetime.utcnow(),
time_on_task_ms=questionnaire.time_on_task_ms)
db.session.add(log)
db.session.commit()

View File

@ -30,7 +30,7 @@ class InvestigatorEndpoint(flask_restful.Resource):
instance = db.session.query(Investigator).filter_by(id=id).first()
updated, errors = self.schema.load(request_data, instance=instance)
if errors: raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now()
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
return self.schema.dump(updated)

View File

@ -54,7 +54,7 @@ class LocationEndpoint(flask_restful.Resource):
request_data['longitude'] = geocode['lng']
updated, errors = self.schema.load(request_data, instance=instance)
if errors: raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now()
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
elastic_index.update_document(updated, 'Location', latitude=updated.latitude, longitude=updated.longitude)

View File

@ -39,7 +39,7 @@ class ParticipantEndpoint(flask_restful.Resource):
raise RestException(RestException.UNRELATED_PARTICIPANT)
updated, errors = self.schema.load(request_data, instance=instance)
if errors: raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now()
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
return self.schema.dump(updated)

View File

@ -2,7 +2,6 @@ import datetime
import flask_restful
import os
from dateutil.tz import tzutc
from flask import request
from sqlalchemy.exc import IntegrityError
from app import app, db, RestException, auth
@ -59,7 +58,7 @@ class QuestionnaireEndpoint(flask_restful.Resource):
if errors:
raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now(tz=tzutc())
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
return schema.dump(updated)

View File

@ -56,7 +56,7 @@ class ResourceEndpoint(flask_restful.Resource):
instance = db.session.query(Resource).filter_by(id=id).first()
updated, errors = self.schema.load(request_data, instance=instance)
if errors: raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now()
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
elastic_index.update_document(updated, 'Resource')

View File

@ -76,7 +76,7 @@ class SearchEndpoint(flask_restful.Resource):
category = Category(name="Topics")
category.children = db.session.query(Category)\
.filter(Category.parent_id == None)\
.order_by(Category.name)\
.order_by(Category.name.desc())\
.all()
else:
c = category

View File

@ -37,7 +37,7 @@ class StudyEndpoint(flask_restful.Resource):
instance = db.session.query(Study).filter_by(id=id).first()
updated, errors = self.schema.load(request_data, instance=instance)
if errors: raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now()
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
elastic_index.update_document(updated, 'Study')

View File

@ -14,7 +14,7 @@ def logo(user_id, code):
email_log = EmailLog.query.filter_by(user_id=user_id, tracking_code=code).first()
if email_log:
email_log.viewed = True
email_log.date_viewed = datetime.datetime.now()
email_log.date_viewed = datetime.datetime.utcnow()
db.session.add(email_log)
db.session.commit()
return send_file("static/UVA_STAR-logo.png", mimetype='image/png')

View File

@ -50,7 +50,7 @@ class UserEndpoint(flask_restful.Resource):
instance = db.session.query(User).filter_by(id=id).first()
updated, errors = self.schema.load(request_data, instance=instance)
if errors: raise RestException(RestException.INVALID_OBJECT, details=errors)
updated.last_updated = datetime.datetime.now()
updated.last_updated = datetime.datetime.utcnow()
db.session.add(updated)
db.session.commit()
return self.schema.dump(updated)

View File

@ -398,7 +398,8 @@ class UserSchema(ModelSchema):
class Meta:
model = User
fields = ('id', 'last_updated', 'registration_date', 'last_login', 'email', 'password', 'role',
'permissions', 'participants', 'token', 'token_url', 'user_favorites', 'participant_count')
'permissions', 'participants', 'token', 'token_url', 'user_favorites', 'participant_count',
'created_password', 'identity', 'percent_self_registration_complete')
password = fields.String(load_only=True)
participants = fields.Nested(ParticipantSchema, dump_only=True, many=True)
participant_count = fields.Integer(required=False, allow_none=True)
@ -406,6 +407,7 @@ class UserSchema(ModelSchema):
id = fields.Integer(required=False, allow_none=True)
role = EnumField(Role)
permissions = fields.Method('get_permissions', dump_only=True)
percent_self_registration_complete = fields.Integer(required=False, allow_none=True)
def get_permissions(self, obj):
permissions = []

View File

@ -1,14 +1,13 @@
{% extends "base_email.html" %}
{% block content %}
<p style="font-family: sans-serif; font-size: 24px; font-weight: bold; margin: 0; Margin-bottom: 15px;">
Welcome to Autism DRIVE.
Welcome to Autism DRIVE. There are few more steps to complete your profile.
</p>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">
There are few more steps to complete your profile. Once you provide more information about yourself,
we will use this information to contact you about upcoming studies and emerging resources that may be of
interest based on details you provided. You will have additional opportunities to create profiles for your
children or dependents.
Once you provide more information about yourself, we will use this information to contact you about upcoming
studies and emerging resources that may be of interest based on details you provided. You will have additional
opportunities to create profiles for your children or dependents.
</p>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">
To complete your profile, continue <a href="{{ profile_url }}">here.</a>

View File

@ -1,13 +1,13 @@
{% extends "base_email.html" %}
{% block content %}
<p style="font-family: sans-serif; font-size: 24px; font-weight: bold; margin: 0; Margin-bottom: 15px;">
You have a new account on the Autism DRIVE.
Welcome to Autism DRIVE. Now you have a new account.
</p>
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">
Someone recently created an account for you on the Autism DRIVE. please click the button below to confirm your email
address and create your password. Please note that this password-setting link will expire in 24 hours. If that
happens you can set your password by requesting a password reset <a href="{{ forgot_pass_url }}">here.</a>
Please click the button below to confirm your email address and create your password. Please note that this
password-setting link will expire in 24 hours. If that happens you can set your password by requesting a
password reset <a href="{{ forgot_pass_url }}">here.</a>
</p>
<table border="0" cellpadding="0" cellspacing="0">

View File

@ -1,8 +1,7 @@
You have a new account on the Autism DRIVE.
Welcome to Autism DRIVE. Now you have a new account.
Someone recently created an account for you on the Autism DRIVE. To create your profile,
please place the following url in your browser to confirm your email address:
{{confirm_url}}
To create your profile, please place the following url in your browser to confirm your
email address: {{confirm_url}}
Please note that this password-setting link will expire in 24 hours. If that happens you can
set your password by requesting a password reset at: {{ forgot_pass_url }}

View File

@ -6,7 +6,7 @@ marshmallow_sqlalchemy==0.13.2
Flask-Migrate==2.1.1
Werkzeug==0.16.1
# DO NOT use pyscopg2-binary, it will seg fault in the worst possible way in production.
psycopg2==2.7.5
psycopg2==2.8.6
flask-bcrypt==0.7.1
flask-cors==3.0.3
flask-httpauth==3.2.4

View File

@ -154,6 +154,8 @@ THE SOFTWARE.
@angular/material/dialog
@angular/material/divider
@angular/material/expansion
@angular/material/form-field

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -32,6 +32,6 @@
<body>
<app-root></app-root>
<script src="runtime-es2015.858f8dd898b75fe86926.js" type="module"></script><script src="polyfills-es2015.696ad2b135b52dd9855f.js" type="module"></script><script src="runtime-es5.741402d1d47331ce975c.js" nomodule></script><script src="polyfills-es5.6ba1fc6238374eb32f77.js" nomodule></script><script src="main-es2015.21f1728e983bb93565b7.js" type="module"></script><script src="main-es5.ef0da0f86e6d77858e01.js" nomodule></script></body>
<script src="runtime-es2015.858f8dd898b75fe86926.js" type="module"></script><script src="polyfills-es2015.b119ee49bbbfb46089bb.js" type="module"></script><script src="runtime-es5.741402d1d47331ce975c.js" nomodule></script><script src="polyfills-es5.8b864a0703dc82fe4e63.js" nomodule></script><script src="main-es2015.41b2160c0dfceb047dbd.js" type="module"></script><script src="main-es5.ca6c55fbe0110fd2ec82.js" nomodule></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