Пример #1
0
def create_push_notification_tasks():
    # we reuse the high level strategy from data processing celery tasks, see that documentation.
    expiry = (datetime.utcnow() + timedelta(minutes=5)).replace(second=30, microsecond=0)
    now = timezone.now()
    surveys, schedules, patient_ids = get_surveys_and_schedules(now)
    print(surveys)
    print(schedules)
    print(patient_ids)
    with make_error_sentry(sentry_type=SentryTypes.data_processing):
        if not check_firebase_instance():
            print("Firebase is not configured, cannot queue notifications.")
            return

        # surveys and schedules are guaranteed to have the same keys, assembling the data structures
        # is a pain, so it is factored out. sorry, but not sorry. it was a mess.
        for fcm_token in surveys.keys():
            print(f"Queueing up push notification for user {patient_ids[fcm_token]} for {surveys[fcm_token]}")
            safe_queue_push(
                args=[fcm_token, surveys[fcm_token], schedules[fcm_token]],
                max_retries=0,
                expires=expiry,
                task_track_started=True,
                task_publish_retry=False,
                retry=False,
            )
Пример #2
0
def render_edit_participant(participant: Participant, study: Study):
    # to reduce database queries we get all the data across 4 queries and then merge it together.
    # dicts of intervention id to intervention date string, and of field names to value
    # (this was quite slow previously)
    intervention_dates_map = {
        intervention_id:  # this is the intervention's id, not the intervention_date's id.
        intervention_date.strftime(API_DATE_FORMAT) if isinstance(
            intervention_date, date) else ""
        for intervention_id, intervention_date in
        participant.intervention_dates.values_list("intervention_id", "date")
    }
    participant_fields_map = {
        name: value
        for name, value in participant.field_values.values_list(
            "field__field_name", "value")
    }

    # list of tuples of (intervention id, intervention name, intervention date)
    intervention_data = [
        (intervention.id, intervention.name,
         intervention_dates_map.get(intervention.id, ""))
        for intervention in study.interventions.order_by("name")
    ]
    # list of tuples of field name, value.
    field_data = [(field_id, field_name,
                   participant_fields_map.get(field_name, ""))
                  for field_id, field_name in study.fields.order_by(
                      "field_name").values_list('id', "field_name")]

    return render_template(
        'edit_participant.html',
        participant=participant,
        study=study,
        intervention_data=intervention_data,
        field_values=field_data,
        push_notifications_enabled_for_ios=check_firebase_instance(
            require_ios=True),
        push_notifications_enabled_for_android=check_firebase_instance(
            require_android=True))
Пример #3
0
def view_study(study_id=None):
    study = Study.objects.get(pk=study_id)
    participants = study.participants.all()

    # creates dicts of Custom Fields and Interventions to be easily accessed in the template
    for p in participants:
        p.field_dict = {tag.field.field_name: tag.value for tag in p.field_values.all()}
        p.intervention_dict = {tag.intervention.name: tag.date for tag in p.intervention_dates.all()}

    return render_template(
        'view_study.html',
        study=study,
        participants=participants,
        audio_survey_ids=study.get_survey_ids_and_object_ids('audio_survey'),
        image_survey_ids=study.get_survey_ids_and_object_ids('image_survey'),
        tracking_survey_ids=study.get_survey_ids_and_object_ids('tracking_survey'),
        # these need to be lists because they will be converted to json.
        study_fields=list(study.fields.all().values_list('field_name', flat=True)),
        interventions=list(study.interventions.all().values_list("name", flat=True)),
        page_location='study_landing',
        study_id=study_id,
        push_notifications_enabled=check_firebase_instance(require_android=True) or
                                   check_firebase_instance(require_ios=True),
    )
Пример #4
0
def render_edit_survey(survey_id=None):
    try:
        survey = Survey.objects.get(pk=survey_id)
    except Survey.DoesNotExist:
        return abort(404)

    return render_template(
        'edit_survey.html',
        survey=survey.as_unpacked_native_python(),
        study=survey.study,
        allowed_studies=get_researcher_allowed_studies(),
        is_admin=researcher_is_an_admin(),
        domain_name=DOMAIN_NAME,  # used in a Javascript alert, see survey-editor.js
        interventions_dict={
            intervention.id: intervention.name for intervention in survey.study.interventions.all()
        },
        weekly_timings=survey.weekly_timings(),
        relative_timings=survey.relative_timings(),
        absolute_timings=survey.absolute_timings(),
        push_notifications_enabled=check_firebase_instance(require_android=True) or \
                                   check_firebase_instance(require_ios=True),
        today=localtime(timezone.now(), survey.study.timezone).strftime('%Y-%m-%d'),

    )
Пример #5
0
def celery_send_push_notification(fcm_token: str, survey_obj_ids: List[str], schedule_pks: List[int]):
    ''' Celery task that sends push notifications.   Note that this list of pks may contain duplicates.'''
    patient_id = ParticipantFCMHistory.objects.filter(token=fcm_token) \
        .values_list("participant__patient_id", flat=True).get()

    with make_error_sentry(sentry_type=SentryTypes.data_processing):
        if not check_firebase_instance():
            print("Firebase credentials are not configured.")
            return

        # use the earliest timed schedule as our reference for the sent_time parameter.  (why?)
        participant = Participant.objects.get(patient_id=patient_id)
        schedules = ScheduledEvent.objects.filter(pk__in=schedule_pks)
        reference_schedule = schedules.order_by("scheduled_time").first()
        survey_obj_ids = list(set(survey_obj_ids))  # already deduped; whatever.

        print(f"Sending push notification to {patient_id} for {survey_obj_ids}...")
        try:
            send_push_notification(participant, reference_schedule, survey_obj_ids, fcm_token)
        # error types are documented at firebase.google.com/docs/reference/fcm/rest/v1/ErrorCode
        except UnregisteredError as e:
            # is an internal 404 http response, it means the token used was wrong.
            # mark the fcm history as out of date.
            return

        except QuotaExceededError:
            # limits are very high, this is effectively impossible, but it is possible, so we catch it.
            raise

        except ThirdPartyAuthError as e:
            failed_send_handler(participant, fcm_token, str(e), schedules)
            # This means the credentials used were wrong for the target app instance.  This can occur
            # both with bad server credentials, and with bad device credentials.
            # We have only seen this error statement, error name is generic so there may be others.
            if str(e) != "Auth error from APNS or Web Push Service":
                raise
            return

        except ValueError as e:
            # This case occurs ever? is tested for in check_firebase_instance... weird race condition?
            # Error should be transient, and like all other cases we enqueue the next weekly surveys regardless.
            if "The default Firebase app does not exist" in str(e):
                enqueue_weekly_surveys(participant, schedules)
                return
            else:
                raise

        success_send_handler(participant, fcm_token, schedules)
Пример #6
0
def send_notification():
    """
    Sends a push notification to the participant, used for testing
    Expects a patient_id in the request body.
    """
    print(check_firebase_instance())
    message = messaging.Message(
        data={
            'type': 'fake',
            'content': 'hello good sir',
        },
        token=get_session_participant().get_fcm_token().token,
    )
    response = messaging.send(message)
    print('Successfully sent notification message:', response)
    return '', 204