Example #1
0
def validate_populate_imports(request, records):
    """
    Return list of errors, imports and forms

    :request: request obj
    :records: a list of csv row data

    :return: errors, imports, and forms for template rendering
    """
    errors, imports, forms = ([], [], [])
    for record in records:
        schema_dict = {
            'name': record['schema_name'],
            'title': record['schema_title'],
            'publish_date': record['publish_date'].strftime('%Y-%m-%d')
        }

        FormForm = FormFormFactory(context=None, request=request)
        form_form = FormForm.from_json(schema_dict)

        if not form_form.validate():
            schema_error = {}
            schema_error['errors'] = wtferrors(form_form)
            schema_error['schema_name'] = schema_dict['name']
            schema_error['schema_title'] = schema_dict['title']
            schema_error['name'] = 'N/A'
            schema_error['title'] = 'N/A'
            errors.append(schema_error)

        else:
            schema = datastore.Schema.from_json(schema_dict)

            if schema.to_json() not in forms:
                forms.append(schema.to_json())

            choices = parse.get_choices(record['choices'])
            # below needed because we are calling from_json on record
            record['choices'] = choices
            FieldForm = FieldFormFactory(context=schema, request=request)
            form = FieldForm.from_json(record)

            if not form.validate():
                output = log_errors(wtferrors(form), record)
                errors.append(output)

            else:
                imports.append((datastore.Attribute(
                    name=record['name'],
                    title=record['title'],
                    description=record['description'],
                    is_required=record['is_required'],
                    is_collection=record['is_collection'],
                    is_private=record['is_private'],
                    type=record['type'],
                    order=record['order'],
                    choices=choices
                ), schema))

    return errors, imports, forms
Example #2
0
def edit_json(context, request):
    """
    Add/Edit form for fields.
    """
    check_csrf_token(request)

    db_session = request.db_session

    form = FieldFormFactory(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    is_new = isinstance(context, models.AttributeFactory)

    if not is_new:
        attribute = context
    else:
        # Add the attribute and temporarily set to large display order
        attribute = datastore.Attribute(schema=context.__parent__, order=-1)
        db_session.add(attribute)

    attribute.apply(form.data)

    if is_new:
        # now we can move the attribute
        move_json(attribute, request)

    db_session.flush()

    return view_json(attribute, request)
Example #3
0
def edit_json(context, request):
    """
    Adds/Edits a external service record.

    If the operation was successful, a redirect to the new record details
    will be returns. Otherwise a json record of validation errors will
    be returned.
    """
    check_csrf_token(request)
    db_session = request.db_session

    form = ExternalServiceForm(context, request).from_json(request.json_body)

    if not form.validate():
        return HTTPBadRequest(json={'errors': wtferrors(form)})

    if isinstance(context, models.ExternalServiceFactory):
        study = context.__parent__
        service = models.ExternalService(study=study)
    else:
        study = context.study
        service = context

    service.name = slugify(form.title.data)
    service.title = form.title.data
    service.description = form.description.data
    service.url_template = form.url_template.data
    db_session.flush()

    success_url = request.route_path('studies.external_service',
                                     study=study.name,
                                     service=service.name)

    return HTTPSeeOther(location=success_url)
Example #4
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    form = EnrollmentSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if isinstance(context, models.EnrollmentFactory):
        enrollment = models.Enrollment(
            patient=context.__parent__, study=form.study.data)
    else:
        enrollment = context

    enrollment.patient.modify_date = datetime.now()
    enrollment.consent_date = form.consent_date.data
    enrollment.latest_consent_date = form.latest_consent_date.data
    enrollment.reference_number = form.reference_number.data

    if not form.study.data.termination_schema:
        enrollment.termination_date = form.termination_date.data

    db_session.flush()
    return view_json(enrollment, request)
Example #5
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    form = StudySchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if isinstance(context, models.StudyFactory):
        study = models.Study()
        db_session.add(study)
    else:
        study = context

    study.name = slugify(form.title.data)
    study.title = form.title.data
    study.code = form.code.data
    study.short_title = form.short_title.data
    study.consent_date = form.consent_date.data
    study.termination_schema = form.termination_form.data
    study.is_randomized = form.is_randomized.data
    study.is_blinded = \
        None if not study.is_randomized else form.is_blinded.data
    study.randomization_schema = \
        None if not study.is_randomized else form.randomization_form.data

    db_session.flush()

    return view_json(study, request)
Example #6
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    form = StudySchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if isinstance(context, models.StudyFactory):
        study = models.Study()
        db_session.add(study)
    else:
        study = context

    study.name = slugify(form.title.data)
    study.title = form.title.data
    study.code = form.code.data
    study.short_title = form.short_title.data
    study.consent_date = form.consent_date.data
    study.termination_schema = form.termination_form.data
    study.is_randomized = form.is_randomized.data
    study.is_blinded = \
        None if not study.is_randomized else form.is_blinded.data
    study.randomization_schema = \
        None if not study.is_randomized else form.randomization_form.data

    db_session.flush()

    return view_json(study, request)
Example #7
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    form = EnrollmentSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if isinstance(context, models.EnrollmentFactory):
        enrollment = models.Enrollment(patient=context.__parent__,
                                       study=form.study.data)
    else:
        enrollment = context

    enrollment.patient.modify_date = datetime.now()
    enrollment.consent_date = form.consent_date.data
    enrollment.latest_consent_date = form.latest_consent_date.data
    enrollment.reference_number = form.reference_number.data

    if not form.study.data.termination_schema:
        enrollment.termination_date = form.termination_date.data

    db_session.flush()
    return view_json(enrollment, request)
Example #8
0
def bulk_delete_json(context, request):
    """
    Deletes forms in bulk
    """
    check_csrf_token(request)
    db_session = request.db_session

    class DeleteForm(Form):
        forms = wtforms.FieldList(
            ModelField(db_session=db_session, class_=datastore.Entity),
            validators=[wtforms.validators.DataRequired()])

    form = DeleteForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    entity_ids = [entity.id for entity in form.forms.data]

    external = context.__parent__.__tablename__
    key = context.__parent__.id

    (db_session.query(datastore.Entity).filter(
        datastore.Entity.id.in_(
            db_session.query(datastore.Context.entity_id).filter(
                datastore.Context.entity_id.in_(entity_ids)).filter(
                    datastore.Context.external == external).filter(
                        datastore.Context.key == key))).delete('fetch'))

    db_session.flush()

    return HTTPOk()
Example #9
0
def edit_schedule_json(context, request):
    """
    Enables/Disables a form for a cycle

    Request body json parameters:
        schema -- name of the schema (will used study-enabled versions)
        cycle -- cycle id
        enabled -- true/false
    """
    check_csrf_token(request)
    db_session = request.db_session

    def check_cycle_association(form, field):
        if field.data.study != context:
            raise wtforms.ValidationError(
                request.localizer.translate(_(u'Not a valid choice')))

    def check_form_association(form, field):
        query = (db_session.query(datastore.Schema).join(
            models.study_schema_table).filter(
                datastore.Schema.name == field.data).filter(
                    models.study_schema_table.c.study_id == context.id))
        (exists, ) = db_session.query(query.exists()).one()
        if not exists:
            raise wtforms.ValidationError(
                request.localizer.translate(_(u'Not a valid choice')))

    class ScheduleForm(Form):
        schema = wtforms.StringField(validators=[
            wtforms.validators.InputRequired(), check_form_association
        ])
        cycle = ModelField(db_session=db_session,
                           class_=models.Cycle,
                           validators=[
                               wtforms.validators.InputRequired(),
                               check_cycle_association
                           ])
        enabled = wtforms.BooleanField()

    form = ScheduleForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    schema_name = form.schema.data
    cycle = form.cycle.data
    enabled = form.enabled.data

    study_items = set(i for i in context.schemata if i.name == schema_name)
    cycle_items = set(i for i in cycle.schemata if i.name == schema_name)

    if enabled:
        # Match cycle schemata to the study's schemata for the given name
        cycle.schemata.difference_update(cycle_items - study_items)
        cycle.schemata.update(study_items)
    else:
        cycle.schemata.difference_update(study_items | cycle_items)

    return HTTPOk()
Example #10
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    is_new = isinstance(context, models.PatientFactory)
    form = PatientSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if is_new:
        # if any errors occurr after this, this PID is essentially wasted
        patient = models.Patient(
            pid=six.text_type(generate(db_session, form.site.data.name)))
        db_session.add(patient)
    else:
        patient = context

    patient.site = form.site.data

    if form.references.data:
        inputs = dict(
            ((r['reference_type'].id, r['reference_number']), r)
            for r in form.references.data)

        for r in patient.references:
            try:
                # Remove already-existing values from the inputs
                del inputs[(r.reference_type.id, r.reference_number)]
            except KeyError:
                # References not in the inputs indicate they have been removed
                db_session.delete(r)

        for r in six.itervalues(inputs):
            db_session.add(models.PatientReference(
                patient=patient,
                reference_type=r['reference_type'],
                reference_number=r['reference_number']))

    # Add the patient forms
    if is_new:
        schemata_query = (
            db_session.query(datastore.Schema)
            .join(models.patient_schema_table))
        pending_entry = (
            db_session.query(datastore.State)
            .filter_by(name=u'pending-entry')
            .one())
        for schema in schemata_query:
            patient.entities.add(datastore.Entity(
                schema=schema,
                state=pending_entry
            ))

    db_session.flush()
    db_session.refresh(patient)

    return view_json(patient, request)
Example #11
0
def move_json(context, request):
    """
    Moves the field to the target section and display order within the form
    """
    check_csrf_token(request)

    db_session = request.db_session

    schema = context.schema

    def not_self(form, field):
        if field.data == context.name:
            raise wtforms.ValidationError(_(u'Cannot move value into itself'))

    def not_section(form, field):
        if (context.type == 'section'
                and schema.attributes[field.data].type == 'section'):
            raise wtforms.ValidationError(
                _(u'Nested sections are not supported'))

    class MoveForm(Form):
        target = wtforms.StringField(
            validators=[
                wtforms.validators.Optional(),
                wtforms.validators.AnyOf(
                    schema.attributes, message=_(u'Does not exist')),
                not_self,
                not_section])
        index = wtforms.IntegerField(
            validators=[wtforms.validators.NumberRange(min=0)])

    form = MoveForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if form.target.data:
        section = target = schema.attributes[form.target.data]
    else:
        target = schema
        section = None

    attributes = [a for a in target.itertraverse() if a != context]

    context.parent_attribute = section
    attributes.insert(form.index.data, context)

    # Apply new display orders before re-sorting the entire list
    for i, a in enumerate(attributes):
        a.order = i

    # We need to resort the fields to avoid ordering collisions
    for i, a in enumerate(schema.iterlist()):
        a.order = i

    db_session.flush()

    return HTTPOk()
def edit_json(context, request):
    check_csrf_token(request)

    db_session = request.db_session

    is_new = isinstance(context, models.ReferenceTypeFactory)

    def check_unique(form, field):
        query = (
            db_session.query(models.ReferenceType)
            .filter_by(name=field.data))
        if not is_new:
            query = query.filter(models.ReferenceType.id != context.id)
        exists = (
            db_session.query(sa.literal(True)).filter(query.exists()).scalar())
        if exists:
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'Already exists')))

    class ReferenceTypeForm(Form):
        name = wtforms.StringField(
            validators=[
                wtforms.validators.InputRequired(),
                check_unique])
        title = wtforms.StringField(
            validators=[
                wtforms.validators.InputRequired()])
        description = wtforms.TextAreaField(
            validators=[
                wtforms.validators.Optional()])
        reference_pattern = wtforms.StringField(
            validators=[
                wtforms.validators.Optional()])
        reference_hint = wtforms.StringField(
            validators=[
                wtforms.validators.Optional()])

    form = ReferenceTypeForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if is_new:
        reference_type = models.ReferenceType()
        db_session.add(reference_type)
    else:
        reference_type = context

    form.populate_obj(reference_type)

    db_session.flush()

    return view_json(reference_type, request)
