def device_settings(study_id=None): study = Study.objects.get(pk=study_id) researcher = get_session_researcher() readonly = True if not researcher.check_study_admin( study_id) and not researcher.site_admin else False # if read only.... if request.method == 'GET': return render_template( "device_settings.html", study=study.as_unpacked_native_python(), settings=study.device_settings.as_unpacked_native_python(), readonly=readonly, ) if readonly: abort(403) params = { k: v for k, v in request.values.items() if not k.startswith("consent_section") } consent_sections = { k: v for k, v in request.values.items() if k.startswith("consent_section") } params = checkbox_to_boolean(CHECKBOX_TOGGLES, params) params = string_to_int(TIMER_VALUES, params) # the ios consent sections are a json field but the frontend returns something weird, # see the documentation in unflatten_consent_sections for details params["consent_sections"] = json.dumps( unflatten_consent_sections(consent_sections)) study.device_settings.update(**params) return redirect('/edit_study/{:d}'.format(study.id))
def inject_html_params(): # these variables will be accessible to every template rendering attached to the blueprint return { "allowed_studies": get_researcher_allowed_studies(), "is_admin": researcher_is_an_admin(), "session_researcher": get_session_researcher(), }
def interventions(study_id=None): study = Study.objects.get(pk=study_id) researcher = get_session_researcher() readonly = True if not researcher.check_study_admin( study_id) and not researcher.site_admin else False if request.method == 'GET': return render_template( 'study_interventions.html', study=study, interventions=study.interventions.all(), readonly=readonly, ) if readonly: abort(403) new_intervention = request.values.get('new_intervention', None) if new_intervention: intervention, _ = Intervention.objects.get_or_create( study=study, name=new_intervention) for participant in study.participants.all(): InterventionDate.objects.get_or_create(participant=participant, intervention=intervention) return redirect('/interventions/{:d}'.format(study.id))
def manage_credentials(): serializer = ApiKeySerializer(ApiKey.objects.filter(researcher=get_session_researcher()), many=True) return render_template( 'manage_credentials.html', is_admin=researcher_is_an_admin(), api_keys=sorted(serializer.data, reverse=True, key=lambda x: x['created_on']), )
def study_fields(study_id=None): study = Study.objects.get(pk=study_id) researcher = get_session_researcher() readonly = True if not researcher.check_study_admin( study_id) and not researcher.site_admin else False if request.method == 'GET': return render_template( 'study_custom_fields.html', study=study, fields=study.fields.all(), readonly=readonly, ) if readonly: return abort(403) new_field = request.values.get('new_field', None) if new_field: study_field, _ = StudyField.objects.get_or_create(study=study, field_name=new_field) for participant in study.participants.all(): ParticipantFieldValue.objects.create(participant=participant, field=study_field) return redirect('/study_fields/{:d}'.format(study.id))
def edit_researcher_page(researcher_pk): # Wow this got complex... session_researcher = get_session_researcher() edit_researcher = Researcher.objects.get(pk=researcher_pk) # site admins can edit study admins, but not other site admins. # (users do not edit their own passwords on this page.) editable_password = ( not edit_researcher.username == session_researcher.username and not edit_researcher.site_admin) # if the session researcher is not a site admin then we need to restrict password editing # to only researchers that are not study_admins anywhere. if not session_researcher.site_admin: editable_password = editable_password and not edit_researcher.is_study_admin( ) # edit_study_info is a list of tuples of (study relationship, whether that study is editable by # the current session admin, and the study itself.) visible_studies = session_researcher.get_visible_studies_by_name() if edit_researcher.site_admin: # if the session admin is a site admin then we can skip the complex logic edit_study_info = [(SITE_ADMIN, True, study) for study in visible_studies] else: # When the session admin is just a study admin then we need to determine if the study that # the session admin can see is also one they are an admin on so we can display buttons. administerable_studies = set( get_administerable_studies_by_name().values_list("pk", flat=True)) # We need the overlap of the edit_researcher studies with the studies visible to the session # admin, and we need those relationships for display purposes on the page. edit_study_relationship_map = { study_id: relationship.replace("_", " ").title() for study_id, relationship in edit_researcher.study_relations. filter(study__in=visible_studies).values_list( "study_id", "relationship") } # get the relevant studies, populate with relationship, editability, and the study. edit_study_info = [] for study in visible_studies.filter( pk__in=edit_study_relationship_map.keys()): edit_study_info.append(( edit_study_relationship_map[study.id], study.id in administerable_studies, study, )) return render_template( 'edit_researcher.html', edit_researcher=edit_researcher, edit_study_info=edit_study_info, all_studies=get_administerable_studies_by_name( ), # this is all the studies administerable by the user editable_password=editable_password, redirect_url='/edit_researcher/{:s}'.format(researcher_pk), is_self=edit_researcher.id == session_researcher.id, )
def get_session_researcher_study_ids(): """ Returns the appropriate study ids based on whether a user is a study or site admin """ session_researcher = get_session_researcher() if session_researcher.site_admin: return Study.objects.exclude(deleted=True).values_list("id", flat=True) else: return session_researcher.study_relations.filter( study__deleted=False).values_list("study__id", flat=True)
def get_administerable_studies_by_name(): """ Site admins see all studies, study admins see only studies they are admins on. """ researcher_admin = get_session_researcher() if researcher_admin.site_admin: studies = Study.get_all_studies_by_name() else: studies = researcher_admin.get_administered_studies_by_name() return studies
def get_administerable_researchers(): """ Site admins see all researchers, study admins see researchers on their studies. """ researcher_admin = get_session_researcher() if researcher_admin.site_admin: relevant_researchers = Researcher.filter_alphabetical() else: relevant_researchers = researcher_admin.get_administered_researchers_by_username( ) return relevant_researchers
def delete_researcher(researcher_id): # only site admins can delete researchers from the system. session_researcher = get_session_researcher() if not session_researcher.site_admin: return abort(403) try: researcher = Researcher.objects.get(pk=researcher_id) except Researcher.DoesNotExist: return abort(404) StudyRelation.objects.filter(researcher=researcher).delete() researcher.delete() return redirect('/manage_researchers')
def pipeline_download_page(): warn_researcher_if_hasnt_yet_generated_access_key(get_session_researcher()) # FIXME clean this up. # it is a bit obnoxious to get this data, we need to deduplcate it and then turn it back into a list tags_by_study = { study['id']: list( set( PipelineUploadTags.objects.filter( pipeline_upload__study__id=study['id']).values_list( "tag", flat=True))) for study in get_researcher_allowed_studies() } return render_template( "data_pipeline_web_form.html", tags_by_study=tags_by_study, downloadable_studies=get_researcher_allowed_studies(), )
def edit_custom_field(study_id=None): """Edits the name of a Custom field. Expects field_id anf edit_custom_field in request body""" study = Study.objects.get(pk=study_id) researcher = get_session_researcher() readonly = True if not researcher.check_study_admin( study_id) and not researcher.site_admin else False if readonly: return abort(403) field_id = request.values.get("field_id") new_field_name = request.values.get("edit_custom_field") if field_id: try: field = StudyField.objects.get(id=field_id) except StudyField.DoesNotExist: field = None if field and new_field_name: field.field_name = new_field_name field.save() return redirect('/study_fields/{:d}'.format(study.id))
def create_study(): # Only a SITE admin can create new studies. if not get_session_researcher().site_admin: return abort(403) if request.method == 'GET': studies = [ study.as_unpacked_native_python() for study in Study.get_all_studies_by_name() ] return render_template('create_study.html', studies=studies) name = request.form.get('name', '') encryption_key = request.form.get('encryption_key', '') is_test = request.form.get( 'is_test', "").lower() == 'true' # 'true' -> True, 'false' -> False duplicate_existing_study = request.form.get('copy_existing_study', None) == 'true' if not (len(name) <= 2**16) or escape(name) != name: raise Exception("safety check on new study name failed") try: new_study = Study.create_with_object_id(name=name, encryption_key=encryption_key, is_test=is_test) if duplicate_existing_study: old_study = Study.objects.get( pk=request.form.get('existing_study_id', None)) copy_existing_study(new_study, old_study) flash(f'Successfully created study {name}.', 'success') return redirect('/device_settings/{:d}'.format(new_study.pk)) except ValidationError as ve: # display message describing failure based on the validation error (hacky, but works.) for field, message in ve.message_dict.items(): flash(f'{field}: {message[0]}', 'danger') return redirect('/create_study')
def delete_field(study_id=None): """Deletes the specified Custom Field. Expects field in the request body.""" study = Study.objects.get(pk=study_id) researcher = get_session_researcher() readonly = True if not researcher.check_study_admin( study_id) and not researcher.site_admin else False if readonly: return abort(403) field = request.values.get('field', None) if field: try: study_field = StudyField.objects.get(study=study, id=field) except StudyField.DoesNotExist: study_field = None try: if study_field: study_field.delete() except ProtectedError: flash("This field can not be removed because it is already in use", 'danger') return redirect('/study_fields/{:d}'.format(study.id))
def edit_intervention(study_id=None): """ Edits the name of the intervention. Expects intervention_id and edit_intervention in the request body """ study = Study.objects.get(pk=study_id) researcher = get_session_researcher() readonly = True if not researcher.check_study_admin( study_id) and not researcher.site_admin else False if readonly: return abort(403) intervention_id = request.values.get('intervention_id', None) new_name = request.values.get('edit_intervention', None) if intervention_id: try: intervention = Intervention.objects.get(id=intervention_id) except Intervention.DoesNotExist: intervention = None if intervention and new_name: intervention.name = new_name intervention.save() return redirect('/interventions/{:d}'.format(study.id))
def delete_intervention(study_id=None): """Deletes the specified Intervention. Expects intervention in the request body.""" study = Study.objects.get(pk=study_id) researcher = get_session_researcher() readonly = True if not researcher.check_study_admin( study_id) and not researcher.site_admin else False if readonly: return abort(403) intervention_id = request.values.get('intervention') if intervention_id: try: intervention = Intervention.objects.get(id=intervention_id) except Intervention.DoesNotExist: intervention = None try: if intervention: intervention.delete() except ProtectedError: flash( "This Intervention can not be removed because it is already in use", 'danger') return redirect('/interventions/{:d}'.format(study.id))
def data_api_web_form_page(): warn_researcher_if_hasnt_yet_generated_access_key(get_session_researcher()) return render_template("data_api_web_form.html", ALL_DATA_STREAMS=ALL_DATA_STREAMS)
def disable_api_key(): form = DisableApiKeyForm(request.values) if not form.is_valid(): return redirect("/manage_credentials") api_key_id = request.values["api_key_id"] api_key_query = ApiKey.objects.filter(access_key_id=api_key_id).filter(researcher=get_session_researcher()) if not api_key_query.exists(): flash(Markup("No matching API key found to disable"), 'warning') return redirect("/manage_credentials") api_key = api_key_query[0] if not api_key.is_active: flash("That API key has already been disabled: %s" % api_key_id, 'warning') return redirect("/manage_credentials") api_key.is_active = False api_key.save() # flash("The API key %s is now disabled" % str(api_key.access_key_id), 'warning') return redirect("/manage_credentials")