Skip to content

Commit

Permalink
Merge 769ad1d into 7915dea
Browse files Browse the repository at this point in the history
  • Loading branch information
wasade committed Jul 29, 2019
2 parents 7915dea + 769ad1d commit 3d74428
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 17 deletions.
42 changes: 41 additions & 1 deletion amgut/handlers/add_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
from wtforms import (Form, SelectField, DateField, DateTimeField, TextField,
HiddenField, validators)
from tornado.web import authenticated
from tornado import escape
from future.utils import viewitems

from amgut.connections import ag_data
from amgut.lib.util import survey_vioscreen
from amgut.handlers.base_handlers import BaseHandler
from amgut import media_locale

Expand All @@ -21,6 +23,37 @@ class LogSample(Form):
notes = TextField('notes')


class AddHumanFFQHandler(BaseHandler):
@authenticated
def post(self):
self.redirect(media_locale['SITEBASE'] + '/authed/portal/')

def get(self):
ag_login_id = ag_data.get_user_for_kit(self.current_user)
barcode = self.get_secure_cookie('barcode')
participant_name = self.get_secure_cookie('participant_name')
self.clear_cookie('barcode')
self.clear_cookie('participant_name')

if barcode is None or participant_name is None:
self.set_status(404)
self.redirect(media_locale['SITEBASE'] + '/authed/portal/')
return
else:
barcode = escape.json_decode(barcode)
participant_name = escape.json_decode(participant_name)

new_survey_id = ag_data.get_new_survey_id()
ag_data.associate_barcode_to_survey_id(ag_login_id, participant_name,
barcode, new_survey_id)
ag_data.updateVioscreenStatus(new_survey_id, 0) # 0 -> not started
dat = survey_vioscreen(new_survey_id, None, None)

self.render('human_sample_specific_survey.html',
skid=self.current_user,
surveys=[dat])


class AddSample(BaseHandler):
_sample_sites = []
page_type = ''
Expand Down Expand Up @@ -74,7 +107,14 @@ def post(self):
env_sampled, sample_date,
sample_time, participant_name, notes)

self.redirect(media_locale['SITEBASE'] + '/authed/portal/')
if sample_site is None or self.page_type != 'add_sample_human':
self.redirect(media_locale['SITEBASE'] + '/authed/portal/')
else:
self.set_secure_cookie('participant_name',
escape.json_encode(participant_name))
self.set_secure_cookie('barcode', escape.json_encode(barcode))
url = media_locale['SITEBASE'] + '/authed/add_sample_human_ffq/'
self.redirect(url)

@authenticated
def get(self):
Expand Down
14 changes: 7 additions & 7 deletions amgut/handlers/animal_survey.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from urllib import urlencode
from json import dumps
import binascii
import os

from tornado.web import authenticated
from tornado.escape import url_escape
Expand Down Expand Up @@ -38,16 +36,18 @@ def post(self):
animal_survey_id = self.get_argument('survey_id', None)
sitebase = media_locale['SITEBASE']

form = self.animal_survey()
form.process(data=self.request.arguments)
data = {'questions': form.data}
participant_name = form['Pet_Information_127_0'].data[0]

if not animal_survey_id:
animal_survey_id = binascii.hexlify(os.urandom(8))
animal_survey_id = ag_data.get_new_survey_id()

new_survey = True
else:
new_survey = False

form = self.animal_survey()
form.process(data=self.request.arguments)
data = {'questions': form.data}
participant_name = form['Pet_Information_127_0'].data[0]
# If the participant already exists, stop them outright
if new_survey and \
ag_data.check_if_consent_exists(ag_login_id, participant_name):
Expand Down
4 changes: 1 addition & 3 deletions amgut/handlers/new_participant.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import binascii
import os
from json import dumps

from tornado.web import authenticated
Expand Down Expand Up @@ -57,7 +55,7 @@ def post(self):
self.redirect(url)
return

human_survey_id = binascii.hexlify(os.urandom(8))
human_survey_id = ag_data.get_new_survey_id()