Example #13
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    is_new = isinstance(context, models.PatientFactory)
    form = PatientSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if is_new:
        # if any errors occurr after this, this PID is essentially wasted
        patient = models.Patient(
            pid=six.text_type(generate(db_session, form.site.data.name)))
        db_session.add(patient)
    else:
        patient = context

    patient.site = form.site.data

    if form.references.data:
        inputs = dict(((r['reference_type'].id, r['reference_number']), r)
                      for r in form.references.data)

        for r in patient.references:
            try:
                # Remove already-existing values from the inputs
                del inputs[(r.reference_type.id, r.reference_number)]
            except KeyError:
                # References not in the inputs indicate they have been removed
                db_session.delete(r)

        for r in six.itervalues(inputs):
            db_session.add(
                models.PatientReference(
                    patient=patient,
                    reference_type=r['reference_type'],
                    reference_number=r['reference_number']))

    # Add the patient forms
    if is_new:
        schemata_query = (db_session.query(datastore.Schema).join(
            models.patient_schema_table))
        pending_entry = (db_session.query(
            datastore.State).filter_by(name=u'pending-entry').one())
        for schema in schemata_query:
            patient.entities.add(
                datastore.Entity(schema=schema, state=pending_entry))

    db_session.flush()
    db_session.refresh(patient)

    return view_json(patient, request)
def edit_json(context, request):
    check_csrf_token(request)

    db_session = request.db_session

    is_new = isinstance(context, models.ReferenceTypeFactory)

    def check_unique(form, field):
        query = (db_session.query(
            models.ReferenceType).filter_by(name=field.data))
        if not is_new:
            query = query.filter(models.ReferenceType.id != context.id)
        exists = (db_session.query(sa.literal(True)).filter(
            query.exists()).scalar())
        if exists:
            raise wtforms.ValidationError(
                request.localizer.translate(_(u'Already exists')))

    class ReferenceTypeForm(Form):
        name = wtforms.StringField(
            validators=[wtforms.validators.InputRequired(), check_unique])
        title = wtforms.StringField(
            validators=[wtforms.validators.InputRequired()])
        description = wtforms.TextAreaField(
            validators=[wtforms.validators.Optional()])
        reference_pattern = wtforms.StringField(
            validators=[wtforms.validators.Optional()])
        reference_hint = wtforms.StringField(
            validators=[wtforms.validators.Optional()])

    form = ReferenceTypeForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if is_new:
        reference_type = models.ReferenceType()
        db_session.add(reference_type)
    else:
        reference_type = context

    form.populate_obj(reference_type)

    db_session.flush()

    return view_json(reference_type, request)
Example #15
0
def edit_json(context, request):
    db_session = request.db_session
    check_csrf_token(request)

    form = SiteSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json=wtferrors(form))

    if isinstance(context, models.Site):
        site = context
    else:
        site = models.Site()
        db_session.add(site)

    site.name = form.name.data
    site.title = form.title.data
    db_session.flush()

    return view_json(site, request)
Example #16
0
def edit_json(context, request):
    db_session = request.db_session
    check_csrf_token(request)

    form = SiteSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json=wtferrors(form))

    if isinstance(context, models.Site):
        site = context
    else:
        site = models.Site()
        db_session.add(site)

    site.name = form.name.data
    site.title = form.title.data
    db_session.flush()

    return view_json(site, request)
Example #17
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    form = CycleSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if isinstance(context, models.CycleFactory):
        cycle = models.Cycle(study=context.__parent__)
        db_session.add(cycle)
    else:
        cycle = context

    cycle.name = six.text_type(slugify(form.title.data))
    cycle.title = form.title.data
    cycle.week = form.week.data
    cycle.is_interim = form.is_interim.data

    db_session.flush()

    return view_json(cycle, request)
def edit_json(context, request):
    """
    Adds/Edits a external service record.

    If the operation was successful, a redirect to the new record details
    will be returns. Otherwise a json record of validation errors will
    be returned.
    """
    check_csrf_token(request)
    db_session = request.db_session

    form = ExternalServiceForm(context, request).from_json(request.json_body)

    if not form.validate():
        return HTTPBadRequest(json={'errors': wtferrors(form)})

    if isinstance(context, models.ExternalServiceFactory):
        study = context.__parent__
        service = models.ExternalService(study=study)
    else:
        study = context.study
        service = context

    service.name = slugify(form.title.data)
    service.title = form.title.data
    service.description = form.description.data
    service.url_template = form.url_template.data
    db_session.flush()

    success_url = request.route_path(
        'studies.external_service',
        study=study.name,
        service=service.name
    )

    return HTTPSeeOther(location=success_url)
