diff --git a/eventstore/migrations/0065_whatsapptemplatesendstatus.py b/eventstore/migrations/0065_whatsapptemplatesendstatus.py index 32a62ead..bbeb4da3 100644 --- a/eventstore/migrations/0065_whatsapptemplatesendstatus.py +++ b/eventstore/migrations/0065_whatsapptemplatesendstatus.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eventstore", "0064_openhimqueue_timestamp"), ] diff --git a/eventstore/migrations/0066_whatsapptemplatesendstatus_status.py b/eventstore/migrations/0066_whatsapptemplatesendstatus_status.py index 658a8f34..c977341e 100644 --- a/eventstore/migrations/0066_whatsapptemplatesendstatus_status.py +++ b/eventstore/migrations/0066_whatsapptemplatesendstatus_status.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eventstore", "0065_whatsapptemplatesendstatus"), ] diff --git a/eventstore/migrations/0067_rename_registration_completed_at_whatsapptemplatesendstatus_action_completed_at_and_more.py b/eventstore/migrations/0067_rename_registration_completed_at_whatsapptemplatesendstatus_action_completed_at_and_more.py index a7cb1127..9476babb 100644 --- a/eventstore/migrations/0067_rename_registration_completed_at_whatsapptemplatesendstatus_action_completed_at_and_more.py +++ b/eventstore/migrations/0067_rename_registration_completed_at_whatsapptemplatesendstatus_action_completed_at_and_more.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("eventstore", "0066_whatsapptemplatesendstatus_status"), ] diff --git a/eventstore/migrations/0068_whatsapptemplatesendstatus_contact_uuid_and_more.py b/eventstore/migrations/0068_whatsapptemplatesendstatus_contact_uuid_and_more.py index 48b9cf55..b8016d83 100644 --- a/eventstore/migrations/0068_whatsapptemplatesendstatus_contact_uuid_and_more.py +++ b/eventstore/migrations/0068_whatsapptemplatesendstatus_contact_uuid_and_more.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ( "eventstore", diff --git a/eventstore/tasks.py b/eventstore/tasks.py index e052878b..142562e5 100644 --- a/eventstore/tasks.py +++ b/eventstore/tasks.py @@ -510,25 +510,6 @@ def post_random_mc_contacts_to_slack_channel(): return post_random_contacts_to_slack_channel() -@app.task( - autoretry_for=(RequestException, SoftTimeLimitExceeded, TembaHttpError), - retry_backoff=True, - max_retries=15, - acks_late=True, - soft_time_limit=10, - time_limit=15, -) -def post_random_mqr_contacts_to_slack_channel(): - if not settings.MQR_STUDY_ACTIVE: - return - study_start_date = datetime.strptime( - settings.MQR_STUDY_START_DATE, "%Y-%m-%d" - ).date() - return post_random_contacts_to_slack_channel( - "MQR", study_start_date, "MQR active contacts" - ) - - def post_random_contacts_to_slack_channel( contact_type="MomConnect", start_date=None, group=None ): diff --git a/mqr/__init__.py b/mqr/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/mqr/admin.py b/mqr/admin.py deleted file mode 100644 index 34684848..00000000 --- a/mqr/admin.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.contrib import admin - -from mqr.models import BaselineSurveyResult - - -@admin.register(BaselineSurveyResult) -class BaselineSurveyResultAdmin(admin.ModelAdmin): - readonly_fields = ("msisdn", "created_by", "created_at") - list_display = ("msisdn", "created_at", "airtime_sent") diff --git a/mqr/apps.py b/mqr/apps.py deleted file mode 100644 index 22dfbd26..00000000 --- a/mqr/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class MqrConfig(AppConfig): - name = "mqr" diff --git a/mqr/migrations/0001_initial.py b/mqr/migrations/0001_initial.py deleted file mode 100644 index 412f78ee..00000000 --- a/mqr/migrations/0001_initial.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 2.2.24 on 2022-03-01 09:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - initial = True - - dependencies = [] - - operations = [ - migrations.CreateModel( - name="MqrStrata", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("province", models.CharField(max_length=25)), - ("weeks_pregnant_bucket", models.CharField(max_length=12)), - ("age_bucket", models.CharField(max_length=12)), - ("next_index", models.IntegerField(default=0)), - ("order", models.CharField(max_length=50)), - ], - ), - ] diff --git a/mqr/migrations/0002_baselinesurveyresult.py b/mqr/migrations/0002_baselinesurveyresult.py deleted file mode 100644 index 52bf61ba..00000000 --- a/mqr/migrations/0002_baselinesurveyresult.py +++ /dev/null @@ -1,216 +0,0 @@ -# Generated by Django 2.2.24 on 2022-03-17 11:40 - -import functools - -from django.db import migrations, models - -import eventstore.validators - - -class Migration(migrations.Migration): - dependencies = [ - ("mqr", "0001_initial"), - ] - - operations = [ - migrations.CreateModel( - name="BaselineSurveyResult", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "msisdn", - models.CharField( - max_length=255, - validators=[ - functools.partial( - eventstore.validators._phone_number, - *(), - **{"country": "ZA"} - ) - ], - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "created_by", - models.CharField(blank=True, default="", max_length=255), - ), - ( - "breastfeed", - models.CharField( - choices=[("yes", "Yes"), ("no", "No"), ("skip", "Skip")], - default=None, - max_length=4, - null=True, - ), - ), - ( - "breastfeed_period", - models.CharField( - choices=[ - ("0_3_months", "0-3 months"), - ("4_5_months", "4-5 months"), - ("6_months", "For 6 months"), - ("over_6_months", "Longer than 6 months"), - ("not_only_breastfeed", "I don't want to only breastfeed"), - ("dont_know", "I don't know"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ( - "vaccine_importance", - models.CharField( - choices=[ - ("strongly_agree", "I strongly agree"), - ("agree", "I agree"), - ("neutral", "I don't agree or disagree"), - ("disagree", "I disagree"), - ("strongly_disagree", "I strongly disagree"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ( - "vaccine_benifits", - models.CharField( - choices=[ - ("strongly_agree", "I strongly agree"), - ("agree", "I agree"), - ("neutral", "I don't agree or disagree"), - ("disagree", "I disagree"), - ("strongly_disagree", "I strongly disagree"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ( - "clinic_visit_frequency", - models.CharField( - choices=[ - ("more_than_once_a_month", "More than once a month"), - ("once_a_month", "Once a month"), - ("once_2_3_months", "Once every 2 to 3 months"), - ("once_4_5_months", "Once every 4 to 5 months"), - ("once_6_9_months", "Once every 6 to 9 months"), - ("never", "Never"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ( - "vegetables", - models.CharField( - choices=[("yes", "Yes"), ("no", "No"), ("skip", "Skip")], - default=None, - max_length=4, - null=True, - ), - ), - ( - "fruit", - models.CharField( - choices=[("yes", "Yes"), ("no", "No"), ("skip", "Skip")], - default=None, - max_length=4, - null=True, - ), - ), - ( - "dairy", - models.CharField( - choices=[("yes", "Yes"), ("no", "No"), ("skip", "Skip")], - default=None, - max_length=4, - null=True, - ), - ), - ( - "liver_frequency", - models.CharField( - choices=[ - ("2_3_times_week", "2-3 times a week"), - ("once_a_week", "Once a week"), - ("once_a_month", "Once a month"), - ("less_once_a_month", "Less than once a month"), - ("never", "Never"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ( - "danger_sign", - models.CharField( - choices=[ - ( - "swollen_feet_legs", - "Swollen feet and legs even after sleep", - ), - ("bloating", "Bloating"), - ("gas", "Gas"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ( - "marital_status", - models.CharField( - choices=[ - ("never_married", "Never married"), - ("married", "Married"), - ("separated_or_divorced", "Separated or divorced"), - ("widowed", "Widowed"), - ("parter_or_boyfriend", "Have a partner or boyfriend"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ( - "education_level", - models.CharField( - choices=[ - ("less_grade_7", "Less than Grade 7"), - ("between_grade_7_12", "Between Grades 7-12"), - ("matric", "Matric"), - ("diploma", "Diploma"), - ("degree_or_higher", "University degree or higher"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ], - ), - ] diff --git a/mqr/migrations/0003_auto_20220322_0539.py b/mqr/migrations/0003_auto_20220322_0539.py deleted file mode 100644 index ba52c6d7..00000000 --- a/mqr/migrations/0003_auto_20220322_0539.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2.24 on 2022-03-22 05:39 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("mqr", "0002_baselinesurveyresult"), - ] - - operations = [ - migrations.AddField( - model_name="baselinesurveyresult", - name="airtime_sent", - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name="baselinesurveyresult", - name="airtime_sent_at", - field=models.DateTimeField(null=True), - ), - ] diff --git a/mqr/migrations/0004_auto_20220322_0733.py b/mqr/migrations/0004_auto_20220322_0733.py deleted file mode 100644 index 01024ff9..00000000 --- a/mqr/migrations/0004_auto_20220322_0733.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 2.2.24 on 2022-03-22 07:33 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("mqr", "0003_auto_20220322_0539"), - ] - - operations = [ - migrations.RenameField( - model_name="baselinesurveyresult", - old_name="danger_sign", - new_name="danger_sign2", - ), - migrations.AddField( - model_name="baselinesurveyresult", - name="danger_sign1", - field=models.CharField( - choices=[ - ("weight_gain", "Weight gain of 4-5 kilograms"), - ("vaginal_bleeding", "Vaginal bleeding"), - ("nose_bleeds", "Nose bleeds"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ] diff --git a/mqr/migrations/0005_auto_20220322_1350.py b/mqr/migrations/0005_auto_20220322_1350.py deleted file mode 100644 index 5114e4bb..00000000 --- a/mqr/migrations/0005_auto_20220322_1350.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 2.2.24 on 2022-03-22 13:50 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("mqr", "0004_auto_20220322_0733"), - ] - - operations = [ - migrations.RenameField( - model_name="baselinesurveyresult", - old_name="vaccine_benifits", - new_name="vaccine_benefits", - ), - ] diff --git a/mqr/migrations/0006_auto_20220323_0758.py b/mqr/migrations/0006_auto_20220323_0758.py deleted file mode 100644 index c7eadbef..00000000 --- a/mqr/migrations/0006_auto_20220323_0758.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 2.2.24 on 2022-03-23 07:58 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("mqr", "0005_auto_20220322_1350"), - ] - - operations = [ - migrations.AlterField( - model_name="baselinesurveyresult", - name="clinic_visit_frequency", - field=models.CharField( - choices=[ - ("more_than_once_a_month", "More than once a month"), - ("once_a_month", "Once a month"), - ("once_2_3_months", "Once every 2 to 3 months"), - ("once_4_5_months", "Once every 4 to 5 months"), - ("once_6_9_months", "Once every 6 to 9 months"), - ("never", "Never"), - ("skip", "Skip"), - ], - default=None, - max_length=25, - null=True, - ), - ), - ] diff --git a/mqr/migrations/0007_auto_20220323_0852.py b/mqr/migrations/0007_auto_20220323_0852.py deleted file mode 100644 index 3564dacc..00000000 --- a/mqr/migrations/0007_auto_20220323_0852.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 2.2.24 on 2022-03-23 08:52 - -import functools - -from django.db import migrations, models - -import eventstore.validators - - -class Migration(migrations.Migration): - dependencies = [ - ("mqr", "0006_auto_20220323_0758"), - ] - - operations = [ - migrations.RemoveField( - model_name="baselinesurveyresult", - name="id", - ), - migrations.AlterField( - model_name="baselinesurveyresult", - name="msisdn", - field=models.CharField( - max_length=255, - primary_key=True, - serialize=False, - validators=[ - functools.partial( - eventstore.validators._phone_number, *(), **{"country": "ZA"} - ) - ], - ), - ), - ] diff --git a/mqr/migrations/0008_auto_20220404_0905.py b/mqr/migrations/0008_auto_20220404_0905.py deleted file mode 100644 index 0bff7c1d..00000000 --- a/mqr/migrations/0008_auto_20220404_0905.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.24 on 2022-04-04 09:05 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("mqr", "0007_auto_20220323_0852"), - ] - - operations = [ - migrations.AlterField( - model_name="baselinesurveyresult", - name="marital_status", - field=models.CharField( - choices=[ - ("never_married", "Never married"), - ("married", "Married"), - ("separated_or_divorced", "Separated or divorced"), - ("widowed", "Widowed"), - ("partner_or_boyfriend", "Have a partner or boyfriend"), - ("skip", "Skip"), - ], - default=None, - max_length=20, - null=True, - ), - ), - ] diff --git a/mqr/migrations/0009_auto_20220425_0911.py b/mqr/migrations/0009_auto_20220425_0911.py deleted file mode 100644 index 56f13c9b..00000000 --- a/mqr/migrations/0009_auto_20220425_0911.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2.24 on 2022-04-25 09:11 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("mqr", "0008_auto_20220404_0905"), - ] - - operations = [ - migrations.AlterField( - model_name="baselinesurveyresult", - name="marital_status", - field=models.CharField( - choices=[ - ("never_married", "Never married"), - ("married", "Married"), - ("separated_or_divorced", "Separated or divorced"), - ("widowed", "Widowed"), - ("partner_or_boyfriend", "Have a partner or boyfriend"), - ("skip", "Skip"), - ], - default=None, - max_length=25, - null=True, - ), - ), - ] diff --git a/mqr/migrations/__init__.py b/mqr/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/mqr/models.py b/mqr/models.py deleted file mode 100644 index b66e4667..00000000 --- a/mqr/models.py +++ /dev/null @@ -1,197 +0,0 @@ -from django.db import models - -from eventstore.validators import za_phone_number - - -class MqrStrata(models.Model): - province = models.CharField(max_length=25, null=False, blank=False) - weeks_pregnant_bucket = models.CharField(max_length=12, null=False, blank=False) - age_bucket = models.CharField(max_length=12, null=False, blank=False) - next_index = models.IntegerField(default=0, null=False, blank=False) - order = models.CharField(max_length=50, null=False, blank=False) - - -class BaselineSurveyResult(models.Model): - class YesNoSkip: - YES = "yes" - NO = "no" - SKIP = "skip" - choices = ((YES, "Yes"), (NO, "No"), (SKIP, "Skip")) - - class BreastfeedPeriod: - MONTHS_0_3 = "0_3_months" - MONTHS_4_5 = "4_5_months" - MONTHS_6 = "6_months" - MONTHS_6_PLUS = "over_6_months" - NOT_ONLY = "not_only_breastfeed" - DONT_KNOW = "dont_know" - SKIP = "skip" - - choices = ( - (MONTHS_0_3, "0-3 months"), - (MONTHS_4_5, "4-5 months"), - (MONTHS_6, "For 6 months"), - (MONTHS_6_PLUS, "Longer than 6 months"), - (NOT_ONLY, "I don't want to only breastfeed"), - (DONT_KNOW, "I don't know"), - (SKIP, "Skip"), - ) - - class AgreeDisagree: - STRONG_AGREE = "strongly_agree" - AGREE = "agree" - NEUTRAL = "neutral" - DISAGREE = "disagree" - STRONG_DISAGREE = "strongly_disagree" - SKIP = "skip" - - choices = ( - (STRONG_AGREE, "I strongly agree"), - (AGREE, "I agree"), - (NEUTRAL, "I don't agree or disagree"), - (DISAGREE, "I disagree"), - (STRONG_DISAGREE, "I strongly disagree"), - (SKIP, "Skip"), - ) - - class ClinicVisitFrequency: - MORE_ONCE_MONTH = "more_than_once_a_month" - ONCE_MONTH = "once_a_month" - ONCE_2_3_MONTHS = "once_2_3_months" - ONCE_4_5_MONTHS = "once_4_5_months" - ONCE_6_9_MONTHS = "once_6_9_months" - NEVER = "never" - SKIP = "skip" - - choices = ( - (MORE_ONCE_MONTH, "More than once a month"), - (ONCE_MONTH, "Once a month"), - (ONCE_2_3_MONTHS, "Once every 2 to 3 months"), - (ONCE_4_5_MONTHS, "Once every 4 to 5 months"), - (ONCE_6_9_MONTHS, "Once every 6 to 9 months"), - (NEVER, "Never"), - (SKIP, "Skip"), - ) - - class LiverFrequency: - WEEK_2_3 = "2_3_times_week" - ONCE_WEEK = "once_a_week" - ONCE_MONTH = "once_a_month" - LESS_ONCE_MONTH = "less_once_a_month" - NEVER = "never" - SKIP = "skip" - - choices = ( - (WEEK_2_3, "2-3 times a week"), - (ONCE_WEEK, "Once a week"), - (ONCE_MONTH, "Once a month"), - (LESS_ONCE_MONTH, "Less than once a month"), - (NEVER, "Never"), - (SKIP, "Skip"), - ) - - class DangerSign1: - WEIGHT_GAIN = "weight_gain" - VAGINAL_BLEED = "vaginal_bleeding" - NOSE_BLEED = "nose_bleeds" - SKIP = "skip" - - choices = ( - (WEIGHT_GAIN, "Weight gain of 4-5 kilograms"), - (VAGINAL_BLEED, "Vaginal bleeding"), - (NOSE_BLEED, "Nose bleeds"), - (SKIP, "Skip"), - ) - - class DangerSign2: - SWOLLEN = "swollen_feet_legs" - BLOAT = "bloating" - GAS = "gas" - SKIP = "skip" - - choices = ( - (SWOLLEN, "Swollen feet and legs even after sleep"), - (BLOAT, "Bloating"), - (GAS, "Gas"), - (SKIP, "Skip"), - ) - - class MaritalStatus: - NEVER_MARRIED = "never_married" - MARRIED = "married" - SEPARATED = "separated_or_divorced" - WIDOWED = "widowed" - PARTNER = "partner_or_boyfriend" - SKIP = "skip" - choices = ( - (NEVER_MARRIED, "Never married"), - (MARRIED, "Married"), - (SEPARATED, "Separated or divorced"), - (WIDOWED, "Widowed"), - (PARTNER, "Have a partner or boyfriend"), - (SKIP, "Skip"), - ) - - class EducationLevel: - LESS_GRADE_7 = "less_grade_7" - BETWEEN_GRADE_7_12 = "between_grade_7_12" - MATRIC = "matric" - DIPLOMA = "diploma" - DEGREE_OR_HIGHER = "degree_or_higher" - SKIP = "skip" - choices = ( - (LESS_GRADE_7, "Less than Grade 7"), - (BETWEEN_GRADE_7_12, "Between Grades 7-12"), - (MATRIC, "Matric"), - (DIPLOMA, "Diploma"), - (DEGREE_OR_HIGHER, "University degree or higher"), - (SKIP, "Skip"), - ) - - msisdn = models.CharField( - primary_key=True, max_length=255, validators=[za_phone_number] - ) - created_at = models.DateTimeField(auto_now_add=True) - created_by = models.CharField(max_length=255, blank=True, default="") - updated_at = models.DateTimeField(auto_now=True) - breastfeed = models.CharField( - max_length=4, choices=YesNoSkip.choices, null=True, default=None - ) - breastfeed_period = models.CharField( - max_length=20, choices=BreastfeedPeriod.choices, null=True, default=None - ) - vaccine_importance = models.CharField( - max_length=20, choices=AgreeDisagree.choices, null=True, default=None - ) - vaccine_benefits = models.CharField( - max_length=20, choices=AgreeDisagree.choices, null=True, default=None - ) - clinic_visit_frequency = models.CharField( - max_length=25, choices=ClinicVisitFrequency.choices, null=True, default=None - ) - vegetables = models.CharField( - max_length=4, choices=YesNoSkip.choices, null=True, default=None - ) - fruit = models.CharField( - max_length=4, choices=YesNoSkip.choices, null=True, default=None - ) - dairy = models.CharField( - max_length=4, choices=YesNoSkip.choices, null=True, default=None - ) - liver_frequency = models.CharField( - max_length=20, choices=LiverFrequency.choices, null=True, default=None - ) - danger_sign1 = models.CharField( - max_length=20, choices=DangerSign1.choices, null=True, default=None - ) - danger_sign2 = models.CharField( - max_length=20, choices=DangerSign2.choices, null=True, default=None - ) - marital_status = models.CharField( - max_length=25, choices=MaritalStatus.choices, null=True, default=None - ) - education_level = models.CharField( - max_length=20, choices=EducationLevel.choices, null=True, default=None - ) - airtime_sent = models.BooleanField(default=False) - airtime_sent_at = models.DateTimeField(null=True) diff --git a/mqr/serializers.py b/mqr/serializers.py deleted file mode 100644 index 3a2211cc..00000000 --- a/mqr/serializers.py +++ /dev/null @@ -1,68 +0,0 @@ -from django.utils import timezone -from rest_framework import serializers - -from eventstore.serializers import MSISDNField -from mqr.models import BaselineSurveyResult - - -class BaselineSurveyResultSerializer(serializers.ModelSerializer): - class Meta: - model = BaselineSurveyResult - fields = "__all__" - read_only_fields = ("id", "created_by") - - def update(self, instance, validated_data): - if validated_data.get("airtime_sent"): - validated_data["airtime_sent_at"] = timezone.now() - super().update(instance, validated_data) - return instance - - -class MqrStrataSerializer(serializers.Serializer): - facility_code = serializers.CharField(required=True) - registration_date = serializers.DateField(required=True) - estimated_delivery_date = serializers.DateField(required=True) - mom_age = serializers.IntegerField(required=True) - - -class BaseMessageSerializer(serializers.Serializer): - contact_uuid = serializers.UUIDField(required=True) - run_uuid = serializers.UUIDField(required=True) - - -class NextMessageSerializer(BaseMessageSerializer): - edd_or_dob_date = serializers.DateField(required=True) - subscription_type = serializers.CharField(required=True) - arm = serializers.CharField(required=True) - mom_name = serializers.CharField(required=True) - tag_extra = serializers.CharField(required=False, allow_blank=True) - - -class MidweekArmMessageSerializer(BaseMessageSerializer): - last_tag = serializers.CharField(required=True) - mom_name = serializers.CharField(required=True) - - -class NextArmMessageSerializer(BaseMessageSerializer): - last_tag = serializers.CharField(required=True) - mom_name = serializers.CharField(required=True) - sequence = serializers.CharField(required=True) - - -class FaqSerializer(BaseMessageSerializer): - tag = serializers.CharField(required=True) - faq_number = serializers.IntegerField(required=True) - viewed = serializers.ListField(required=False) - - -class FaqMenuSerializer(serializers.Serializer): - tag = serializers.CharField(required=True) - menu_offset = serializers.IntegerField(required=False) - - -class FirstSendDateSerializer(serializers.Serializer): - edd_or_dob_date = serializers.DateField(required=True) - - -class MqrEndlineChecksSerializer(serializers.Serializer): - msisdn = MSISDNField(country="ZA") diff --git a/mqr/tests/__init__.py b/mqr/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/mqr/tests/test_utils.py b/mqr/tests/test_utils.py deleted file mode 100644 index d32ced24..00000000 --- a/mqr/tests/test_utils.py +++ /dev/null @@ -1,567 +0,0 @@ -from datetime import date, datetime, timedelta -from unittest import TestCase -from unittest.mock import patch - -import responses -from django.test import override_settings - -from mqr import utils - - -def override_get_today(): - return datetime.strptime("20220301", "%Y%m%d").date() - - -def get_date(d): - return datetime.strptime(d, "%Y-%m-%d").date() - - -class TestGetWeek(TestCase): - def setUp(self): - utils.get_today = override_get_today - - def test_get_weeks(self): - # While today = 2022-03-01 - - # Prebirth: when EDD is today (2022-03-01) = 40 weeks pregnant - edd = get_date("2022-03-01") - self.assertEqual(utils.get_week("pre", edd), 40) - - # Prebirth: when EDD is today - 7 days (2022-03-28) = 41 weeks pregnant - edd = get_date("2022-02-22") - self.assertEqual(utils.get_week("pre", edd), 41) - - # Prebirth: when EDD is today + 28 days (2022-03-29) = 36 weeks pregnant - edd = get_date("2022-03-29") - self.assertEqual(utils.get_week("pre", edd), 36) - - # Prebirth: when EDD is today + 245 days (2022-11-01) = 5 weeks pregnant - edd = get_date("2022-11-01") - self.assertEqual(utils.get_week("pre", edd), 5) - - # Postbirth: when baby DOB is today (2022-03-01) = 0 weeks - baby_dob = get_date("2022-03-01") - self.assertEqual(utils.get_week("post", baby_dob), 0) - - # Postbirth: when baby DOB is today + 4 weeks (2022-03-29) = 4 weeks - baby_dob = get_date("2022-03-29") - self.assertEqual(utils.get_week("post", baby_dob), 4) - - # Postbirth: when baby DOB is today + 8 weeks (2022-04-26) = 8 weeks - baby_dob = get_date("2022-04-26") - self.assertEqual(utils.get_week("post", baby_dob), 8) - - -class TestGetTag(TestCase): - def setUp(self): - utils.get_today = override_get_today - - def test_get_tag(self): - """ - Returns the correct tag - """ - edd = get_date("2022-11-01") - self.assertEqual(utils.get_tag("RCM", "pre", edd), "rcm_week_pre5") - - few_weeks_ago = override_get_today() - timedelta(days=23) - self.assertEqual(utils.get_tag("BCM", "post", few_weeks_ago), "bcm_week_post3") - - def test_get_tag_with_sequence(self): - """ - Returns the correct tag with a sequence - """ - self.assertEqual( - utils.get_tag("RCM", "pre", override_get_today(), "a"), - "rcm_week_pre40_a", - ) - - few_weeks_ago = override_get_today() - timedelta(days=23) - self.assertEqual(utils.get_tag("BCM", "post", few_weeks_ago), "bcm_week_post3") - - -class TestGetMessage(TestCase): - @responses.activate - def test_get_message_non_template(self): - """ - Returns the message body - """ - responses.add( - responses.GET, - "http://contentrepo/api/v2/pages/1111/?whatsapp=True&tracking=yes", - json={"body": {"text": {"value": {"message": "Test Message"}}}}, - status=200, - ) - is_template, has_parameters, message, template_name = utils.get_message( - 1111, {"tracking": "yes"} - ) - self.assertFalse(is_template) - self.assertFalse(has_parameters) - self.assertEqual(message, "Test Message") - self.assertIsNone(template_name) - - @responses.activate - def test_get_message_template(self): - """ - Returns the message body - """ - responses.add( - responses.GET, - "http://contentrepo/api/v2/pages/1111/?whatsapp=True&tracking=yes", - json={ - "body": { - "text": {"value": {"message": "Test Message"}}, - "revision": 123123, - }, - "tags": ["whatsapp_template"], - "title": "bcm_week_post3", - }, - status=200, - ) - is_template, has_parameters, message, template_name = utils.get_message( - 1111, {"tracking": "yes"} - ) - self.assertTrue(is_template) - self.assertFalse(has_parameters) - self.assertEqual(template_name, "bcm_week_post3_123123") - - @responses.activate - def test_get_message_template_with_parameters(self): - """ - Returns the message body - """ - responses.add( - responses.GET, - "http://contentrepo/api/v2/pages/1111/?whatsapp=True&tracking=yes", - json={ - "body": { - "text": {"value": {"message": "Test Message {{1}}"}}, - "revision": 123123, - }, - "tags": ["whatsapp_template"], - "title": "bcm_week_post3", - }, - status=200, - ) - is_template, has_parameters, message, template_name = utils.get_message( - 1111, {"tracking": "yes"} - ) - self.assertTrue(is_template) - self.assertTrue(has_parameters) - self.assertEqual(template_name, "bcm_week_post3_123123") - - -class TestGetMessageDetails(TestCase): - @responses.activate - def test_get_message_details_not_found(self): - tag = "bcm_week_post3" - responses.add( - responses.GET, - f"http://contentrepo/api/v2/pages?tag={tag}", - json={"results": []}, - status=200, - ) - - details = utils.get_message_details(tag, {}) - - self.assertEqual(details, {"warning": "no message found"}) - - @responses.activate - def test_get_message_details_too_many(self): - tag = "bcm_week_post3" - responses.add( - responses.GET, - f"http://contentrepo/api/v2/pages?tag={tag}", - json={"results": [1, 2]}, - status=200, - ) - - details = utils.get_message_details(tag, {}) - - self.assertEqual(details, {"error": "multiple message found"}) - - @responses.activate - @patch("mqr.utils.get_message") - def test_get_message_details(self, mock_get_message): - mock_get_message.return_value = (False, False, "Test Message {{1}}", None) - - tag = "bcm_week_post3" - responses.add( - responses.GET, - f"http://contentrepo/api/v2/pages?tag={tag}", - json={"results": [{"id": 1111}]}, - status=200, - ) - - details = utils.get_message_details(tag, {}, "Mom") - - self.assertEqual( - details, - { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - "template_name": None, - }, - ) - - -class TestGetNextMessage(TestCase): - def setUp(self): - utils.get_today = override_get_today - - @patch("mqr.utils.get_message_details") - def test_get_next_message(self, mock_get_message_details): - mock_get_message_details.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - } - - edd = datetime.strptime("20220701", "%Y%m%d").date() - response = utils.get_next_message(edd, "pre", "RCM", None, "Mom", {}) - - self.assertEqual( - response, - { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - "next_send_date": utils.get_next_send_date(), - "tag": "rcm_week_pre23", - }, - ) - - -class TestGetMidweekArmMessage(TestCase): - def setUp(self): - utils.get_today = override_get_today - - @patch("mqr.utils.get_message_details") - def test_get_midweek_arm_message(self, mock_get_message_details): - mock_get_message_details.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - } - - response = utils.get_midweek_arm_message("arm_week_pre39", "Mom", {}) - - self.assertEqual( - response, - { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - "tag": "arm_week_pre39_mid", - }, - ) - - mock_get_message_details.assert_called_with("arm_week_pre39_mid", {}, "Mom") - - -class TestGetNextArmMessage(TestCase): - def setUp(self): - utils.get_today = override_get_today - - @responses.activate - @patch("mqr.utils.get_message_details") - def test_get_next_message_with_sequence(self, mock_get_message_details): - mock_get_message_details.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - } - - responses.add( - responses.GET, - "http://contentrepo/api/v2/pages?tag=rcm_week_pre23_b", - json={"results": [{"id": 1111}]}, - status=200, - ) - - response = utils.get_next_arm_message("rcm_week_pre23", "a", "Mom", {}) - - message_prompt = "To get another helpful message tomorrow, reply *YES*." - - self.assertEqual( - response, - { - "has_next_message": True, - "is_template": False, - "has_parameters": False, - "message": f"Test Message Mom\n\n{message_prompt}", - }, - ) - - @responses.activate - @patch("mqr.utils.get_message_details") - def test_get_next_message_with_sequence_last(self, mock_get_message_details): - mock_get_message_details.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - } - - responses.add( - responses.GET, - "http://contentrepo/api/v2/pages?tag=rcm_week_pre23_b", - json={"results": []}, - status=200, - ) - - response = utils.get_next_arm_message("rcm_week_pre23", "a", "Mom", {}) - - message_prompt = "-----\nReply:\n*MENU* for the main menu 📌" - - self.assertEqual( - response, - { - "has_next_message": False, - "is_template": False, - "has_parameters": False, - "message": f"Test Message Mom\n\n{message_prompt}", - }, - ) - - @responses.activate - @patch("mqr.utils.get_message_details") - def test_get_next_message_with_sequence_and_existing_prompt( - self, mock_get_message_details - ): - mock_get_message_details.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom\n\nTo get more information about coping " - "after the birth, reply *YES*.", - } - - responses.add( - responses.GET, - "http://contentrepo/api/v2/pages?tag=rcm_week_pre23_b", - json={"results": [{"id": 1111}]}, - status=200, - ) - - response = utils.get_next_arm_message("rcm_week_pre23", "a", "Mom", {}) - - self.assertEqual( - response, - { - "has_next_message": True, - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom\n\nTo get more information about coping " - "after the birth, reply *YES*.", - }, - ) - - -class TestGetFaqMessage(TestCase): - @patch("mqr.utils.get_faq_menu") - @patch("mqr.utils.get_message_details") - def test_get_faq_message(self, mock_get_message_details, mock_get_faq_menu): - mock_get_message_details.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - } - mock_get_faq_menu.return_value = ("*1* question1?\n*2* question 2?", "1,3") - - response = utils.get_faq_message("rcm_week_pre21", 2, [], {}) - - self.assertEqual( - response, - { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - "faq_menu": "*1* question1?\n*2* question 2?", - "faq_numbers": "1,3", - "viewed": ["rcm_week_pre21_faq2"], - }, - ) - - mock_get_faq_menu.assert_called_with( - "rcm_week_pre21", ["rcm_week_pre21_faq2"], False - ) - - @patch("mqr.utils.get_faq_menu") - @patch("mqr.utils.get_message_details") - def test_get_faq_message_rcm_bcm(self, mock_get_message_details, mock_get_faq_menu): - mock_get_message_details.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - } - mock_get_faq_menu.return_value = ("*1* question1?\n*2* question 2?", "1,3") - - response = utils.get_faq_message("rcm_bcm_week_pre21", 2, [], {}) - - self.assertEqual( - response, - { - "is_template": False, - "has_parameters": False, - "message": "Test Message Mom", - "faq_menu": "*1* question1?\n*2* question 2?", - "faq_numbers": "1,3", - "viewed": ["rcm_week_pre21_faq2"], - }, - ) - - mock_get_faq_menu.assert_called_with( - "rcm_week_pre21", ["rcm_week_pre21_faq2"], True - ) - - -class TestGetFaqMenu(TestCase): - @responses.activate - def test_get_faq_menu(self): - tag = "rcm_week_pre21" - responses.add( - responses.GET, - f"http://contentrepo/faqmenu?viewed=&tag={tag}", - json=[ - {"order": 1, "title": "Question 1?"}, - {"order": 3, "title": "Question 3?"}, - ], - status=200, - ) - - menu, faq_numbers = utils.get_faq_menu(tag, [], False) - - self.assertEqual(menu, "*1* - Question 1?\n*2* - Question 3?") - self.assertEqual(faq_numbers, "1,3") - - @responses.activate - def test_get_faq_menu_with_viewed(self): - tag = "rcm_week_pre21" - responses.add( - responses.GET, - f"http://contentrepo/faqmenu?viewed={tag}_faq1&tag={tag}", - json=[ - {"order": 1, "title": "Question 1?"}, - {"order": 3, "title": "Question 3?"}, - ], - status=200, - ) - - menu, faq_numbers = utils.get_faq_menu(tag, [f"{tag}_faq1"], False) - - self.assertEqual(menu, "*1* - Question 1?\n*2* - Question 3?") - self.assertEqual(faq_numbers, "1,3") - - @responses.activate - def test_get_faq_menu_bcm(self): - tag = "rcm_week_pre21" - responses.add( - responses.GET, - f"http://contentrepo/faqmenu?viewed=&tag={tag}", - json=[ - {"order": 1, "title": "Question 1?"}, - {"order": 3, "title": "Question 3?"}, - ], - status=200, - ) - - menu, faq_numbers = utils.get_faq_menu(tag, [], True) - - test_menu = [ - "*1* - Question 1?", - "*2* - Question 3?", - "*3* - *FIND* more topics 🔎", - ] - - self.assertEqual(menu, "\n".join(test_menu)) - self.assertEqual(faq_numbers, "1,3") - - @responses.activate - def test_get_faq_menu_with_menu_offset(self): - tag = "rcm_week_pre21" - responses.add( - responses.GET, - f"http://contentrepo/faqmenu?viewed=&tag={tag}", - json=[ - {"order": 1, "title": "Question 1?"}, - {"order": 3, "title": "Question 3?"}, - ], - status=200, - ) - - menu, faq_numbers = utils.get_faq_menu(tag, [], False, 3) - - self.assertEqual(menu, "*4* - Question 1?\n*5* - Question 3?") - self.assertEqual(faq_numbers, "1,3") - - -class TestGetNextSendDate(TestCase): - def setUp(self): - utils.get_today = override_get_today - - def test_get_next_send_date(self): - next_date = utils.get_next_send_date() - self.assertEqual(next_date, date(2022, 3, 8)) - - -class TestGetFirstSendDate(TestCase): - def setUp(self): - utils.get_today = override_get_today - - def test_get_first_send_date_prebirth(self): - first_send_date = utils.get_first_send_date(date(2022, 5, 13)) - self.assertEqual(first_send_date, date(2022, 3, 4)) - - def test_get_first_send_date_postbirth(self): - first_send_date = utils.get_first_send_date(date(2022, 2, 13)) - self.assertEqual(first_send_date, date(2022, 3, 6)) - - def test_get_first_send_date_postbirth_today(self): - first_send_date = utils.get_first_send_date(override_get_today()) - self.assertEqual(first_send_date, date(2022, 3, 8)) - - -class TestIsStudyActiveForWeeksPregnant(TestCase): - def test_is_study_active_for_weeks_pregnant(self): - utils.get_today = lambda: date(2022, 5, 25) - - edd = date(2022, 11, 15) # 16 weeks - study_active = utils.is_study_active_for_weeks_pregnant(edd) - self.assertFalse(study_active) - - edd = date(2022, 11, 8) # 17 weeks - study_active = utils.is_study_active_for_weeks_pregnant(edd) - self.assertTrue(study_active) - - utils.get_today = lambda: date(2022, 5, 31) - - edd = date(2022, 11, 14) # 17 weeks - study_active = utils.is_study_active_for_weeks_pregnant(edd) - self.assertFalse(study_active) - - edd = date(2022, 11, 1) # 18 weeks - study_active = utils.is_study_active_for_weeks_pregnant(edd) - self.assertTrue(study_active) - - @override_settings(MQR_WEEK_LIMIT_OFFSET=4) - def test_is_study_active_for_weeks_pregnant_with_offset(self): - utils.get_today = lambda: date(2022, 5, 25) - - # Study 2 weeks in, everything is valid - edd = date(2022, 11, 15) # 16 weeks - study_active = utils.is_study_active_for_weeks_pregnant(edd) - self.assertTrue(study_active) - - edd = date(2022, 11, 8) # 17 weeks - study_active = utils.is_study_active_for_weeks_pregnant(edd) - self.assertTrue(study_active) - - # Study 7 weeks in, validate against edd - utils.get_today = lambda: date(2022, 6, 30) - - edd = date(2022, 12, 12) # 17 weeks - study_active = utils.is_study_active_for_weeks_pregnant(edd) - self.assertFalse(study_active) - - edd = date(2022, 12, 5) # 18 weeks - study_active = utils.is_study_active_for_weeks_pregnant(edd) - self.assertTrue(study_active) diff --git a/mqr/tests/test_views.py b/mqr/tests/test_views.py deleted file mode 100644 index f1de630e..00000000 --- a/mqr/tests/test_views.py +++ /dev/null @@ -1,1037 +0,0 @@ -import datetime -import json -import uuid -from unittest.mock import patch - -import responses -from django.contrib.auth import get_user_model -from django.test import override_settings -from django.urls import reverse -from rest_framework import status -from rest_framework.test import APITestCase -from temba_client.v2 import TembaClient - -from mqr import views -from mqr.models import BaselineSurveyResult, MqrStrata -from registrations.models import ClinicCode - - -class NextMessageViewTests(APITestCase): - url = reverse("mqr-nextmessage") - - def test_unauthenticated(self): - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - { - "arm": ["This field is required."], - "edd_or_dob_date": ["This field is required."], - "subscription_type": ["This field is required."], - "mom_name": ["This field is required."], - "contact_uuid": ["This field is required."], - "run_uuid": ["This field is required."], - }, - ) - - @patch("mqr.views.get_next_message") - def test_next_message(self, mock_get_next_message): - contact_uuid = str(uuid.uuid4()) - run_uuid = str(uuid.uuid4()) - - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_next_message.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message 1", - "next_send_date": "2022-03-14", - "tag": "BCM_week_PRE19", - } - - response = self.client.post( - self.url, - { - "arm": "BCM", - "edd_or_dob_date": "2022-07-12", - "subscription_type": "PRE", - "mom_name": "Test", - "contact_uuid": contact_uuid, - "run_uuid": run_uuid, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "message": "Test Message 1", - "is_template": False, - "has_parameters": False, - "next_send_date": "2022-03-14", - "tag": "BCM_week_PRE19", - }, - ) - - mock_tracking_data = { - "data__contact_uuid": contact_uuid, - "data__run_uuid": run_uuid, - "data__mqr": "scheduled", - } - - mock_get_next_message.assert_called_with( - datetime.date(2022, 7, 12), - "PRE", - "BCM", - None, - "Test", - mock_tracking_data, - ) - - @patch("mqr.views.get_next_message") - def test_next_message_error(self, mock_get_next_message): - contact_uuid = str(uuid.uuid4()) - run_uuid = str(uuid.uuid4()) - - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_next_message.return_value = {"error": "no message found"} - - response = self.client.post( - self.url, - { - "arm": "BCM", - "edd_or_dob_date": "2022-07-12", - "subscription_type": "PRE", - "mom_name": "Test", - "contact_uuid": contact_uuid, - "run_uuid": run_uuid, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - {"error": "no message found"}, - ) - - -class NextArmMessageViewTests(APITestCase): - url = reverse("mqr-nextarmmessage") - - def test_unauthenticated(self): - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - { - "last_tag": ["This field is required."], - "mom_name": ["This field is required."], - "sequence": ["This field is required."], - "contact_uuid": ["This field is required."], - "run_uuid": ["This field is required."], - }, - ) - - @patch("mqr.views.get_next_arm_message") - def test_next_arm_message(self, mock_get_next_arm_message): - contact_uuid = str(uuid.uuid4()) - run_uuid = str(uuid.uuid4()) - - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_next_arm_message.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message 1", - "next_send_date": "2022-03-14", - "tag": "BCM_week_PRE19", - } - - response = self.client.post( - self.url, - { - "last_tag": "rcm_week_pre23", - "sequence": "a", - "mom_name": "Test", - "contact_uuid": contact_uuid, - "run_uuid": run_uuid, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "message": "Test Message 1", - "is_template": False, - "has_parameters": False, - "next_send_date": "2022-03-14", - "tag": "BCM_week_PRE19", - }, - ) - - mock_tracking_data = { - "data__contact_uuid": contact_uuid, - "data__run_uuid": run_uuid, - "data__mqr": "scheduled", - } - - mock_get_next_arm_message.assert_called_with( - "rcm_week_pre23", - "a", - "Test", - mock_tracking_data, - ) - - @patch("mqr.views.get_next_arm_message") - def test_next_arm_message_error(self, mock_get_next_arm_message): - contact_uuid = str(uuid.uuid4()) - run_uuid = str(uuid.uuid4()) - - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_next_arm_message.return_value = {"error": "no message found"} - - response = self.client.post( - self.url, - { - "last_tag": "rcm_week_pre23", - "sequence": "a", - "mom_name": "Test", - "contact_uuid": contact_uuid, - "run_uuid": run_uuid, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - {"error": "no message found"}, - ) - - -class MidweekArmMessageViewTests(APITestCase): - url = reverse("mqr-midweekarmmessage") - - def test_unauthenticated(self): - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - { - "last_tag": ["This field is required."], - "mom_name": ["This field is required."], - "contact_uuid": ["This field is required."], - "run_uuid": ["This field is required."], - }, - ) - - @patch("mqr.views.get_midweek_arm_message") - def test_arm_midweek_message(self, mock_get_midweek_arm_message): - contact_uuid = str(uuid.uuid4()) - run_uuid = str(uuid.uuid4()) - - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_midweek_arm_message.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message 1", - "next_send_date": "2022-03-14", - "tag": "arm_week_pre39_mid", - } - - response = self.client.post( - self.url, - { - "last_tag": "arm_week_pre39", - "mom_name": "Test", - "contact_uuid": contact_uuid, - "run_uuid": run_uuid, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "message": "Test Message 1", - "is_template": False, - "has_parameters": False, - "next_send_date": "2022-03-14", - "tag": "arm_week_pre39_mid", - }, - ) - - mock_tracking_data = { - "data__contact_uuid": contact_uuid, - "data__run_uuid": run_uuid, - "data__mqr": "scheduled", - } - - mock_get_midweek_arm_message.assert_called_with( - "arm_week_pre39", - "Test", - mock_tracking_data, - ) - - @patch("mqr.views.get_midweek_arm_message") - def test_midweek_arm_message_error(self, mock_get_midweek_arm_message): - contact_uuid = str(uuid.uuid4()) - run_uuid = str(uuid.uuid4()) - - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_midweek_arm_message.return_value = {"error": "no message found"} - - response = self.client.post( - self.url, - { - "last_tag": "arm_week_pre39", - "mom_name": "Test", - "contact_uuid": contact_uuid, - "run_uuid": run_uuid, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - {"error": "no message found"}, - ) - - -class FaqViewTests(APITestCase): - url = reverse("mqr-faq") - - def test_unauthenticated(self): - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - { - "tag": ["This field is required."], - "faq_number": ["This field is required."], - "contact_uuid": ["This field is required."], - "run_uuid": ["This field is required."], - }, - ) - - @patch("mqr.views.get_faq_message") - def test_faq_message(self, mock_get_faq_message): - contact_uuid = str(uuid.uuid4()) - run_uuid = str(uuid.uuid4()) - - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_faq_message.return_value = { - "is_template": False, - "has_parameters": False, - "message": "Test Message 1", - } - - response = self.client.post( - self.url, - { - "tag": "BCM_week_pre22", - "faq_number": 1, - "viewed": ["test"], - "contact_uuid": contact_uuid, - "run_uuid": run_uuid, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "message": "Test Message 1", - "is_template": False, - "has_parameters": False, - }, - ) - - mock_tracking_data = { - "data__contact_uuid": contact_uuid, - "data__run_uuid": run_uuid, - "data__mqr": "faq", - } - - mock_get_faq_message.assert_called_with( - "bcm_week_pre22", 1, ["test"], mock_tracking_data - ) - - -class FaqMenuViewTests(APITestCase): - url = reverse("mqr-faq-menu") - - def test_unauthenticated(self): - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - {"tag": ["This field is required."]}, - ) - - @patch("mqr.views.get_faq_menu") - def test_faq_menu(self, mock_get_faq_menu): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_faq_menu.return_value = ("2 - menu1, 3 - Menu2", "1,2") - - response = self.client.post( - self.url, - {"tag": "RCM_BCM_week_pre22", "menu_offset": 1}, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "menu": "2 - menu1, 3 - Menu2", - "faq_numbers": "1,2", - }, - ) - - mock_get_faq_menu.assert_called_with("rcm_week_pre22", [], False, 1) - - -class StrataValidation(APITestCase): - url = reverse("mqr_strataarm_validation") - - def test_random_arm_unauthorized_user(self): - """ - unauthorized user access denied - Returns: status code 401 - - """ - - response = self.client.get(self.url) - - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - { - "estimated_delivery_date": ["This field is required."], - "facility_code": ["This field is required."], - "mom_age": ["This field is required."], - "registration_date": ["This field is required."], - }, - ) - - @override_settings(MQR_STUDY_START_DATE="2022-02-21") - def test_random_arm_validate(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - response = self.client.post( - self.url, - data={ - "facility_code": "123456", - "estimated_delivery_date": datetime.date(2022, 11, 15), - "registration_date": datetime.date(2022, 3, 8), - "mom_age": 32, - }, - format="json", - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - {"Excluded": True, "reason": "study not active for weeks pregnant"}, - ) - - def test_random_arm_exclude(self): - """ - Exclude if person doesn't qualify for study - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - ClinicCode.objects.create( - code="123456", value=1, uid=1, name="test", province="EC" - ) - - response = self.client.post( - self.url, - data={ - "estimated_delivery_date": "2022-01-30", - "facility_code": "123456", - "registration_date": datetime.date(2022, 3, 8), - "mom_age": "38", - }, - format="json", - ) - - self.assertEqual( - response.json(), - {"Valid": False, "reason": "clinic: 123456, weeks: None"}, - ) - - @override_settings(MQR_STUDY_START_DATE="2022-01-03") - def test_random_arm_exclude_study_limit(self): - """ - Exclude if person doesn't qualify for study based on min study pregnancy weeks - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - ClinicCode.objects.create( - code="123456", value=1, uid=1, name="test", province="EC" - ) - - response = self.client.post( - self.url, - data={ - "facility_code": "123456", - "registration_date": datetime.date(2022, 3, 8), - "estimated_delivery_date": datetime.date(2022, 8, 17), - "mom_age": 32, - }, - format="json", - ) - - self.assertEqual( - response.json(), - {"Excluded": True, "reason": "study not active for weeks pregnant"}, - ) - - -class StrataRandomization(APITestCase): - url = reverse("mqr_randomstrataarm") - - def test_random_arm_unauthorized_user(self): - """ - unauthorized user access denied - Returns: status code 401 - - """ - - response = self.client.get(self.url) - - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - { - "estimated_delivery_date": ["This field is required."], - "facility_code": ["This field is required."], - "mom_age": ["This field is required."], - "registration_date": ["This field is required."], - }, - ) - - def test_random_arm(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - ClinicCode.objects.create( - code="123456", value=1, uid=1, name="test", province="EC" - ) - - response = self.client.post( - self.url, - data={ - "facility_code": "123456", - "estimated_delivery_date": datetime.date(2022, 8, 17), - "registration_date": datetime.date(2022, 3, 8), - "mom_age": 32, - }, - format="json", - ) - - strata_arm = MqrStrata.objects.get( - province="EC", weeks_pregnant_bucket="16-20", age_bucket="31+" - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertContains(response, strata_arm.order.split(",")[0]) - self.assertEqual(strata_arm.next_index, 1) - - def test_get_random_starta_arm(self): - """ - Check the next arm from the existing data - Returns: string response - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - ClinicCode.objects.create( - code="246800", value=2, uid=2, name="test", province="MP" - ) - - MqrStrata.objects.create( - province="MP", - weeks_pregnant_bucket="26-30", - age_bucket="31+", - next_index=1, - order="ARM,RCM_BCM,RCM,RCM_SMS,BCM", - ) - - response = self.client.post( - self.url, - data={ - "facility_code": "246800", - "estimated_delivery_date": datetime.date(2022, 6, 13), - "registration_date": datetime.date(2022, 3, 8), - "mom_age": 34, - }, - format="json", - ) - - self.assertEqual(response.data, {"random_arm": "RCM_BCM"}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_out_of_index_arm(self): - """ - Test for out of index to delete the order after maximum arm - """ - - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - ClinicCode.objects.create( - code="369120", value=3, uid=3, name="test", province="FS" - ) - - MqrStrata.objects.create( - province="FS", - weeks_pregnant_bucket="26-30", - age_bucket="18-30", - next_index=4, - order="ARM,RCM,RCM_SMS,BCM,RCM_BCM", - ) - - # This api call will delete the existing arm - response = self.client.post( - self.url, - data={ - "facility_code": "369120", - "estimated_delivery_date": datetime.date(2022, 6, 13), - "registration_date": datetime.date(2022, 3, 8), - "mom_age": 22, - }, - format="json", - ) - - strata_arm = MqrStrata.objects.filter( - province="FS", weeks_pregnant_bucket="26-30", age_bucket="18-30" - ) - - self.assertEqual(strata_arm.count(), 0) - self.assertEqual(response.data.get("random_arm"), "RCM_BCM") - self.assertEqual(response.status_code, status.HTTP_200_OK) - - -class BaselineSurveyResultViewTests(APITestCase): - url = reverse("baselinesurveyresult-list") - - def test_unauthenticated(self): - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - {"msisdn": ["This field is required."]}, - ) - - def test_successful_create(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - data = { - "msisdn": "27831231234", - "breastfeed": BaselineSurveyResult.YesNoSkip.YES, - "breastfeed_period": BaselineSurveyResult.BreastfeedPeriod.MONTHS_0_3, - "vaccine_importance": BaselineSurveyResult.AgreeDisagree.AGREE, - "vaccine_benefits": BaselineSurveyResult.AgreeDisagree.DISAGREE, - "clinic_visit_frequency": BaselineSurveyResult.ClinicVisitFrequency.NEVER, - "vegetables": BaselineSurveyResult.YesNoSkip.SKIP, - "fruit": BaselineSurveyResult.YesNoSkip.NO, - "dairy": BaselineSurveyResult.YesNoSkip.SKIP, - "liver_frequency": BaselineSurveyResult.LiverFrequency.LESS_ONCE_MONTH, - "danger_sign1": BaselineSurveyResult.DangerSign1.WEIGHT_GAIN, - "danger_sign2": BaselineSurveyResult.DangerSign2.BLOAT, - "marital_status": BaselineSurveyResult.MaritalStatus.MARRIED, - "education_level": BaselineSurveyResult.EducationLevel.DEGREE_OR_HIGHER, - } - - response = self.client.post(self.url, data) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - data = response.json() - self.assertIsNotNone(data.pop("created_at")) - self.assertIsNotNone(data.pop("updated_at")) - self.assertIsNone(data.pop("airtime_sent_at")) - self.assertEqual( - data, - { - "msisdn": "27831231234", - "created_by": "test", - "breastfeed": "yes", - "breastfeed_period": "0_3_months", - "vaccine_importance": "agree", - "vaccine_benefits": "disagree", - "clinic_visit_frequency": "never", - "vegetables": "skip", - "fruit": "no", - "dairy": "skip", - "liver_frequency": "less_once_a_month", - "danger_sign1": "weight_gain", - "danger_sign2": "bloating", - "marital_status": "married", - "education_level": "degree_or_higher", - "airtime_sent": False, - }, - ) - - def test_successful_update(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - msisdn = "27831231234" - result = BaselineSurveyResult.objects.create( - **{ - "msisdn": msisdn, - "breastfeed": BaselineSurveyResult.YesNoSkip.SKIP, - } - ) - - url = reverse("baselinesurveyresult-detail", args=(msisdn,)) - response = self.client.patch( - url, - { - "breastfeed": BaselineSurveyResult.YesNoSkip.YES, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - result.refresh_from_db() - - self.assertEqual(response.json()["msisdn"], result.msisdn) - self.assertEqual(result.breastfeed, BaselineSurveyResult.YesNoSkip.YES) - self.assertIsNone(result.airtime_sent_at) - - def test_successful_update_airtime(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - msisdn = "27831231234" - result = BaselineSurveyResult.objects.create( - **{ - "msisdn": msisdn, - "breastfeed": BaselineSurveyResult.YesNoSkip.SKIP, - } - ) - - url = reverse("baselinesurveyresult-detail", args=(msisdn,)) - response = self.client.patch( - url, - { - "airtime_sent": True, - }, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - result.refresh_from_db() - - self.assertEqual(response.json()["msisdn"], result.msisdn) - self.assertTrue(result.airtime_sent) - self.assertIsNotNone(result.airtime_sent_at) - - def test_get_result_by_msisdn(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - msisdn = "27831231234" - - BaselineSurveyResult.objects.create( - **{ - "msisdn": msisdn, - "breastfeed": BaselineSurveyResult.YesNoSkip.SKIP, - } - ) - BaselineSurveyResult.objects.create( - **{ - "msisdn": "27831112222", - "breastfeed": BaselineSurveyResult.YesNoSkip.SKIP, - } - ) - - response = self.client.get( - self.url, - params={"msisdn": msisdn}, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - result = response.json()["results"][0] - self.assertEqual(result["msisdn"], msisdn) - - def test_get_result_by_msisdn_not_found(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - response = self.client.get( - self.url, - params={"msisdn": "27831231234"}, - ) - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.json()["results"]), 0) - - -class FirstSendDateViewTests(APITestCase): - url = reverse("mqr-firstsenddate") - - def test_unauthenticated(self): - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_data(self): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - response = self.client.post(self.url, {}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - {"edd_or_dob_date": ["This field is required."]}, - ) - - @patch("mqr.views.get_first_send_date") - def test_faq_message(self, mock_get_first_send_date): - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - mock_get_first_send_date.return_value = datetime.date(2022, 7, 15) - - response = self.client.post( - self.url, - { - "edd_or_dob_date": "2022-07-12", - }, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "first_send_date": "2022-07-15", - }, - ) - - mock_get_first_send_date.assert_called_with(datetime.date(2022, 7, 12)) - - -class MqrEndlineChecksViewSetTests(APITestCase): - url = reverse("mqr-endlinechecks") - - def setUp(self): - """ - Helper function to create RapidPro connection instance - """ - views.rapidpro = TembaClient("textit.in", "test-token") - - def add_get_rapidpro_contact( - self, consent="Accepted", arm="RCM_BCM", received=None, optedout=None - ): - """ - Helper function to build mock responses - """ - responses.add( - responses.GET, - "https://textit.in/api/v2/contacts.json?urn=whatsapp%3A27831231234", - json={ - "next": None, - "previous": None, - "results": [ - { - "uuid": "148947f5-a3b6-4b6b-9e9b-25058b1b7800", - "name": "", - "language": "eng", - "groups": [], - "fields": { - "mqr_consent": consent, - "mqr_arm": arm, - "endline_airtime_received": received, - "opted_out": optedout, - }, - "blocked": False, - "stopped": False, - "created_on": "2015-11-11T08:30:24.922024+00:00", - "modified_on": "2015-11-11T08:30:25.525936+00:00", - "urns": ["whatsapp:27712345682"], - } - ], - }, - ) - - @responses.activate - def test_mqr_endline_missing_msisdn(self): - """ - Check for an missing msisdn number - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - response = self.client.post(self.url) - self.assertEqual(response.status_code, 400) - self.assertEqual(response.json(), {"msisdn": ["This field is required."]}) - - @responses.activate - def test_mqr_endline_invalid_msisdn(self): - """ - Check for an invalid msisdn number - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - response = self.client.post(self.url, {"msisdn": "invalid"}) - self.assertEqual(response.status_code, 400) - self.assertEqual( - response.json(), - {"msisdn": ["(1) The string supplied did not seem to be a phone number."]}, - ) - - @responses.activate - def test_mqr_endline_get_contact_not_found(self): - """ - Check that the contact exists - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - responses.add( - responses.GET, - "https://textit.in/api/v2/contacts.json?urn=whatsapp:27831231234", - json={"next": None, "previous": None, "results": []}, - ) - response = self.client.post(self.url, data={"msisdn": "27831231234"}) - self.assertEqual(response.status_code, 404) - - @responses.activate - def test_mqr_endline_get_contact_no_arm_found(self): - """ - Check that the contact exists - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - self.add_get_rapidpro_contact(arm="") - response = self.client.post(self.url, data={"msisdn": "27831231234"}) - self.assertEqual(response.status_code, 404) - - @responses.activate - def test_mqr_endline_airtime_already_received(self): - """ - Check that the contact has not already received the airtime - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - self.add_get_rapidpro_contact(received="TRUE") - response = self.client.post(self.url, data={"msisdn": "27831231234"}) - self.assertEqual(response.status_code, 400) - self.assertEqual(response.json(), {"error": "Airtime already received"}) - - @responses.activate - def test_mqr_endline_validate_start_flow(self): - """ - Check that we can start the flow - """ - user = get_user_model().objects.create_user("test") - self.client.force_authenticate(user) - - self.add_get_rapidpro_contact() - - responses.add( - responses.POST, - "https://textit.in/api/v2/flow_starts.json", - json={ - "uuid": "mqr-send-airtime-flow-uuid", - "flow": { - "uuid": "mqr-send-airtime-flow-uuid", - "name": "MQR sent endline airtime", - }, - "groups": [], - "contacts": [], - "extra": {}, - "restart_participants": True, - "status": "complete", - "created_on": datetime.datetime.now().isoformat(), - "modified_on": datetime.datetime.now().isoformat(), - }, - ) - - response = self.client.post(self.url, data={"msisdn": "27831231234"}) - self.assertEqual(response.status_code, 202) - self.assertEqual( - response.json(), {"uuid": "148947f5-a3b6-4b6b-9e9b-25058b1b7800"} - ) - - request = responses.calls[1] - self.assertEqual( - json.loads(request.request.body), - { - "flow": "mqr-send-airtime-flow-uuid", - "urns": ["whatsapp:27831231234"], - "extra": {}, - }, - ) diff --git a/mqr/urls.py b/mqr/urls.py deleted file mode 100644 index 8cca716a..00000000 --- a/mqr/urls.py +++ /dev/null @@ -1,49 +0,0 @@ -from django.urls import include, re_path -from rest_framework import routers - -from . import views - -router = routers.DefaultRouter() -router.trailing_slash = "/?" -router.register("mqrbaselinesurvey", views.BaselineSurveyResultViewSet) - -urlpatterns = [ - re_path( - r"^api/v1/mqr_strataarm_validation/", - views.StrataArmValidationView.as_view(), - name="mqr_strataarm_validation", - ), - re_path( - r"^api/v1/mqr_randomstrataarm", - views.RandomStrataArmView.as_view(), - name="mqr_randomstrataarm", - ), - re_path(r"^api/v1/mqr-faq/", views.FaqView.as_view(), name="mqr-faq"), - re_path(r"^api/v1/mqr-faq-menu/", views.FaqMenuView.as_view(), name="mqr-faq-menu"), - re_path( - r"^api/v1/mqr-nextmessage/", - views.NextMessageView.as_view(), - name="mqr-nextmessage", - ), - re_path( - r"^api/v1/mqr-midweekarmmessage/", - views.MidweekArmMessageView.as_view(), - name="mqr-midweekarmmessage", - ), - re_path( - r"^api/v1/mqr-nextarmmessage/", - views.NextArmMessageView.as_view(), - name="mqr-nextarmmessage", - ), - re_path( - r"^api/v1/mqr-firstsenddate", - views.FirstSendDateView.as_view(), - name="mqr-firstsenddate", - ), - re_path( - r"^api/v1/mqr-endlinechecks", - views.MqrEndlineChecksViewSet.as_view(), - name="mqr-endlinechecks", - ), - re_path(r"^api/v1/", include(router.urls)), -] diff --git a/mqr/utils.py b/mqr/utils.py deleted file mode 100644 index d8ee94f4..00000000 --- a/mqr/utils.py +++ /dev/null @@ -1,231 +0,0 @@ -from datetime import datetime, timedelta -from urllib.parse import urljoin - -import requests -from django.conf import settings - -from ndoh_hub.utils import get_today -from registrations.models import ClinicCode - - -def get_week(subscription_type, edd_or_dob_date): - if subscription_type == "pre": - if edd_or_dob_date < get_today(): - return 40 + (abs(get_today() - edd_or_dob_date).days // 7) - else: - return 40 - (abs(get_today() - edd_or_dob_date).days // 7) - else: - return abs(get_today() - edd_or_dob_date).days // 7 - - -def get_tag(arm, subscription_type, edd_or_dob_date, tag_extra=None): - week = get_week(subscription_type, edd_or_dob_date) - - label = f"{arm}_week_{subscription_type}{week}" - if tag_extra: - label = f"{label}_{tag_extra}" - return label.lower() - - -def get_message(page_id, tracking_data): - tracking_data["whatsapp"] = "True" - url = urljoin(settings.MQR_CONTENTREPO_URL, f"/api/v2/pages/{page_id}/") - response = requests.get(url, params=tracking_data) - response.raise_for_status() - - page = response.json() - message = page["body"]["text"]["value"]["message"] - - if "whatsapp_template" in page.get("tags", []): - page_title = page["title"] - latest_revision_id = page["body"]["revision"] - template_name = f"{page_title}_{latest_revision_id}" - return True, "{{1}}" in message, message, template_name - - return False, False, message, None - - -def get_message_details(tag, tracking_data, mom_name=None): - url = urljoin(settings.MQR_CONTENTREPO_URL, f"api/v2/pages?tag={tag}") - response = requests.get(url) - response.raise_for_status() - - if len(response.json()["results"]) == 1: - page_id = response.json()["results"][0]["id"] - is_template, has_parameters, message, template_name = get_message( - page_id, tracking_data - ) - - if mom_name: - message = message.replace("{{1}}", mom_name) - - return { - "is_template": is_template, - "has_parameters": has_parameters, - "message": message, - "template_name": template_name, - } - if len(response.json()["results"]) == 0: - return {"warning": "no message found"} - elif len(response.json()["results"]) > 1: - return {"error": "multiple message found"} - - -def get_next_message( - edd_or_dob_date, - subscription_type, - arm, - tag_extra, - mom_name, - tracking_data, -): - tag = get_tag(arm, subscription_type, edd_or_dob_date, tag_extra) - - response = get_message_details(tag, tracking_data, mom_name) - - response["next_send_date"] = get_next_send_date() - response["tag"] = tag - - return response - - -def get_midweek_arm_message(last_tag, mom_name, tracking_data): - tag = f"{last_tag}_mid" - response = get_message_details(tag, tracking_data, mom_name) - response["tag"] = tag - return response - - -def get_next_arm_message(last_tag, sequence, mom_name, tracking_data): - tag = f"{last_tag}_{sequence}" - response = get_message_details(tag, tracking_data, mom_name) - - next_sequence = chr(ord(sequence) + 1) - next_tag = f"{last_tag}_{next_sequence}" - url = urljoin(settings.MQR_CONTENTREPO_URL, f"api/v2/pages?tag={next_tag}") - contentrepo_response = requests.get(url) - contentrepo_response.raise_for_status() - - add_prompt = True - - if "*yes*" in response["message"].split("\n")[-1].lower(): - add_prompt = False - - if len(contentrepo_response.json()["results"]) == 1: - prompt_message = "To get another helpful message tomorrow, reply *YES*." - response["has_next_message"] = True - else: - prompt_message = "-----\nReply:\n*MENU* for the main menu 📌" - response["has_next_message"] = False - - if add_prompt: - base_message = response["message"] - response["message"] = f"{base_message}\n\n{prompt_message}" - - return response - - -def get_faq_message(tag, faq_number, viewed, tracking_data): - bcm = "_bcm_" in tag - tag = tag.replace("_bcm_", "_") - - faq_tag = f"{tag}_faq{faq_number}" - response = get_message_details(faq_tag, tracking_data) - - viewed.append(faq_tag) - - faq_menu, faq_numbers = get_faq_menu(tag, viewed, bcm) - - response["faq_menu"] = faq_menu - response["faq_numbers"] = faq_numbers - response["viewed"] = viewed - - return response - - -def get_faq_menu(tag, viewed, bcm, menu_offset=0): - viewed_filter = ",".join(viewed) - url = urljoin( - settings.MQR_CONTENTREPO_URL, f"/faqmenu?viewed={viewed_filter}&tag={tag}" - ) - response = requests.get(url) - response.raise_for_status() - - pages = response.json() - - faq_numbers = [] - menu = [] - for i, page in enumerate(pages): - order = str(page["order"]) - title = page["title"].replace("*", "") - - faq_numbers.append(order) - menu.append(f"*{i+1+menu_offset}* - {title}") - - if bcm: - option = len(menu) + 1 + menu_offset - menu.append(f"*{option}* - *FIND* more topics 🔎") - - return "\n".join(menu), ",".join(faq_numbers) - - -def get_next_send_date(): - return get_today() + timedelta(weeks=1) - - -def get_first_send_date(edd_or_dob_date): - full_weeks = (get_today() - edd_or_dob_date).days // 7 + 1 - return edd_or_dob_date + timedelta(weeks=full_weeks) - - -def is_study_active_for_weeks_pregnant(estimated_delivery_date): - study_start_date = datetime.strptime( - settings.MQR_STUDY_START_DATE, "%Y-%m-%d" - ).date() - - weeks_pregnant = get_week("pre", estimated_delivery_date) - study_active_weeks = (get_today() - study_start_date).days // 7 - - if study_active_weeks <= settings.MQR_WEEK_LIMIT_OFFSET: - return True - - study_min_weeks = study_active_weeks + 15 - settings.MQR_WEEK_LIMIT_OFFSET - - return weeks_pregnant >= study_min_weeks - - -def get_facility_province(facility_code): - try: - clinic_code = ClinicCode.objects.get(code=facility_code) - except ClinicCode.DoesNotExist: - clinic_code = None - - return clinic_code - - -def get_weeks_pregnant(registration_date, estimated_date): - full_term_weeks = 40 - - # Get remaining weeks - remaining_weeks = (estimated_date - registration_date).days // 7 - - weeks_pregnant = full_term_weeks - remaining_weeks - - if 16 <= weeks_pregnant <= 20: - return "16-20" - elif 21 <= weeks_pregnant <= 25: - return "21-25" - elif 26 <= weeks_pregnant <= 30: - return "26-30" - else: - return None - - -def get_age_bucket(mom_age): - # Get age range - if 18 <= mom_age <= 30: - return "18-30" - elif mom_age >= 31: - return "31+" - else: - return None diff --git a/mqr/views.py b/mqr/views.py deleted file mode 100644 index 2367744c..00000000 --- a/mqr/views.py +++ /dev/null @@ -1,376 +0,0 @@ -import random - -from django.conf import settings -from django.http import Http404, JsonResponse -from django_filters import rest_framework as filters -from rest_framework import generics, permissions, status -from rest_framework.mixins import ( - CreateModelMixin, - ListModelMixin, - RetrieveModelMixin, - UpdateModelMixin, -) -from rest_framework.response import Response -from rest_framework.viewsets import GenericViewSet - -from eventstore.views import CursorPaginationFactory -from mqr.serializers import ( - FaqMenuSerializer, - FaqSerializer, - FirstSendDateSerializer, - MidweekArmMessageSerializer, - MqrEndlineChecksSerializer, - NextArmMessageSerializer, - NextMessageSerializer, -) -from mqr.utils import ( - get_age_bucket, - get_facility_province, - get_faq_menu, - get_faq_message, - get_first_send_date, - get_midweek_arm_message, - get_next_arm_message, - get_next_message, - get_weeks_pregnant, - is_study_active_for_weeks_pregnant, -) -from ndoh_hub.utils import rapidpro - -from .models import BaselineSurveyResult, MqrStrata -from .serializers import BaselineSurveyResultSerializer, MqrStrataSerializer - -STUDY_ARMS = ["ARM", "RCM", "RCM_BCM", "RCM_SMS"] - - -class StrataArmValidationView(generics.GenericAPIView): - permission_classes = (permissions.IsAuthenticated,) - serializer_class = MqrStrataSerializer - - def post(self, request): - serializer = MqrStrataSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - # use facility code and look up on db for province - facility_code = serializer.validated_data.get("facility_code") - estimated_delivery_date = serializer.validated_data.get( - "estimated_delivery_date" - ) - registration_date = serializer.validated_data.get("registration_date") - mom_age = serializer.validated_data.get("mom_age") - - if not is_study_active_for_weeks_pregnant(estimated_delivery_date): - return Response( - {"Excluded": True, "reason": "study not active for weeks pregnant"} - ) - - clinic_code = get_facility_province(facility_code) - weeks_pregnant_bucket = get_weeks_pregnant( - registration_date, estimated_delivery_date - ) - age_bucket = get_age_bucket(mom_age) - - if clinic_code and weeks_pregnant_bucket and age_bucket: - return Response( - { - "Valid": True, - } - ) - - clinic = clinic_code.code if clinic_code else None - - return Response( - { - "Valid": False, - "reason": f"clinic: {clinic}, weeks: {weeks_pregnant_bucket}", - } - ) - - -class RandomStrataArmView(generics.GenericAPIView): - def post(self, request): - """ - Randomization of the ARMs. - """ - serializer = MqrStrataSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - facility_code = serializer.validated_data.get("facility_code") - estimated_delivery_date = serializer.validated_data.get( - "estimated_delivery_date" - ) - registration_date = serializer.validated_data.get("registration_date") - mom_age = serializer.validated_data.get("mom_age") - - clinic_code = get_facility_province(facility_code) - weeks_pregnant_bucket = get_weeks_pregnant( - registration_date, estimated_delivery_date - ) - age_bucket = get_age_bucket(mom_age) - - if mom_age and weeks_pregnant_bucket and age_bucket: - province = clinic_code.province - - strata, created = MqrStrata.objects.get_or_create( - province=province, - weeks_pregnant_bucket=weeks_pregnant_bucket, - age_bucket=age_bucket, - ) - - if created: - random.shuffle(STUDY_ARMS) - random_arms = STUDY_ARMS - strata.order = ",".join(random_arms) - else: - random_arms = strata.order.split(",") - - arm = random_arms[strata.next_index] - - if strata.next_index + 1 == len(random_arms): - strata.delete() - else: - strata.next_index += 1 - strata.save() - - return Response({"random_arm": arm}) - - clinic = clinic_code.code if clinic_code else None - return Response( - { - "Excluded": True, - "reason": f"clinic: {clinic}, weeks: {weeks_pregnant_bucket}, " - f"age: {mom_age}", - } - ) - - -class BaseMessageView(generics.GenericAPIView): - def get_tracking_data(self, serializer, message_type): - return { - "data__contact_uuid": str(serializer.validated_data.get("contact_uuid")), - "data__run_uuid": str(serializer.validated_data.get("run_uuid")), - "data__mqr": message_type, - } - - -class NextMessageView(BaseMessageView): - permission_classes = (permissions.IsAuthenticated,) - - def post(self, request, *args, **kwargs): - """ - Gets the next message that we need to send from the content repo - """ - serializer = NextMessageSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - edd_or_dob_date = serializer.validated_data.get("edd_or_dob_date") - subscription_type = serializer.validated_data.get("subscription_type") - arm = serializer.validated_data.get("arm") - mom_name = serializer.validated_data.get("mom_name") - tag_extra = serializer.validated_data.get("tag_extra") - - response = get_next_message( - edd_or_dob_date, - subscription_type, - arm, - tag_extra, - mom_name, - self.get_tracking_data(serializer, "scheduled"), - ) - - if "error" in response: - return Response(response, status=status.HTTP_400_BAD_REQUEST) - - return Response(response, status=status.HTTP_200_OK) - - -class MidweekArmMessageView(BaseMessageView): - permission_classes = (permissions.IsAuthenticated,) - - def post(self, request, *args, **kwargs): - """ - Gets the midweek ARM message that we need to send from the content repo - """ - serializer = MidweekArmMessageSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - last_tag = serializer.validated_data.get("last_tag") - mom_name = serializer.validated_data.get("mom_name") - - response = get_midweek_arm_message( - last_tag, - mom_name, - self.get_tracking_data(serializer, "scheduled"), - ) - if "error" in response: - return Response(response, status=status.HTTP_400_BAD_REQUEST) - - return Response(response, status=status.HTTP_200_OK) - - -class NextArmMessageView(BaseMessageView): - permission_classes = (permissions.IsAuthenticated,) - - def post(self, request, *args, **kwargs): - """ - Gets the next ARM message that we need to send from the content repo - """ - serializer = NextArmMessageSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - last_tag = serializer.validated_data.get("last_tag") - mom_name = serializer.validated_data.get("mom_name") - sequence = serializer.validated_data.get("sequence") - - response = get_next_arm_message( - last_tag, - sequence, - mom_name, - self.get_tracking_data(serializer, "scheduled"), - ) - if "error" in response: - return Response(response, status=status.HTTP_400_BAD_REQUEST) - - return Response(response, status=status.HTTP_200_OK) - - -class FaqView(BaseMessageView): - permission_classes = (permissions.IsAuthenticated,) - - def post(self, request, *args, **kwargs): - """ - Get FAQ from content repo based on tag and faq number - """ - serializer = FaqSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - tag = serializer.validated_data.get("tag").lower() - faq_number = serializer.validated_data.get("faq_number") - viewed = serializer.validated_data.get("viewed", []) - - response = get_faq_message( - tag, - faq_number, - viewed, - self.get_tracking_data(serializer, "faq"), - ) - - return Response(response, status=status.HTTP_200_OK) - - -class FaqMenuView(generics.GenericAPIView): - permission_classes = (permissions.IsAuthenticated,) - - def post(self, request, *args, **kwargs): - """ - Get FAQ MENU from content repo based on tag - """ - serializer = FaqMenuSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - tag = serializer.validated_data.get("tag").lower() - menu_offset = serializer.validated_data.get("menu_offset", 0) - - tag = tag.replace("_bcm_", "_") - - menu, faq_numbers = get_faq_menu(tag, [], False, menu_offset) - - response = {"menu": menu, "faq_numbers": faq_numbers} - - return Response(response, status=status.HTTP_200_OK) - - -class BaselineSurveyResultFilter(filters.FilterSet): - updated_at_gt = filters.IsoDateTimeFilter(field_name="updated_at", lookup_expr="gt") - - class Meta: - model = BaselineSurveyResult - fields: list = ["msisdn"] - - -class BaselineSurveyResultViewSet( - GenericViewSet, - CreateModelMixin, - ListModelMixin, - RetrieveModelMixin, - UpdateModelMixin, -): - queryset = BaselineSurveyResult.objects.all() - serializer_class = BaselineSurveyResultSerializer - pagination_class = CursorPaginationFactory("updated_at") - filter_backends = [filters.DjangoFilterBackend] - filterset_class = BaselineSurveyResultFilter - - def create(self, request): - serializer = BaselineSurveyResultSerializer( - data=request.data, context={"request": request} - ) - - if serializer.is_valid(): - data = serializer.validated_data.copy() - data["created_by"] = request.user.username - - obj, created = BaselineSurveyResult.objects.update_or_create( - msisdn=data["msisdn"], defaults=data - ) - code = status.HTTP_201_CREATED if created else status.HTTP_200_OK - return JsonResponse(BaselineSurveyResultSerializer(obj).data, status=code) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - -class FirstSendDateView(generics.GenericAPIView): - permission_classes = (permissions.IsAuthenticated,) - - def post(self, request, *args, **kwargs): - """ - Get First send date based on EDD or baby DOB date - """ - serializer = FirstSendDateSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - edd_or_dob_date = serializer.validated_data.get("edd_or_dob_date") - - first_send_date = get_first_send_date(edd_or_dob_date) - - response = {"first_send_date": first_send_date} - - return Response(response, status=status.HTTP_200_OK) - - -class MqrEndlineChecksViewSet(generics.GenericAPIView): - # Lookup contact - - def post(self, request, *args, **kwargs): - serializer = MqrEndlineChecksSerializer(data=request.data) - serializer.is_valid(raise_exception=True) - - wa_id = serializer.validated_data.get("msisdn").replace("+", "") - - contact = rapidpro.get_contacts(urn=f"whatsapp:{wa_id}").first() - - # Contact not found - if contact is None: - raise Http404() - - if contact.fields.get("endline_airtime_received", "FALSE") == "TRUE": - return Response( - {"error": "Airtime already received"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - if not contact.fields.get("mqr_arm"): - raise Http404() - - self.start_topup_flow(wa_id) - - return_data = {"uuid": contact.uuid} - return Response(return_data, status=status.HTTP_202_ACCEPTED) - - def start_topup_flow(self, whatsapp_id): - if rapidpro and settings.MQR_SEND_AIRTIME_FLOW_ID: - return rapidpro.create_flow_start( - extra={}, - flow=settings.MQR_SEND_AIRTIME_FLOW_ID, - urns=[f"whatsapp:{whatsapp_id.lstrip('+')}"], - ) diff --git a/ndoh_hub/settings.py b/ndoh_hub/settings.py index fe7bd92f..e7acf7ee 100644 --- a/ndoh_hub/settings.py +++ b/ndoh_hub/settings.py @@ -60,7 +60,6 @@ "registrations", "changes", "eventstore", - "mqr", "aaq", ) @@ -270,14 +269,6 @@ day_of_week=RANDOM_CONTACTS_DAY_OF_WEEK, ), }, - "post-random-mqr-contacts-to-slack-channel": { - "task": "eventstore.tasks.post_random_mqr_contacts_to_slack_channel", - "schedule": crontab( - minute="0", - hour=RANDOM_CONTACTS_HOUR, - day_of_week=RANDOM_CONTACTS_DAY_OF_WEEK, - ), - }, "process-whatsapp-template-send-status": { "task": "eventstore.tasks.process_whatsapp_template_send_status", "schedule": 300.0, @@ -402,13 +393,6 @@ SLACK_CHANNEL = env.str("SLACK_CHANNEL", None) RANDOM_CONTACT_LIMIT = env.str("RANDOM_CONTACT_LIMIT", 10) -# MQR -MQR_CONTENTREPO_URL = env.str("MQR_CONTENTREPO_URL", None) -MQR_STUDY_START_DATE = env.str("MQR_STUDY_START_DATE", "2022-05-09") -MQR_SEND_AIRTIME_FLOW_ID = env.str("MQR_SEND_AIRTIME_FLOW_ID", None) -MQR_STUDY_ACTIVE = env.bool("MQR_STUDY_ACTIVE", False) -MQR_WEEK_LIMIT_OFFSET = env.int("MQR_WEEK_LIMIT_OFFSET", 0) - # AAQ-Beta AAQ_CORE_API_URL = env.str("AAQ_CORE_API_URL", None) AAQ_CORE_INBOUND_CHECK_AUTH = env.str("AAQ_CORE_INBOUND_CHECK_AUTH", None) diff --git a/ndoh_hub/testsettings.py b/ndoh_hub/testsettings.py index 81a34420..e9fa8f9c 100644 --- a/ndoh_hub/testsettings.py +++ b/ndoh_hub/testsettings.py @@ -23,9 +23,6 @@ HANDLE_EXPIRED_HELPDESK_CONTACTS_ENABLED = True -MQR_CONTENTREPO_URL = "http://contentrepo" -MQR_SEND_AIRTIME_FLOW_ID = "mqr-send-airtime-flow-uuid" - AAQ_CORE_API_URL = "http://aaqcore" AAQ_UD_API_URL = "http://aaqud" AAQ_V2_API_URL = "http://aaq_v2" diff --git a/ndoh_hub/urls.py b/ndoh_hub/urls.py index 9d46aa07..b6a4823a 100644 --- a/ndoh_hub/urls.py +++ b/ndoh_hub/urls.py @@ -85,7 +85,6 @@ re_path(r"^api/auth/", include("rest_framework.urls", namespace="rest_framework")), re_path(r"^api/token-auth/", obtain_auth_token), re_path(r"^docs/", include_docs_urls(title="NDOH Hub")), - re_path(r"^", include("mqr.urls")), re_path(r"^", include("aaq.urls")), re_path(r"^", include("registrations.urls")), re_path(