consent = {'participant_name': participant_name,
'participant_email': participant_email,
Expand Down
4 changes: 1 addition & 3 deletions amgut/handlers/secondary_survey.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from urllib import urlencode
from tornado.escape import url_unescape
from json import dumps
import binascii
import os

from tornado.web import authenticated

Expand Down Expand Up @@ -54,7 +52,7 @@ def post(self):
sitebase = media_locale['SITEBASE']

if not survey_id:
survey_id = binascii.hexlify(os.urandom(8))
survey_id = ag_data.get_new_survey_id()

sec_survey = self.sec_surveys[survey_type]
survey_class = make_survey_class(sec_survey.groups[0],
Expand Down
80 changes: 78 additions & 2 deletions amgut/lib/data_access/ag_data_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import bcrypt
import numpy as np
import pandas as pd
import random
import string

from amgut.lib.data_access.sql_connection import TRN

Expand Down Expand Up @@ -540,10 +542,16 @@ def logParticipantSample(self, ag_login_id, barcode, sample_site,
participant_name, notes):
with TRN:
if sample_site is not None:
# Get survey id
# Get non timepoint specific survey IDs.
# As of this comment, a non timepoint specific survey is
# implicit, and currently limited to vioscreen FFQs
# We do not want to associate timepoint specific surveys
# with the wrong barcode
sql = """SELECT survey_id
FROM ag_login_surveys
WHERE ag_login_id = %s AND participant_name = %s"""
WHERE ag_login_id = %s
AND participant_name = %s
AND vioscreen_status is null"""

TRN.add(sql, (ag_login_id, participant_name))
survey_ids = TRN.execute_fetchindex()
Expand Down Expand Up @@ -634,6 +642,45 @@ def getHumanParticipants(self, ag_login_id):
TRN.add(sql, [ag_login_id, 1])
return TRN.execute_fetchflatten()

def associate_barcode_to_survey_id(self, ag_login_id, participant_name,
barcode, survey_id):
"""Associate a barcode to an existing survey ID
Parameters
----------
ag_login_id : str
A valid AG login ID
participant_name : str
The name of a participant associated with the login
barcode : str
A valid barcode associated with the login
survey_id : str
A valid survey ID
"""
with TRN:
# first let's sanity check things
sql = """SELECT ag_login_id, participant_name, barcode
FROM ag.ag_login_surveys
JOIN ag.source_barcodes_surveys USING(survey_id)
WHERE ag_login_id=%s
AND participant_name=%s
AND barcode=%s"""
TRN.add(sql, [ag_login_id, participant_name, barcode])
results = TRN.execute_fetchflatten()

if len(results) == 0:
raise ValueError("Unexpected name and ID relation")

sql = """INSERT INTO ag_login_surveys
(ag_login_id, survey_id, participant_name)
VALUES (%s, %s, %s)"""
TRN.add(sql, [ag_login_id, survey_id, participant_name])

sql = """INSERT INTO ag.source_barcodes_surveys
(survey_id, barcode)
VALUES (%s, %s)"""
TRN.add(sql, [survey_id, barcode])

def updateVioscreenStatus(self, survey_id, status):
with TRN:
sql = """UPDATE ag_login_surveys
Expand Down Expand Up @@ -1102,6 +1149,35 @@ def get_participants_surveys(self, ag_login_id, participant_name,
raise ValueError("No survey IDs found!")
return surveys

def get_new_survey_id(self):
"""Return a new unique survey ID
Notes
-----
This is *NOT* atomic. At the creation of this method, it is not
possible to store a survey ID without first storing consent. That
would require a fairly large structural change. This method replaces
the existing non-atomic logic, with logic that is much safer but not
perfect.
Returns
-------
str
A unique survey ID
"""
alpha = string.ascii_letters + string.digits
with TRN:
sql = """SELECT survey_id
FROM ag.ag_login_surveys"""
TRN.add(sql)
existing = {i[0] for i in TRN.execute()[0]}

new_id = ''.join([random.choice(alpha) for i in range(16)])
while new_id in existing:
new_id = ''.join([random.choice(alpha) for i in range(16)])

return new_id

def get_countries(self):
"""
Returns
Expand Down
83 changes: 83 additions & 0 deletions amgut/lib/data_access/test/test_ag_data_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,16 @@ def test_getConsentNotPresent(self):
with self.assertRaises(ValueError):
self.ag_data.getConsent("42")

def test_get_new_survey_id(self):
with TRN:
sql = """SELECT survey_id FROM ag.ag_login_surveys"""
TRN.add(sql)
results = {i[0] for i in TRN.execute()[0]}

for i in range(100):
new_id = self.ag_data.get_new_survey_id()
self.assertNotIn(new_id, results)

def test_logParticipantSample_badinfo(self):
# bad ag_login_id
with self.assertRaises(ValueError):
Expand All @@ -333,6 +343,79 @@ def test_logParticipantSample_badinfo(self):
'stool', None, datetime.date(2015, 9, 27),
datetime.time(15, 54), 'BADNAME', '')

@rollback
def test_associate_barcode_to_survey_id(self):
name = 'Name - öV2NA"+u+$'
id_ = '1835e434-b4a4-4f0d-a781-25ba54070c0b'
barcode = '000033139'

with TRN:
self.ag_data.associate_barcode_to_survey_id(id_, name, barcode,
'xyz')
self.ag_data.associate_barcode_to_survey_id(id_, name, barcode,
'yzx')
self.ag_data.associate_barcode_to_survey_id(id_, name, barcode,
'foo')
sql = """SELECT survey_id
FROM ag.source_barcodes_surveys
WHERE barcode = %s"""

TRN.add(sql, [barcode])
obs = set(TRN.execute_fetchflatten())
exp = {'xyz', 'yzx', 'foo'}
self.assertTrue(exp.issubset(obs))

with self.assertRaises(ValueError):
self.ag_data.associate_barcode_to_survey_id(id_, name + 'foo',
barcode, 'xyz')

with self.assertRaises(ValueError):
self.ag_data.associate_barcode_to_survey_id(id_, name,
'000004216', 'xyz')

@rollback
def test_logParticipantSample_avoid_vios(self):
participant_name = 'Name - öV2NA"+u+$'
ag_login_id = '1835e434-b4a4-4f0d-a781-25ba54070c0b'
barcode = '000033139'

a = self.ag_data.get_new_survey_id()
b = 'not a vios'

self.ag_data.associate_barcode_to_survey_id(ag_login_id,
participant_name,
barcode, a)

with TRN:
sql = """INSERT INTO ag_login_surveys
(ag_login_id, survey_id, participant_name)
VALUES (%s, %s, %s)"""
TRN.add(sql, [ag_login_id, b, participant_name])

self.ag_data.updateVioscreenStatus(a, 0)

with TRN:
sql = """SELECT survey_id FROM ag.source_barcodes_surveys
WHERE barcode = %s"""
TRN.add(sql, [barcode])
obs = set(TRN.execute_fetchflatten())
self.assertEqual(len(obs), 2)
self.assertTrue(a in obs)
self.assertFalse(b in obs)

self.ag_data.logParticipantSample(
ag_login_id, barcode, 'Stool', None, datetime.date(2015, 9, 27),
datetime.time(15, 54), participant_name, '')

with TRN:
sql = """SELECT survey_id FROM ag.source_barcodes_surveys
WHERE barcode = %s"""
TRN.add(sql, [barcode])
obs = set(TRN.execute_fetchflatten())
self.assertEqual(len(obs), 3)
self.assertTrue(a in obs)
self.assertTrue(b in obs)

@rollback
def test_logParticipantSample_tomultiplesurveys(self):
ag_login_id = '5a10ea3e-9c7f-4ec3-9e96-3dc42e896668'
Expand Down
2 changes: 1 addition & 1 deletion amgut/lib/data_access/test/test_survey.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def insert_data(self):
c = ascii_letters + '1234567890'
notes_test = ''.join([choice(c) for i in range(40)])

survey_id = '817ff95701f4dd10'
survey_id = ag_data.get_new_survey_id()
survey = Survey(2)
consent = {
'login_id': 'eba20873-b7db-33cc-e040-8a80115d392c',
Expand Down
1 change: 1 addition & 0 deletions amgut/lib/locale_data/english_gut.py
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,7 @@
_HUMAN_SURVEY_COMPLETED = {
'AVAILABLE_SURVEYS': 'Below are a few additional surveys that you may be interested in completing. There is no requirement to take these surveys, and your decision does not affect your involvement in the project in any way.',
'COMPLETED_HEADER': 'Congratulations!',
'COMPLETED_SAMPLE_ASSIGNED_HEADER': 'Thank you for logging your sample!',
'COMPLETED_TEXT': 'You are now an enrolled participant in the %(PROJECT_TITLE)s! As a reminder, you still need to associate your sample(s) with the survey to complete the process. If your sample(s) are not associated with a survey, we will not be able to process them.' % media_locale,
'SURVEY_ASD': '<h3 style="text-align: center"><a href="%s" target="_blank">ASD-Cohort survey</a></h3><a href="http://www.anl.gov/contributors/jack-gilbert">Dr. Jack Gilbert</a> is exploring the relationship between gut dysbiosis and Autism Spectrum Disorders, and in conjunction with the {0}, we started an ASD-Cohort study. This additional survey contains questions specific to that cohort, but it is open to any participant to take if they so choose.'.format(AMGUT_CONFIG.project_name),
'SURVEY_VIOSCREEN': '<h3 style="text-align: center"><a href="%s">Dietary Survey</a></h3>The {0} and its sister projects are very interested in diet. If you\'d like to provide additional detail about your diet, please click above to take a detailed diet survey (known as an Food Frequency Questionnaire). This is a validated FFQ, and is the one used by the Mayo Clinic.'.format(AMGUT_CONFIG.project_name),
Expand Down
20 changes: 20 additions & 0 deletions amgut/templates/human_sample_specific_survey.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{% extends sitebase.html %}
{% block content %}
{% from amgut import text_locale, media_locale %}
{% set tl = text_locale['human_survey_completed.html'] %}
<h2>{% raw tl['COMPLETED_SAMPLE_ASSIGNED_HEADER'] %}</h2>
<div style="text-align: left; padding-left: 10px; padding-right: 10px">
<p>{% raw tl['AVAILABLE_SURVEYS'] %}</p>

{% for payload in surveys %}
<p>{% raw payload %}</p>
{% end %}
</div>
<form action='{% raw media_locale["SITEBASE"] %}/authed/human_survey_completed/' id="human_survey_completed" method="POST">
<input type="hidden" name="go_home" value="Done">
<input type="submit" value="Back to portal"></td>
</form>
<br>
<br/>
{% end %}

2 changes: 2 additions & 0 deletions amgut/webserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from amgut.handlers.human_survey_completed import HumanSurveyCompletedHandler
from amgut.handlers.vioscreen import VioscreenPassthroughHandler
from amgut.handlers.add_sample import (AddHumanSampleHandler,
AddHumanFFQHandler,
AddGeneralSampleHandler,
AddAnimalSampleHandler)
from amgut.handlers.new_participant import NewParticipantHandler
Expand Down Expand Up @@ -88,6 +89,7 @@ def __init__(self):
PersonalMicrobiomeOverviewHandler),
(r"/authed/portal/", PortalHandler),
(r"/authed/add_sample_human/", AddHumanSampleHandler),
(r"/authed/add_sample_human_ffq/", AddHumanFFQHandler),
(r"/authed/add_sample_animal/", AddAnimalSampleHandler),
(r"/authed/add_sample_general/", AddGeneralSampleHandler),
(r"/authed/change_password/", ChangePasswordHandler),
Expand Down

0 comments on commit 3d74428

Please sign in to comment.