Example #19
0
def bulk_delete_json(context, request):
    """
    Deletes forms in bulk
    """
    check_csrf_token(request)
    db_session = request.db_session

    class DeleteForm(Form):
        forms = wtforms.FieldList(
            ModelField(
                db_session=db_session,
                class_=datastore.Entity),
            validators=[
                wtforms.validators.DataRequired()])

    form = DeleteForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    entity_ids = [entity.id for entity in form.forms.data]

    external = context.__parent__.__tablename__
    key = context.__parent__.id

    (db_session.query(datastore.Entity)
        .filter(datastore.Entity.id.in_(
            db_session.query(datastore.Context.entity_id)
            .filter(datastore.Context.entity_id.in_(entity_ids))
            .filter(datastore.Context.external == external)
            .filter(datastore.Context.key == key)))
        .delete('fetch'))

    db_session.flush()

    return HTTPOk()
Example #20
0
def add_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    def check_study_form(form, field):
        if isinstance(context.__parent__, models.Patient):
            query = (
                db_session.query(datastore.Schema)
                .join(models.study_schema_table)
                .join(models.Study)
                .filter(datastore.Schema.id == field.data.id))
            (exists,) = db_session.query(query.exists()).one()
            if not exists:
                raise wtforms.ValidationError(request.localizer.translate(
                    _(u'This form is not assosiated with a study')))
        elif isinstance(context.__parent__, models.Visit):
            query = (
                db_session.query(models.Visit)
                .filter(models.Visit.id == context.__parent__.id)
                .join(models.Visit.cycles)
                .join(models.Cycle.study)
                .filter(
                    models.Cycle.schemata.any(id=field.data.id)
                    | models.Study.schemata.any(id=field.data.id)))
            (exists,) = db_session.query(query.exists()).one()
            if not exists:
                raise wtforms.ValidationError(request.localizer.translate(
                    _('${schema} is not part of the studies for this visit'),
                    mapping={'schema': field.data.title}))

    class AddForm(Form):
        schema = ModelField(
            db_session=db_session,
            class_=datastore.Schema,
            validators=[
                wtforms.validators.InputRequired(),
                check_study_form])
        collect_date = DateField(
            validators=[
                wtforms.validators.InputRequired(),
                DateRange(min=date(1900, 1, 1)),
            ])

    form = AddForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    default_state = (
        db_session.query(datastore.State)
        .filter_by(name='pending-entry')
        .one())

    entity = datastore.Entity(
        schema=form.schema.data,
        collect_date=form.collect_date.data,
        state=default_state)

    if isinstance(context.__parent__, models.Visit):
        context.__parent__.entities.add(entity)
        context.__parent__.patient.entities.add(entity)
        next = request.current_route_path(
            _route_name='studies.visit_form', form=entity.id)
    elif isinstance(context.__parent__, models.Patient):
        context.__parent__.entities.add(entity)
        next = request.current_route_path(
            _route_name='studies.patient_form', form=entity.id)

    db_session.flush()

    request.session.flash(
        _('Successfully added new ${form}',
            mapping={'form': entity.schema.title}),
        'success')

    return {'__next__': next}
Example #21
0
def edit_schedule_json(context, request):
    """
    Enables/Disables a form for a cycle

    Request body json parameters:
        schema -- name of the schema (will used study-enabled versions)
        cycle -- cycle id
        enabled -- true/false
    """
    check_csrf_token(request)
    db_session = request.db_session

    def check_cycle_association(form, field):
        if field.data.study != context:
            raise wtforms.ValidationError(request.localizer.translate(_(
                u'Not a valid choice')))

    def check_form_association(form, field):
        query = (
            db_session.query(datastore.Schema)
            .join(models.study_schema_table)
            .filter(datastore.Schema.name == field.data)
            .filter(models.study_schema_table.c.study_id == context.id))
        (exists,) = db_session.query(query.exists()).one()
        if not exists:
            raise wtforms.ValidationError(request.localizer.translate(_(
                u'Not a valid choice')))

    class ScheduleForm(Form):
        schema = wtforms.StringField(
            validators=[
                wtforms.validators.InputRequired(),
                check_form_association])
        cycle = ModelField(
            db_session=db_session,
            class_=models.Cycle,
            validators=[
                wtforms.validators.InputRequired(),
                check_cycle_association])
        enabled = wtforms.BooleanField()

    form = ScheduleForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    schema_name = form.schema.data
    cycle = form.cycle.data
    enabled = form.enabled.data

    study_items = set(i for i in context.schemata if i.name == schema_name)
    cycle_items = set(i for i in cycle.schemata if i.name == schema_name)

    if enabled:
        # Match cycle schemata to the study's schemata for the given name
        cycle.schemata.difference_update(cycle_items - study_items)
        cycle.schemata.update(study_items)
    else:
        cycle.schemata.difference_update(study_items | cycle_items)

    return HTTPOk()
Example #22
0
def add_schema_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    def check_not_patient_schema(form, field):
        (exists,) = (
            db_session.query(
                db_session.query(datastore.Schema)
                .join(models.patient_schema_table)
                .filter(datastore.Schema.name == field.data)
                .exists())
            .one())
        if exists:
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'Already a patient form')))

    def check_not_randomization_schema(form, field):
        if (context.randomization_schema
                and context.randomization_schema.name == field.data):
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'Already a randomization form')))

    def check_not_termination_schema(form, field):
        if (context.termination_schema is not None
                and context.termination_schema.name == field.data):
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'Already a termination form')))

    def check_same_schema(form, field):
        versions = form.versions.data
        schema = form.schema.data
        invalid = [i.publish_date for i in versions if i.name != schema]
        if invalid:
            raise wtforms.ValidationError(request.localizer.translate(_(
                _(u'Incorrect versions: ${versions}'),
                mapping={'versions': ', '.join(map(str, invalid))})))

    def check_published(form, field):
        if field.data.publish_date is None:
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'Selected version is not published')))

    class SchemaManagementForm(Form):
        schema = wtforms.StringField(
            validators=[
                wtforms.validators.InputRequired(),
                check_not_patient_schema,
                check_not_randomization_schema,
                check_not_termination_schema])
        versions = wtforms.FieldList(
            ModelField(
                db_session=db_session,
                class_=datastore.Schema,
                validators=[
                    wtforms.validators.InputRequired(),
                    check_published]),
            validators=[
                wtforms.validators.DataRequired(),
                check_same_schema])

    form = SchemaManagementForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    old_items = set(i for i in context.schemata if i.name == form.schema.data)
    new_items = set(form.versions.data)

    # Remove unselected
    context.schemata.difference_update(old_items - new_items)

    # Add newly selected
    context.schemata.update(new_items)

    # Get a list of cycles to update
    cycles = (
        db_session.query(models.Cycle)
        .options(orm.joinedload(models.Cycle.schemata))
        .filter(models.Cycle.study == context)
        .filter(models.Cycle.schemata.any(name=form.schema.data)))

    # Also update available cycle schemata versions
    for cycle in cycles:
        cycle.schemata.difference_update(old_items - new_items)
        cycle.schemata.update(new_items)

    return form2json(new_items)[0]
Example #23
0
def checkout(context, request):
    """
    Generating a listing of available data for export.

    Because the exports can take a while to generate, this view serves
    as a "checkout" page so that the user can select which files they want.
    The actual exporting process is then queued in a another thread so the user
    isn't left with an unresponsive page.
    """
    db_session = request.db_session
    plans = request.registry.settings['studies.export.plans']
    exportables = exports.list_all(plans,
                                   request.db_session,
                                   include_rand=False)
    limit = request.registry.settings.get('app.export.limit')
    exceeded = limit is not None and query_exports(request).count() > limit
    errors = {}

    if request.method == 'POST' and check_csrf_token(request) and not exceeded:

        def check_exportable(form, field):
            if any(value not in exportables for value in field.data):
                raise wtforms.ValidationError(
                    request.localizer.translate(_(u'Invalid selection')))

        class CheckoutForm(Form):
            contents = wtforms.SelectMultipleField(
                choices=[(k, v.title) for k, v in six.iteritems(exportables)],
                validators=[wtforms.validators.InputRequired()])
            expand_collections = wtforms.BooleanField(default=False)
            use_choice_labels = wtforms.BooleanField(default=False)

        form = CheckoutForm(request.POST)

        if not form.validate():
            errors = wtferrors(form)
        else:
            task_id = six.text_type(str(uuid.uuid4()))
            db_session.add(
                models.Export(
                    name=task_id,
                    expand_collections=form.expand_collections.data,
                    use_choice_labels=form.use_choice_labels.data,
                    owner_user=(db_session.query(datastore.User).filter_by(
                        key=request.authenticated_userid).one()),
                    contents=[
                        exportables[k].to_json() for k in form.contents.data
                    ]))

            def apply_after_commit(success):
                if success:
                    tasks.make_export.apply_async(args=[task_id],
                                                  task_id=task_id,
                                                  countdown=4)

            # Avoid race-condition by executing the task after succesful commit
            transaction.get().addAfterCommitHook(apply_after_commit)

            msg = _(u'Your request has been received!')
            request.session.flash(msg, 'success')

            next_url = request.route_path('studies.exports_status')
            return HTTPFound(location=next_url)

    return {
        'errors': errors,
        'exceeded': exceeded,
        'limit': limit,
        'exportables': exportables
    }
Example #24
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session
    is_new = isinstance(context, models.VisitFactory)
    form = VisitSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if is_new:
        visit = models.Visit(patient=context.__parent__)
        db_session.add(visit)
    else:
        visit = context

    visit.patient.modify_date = datetime.now()
    visit.visit_date = form.visit_date.data

    # Set the entire list and let sqlalchemy prune the orphans
    visit.cycles = form.cycles.data

    db_session.flush()

    # TODO: hard coded for now, will be removed when workflows are in place
    default_state = (db_session.query(
        datastore.State).filter_by(name='pending-entry').one())

    # Update collect date for those forms that were never modified
    if not is_new:
        for entity in visit.entities:
            if entity.state == default_state \
                    and entity.collect_date != visit.visit_date:
                entity.collect_date = visit.visit_date

    if form.include_forms.data:

        relative_schema = (
            db_session.query(
                datastore.Schema.id.label('id'),
                datastore.Schema.name.label('name'),
                datastore.Schema.publish_date.label('publish_date'),
                sa.func.row_number().over(
                    partition_by=datastore.Schema.name,
                    order_by=(
                        # Rank by versions before the visit date or closest
                        (datastore.Schema.publish_date <= visit.visit_date
                         ).desc(),
                        sa.func.abs(datastore.Schema.publish_date -
                                    visit.visit_date).asc()),
                ).label('row_number')).join(models.Cycle.schemata).filter(
                    models.Cycle.id.in_([
                        c.id for c in visit.cycles
                    ])).filter(datastore.Schema.retract_date == sa.null()).cte(
                        'relative_schema'))

        schemata_query = (db_session.query(datastore.Schema).join(
            relative_schema,
            relative_schema.c.id == datastore.Schema.id).filter(
                relative_schema.c.row_number == 1))

        # Ignore already-added schemata
        if isinstance(context, models.Visit) and visit.entities:
            schemata_query = schemata_query.filter(~datastore.Schema.name.in_(
                [entity.schema.name for entity in visit.entities]))

        for schema in schemata_query:
            entity = datastore.Entity(schema=schema,
                                      collect_date=visit.visit_date,
                                      state=default_state)
            visit.patient.entities.add(entity)
            visit.entities.add(entity)

    # Lab might not be enabled on a environments, check first
    if form.include_specimen.data and db_session.bind.has_table('specimen'):
        from occams_lims import models as lab
        drawstate = (db_session.query(
            lab.SpecimenState).filter_by(name=u'pending-draw').one())
        location_id = visit.patient.site.lab_location.id
        for cycle in visit.cycles:
            if cycle in form.cycles.data:
                for specimen_type in cycle.specimen_types:
                    db_session.add(
                        lab.Specimen(patient=visit.patient,
                                     cycle=cycle,
                                     specimen_type=specimen_type,
                                     state=drawstate,
                                     collect_date=visit.visit_date,
                                     location_id=location_id,
                                     tubes=specimen_type.default_tubes))

    db_session.flush()

    return view_json(visit, request)
Example #25
0
def forms_add_json(context, request):
    """
    Updates the available patient forms
    """
    check_csrf_token(request)
    db_session = request.db_session

    def check_not_study_form(form, field):
        studies = (db_session.query(models.Study).filter(
            models.Study.schemata.any(id=field.data.id)).order_by(
                models.Study.title).all())
        if studies:
            raise wtforms.ValidationError(
                request.localizer.translate(
                    _(u'This form is already used by: {studies}'),
                    mapping={'studies': ', '.join(s.title for s in studies)}))

    def check_not_termination_form(form, field):
        studies = (db_session.query(models.Study).filter(
            models.Study.termination_schema.has(
                name=field.data.name)).order_by(models.Study.title).all())
        if studies:
            raise wtforms.ValidationError(
                request.localizer.translate(_(
                    u'This form is already a termination form for: {studies}'),
                                            mapping={
                                                'studies':
                                                ', '.join(s.title
                                                          for s in studies)
                                            }))

    def check_not_randomization_form(form, field):
        studies = (db_session.query(models.Study).filter(
            models.Study.randomization_schema.has(
                name=field.data.name)).order_by(models.Study.title).all())
        if studies:
            raise wtforms.ValidationError(
                request.localizer.translate(_(
                    u'This form is already a randomization form for: {studies}'
                ),
                                            mapping={
                                                'studies':
                                                ', '.join(s.title
                                                          for s in studies)
                                            }))

    def check_unique_form(form, field):
        exists = (db_session.query(sa.literal(True)).filter(
            db_session.query(datastore.Schema).join(
                models.patient_schema_table).filter(
                    datastore.Schema.name ==
                    field.data.name).exists()).scalar())
        if exists:
            raise wtforms.ValidationError(
                request.localizer.translate(
                    _(u'Only a single version of forms are currently supported'
                      )))

    class AddForm(Form):
        form = ModelField(db_session=db_session,
                          class_=datastore.Schema,
                          validators=[
                              wtforms.validators.InputRequired(),
                              check_not_study_form,
                              check_not_randomization_form,
                              check_not_termination_form, check_unique_form
                          ])

    form = AddForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    db_session.execute(models.patient_schema_table.insert().values(
        schema_id=form.form.data.id))

    mark_changed(db_session)

    return form2json(form.form.data)
Example #26
0
def randomize_ajax(context, request):
    """
    Procesess a patient's randomiation by completing randomization form

    Rules:

    * The user can only randomize one patient at a time.
    * If another randomization is in progress, both are restarted.
    * A randomization session may not "continue" from another.

    In order to address a single randomization at a time, the process assigns
    a "process id" or ``procid`` for the duration of the process, this way
    if a new process begins it will have a different token which will not
    match the current process and nullify everything. This is done by
    passing the ``procid`` via POST or GET depending on the phase of data
    entry and matching it against the session-stored ``procid``. If the they
    do not match, the operation is cancelled.

    The process goes as follows:

    # CHALLENGE: Upon first request the user will be issued a ``procid``
      token this token will remain unchainged for the duration of the
      randomization process. If it changes, the process restarts. The goal
      of the challenge stage is to ensure the user confirms their intent
      to randomize.
    # ENTER: After passing the challenge stage, the user will then have
      opportunity to enter the randomization schema form data that will
      be used to determine assignement to the study arm.
    # VERIFY: The user will then have to verify the data again to ensure
      accurate responses. If the user fails this stage, they will have
      to pass the ENTER stage again. Upon sucessfull verification the
      ``procid`` expires and the patient is randomized. The user will not
      be shown the challenge/entry forms again and only the randomization
      information information will be rendered for future reference to the
      user.
    """

    db_session = request.db_session
    enrollment = context

    if not enrollment.is_randomized:
        # Ensure a ``procid`` is assigned for the duration of the process
        # This way, if a new request mismatches, we can expire the operation
        if 'procid' not in request.GET and 'procid' not in request.POST:
            internal_procid = str(uuid.uuid4())
            request.session[RAND_INFO_KEY] = {
                'procid': internal_procid,
                'stage': RAND_CHALLENGE,
                'formdata': None
            }
            return HTTPFound(
               location=request.current_route_path(
                    _query={'procid': internal_procid}))

        external_procid = request.GET.get('procid') or request.POST.get('procid')
        internal_procid = request.session.get(RAND_INFO_KEY, {}).get('procid')

        # compare internal and external ID to determine if a new process has
        # been initiated in a new tab
        if external_procid is not None and external_procid != internal_procid:
            try:
                del request.session[RAND_INFO_KEY]
            except KeyError:  # pragma: no cover
                pass
            request.session.flash(
                _(u'You have another randomization in progress, '
                  u'starting over.'),
                'warning')
            return HTTPFound(location=request.current_route_path(_query={}))

    if request.method == 'POST':
        check_csrf_token(request)

        if enrollment.is_randomized:
            request.session.flash(
                _(u'This patient is already randomized for this study'),
                'warning')
            return HTTPFound(location=request.current_route_path(_query={}))

        if request.session[RAND_INFO_KEY]['stage'] == RAND_CHALLENGE:
            Form = _make_challenge_form(enrollment, request)
            form = Form(request.POST)
            if not form.validate():
                raise HTTPBadRequest(json={'errors': wtferrors(form)})
            else:
                request.session[RAND_INFO_KEY]['stage'] = RAND_ENTER
                request.session.changed()
                return HTTPFound(location=request.current_route_path(
                    _query={'procid': internal_procid}))

        elif request.session[RAND_INFO_KEY]['stage'] == RAND_ENTER:
            Form = make_form(
                db_session,
                enrollment.study.randomization_schema,
                show_metadata=False)
            form = Form(request.POST)
            if not form.validate():
                raise HTTPBadRequest(json={'errors': wtferrors(form)})
            else:
                request.session[RAND_INFO_KEY]['stage'] = RAND_VERIFY
                request.session[RAND_INFO_KEY]['formdata'] = form.data
                request.session.changed()
                return HTTPFound(location=request.current_route_path(
                    _query={'procid': internal_procid}))

        elif request.session[RAND_INFO_KEY]['stage'] == RAND_VERIFY:
            Form = make_form(
                db_session,
                enrollment.study.randomization_schema,
                show_metadata=False)
            form = Form(request.POST)
            if not form.validate():
                raise HTTPBadRequest(json={'errors': wtferrors(form)})
            else:
                previous_data = \
                    request.session[RAND_INFO_KEY].get('formdata') or {}
                # ensure entered values match previous values
                for field, value in form.data.items():
                    if value != previous_data.get(field):
                        # start over
                        request.session[RAND_INFO_KEY]['stage'] = RAND_ENTER
                        request.session[RAND_INFO_KEY]['formdata'] = None
                        request.session.flash(
                            _(u'Your responses do not match previously '
                              u'entered responses. '
                              u'You will need to reenter your responses.'),
                            'warning')
                        return HTTPFound(location=request.current_route_path(
                            _query={'procid': internal_procid}))
                else:
                    report = build_report(
                        db_session, enrollment.study.randomization_schema.name)
                    data = form.data

                    # Get an unassigned entity that matches the input criteria
                    query = (
                        db_session.query(models.Stratum)
                        .filter(models.Stratum.study == enrollment.study)
                        .filter(models.Stratum.patient == sa.null())
                        .join(models.Stratum.contexts)
                        .join(datastore.Context.entity)
                        .add_entity(datastore.Entity)
                        .join(report, report.c.id == datastore.Entity.id)
                        .filter(sa.and_(
                            *[(getattr(report.c, k) == v)
                                for k, v in data.items()]))
                        .order_by(models.Stratum.id.asc())
                        .limit(1))

                    try:
                        (stratum, entity) = query.one()
                    except orm.exc.NoResultFound:
                        raise HTTPBadRequest(
                            body=_(u'Randomization numbers depleted'))

                    # so far so good, set the contexts and complete the request
                    stratum.patient = enrollment.patient
                    entity.state = (
                        db_session.query(datastore.State)
                        .filter_by(name=u'complete')
                        .one())
                    entity.collect_date = date.today()
                    enrollment.patient.entities.add(entity)
                    enrollment.entities.add(entity)
                    db_session.flush()
                    del request.session[RAND_INFO_KEY]
                    request.session.flash(
                        _(u'Randomization complete'), 'success')
                    return HTTPFound(
                        location=request.current_route_path(_query={}))

        else:
            log.warn(
                u'Detected unknown randomization stage: {}'.format(
                    str(request.session[RAND_INFO_KEY])))
            request.session.flash(
                _(u'Unable to determine randomization state. Restarting'),
                'warning')
            del request.session[RAND_INFO_KEY]
            return HTTPFound(location=request.current_route_path(_query={}))

    if enrollment.is_randomized:
        template = '../templates/enrollment/randomize-view.pt'
        form = _get_randomized_form(enrollment, request)
    elif request.session[RAND_INFO_KEY]['stage'] == RAND_CHALLENGE:
        template = '../templates/enrollment/randomize-challenge.pt'
        Form = _make_challenge_form(enrollment, request)
        Form.procid = wtforms.HiddenField()
        form = Form(procid=internal_procid)
        form.meta.entity = None
        form.meta.schema = enrollment.study.randomization_schema
    elif request.session[RAND_INFO_KEY]['stage'] == RAND_ENTER:
        template = '../templates/enrollment/randomize-enter.pt'
        Form = make_form(
            db_session,
            enrollment.study.randomization_schema,
            show_metadata=False)
        Form.procid = wtforms.HiddenField()
        form = Form(procid=internal_procid)
    elif request.session[RAND_INFO_KEY]['stage'] == RAND_VERIFY:
        template = '../templates/enrollment/randomize-verify.pt'
        Form = make_form(
            db_session,
            enrollment.study.randomization_schema,
            show_metadata=False)
        form = Form()
        Form.procid = wtforms.HiddenField()
        form = Form(procid=internal_procid)

    return {
        'is_randomized': enrollment.is_randomized,
        'enrollment': view_json(enrollment, request),
        'content': render(template, {
            'context': enrollment,
            'request': request,
            'form': render_form(
                form,
                disabled=enrollment.is_randomized,
                show_footer=False,
                attr={
                    'id': 'enrollment-randomization',
                    'method': 'POST',
                    'action': request.current_route_path(),
                    'role': 'form',
                    'data-bind':
                        'formentry: {}, submit: $root.randomizeEnrollment'
                })
        })
    }
Example #27
0
def terminate_ajax(context, request):
    db_session = request.db_session
    try:
        entity = (
            db_session.query(datastore.Entity)
            .join(datastore.Entity.schema)
            .filter(datastore.Schema.name.in_(
                # Only search for forms being used as temrination forms
                db_session.query(datastore.Schema.name)
                .join(models.Study.termination_schema)
                .subquery()))
            .join(datastore.Context)
            .filter_by(external='enrollment', key=context.id)
            .one())
    except orm.exc.MultipleResultsFound:
        raise Exception('Should only have one...')
    except orm.exc.NoResultFound:
        schema = context.study.termination_schema
        entity = datastore.Entity(schema=schema)
        # XXX: This is really bad form as we're applying
        # side-effects to a GET request, but there is no time
        # to make this look prety...
        # If you remove this line you will be creating random termination
        # entries...
        context.entities.add(entity)
    else:
        schema = entity.schema

    if not entity.state:
        entity.state = (
            db_session.query(datastore.State)
            .filter_by(name='pending-entry')
            .one())

    if 'termination_date' not in schema.attributes:
        msg = 'There is no "termination_date" configured on: {}'
        log.warn(msg.format(schema.name))

    if request.has_permission('retract'):
        transition = modes.ALL
    elif request.has_permission('transition'):
        transition = modes.AVAILABLE
    else:
        transition = modes.AUTO

    Form = make_form(
        db_session, schema,
        entity=entity, transition=transition, show_metadata=False)

    form = Form(request.POST, data=entity_data(entity))

    def validate_termination_date(form, field):
        if not (field.data >= context.latest_consent_date):
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'Termination must be on or after latest consent (${date})'),
                mapping={'date': context.latest_consent_date}
            ))

    # Inject a validator into the termination form so that we
    # ensure that the termination date provided is valid
    form.termination_date.validators.append(validate_termination_date)

    if request.method == 'POST':
        check_csrf_token(request)
        if form.validate():
            if not entity.id:
                # changing termination version *should* not be
                # allowed, just assign the schema that's already being used
                context.entities.add(entity)
            upload_dir = request.registry.settings['studies.blob.dir']
            apply_data(db_session, entity, form.data, upload_dir)
            context.termination_date = form.termination_date.data
            db_session.flush()
            return HTTPOk(json=view_json(context, request))
        else:
            return HTTPBadRequest(json={'errors': wtferrors(form)})

    return render_form(
        form,
        cancel_url=request.current_route_path(_route_name='studies.patient'),
        attr={
            'method': 'POST',
            'action': request.current_route_path(),
            'role': 'form',
            'data-bind': 'formentry: {}, submit: $root.terminateEnrollment'
        }
    )
Example #28
0
def forms_add_json(context, request):
    """
    Updates the available patient forms
    """
    check_csrf_token(request)
    db_session = request.db_session

    def check_not_study_form(form, field):
        studies = (
            db_session.query(models.Study)
            .filter(models.Study.schemata.any(id=field.data.id))
            .order_by(models.Study.title)
            .all())
        if studies:
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'This form is already used by: {studies}'),
                mapping={'studies': ', '.join(s.title for s in studies)}))

    def check_not_termination_form(form, field):
        studies = (
            db_session.query(models.Study)
            .filter(models.Study.termination_schema.has(name=field.data.name))
            .order_by(models.Study.title)
            .all())
        if studies:
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'This form is already a termination form for: {studies}'),
                mapping={'studies': ', '.join(s.title for s in studies)}))

    def check_not_randomization_form(form, field):
        studies = (
            db_session.query(models.Study)
            .filter(models.Study.randomization_schema.has(
                name=field.data.name))
            .order_by(models.Study.title)
            .all())
        if studies:
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'This form is already a randomization form for: {studies}'),
                mapping={'studies': ', '.join(s.title for s in studies)}))

    def check_unique_form(form, field):
        exists = (
            db_session.query(sa.literal(True))
            .filter(
                db_session.query(datastore.Schema)
                .join(models.patient_schema_table)
                .filter(datastore.Schema.name == field.data.name)
                .exists())
            .scalar())
        if exists:
            raise wtforms.ValidationError(request.localizer.translate(
                _(u'Only a single version of forms are currently supported')))

    class AddForm(Form):
        form = ModelField(
            db_session=db_session,
            class_=datastore.Schema,
            validators=[
                wtforms.validators.InputRequired(),
                check_not_study_form,
                check_not_randomization_form,
                check_not_termination_form,
                check_unique_form])

    form = AddForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    db_session.execute(
        models.patient_schema_table.insert()
        .values(schema_id=form.form.data.id))

    mark_changed(db_session)

    return form2json(form.form.data)
Example #29
0
def randomize_ajax(context, request):
    """
    Procesess a patient's randomiation by completing randomization form

    Rules:

    * The user can only randomize one patient at a time.
    * If another randomization is in progress, both are restarted.
    * A randomization session may not "continue" from another.

    In order to address a single randomization at a time, the process assigns
    a "process id" or ``procid`` for the duration of the process, this way
    if a new process begins it will have a different token which will not
    match the current process and nullify everything. This is done by
    passing the ``procid`` via POST or GET depending on the phase of data
    entry and matching it against the session-stored ``procid``. If the they
    do not match, the operation is cancelled.

    The process goes as follows:

    # CHALLENGE: Upon first request the user will be issued a ``procid``
      token this token will remain unchainged for the duration of the
      randomization process. If it changes, the process restarts. The goal
      of the challenge stage is to ensure the user confirms their intent
      to randomize.
    # ENTER: After passing the challenge stage, the user will then have
      opportunity to enter the randomization schema form data that will
      be used to determine assignement to the study arm.
    # VERIFY: The user will then have to verify the data again to ensure
      accurate responses. If the user fails this stage, they will have
      to pass the ENTER stage again. Upon sucessfull verification the
      ``procid`` expires and the patient is randomized. The user will not
      be shown the challenge/entry forms again and only the randomization
      information information will be rendered for future reference to the
      user.
    """

    db_session = request.db_session
    enrollment = context

    if not enrollment.is_randomized:
        # Ensure a ``procid`` is assigned for the duration of the process
        # This way, if a new request mismatches, we can expire the operation
        if 'procid' not in request.GET and 'procid' not in request.POST:
            internal_procid = str(uuid.uuid4())
            request.session[RAND_INFO_KEY] = {
                'procid': internal_procid,
                'stage': RAND_CHALLENGE,
                'formdata': None
            }
            return HTTPFound(location=request.current_route_path(
                _query={'procid': internal_procid}))

        external_procid = request.GET.get('procid') or request.POST.get(
            'procid')
        internal_procid = request.session.get(RAND_INFO_KEY, {}).get('procid')

        # compare internal and external ID to determine if a new process has
        # been initiated in a new tab
        if external_procid is not None and external_procid != internal_procid:
            try:
                del request.session[RAND_INFO_KEY]
            except KeyError:  # pragma: no cover
                pass
            request.session.flash(
                _(u'You have another randomization in progress, '
                  u'starting over.'), 'warning')
            return HTTPFound(location=request.current_route_path(_query={}))

    if request.method == 'POST':
        check_csrf_token(request)

        if enrollment.is_randomized:
            request.session.flash(
                _(u'This patient is already randomized for this study'),
                'warning')
            return HTTPFound(location=request.current_route_path(_query={}))

        if request.session[RAND_INFO_KEY]['stage'] == RAND_CHALLENGE:
            Form = _make_challenge_form(enrollment, request)
            form = Form(request.POST)
            if not form.validate():
                raise HTTPBadRequest(json={'errors': wtferrors(form)})
            else:
                request.session[RAND_INFO_KEY]['stage'] = RAND_ENTER
                request.session.changed()
                return HTTPFound(location=request.current_route_path(
                    _query={'procid': internal_procid}))

        elif request.session[RAND_INFO_KEY]['stage'] == RAND_ENTER:
            Form = make_form(db_session,
                             enrollment.study.randomization_schema,
                             show_metadata=False)
            form = Form(request.POST)
            if not form.validate():
                raise HTTPBadRequest(json={'errors': wtferrors(form)})
            else:
                request.session[RAND_INFO_KEY]['stage'] = RAND_VERIFY
                request.session[RAND_INFO_KEY]['formdata'] = form.data
                request.session.changed()
                return HTTPFound(location=request.current_route_path(
                    _query={'procid': internal_procid}))

        elif request.session[RAND_INFO_KEY]['stage'] == RAND_VERIFY:
            Form = make_form(db_session,
                             enrollment.study.randomization_schema,
                             show_metadata=False)
            form = Form(request.POST)
            if not form.validate():
                raise HTTPBadRequest(json={'errors': wtferrors(form)})
            else:
                previous_data = \
                    request.session[RAND_INFO_KEY].get('formdata') or {}
                # ensure entered values match previous values
                for field, value in form.data.items():
                    if value != previous_data.get(field):
                        # start over
                        request.session[RAND_INFO_KEY]['stage'] = RAND_ENTER
                        request.session[RAND_INFO_KEY]['formdata'] = None
                        request.session.flash(
                            _(u'Your responses do not match previously '
                              u'entered responses. '
                              u'You will need to reenter your responses.'),
                            'warning')
                        return HTTPFound(location=request.current_route_path(
                            _query={'procid': internal_procid}))
                else:
                    report = build_report(
                        db_session, enrollment.study.randomization_schema.name)
                    data = form.data

                    # Get an unassigned entity that matches the input criteria
                    query = (db_session.query(models.Stratum).filter(
                        models.Stratum.study == enrollment.study).filter(
                            models.Stratum.patient == sa.null()).join(
                                models.Stratum.contexts).join(
                                    datastore.Context.entity).add_entity(
                                        datastore.Entity).join(
                                            report,
                                            report.c.id == datastore.Entity.id)
                             .filter(
                                 sa.and_(
                                     *[(getattr(report.c, k) == v)
                                       for k, v in data.items()])).order_by(
                                           models.Stratum.id.asc()).limit(1))

                    try:
                        (stratum, entity) = query.one()
                    except orm.exc.NoResultFound:
                        raise HTTPBadRequest(
                            body=_(u'Randomization numbers depleted'))

                    # so far so good, set the contexts and complete the request
                    stratum.patient = enrollment.patient
                    entity.state = (db_session.query(
                        datastore.State).filter_by(name=u'complete').one())
                    entity.collect_date = date.today()
                    enrollment.patient.entities.add(entity)
                    enrollment.entities.add(entity)
                    db_session.flush()
                    del request.session[RAND_INFO_KEY]
                    request.session.flash(_(u'Randomization complete'),
                                          'success')
                    return HTTPFound(location=request.current_route_path(
                        _query={}))

        else:
            log.warn(u'Detected unknown randomization stage: {}'.format(
                str(request.session[RAND_INFO_KEY])))
            request.session.flash(
                _(u'Unable to determine randomization state. Restarting'),
                'warning')
            del request.session[RAND_INFO_KEY]
            return HTTPFound(location=request.current_route_path(_query={}))

    if enrollment.is_randomized:
        template = '../templates/enrollment/randomize-view.pt'
        form = _get_randomized_form(enrollment, request)
    elif request.session[RAND_INFO_KEY]['stage'] == RAND_CHALLENGE:
        template = '../templates/enrollment/randomize-challenge.pt'
        Form = _make_challenge_form(enrollment, request)
        Form.procid = wtforms.HiddenField()
        form = Form(procid=internal_procid)
        form.meta.entity = None
        form.meta.schema = enrollment.study.randomization_schema
    elif request.session[RAND_INFO_KEY]['stage'] == RAND_ENTER:
        template = '../templates/enrollment/randomize-enter.pt'
        Form = make_form(db_session,
                         enrollment.study.randomization_schema,
                         show_metadata=False)
        Form.procid = wtforms.HiddenField()
        form = Form(procid=internal_procid)
    elif request.session[RAND_INFO_KEY]['stage'] == RAND_VERIFY:
        template = '../templates/enrollment/randomize-verify.pt'
        Form = make_form(db_session,
                         enrollment.study.randomization_schema,
                         show_metadata=False)
        form = Form()
        Form.procid = wtforms.HiddenField()
        form = Form(procid=internal_procid)

    return {
        'is_randomized':
        enrollment.is_randomized,
        'enrollment':
        view_json(enrollment, request),
        'content':
        render(
            template, {
                'context':
                enrollment,
                'request':
                request,
                'form':
                render_form(
                    form,
                    disabled=enrollment.is_randomized,
                    show_footer=False,
                    attr={
                        'id':
                        'enrollment-randomization',
                        'method':
                        'POST',
                        'action':
                        request.current_route_path(),
                        'role':
                        'form',
                        'data-bind':
                        'formentry: {}, submit: $root.randomizeEnrollment'
                    })
            })
    }
Example #30
0
def add_schema_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    def check_not_patient_schema(form, field):
        (exists, ) = (db_session.query(
            db_session.query(datastore.Schema).join(
                models.patient_schema_table).filter(
                    datastore.Schema.name == field.data).exists()).one())
        if exists:
            raise wtforms.ValidationError(
                request.localizer.translate(_(u'Already a patient form')))

    def check_not_randomization_schema(form, field):
        if (context.randomization_schema
                and context.randomization_schema.name == field.data):
            raise wtforms.ValidationError(
                request.localizer.translate(
                    _(u'Already a randomization form')))

    def check_not_termination_schema(form, field):
        if (context.termination_schema is not None
                and context.termination_schema.name == field.data):
            raise wtforms.ValidationError(
                request.localizer.translate(_(u'Already a termination form')))

    def check_same_schema(form, field):
        versions = form.versions.data
        schema = form.schema.data
        invalid = [i.publish_date for i in versions if i.name != schema]
        if invalid:
            raise wtforms.ValidationError(
                request.localizer.translate(
                    _(_(u'Incorrect versions: ${versions}'),
                      mapping={'versions': ', '.join(map(str, invalid))})))

    def check_published(form, field):
        if field.data.publish_date is None:
            raise wtforms.ValidationError(
                request.localizer.translate(
                    _(u'Selected version is not published')))

    class SchemaManagementForm(Form):
        schema = wtforms.StringField(validators=[
            wtforms.validators.InputRequired(), check_not_patient_schema,
            check_not_randomization_schema, check_not_termination_schema
        ])
        versions = wtforms.FieldList(
            ModelField(db_session=db_session,
                       class_=datastore.Schema,
                       validators=[
                           wtforms.validators.InputRequired(), check_published
                       ]),
            validators=[wtforms.validators.DataRequired(), check_same_schema])

    form = SchemaManagementForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    old_items = set(i for i in context.schemata if i.name == form.schema.data)
    new_items = set(form.versions.data)

    # Remove unselected
    context.schemata.difference_update(old_items - new_items)

    # Add newly selected
    context.schemata.update(new_items)

    # Get a list of cycles to update
    cycles = (db_session.query(models.Cycle).options(
        orm.joinedload(models.Cycle.schemata)).filter(
            models.Cycle.study == context).filter(
                models.Cycle.schemata.any(name=form.schema.data)))

    # Also update available cycle schemata versions
    for cycle in cycles:
        cycle.schemata.difference_update(old_items - new_items)
        cycle.schemata.update(new_items)

    return form2json(new_items)[0]
Example #31
0
def checkout(context, request):
    """
    Generating a listing of available data for export.

    Because the exports can take a while to generate, this view serves
    as a "checkout" page so that the user can select which files they want.
    The actual exporting process is then queued in a another thread so the user
    isn't left with an unresponsive page.
    """
    db_session = request.db_session
    plans = request.registry.settings['studies.export.plans']
    exportables = exports.list_all(
        plans, request.db_session, include_rand=False)
    limit = request.registry.settings.get('app.export.limit')
    exceeded = limit is not None and query_exports(request).count() > limit
    errors = {}

    if request.method == 'POST' and check_csrf_token(request) and not exceeded:

        def check_exportable(form, field):
            if any(value not in exportables for value in field.data):
                raise wtforms.ValidationError(request.localizer.translate(
                    _(u'Invalid selection')))

        class CheckoutForm(Form):
            contents = wtforms.SelectMultipleField(
                choices=[(k, v.title) for k, v in six.iteritems(exportables)],
                validators=[
                    wtforms.validators.InputRequired()])
            expand_collections = wtforms.BooleanField(default=False)
            use_choice_labels = wtforms.BooleanField(default=False)

        form = CheckoutForm(request.POST)

        if not form.validate():
            errors = wtferrors(form)
        else:
            task_id = six.text_type(str(uuid.uuid4()))
            db_session.add(models.Export(
                name=task_id,
                expand_collections=form.expand_collections.data,
                use_choice_labels=form.use_choice_labels.data,
                owner_user=(db_session.query(datastore.User)
                            .filter_by(key=request.authenticated_userid)
                            .one()),
                contents=[exportables[k].to_json() for k in form.contents.data]
            ))

            def apply_after_commit(success):
                if success:
                    tasks.make_export.apply_async(
                        args=[task_id],
                        task_id=task_id,
                        countdown=4)

            # Avoid race-condition by executing the task after succesful commit
            transaction.get().addAfterCommitHook(apply_after_commit)

            msg = _(u'Your request has been received!')
            request.session.flash(msg, 'success')

            next_url = request.route_path('studies.exports_status')
            return HTTPFound(location=next_url)

    return {
        'errors': errors,
        'exceeded': exceeded,
        'limit': limit,
        'exportables': exportables
    }
Example #32
0
def add_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session

    def check_study_form(form, field):
        if isinstance(context.__parent__, models.Patient):
            query = (db_session.query(datastore.Schema).join(
                models.study_schema_table).join(
                    models.Study).filter(datastore.Schema.id == field.data.id))
            (exists, ) = db_session.query(query.exists()).one()
            if not exists:
                raise wtforms.ValidationError(
                    request.localizer.translate(
                        _(u'This form is not assosiated with a study')))
        elif isinstance(context.__parent__, models.Visit):
            query = (db_session.query(models.Visit).filter(
                models.Visit.id == context.__parent__.id).join(
                    models.Visit.cycles).join(models.Cycle.study).filter(
                        models.Cycle.schemata.any(id=field.data.id)
                        | models.Study.schemata.any(id=field.data.id)))
            (exists, ) = db_session.query(query.exists()).one()
            if not exists:
                raise wtforms.ValidationError(
                    request.localizer.translate(_(
                        '${schema} is not part of the studies for this visit'),
                                                mapping={
                                                    'schema': field.data.title
                                                }))

    class AddForm(Form):
        schema = ModelField(
            db_session=db_session,
            class_=datastore.Schema,
            validators=[wtforms.validators.InputRequired(), check_study_form])
        collect_date = DateField(
            validators=[wtforms.validators.InputRequired()])

    form = AddForm.from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    default_state = (db_session.query(
        datastore.State).filter_by(name='pending-entry').one())

    entity = datastore.Entity(schema=form.schema.data,
                              collect_date=form.collect_date.data,
                              state=default_state)

    if isinstance(context.__parent__, models.Visit):
        context.__parent__.entities.add(entity)
        context.__parent__.patient.entities.add(entity)
        next = request.current_route_path(_route_name='studies.visit_form',
                                          form=entity.id)
    elif isinstance(context.__parent__, models.Patient):
        context.__parent__.entities.add(entity)
        next = request.current_route_path(_route_name='studies.patient_form',
                                          form=entity.id)

    db_session.flush()

    request.session.flash(
        _('Successfully added new ${form}',
          mapping={'form': entity.schema.title}), 'success')

    return {'__next__': next}
Example #33
0
def edit_json(context, request):
    check_csrf_token(request)
    db_session = request.db_session
    is_new = isinstance(context, models.VisitFactory)
    form = VisitSchema(context, request).from_json(request.json_body)

    if not form.validate():
        raise HTTPBadRequest(json={'errors': wtferrors(form)})

    if is_new:
        visit = models.Visit(patient=context.__parent__)
        db_session.add(visit)
    else:
        visit = context

    visit.patient.modify_date = datetime.now()
    visit.visit_date = form.visit_date.data

    # Set the entire list and let sqlalchemy prune the orphans
    visit.cycles = form.cycles.data

    db_session.flush()

    # TODO: hard coded for now, will be removed when workflows are in place
    default_state = (
        db_session.query(datastore.State)
        .filter_by(name='pending-entry').one())

    # Update collect date for those forms that were never modified
    if not is_new:
        for entity in visit.entities:
            if entity.state == default_state \
                    and entity.collect_date != visit.visit_date:
                entity.collect_date = visit.visit_date

    if form.include_forms.data:

        # find the most recent cycle form version relative to the visit
        recents = (
            db_session.query(
                datastore.Schema.name.label('name'),
                sa.func.max(
                    datastore.Schema.publish_date).label('publish_date'))
            .join(models.Cycle.schemata)
            .filter(models.Cycle.id.in_([c.id for c in visit.cycles]))
            .filter(datastore.Schema.publish_date <= visit.visit_date)
            .filter(datastore.Schema.retract_date == sa.null())
            .group_by(datastore.Schema.name)
            .subquery('max_version'))

        # retrive the full schema record of the previous find
        schemata_query = (
            db_session.query(datastore.Schema)
            .join(recents, (
                (datastore.Schema.name == recents.c.name)
                & (datastore.Schema.publish_date == recents.c.publish_date))))

        # Ignore already-added schemata
        if isinstance(context, models.Visit) and visit.entities:
            schemata_query = schemata_query.filter(
                ~datastore.Schema.name.in_(
                    [entity.schema.name for entity in visit.entities]))

        for schema in schemata_query:
            entity = datastore.Entity(
                schema=schema,
                collect_date=visit.visit_date,
                state=default_state)
            visit.patient.entities.add(entity)
            visit.entities.add(entity)

    # Lab might not be enabled on a environments, check first
    if form.include_specimen.data and db_session.bind.has_table('specimen'):
        from occams_lims import models as lab
        drawstate = (
            db_session.query(lab.SpecimenState)
            .filter_by(name=u'pending-draw')
            .one())
        location_id = visit.patient.site.lab_location.id
        for cycle in visit.cycles:
            if cycle in form.cycles.data:
                for specimen_type in cycle.specimen_types:
                    db_session.add(lab.Specimen(
                        patient=visit.patient,
                        cycle=cycle,
                        specimen_type=specimen_type,
                        state=drawstate,
                        collect_date=visit.visit_date,
                        location_id=location_id,
                        tubes=specimen_type.default_tubes))

    db_session.flush()

    return view_json(visit, request)
Example #34
0
def terminate_ajax(context, request):
    db_session = request.db_session
    try:
        entity = (
            db_session.query(datastore.Entity).join(
                datastore.Entity.schema).filter(
                    datastore.Schema.name.in_(
                        # Only search for forms being used as temrination forms
                        db_session.query(datastore.Schema.name).join(
                            models.Study.termination_schema
                        ).subquery())).join(datastore.Context).filter_by(
                            external='enrollment', key=context.id).one())
    except orm.exc.MultipleResultsFound:
        raise Exception('Should only have one...')
    except orm.exc.NoResultFound:
        schema = context.study.termination_schema
        entity = datastore.Entity(schema=schema)
        # XXX: This is really bad form as we're applying
        # side-effects to a GET request, but there is no time
        # to make this look prety...
        # If you remove this line you will be creating random termination
        # entries...
        context.entities.add(entity)
        context.patient.entities.add(entity)
    else:
        schema = entity.schema

    if not entity.state:
        entity.state = (db_session.query(
            datastore.State).filter_by(name='pending-entry').one())

    if 'termination_date' not in schema.attributes:
        msg = 'There is no "termination_date" configured on: {}'
        log.warn(msg.format(schema.name))

    if request.has_permission('retract'):
        transition = modes.ALL
    elif request.has_permission('transition'):
        transition = modes.AVAILABLE
    else:
        transition = modes.AUTO

    Form = make_form(db_session,
                     schema,
                     entity=entity,
                     transition=transition,
                     show_metadata=False)

    form = Form(request.POST, data=entity_data(entity))

    def validate_termination_date(form, field):
        if not (field.data >= context.latest_consent_date):
            raise wtforms.ValidationError(
                request.localizer.translate(_(
                    u'Termination must be on or after latest consent (${date})'
                ),
                                            mapping={
                                                'date':
                                                context.latest_consent_date
                                            }))

    # Inject a validator into the termination form so that we
    # ensure that the termination date provided is valid
    form.termination_date.validators.append(validate_termination_date)

    if request.method == 'POST':
        check_csrf_token(request)
        if form.validate():
            if not entity.id:
                # changing termination version *should* not be
                # allowed, just assign the schema that's already being used
                context.entities.add(entity)
            upload_dir = request.registry.settings['studies.blob.dir']
            apply_data(db_session, entity, form.data, upload_dir)
            context.termination_date = form.termination_date.data
            db_session.flush()
            return HTTPOk(json=view_json(context, request))
        else:
            return HTTPBadRequest(json={'errors': wtferrors(form)})

    return render_form(
        form,
        cancel_url=request.current_route_path(_route_name='studies.patient'),
        attr={
            'method': 'POST',
            'action': request.current_route_path(),
            'role': 'form',
            'data-bind': 'formentry: {}, submit: $root.terminateEnrollment'
        })