From 2cd3f7c0199060d7ca2b4c778da62a18bf739a05 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Wed, 25 Sep 2024 17:21:52 +0000 Subject: [PATCH 01/20] refactor(models): change relationship for EnrollmentFlow and TransitAgency This commit changes the implementation of the relationship between EnrollmentFlow and TransitAgency. EnrollmentFlow now has a TransitAgency foreign key field (many flows to one transit agency). This will allow us to have more flexibility with the names we can use in EnrollmentFlow.label (for example, having the name of the transit agency in this field won't be necessary anymore). --- ...transitagency_enrollment_flows_and_more.py | 24 ++ benefits/core/models.py | 290 +++++++++--------- 2 files changed, 171 insertions(+), 143 deletions(-) create mode 100644 benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py diff --git a/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py b/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py new file mode 100644 index 000000000..f2af616bc --- /dev/null +++ b/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.1 on 2024-09-25 16:47 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0027_enrollmentflow_supported_methods"), + ] + + operations = [ + migrations.RemoveField( + model_name="transitagency", + name="enrollment_flows", + ), + migrations.AddField( + model_name="enrollmentflow", + name="transit_agency", + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to="core.transitagency"), + preserve_default=False, + ), + ] diff --git a/benefits/core/models.py b/benefits/core/models.py index 8eb869331..29baad8d9 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -105,6 +105,152 @@ def __str__(self) -> str: return self.client_name +class TransitProcessor(models.Model): + """An entity that applies transit agency fare rules to rider transactions.""" + + id = models.AutoField(primary_key=True) + name = models.TextField(help_text="Primary internal display name for this TransitProcessor instance, e.g. in the Admin.") + api_base_url = models.TextField(help_text="The absolute base URL for the TransitProcessor's API, including https://.") + card_tokenize_url = models.TextField( + help_text="The absolute URL for the client-side card tokenization library provided by the TransitProcessor." + ) + card_tokenize_func = models.TextField( + help_text="The function from the card tokenization library to call on the client to initiate the process." + ) + card_tokenize_env = models.TextField(help_text="The environment in which card tokenization is occurring.") + portal_url = models.TextField( + null=True, blank=True, help_text="The absolute base URL for the TransitProcessor's control portal, including https://." + ) + + def __str__(self): + return self.name + + +class TransitAgency(models.Model): + """An agency offering transit service.""" + + id = models.AutoField(primary_key=True) + active = models.BooleanField(default=False, help_text="Determines if this Agency is enabled for users") + slug = models.TextField(help_text="Used for URL navigation for this agency, e.g. the agency homepage url is /{slug}") + short_name = models.TextField(help_text="The user-facing short name for this agency. Often an uppercase acronym.") + long_name = models.TextField( + help_text="The user-facing long name for this agency. Often the short_name acronym, spelled out." + ) + info_url = models.URLField(help_text="URL of a website/page with more information about the agency's discounts") + phone = models.TextField(help_text="Agency customer support phone number") + index_template = models.TextField(help_text="The template used for this agency's landing page") + eligibility_index_template = models.TextField(help_text="The template used for this agency's eligibility landing page") + eligibility_api_id = models.TextField(help_text="The identifier for this agency used in Eligibility API calls.") + eligibility_api_private_key = models.ForeignKey( + PemData, + related_name="+", + on_delete=models.PROTECT, + help_text="Private key used to sign Eligibility API tokens created on behalf of this Agency.", + ) + eligibility_api_public_key = models.ForeignKey( + PemData, + related_name="+", + on_delete=models.PROTECT, + help_text="Public key corresponding to the agency's private key, used by Eligibility Verification servers to encrypt responses.", # noqa: E501 + ) + eligibility_api_jws_signing_alg = models.TextField( + help_text="The JWS-compatible signing algorithm used in Eligibility API calls." + ) + transit_processor = models.ForeignKey(TransitProcessor, on_delete=models.PROTECT) + transit_processor_audience = models.TextField( + help_text="This agency's audience value used to access the TransitProcessor's API.", default="" + ) + transit_processor_client_id = models.TextField( + help_text="This agency's client_id value used to access the TransitProcessor's API.", default="" + ) + transit_processor_client_secret_name = SecretNameField( + help_text="The name of the secret containing this agency's client_secret value used to access the TransitProcessor's API.", # noqa: E501 + default="", + ) + staff_group = models.OneToOneField( + Group, + on_delete=models.PROTECT, + null=True, + blank=True, + default=None, + help_text="The group of users associated with this TransitAgency.", + related_name="transit_agency", + ) + sso_domain = models.TextField( + null=True, + blank=True, + default="", + help_text="The email domain of users to automatically add to this agency's staff group upon login.", + ) + customer_service_group = models.OneToOneField( + Group, + on_delete=models.PROTECT, + null=True, + blank=True, + default=None, + help_text="The group of users who are allowed to do in-person eligibility verification and enrollment.", + related_name="+", + ) + + def __str__(self): + return self.long_name + + @property + def index_url(self): + """Public-facing URL to the TransitAgency's landing page.""" + return reverse(routes.AGENCY_INDEX, args=[self.slug]) + + @property + def eligibility_index_url(self): + """Public facing URL to the TransitAgency's eligibility page.""" + return reverse(routes.AGENCY_ELIGIBILITY_INDEX, args=[self.slug]) + + @property + def eligibility_api_private_key_data(self): + """This Agency's private key as a string.""" + return self.eligibility_api_private_key.data + + @property + def eligibility_api_public_key_data(self): + """This Agency's public key as a string.""" + return self.eligibility_api_public_key.data + + @property + def transit_processor_client_secret(self): + return get_secret_by_name(self.transit_processor_client_secret_name) + + @property + def enrollment_flows(self): + return self.enrollmentflow_set + + @staticmethod + def by_id(id): + """Get a TransitAgency instance by its ID.""" + logger.debug(f"Get {TransitAgency.__name__} by id: {id}") + return TransitAgency.objects.get(id=id) + + @staticmethod + def by_slug(slug): + """Get a TransitAgency instance by its slug.""" + logger.debug(f"Get {TransitAgency.__name__} by slug: {slug}") + return TransitAgency.objects.filter(slug=slug).first() + + @staticmethod + def all_active(): + """Get all TransitAgency instances marked active.""" + logger.debug(f"Get all active {TransitAgency.__name__}") + return TransitAgency.objects.filter(active=True) + + @staticmethod + def for_user(user: User): + for group in user.groups.all(): + if hasattr(group, "transit_agency"): + return group.transit_agency # this is looking at the TransitAgency's staff_group + + # the loop above returns the first match found. Return None if no match was found. + return None + + class EnrollmentMethods: DIGITAL = "digital" IN_PERSON = "in_person" @@ -235,6 +381,7 @@ class EnrollmentFlow(models.Model): default=[EnrollmentMethods.DIGITAL, EnrollmentMethods.IN_PERSON], help_text="If the flow is supported by digital enrollment, in-person enrollment, or both", ) + transit_agency = models.ForeignKey(TransitAgency, on_delete=models.PROTECT) class Meta: ordering = ["display_order"] @@ -313,149 +460,6 @@ def claims_scheme(self): return self.claims_scheme_override -class TransitProcessor(models.Model): - """An entity that applies transit agency fare rules to rider transactions.""" - - id = models.AutoField(primary_key=True) - name = models.TextField(help_text="Primary internal display name for this TransitProcessor instance, e.g. in the Admin.") - api_base_url = models.TextField(help_text="The absolute base URL for the TransitProcessor's API, including https://.") - card_tokenize_url = models.TextField( - help_text="The absolute URL for the client-side card tokenization library provided by the TransitProcessor." - ) - card_tokenize_func = models.TextField( - help_text="The function from the card tokenization library to call on the client to initiate the process." - ) - card_tokenize_env = models.TextField(help_text="The environment in which card tokenization is occurring.") - portal_url = models.TextField( - null=True, blank=True, help_text="The absolute base URL for the TransitProcessor's control portal, including https://." - ) - - def __str__(self): - return self.name - - -class TransitAgency(models.Model): - """An agency offering transit service.""" - - id = models.AutoField(primary_key=True) - active = models.BooleanField(default=False, help_text="Determines if this Agency is enabled for users") - enrollment_flows = models.ManyToManyField(EnrollmentFlow) - slug = models.TextField(help_text="Used for URL navigation for this agency, e.g. the agency homepage url is /{slug}") - short_name = models.TextField(help_text="The user-facing short name for this agency. Often an uppercase acronym.") - long_name = models.TextField( - help_text="The user-facing long name for this agency. Often the short_name acronym, spelled out." - ) - info_url = models.URLField(help_text="URL of a website/page with more information about the agency's discounts") - phone = models.TextField(help_text="Agency customer support phone number") - index_template = models.TextField(help_text="The template used for this agency's landing page") - eligibility_index_template = models.TextField(help_text="The template used for this agency's eligibility landing page") - eligibility_api_id = models.TextField(help_text="The identifier for this agency used in Eligibility API calls.") - eligibility_api_private_key = models.ForeignKey( - PemData, - related_name="+", - on_delete=models.PROTECT, - help_text="Private key used to sign Eligibility API tokens created on behalf of this Agency.", - ) - eligibility_api_public_key = models.ForeignKey( - PemData, - related_name="+", - on_delete=models.PROTECT, - help_text="Public key corresponding to the agency's private key, used by Eligibility Verification servers to encrypt responses.", # noqa: E501 - ) - eligibility_api_jws_signing_alg = models.TextField( - help_text="The JWS-compatible signing algorithm used in Eligibility API calls." - ) - transit_processor = models.ForeignKey(TransitProcessor, on_delete=models.PROTECT) - transit_processor_audience = models.TextField( - help_text="This agency's audience value used to access the TransitProcessor's API.", default="" - ) - transit_processor_client_id = models.TextField( - help_text="This agency's client_id value used to access the TransitProcessor's API.", default="" - ) - transit_processor_client_secret_name = SecretNameField( - help_text="The name of the secret containing this agency's client_secret value used to access the TransitProcessor's API.", # noqa: E501 - default="", - ) - staff_group = models.OneToOneField( - Group, - on_delete=models.PROTECT, - null=True, - blank=True, - default=None, - help_text="The group of users associated with this TransitAgency.", - related_name="transit_agency", - ) - sso_domain = models.TextField( - null=True, - blank=True, - default="", - help_text="The email domain of users to automatically add to this agency's staff group upon login.", - ) - customer_service_group = models.OneToOneField( - Group, - on_delete=models.PROTECT, - null=True, - blank=True, - default=None, - help_text="The group of users who are allowed to do in-person eligibility verification and enrollment.", - related_name="+", - ) - - def __str__(self): - return self.long_name - - @property - def index_url(self): - """Public-facing URL to the TransitAgency's landing page.""" - return reverse(routes.AGENCY_INDEX, args=[self.slug]) - - @property - def eligibility_index_url(self): - """Public facing URL to the TransitAgency's eligibility page.""" - return reverse(routes.AGENCY_ELIGIBILITY_INDEX, args=[self.slug]) - - @property - def eligibility_api_private_key_data(self): - """This Agency's private key as a string.""" - return self.eligibility_api_private_key.data - - @property - def eligibility_api_public_key_data(self): - """This Agency's public key as a string.""" - return self.eligibility_api_public_key.data - - @property - def transit_processor_client_secret(self): - return get_secret_by_name(self.transit_processor_client_secret_name) - - @staticmethod - def by_id(id): - """Get a TransitAgency instance by its ID.""" - logger.debug(f"Get {TransitAgency.__name__} by id: {id}") - return TransitAgency.objects.get(id=id) - - @staticmethod - def by_slug(slug): - """Get a TransitAgency instance by its slug.""" - logger.debug(f"Get {TransitAgency.__name__} by slug: {slug}") - return TransitAgency.objects.filter(slug=slug).first() - - @staticmethod - def all_active(): - """Get all TransitAgency instances marked active.""" - logger.debug(f"Get all active {TransitAgency.__name__}") - return TransitAgency.objects.filter(active=True) - - @staticmethod - def for_user(user: User): - for group in user.groups.all(): - if hasattr(group, "transit_agency"): - return group.transit_agency # this is looking at the TransitAgency's staff_group - - # the loop above returns the first match found. Return None if no match was found. - return None - - class EnrollmentEvent(models.Model): """A record of a successful enrollment.""" From c62138529bb58d7c7b0abf6a9932078afc37a062 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 25 Sep 2024 22:00:00 +0000 Subject: [PATCH 02/20] chore(fixtures): remove enrollment_methods field from transitagency fixture --- benefits/core/migrations/local_fixtures.json | 1 - 1 file changed, 1 deletion(-) diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index 9d8a4896d..1039d7160 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -191,7 +191,6 @@ "pk": 1, "fields": { "active": true, - "enrollment_flows": [1, 2, 3, 4, 5], "slug": "cst", "short_name": "CST (local)", "long_name": "California State Transit (local)", From f401abeaa22367cf88500dda1c521fccddb4c332 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 25 Sep 2024 23:00:58 +0000 Subject: [PATCH 03/20] test(fix): update enrollmentflow, agency changes on fixtures --- tests/pytest/conftest.py | 10 ++++------ tests/pytest/core/test_models.py | 14 +++++++------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index 6be63571e..9690ffc13 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -75,13 +75,14 @@ def model_ClaimsProvider_no_sign_out(model_ClaimsProvider): @pytest.fixture -def model_EnrollmentFlow(): +def model_EnrollmentFlow(model_TransitAgency): flow = EnrollmentFlow.objects.create( system_name="Test Flow", selection_label_template="eligibility/includes/selection-label.html", label="Test flow label", group_id="group123", enrollment_success_template="enrollment/success.html", + transit_agency_id=model_TransitAgency.id, ) return flow @@ -112,6 +113,7 @@ def model_EnrollmentFlow_with_scope_and_claim(model_EnrollmentFlow, model_Claims @pytest.fixture def model_EnrollmentFlow_with_claims_scheme(model_EnrollmentFlow_with_scope_and_claim): model_EnrollmentFlow_with_scope_and_claim.claims_scheme_override = "scheme" + model_EnrollmentFlow_with_scope_and_claim.transit_agency_id = 1 model_EnrollmentFlow_with_scope_and_claim.save() return model_EnrollmentFlow_with_scope_and_claim @@ -171,7 +173,7 @@ def model_TransitProcessor(): @pytest.fixture -def model_TransitAgency(model_PemData, model_EnrollmentFlow, model_TransitProcessor): +def model_TransitAgency(model_PemData, model_TransitProcessor): agency = TransitAgency.objects.create( slug="test", short_name="TEST", @@ -191,10 +193,6 @@ def model_TransitAgency(model_PemData, model_EnrollmentFlow, model_TransitProces eligibility_index_template="eligibility/index.html", ) - # add many-to-many relationships after creation, need ID on both sides - agency.enrollment_flows.add(model_EnrollmentFlow) - agency.save() - return agency diff --git a/tests/pytest/core/test_models.py b/tests/pytest/core/test_models.py index fa68fa1ab..d52fb07ac 100644 --- a/tests/pytest/core/test_models.py +++ b/tests/pytest/core/test_models.py @@ -120,7 +120,7 @@ def test_EnrollmentFlow_str(model_EnrollmentFlow): @pytest.mark.django_db -def test_EnrollmentFlow_supports_expiration_False(model_EnrollmentFlow_does_not_support_expiration): +def test_EnrollmentFlow_supports_expiration_False(model_EnrollmentFlow, model_EnrollmentFlow_does_not_support_expiration): # test will fail if any error is raised model_EnrollmentFlow_does_not_support_expiration.full_clean() @@ -168,8 +168,8 @@ def test_EnrollmentFlow_supports_expiration(model_EnrollmentFlow_supports_expira @pytest.mark.django_db -def test_EnrollmentFlow_enrollment_index_template(): - new_flow = EnrollmentFlow.objects.create() +def test_EnrollmentFlow_enrollment_index_template(model_TransitAgency): + new_flow = EnrollmentFlow.objects.create(transit_agency_id=model_TransitAgency.id) assert new_flow.enrollment_index_template == "enrollment/index.html" @@ -180,15 +180,15 @@ def test_EnrollmentFlow_enrollment_index_template(): @pytest.mark.django_db -def test_EnrollmentFlow_enrollment_success_template(): - new_flow = EnrollmentFlow.objects.create() +def test_EnrollmentFlow_enrollment_success_template(model_TransitAgency): + new_flow = EnrollmentFlow.objects.create(transit_agency_id=model_TransitAgency.id) assert new_flow.enrollment_success_template == "enrollment/success.html" @pytest.mark.django_db -def test_EnrollmentFlow_supported_enrollment_methods(): - new_flow = EnrollmentFlow.objects.create() +def test_EnrollmentFlow_supported_enrollment_methods(model_TransitAgency): + new_flow = EnrollmentFlow.objects.create(transit_agency_id=model_TransitAgency.id) assert new_flow.supported_enrollment_methods == ["digital", "in_person"] From 68864cc9707997beddcf70d09a4c915da20ebd46 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 25 Sep 2024 23:01:18 +0000 Subject: [PATCH 04/20] fix(fixtures): add transit_agency_id to enrollmentflow --- benefits/core/migrations/local_fixtures.json | 69 +++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index 1039d7160..87cf3aef7 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -48,6 +48,30 @@ "scheme": "dev-cal-itp_benefits" } }, + { + "model": "core.transitagency", + "pk": 1, + "fields": { + "active": true, + "slug": "cst", + "short_name": "CST (local)", + "long_name": "California State Transit (local)", + "info_url": "https://www.agency-website.com", + "phone": "1-800-555-5555", + "index_template": "core/index--cst.html", + "eligibility_index_template": "eligibility/index--cst.html", + "eligibility_api_id": "cst", + "eligibility_api_private_key": 2, + "eligibility_api_public_key": 3, + "eligibility_api_jws_signing_alg": "RS256", + "transit_processor": 1, + "transit_processor_audience": "", + "transit_processor_client_id": "", + "transit_processor_client_secret_name": "cst-transit-processor-client-secret", + "staff_group": 2, + "customer_service_group": 2 + } + }, { "model": "core.enrollmentflow", "pk": 1, @@ -62,7 +86,8 @@ "eligibility_start_template": "eligibility/start--senior.html", "claims_scope": "verify:senior", "claims_claim": "senior", - "supported_enrollment_methods": ["digital", "in_person"] + "supported_enrollment_methods": ["digital", "in_person"], + "transit_agency_id": 1 } }, { @@ -79,7 +104,8 @@ "eligibility_start_template": "eligibility/start--veteran.html", "claims_scope": "verify:veteran", "claims_claim": "veteran", - "supported_enrollment_methods": ["digital", "in_person"] + "supported_enrollment_methods": ["digital", "in_person"], + "transit_agency_id": 1 } }, { @@ -104,7 +130,8 @@ "eligibility_form_class": "benefits.eligibility.forms.CSTAgencyCard", "eligibility_unverified_template": "eligibility/unverified--cst-agency-card.html", "help_template": "core/includes/help--cst-agency-card.html", - "supported_enrollment_methods": ["digital", "in_person"] + "supported_enrollment_methods": ["digital", "in_person"], + "transit_agency_id": 1 } }, { @@ -126,7 +153,8 @@ "help_template": "core/includes/help--calfresh.html", "claims_scope": "verify:calfresh", "claims_claim": "calfresh", - "supported_enrollment_methods": ["digital", "in_person"] + "supported_enrollment_methods": ["digital", "in_person"], + "transit_agency_id": 1 } }, { @@ -144,7 +172,8 @@ "help_template": "core/includes/help--medicare.html", "claims_scope": "verify:medicare", "claims_claim": "medicare", - "supported_enrollment_methods": ["digital", "in_person"] + "supported_enrollment_methods": ["digital", "in_person"], + "transit_agency_id": 1 } }, { @@ -154,7 +183,8 @@ "system_name": "in_person_only", "label": "(CST) In-person Only", "group_id": "group123", - "supported_enrollment_methods": ["in_person"] + "supported_enrollment_methods": ["in_person"], + "transit_agency_id": 1 } }, { @@ -171,7 +201,8 @@ "eligibility_start_template": "eligibility/start--senior.html", "claims_scope": "verify:senior", "claims_claim": "senior", - "supported_enrollment_methods": ["digital"] + "supported_enrollment_methods": ["digital"], + "transit_agency_id": 1 } }, { @@ -186,30 +217,6 @@ "portal_url": "https://www.transit-processor-portal.com" } }, - { - "model": "core.transitagency", - "pk": 1, - "fields": { - "active": true, - "slug": "cst", - "short_name": "CST (local)", - "long_name": "California State Transit (local)", - "info_url": "https://www.agency-website.com", - "phone": "1-800-555-5555", - "index_template": "core/index--cst.html", - "eligibility_index_template": "eligibility/index--cst.html", - "eligibility_api_id": "cst", - "eligibility_api_private_key": 2, - "eligibility_api_public_key": 3, - "eligibility_api_jws_signing_alg": "RS256", - "transit_processor": 1, - "transit_processor_audience": "", - "transit_processor_client_id": "", - "transit_processor_client_secret_name": "cst-transit-processor-client-secret", - "staff_group": 2, - "customer_service_group": 2 - } - }, { "model": "auth.Group", "pk": 2, From 208cf21334555eabcc3c9f4199360a05de4c79f4 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 25 Sep 2024 23:14:31 +0000 Subject: [PATCH 05/20] chore(fixtures): remove (CST) from flow label --- benefits/core/migrations/local_fixtures.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index 87cf3aef7..22960052e 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -77,7 +77,7 @@ "pk": 1, "fields": { "system_name": "senior", - "label": "(CST) Senior Discount", + "label": "Senior Discount", "group_id": "group123", "enrollment_success_template": "enrollment/success--cst.html", "display_order": 2, @@ -95,7 +95,7 @@ "pk": 2, "fields": { "system_name": "veteran", - "label": "(CST) Veteran Discount", + "label": "Veteran Discount", "group_id": "group123", "enrollment_success_template": "enrollment/success--cst.html", "display_order": 4, @@ -113,7 +113,7 @@ "pk": 3, "fields": { "system_name": "agency_card", - "label": "(CST) Agency Card Discount", + "label": "Agency Card Discount", "group_id": "group123", "enrollment_index_template": "enrollment/index--agency-card.html", "enrollment_success_template": "enrollment/success--cst-agency-card.html", @@ -139,7 +139,7 @@ "pk": 4, "fields": { "system_name": "calfresh", - "label": "(CST) CalFresh", + "label": "CalFresh", "group_id": "group123", "supports_expiration": "True", "expiration_days": 5, @@ -162,7 +162,7 @@ "pk": 5, "fields": { "system_name": "medicare", - "label": "(CST) Medicare Discount", + "label": "Medicare Discount", "group_id": "group123", "enrollment_success_template": "enrollment/success--cst.html", "display_order": 1, @@ -181,7 +181,7 @@ "pk": 6, "fields": { "system_name": "in_person_only", - "label": "(CST) In-person Only", + "label": "In-person Only", "group_id": "group123", "supported_enrollment_methods": ["in_person"], "transit_agency_id": 1 @@ -192,7 +192,7 @@ "pk": 7, "fields": { "system_name": "digital_only", - "label": "(CST) Digital Only", + "label": "Digital Only", "group_id": "group123", "enrollment_success_template": "enrollment/success--cst.html", "display_order": 5, From a73179e5b9024d5e587778e50f53b71846a56b43 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 25 Sep 2024 23:20:31 +0000 Subject: [PATCH 06/20] chore(fixtures): give enrollmentflow label the human-readable name --- benefits/core/migrations/local_fixtures.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index 22960052e..25060497f 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -77,7 +77,7 @@ "pk": 1, "fields": { "system_name": "senior", - "label": "Senior Discount", + "label": "Older Adult", "group_id": "group123", "enrollment_success_template": "enrollment/success--cst.html", "display_order": 2, @@ -95,7 +95,7 @@ "pk": 2, "fields": { "system_name": "veteran", - "label": "Veteran Discount", + "label": "US Veteran", "group_id": "group123", "enrollment_success_template": "enrollment/success--cst.html", "display_order": 4, @@ -113,7 +113,7 @@ "pk": 3, "fields": { "system_name": "agency_card", - "label": "Agency Card Discount", + "label": "Agency Card", "group_id": "group123", "enrollment_index_template": "enrollment/index--agency-card.html", "enrollment_success_template": "enrollment/success--cst-agency-card.html", @@ -139,7 +139,7 @@ "pk": 4, "fields": { "system_name": "calfresh", - "label": "CalFresh", + "label": "CalFresh Cardholder", "group_id": "group123", "supports_expiration": "True", "expiration_days": 5, @@ -162,7 +162,7 @@ "pk": 5, "fields": { "system_name": "medicare", - "label": "Medicare Discount", + "label": "Medicare Cardholder", "group_id": "group123", "enrollment_success_template": "enrollment/success--cst.html", "display_order": 1, @@ -181,7 +181,7 @@ "pk": 6, "fields": { "system_name": "in_person_only", - "label": "In-person Only", + "label": "In-Person Only", "group_id": "group123", "supported_enrollment_methods": ["in_person"], "transit_agency_id": 1 From 69f388eee60e03aeffeec2f0e0377fa98b7478db Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 25 Sep 2024 23:29:17 +0000 Subject: [PATCH 07/20] test: update agency/flows relationship in tests --- tests/pytest/core/test_views.py | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/tests/pytest/core/test_views.py b/tests/pytest/core/test_views.py index e3590e5b3..bea3cdaa4 100644 --- a/tests/pytest/core/test_views.py +++ b/tests/pytest/core/test_views.py @@ -108,10 +108,6 @@ def test_agency_public_key(client, model_TransitAgency): def test_agency_card_with_eligibility_api_flow( client, model_TransitAgency, model_EnrollmentFlow_with_eligibility_api, mocked_session_update, mocked_session_reset ): - model_TransitAgency.enrollment_flows.clear() - model_TransitAgency.enrollment_flows.add(model_EnrollmentFlow_with_eligibility_api) - model_TransitAgency.save() - url = reverse(routes.AGENCY_CARD, args=[model_TransitAgency.slug]) response = client.get(url) @@ -134,16 +130,9 @@ def test_agency_card_with_multiple_eligibility_api_flows( new_flow.label = "New flow" new_flow.system_name = "new" new_flow.pk = None + new_flow.transit_agency_id = model_TransitAgency.id new_flow.save() - # note the order these are added to the TransitAgency doesn't matter (and thus, their sorting within that agency) - # it is the order they were created in the database that is used for the query - # we always expect new_flow to be the one to match - model_TransitAgency.enrollment_flows.clear() - model_TransitAgency.enrollment_flows.add(model_EnrollmentFlow_with_eligibility_api) - model_TransitAgency.enrollment_flows.add(new_flow) - model_TransitAgency.save() - url = reverse(routes.AGENCY_CARD, args=[model_TransitAgency.slug]) response = client.get(url) @@ -156,9 +145,8 @@ def test_agency_card_with_multiple_eligibility_api_flows( def test_agency_card_without_eligibility_api_flow( client, model_TransitAgency, model_EnrollmentFlow_with_scope_and_claim, mocked_session_update, mocked_session_reset ): - model_TransitAgency.enrollment_flows.clear() - model_TransitAgency.enrollment_flows.add(model_EnrollmentFlow_with_scope_and_claim) - model_TransitAgency.save() + model_EnrollmentFlow_with_scope_and_claim.transit_agency_id = model_TransitAgency.id + model_EnrollmentFlow_with_scope_and_claim.save() url = reverse(routes.AGENCY_CARD, args=[model_TransitAgency.slug]) response = client.get(url) From 2e2e1c0df27d3f662b5ec69c6f2bec8cc8dab063 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 25 Sep 2024 23:45:44 +0000 Subject: [PATCH 08/20] fix(fixtures): remove digital, inperson only flows for now - breaking tests --- benefits/core/migrations/local_fixtures.json | 29 -------------------- 1 file changed, 29 deletions(-) diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index 25060497f..05c1a5745 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -176,35 +176,6 @@ "transit_agency_id": 1 } }, - { - "model": "core.enrollmentflow", - "pk": 6, - "fields": { - "system_name": "in_person_only", - "label": "In-Person Only", - "group_id": "group123", - "supported_enrollment_methods": ["in_person"], - "transit_agency_id": 1 - } - }, - { - "model": "core.enrollmentflow", - "pk": 7, - "fields": { - "system_name": "digital_only", - "label": "Digital Only", - "group_id": "group123", - "enrollment_success_template": "enrollment/success--cst.html", - "display_order": 5, - "claims_provider": 1, - "selection_label_template": "eligibility/includes/selection-label--senior.html", - "eligibility_start_template": "eligibility/start--senior.html", - "claims_scope": "verify:senior", - "claims_claim": "senior", - "supported_enrollment_methods": ["digital"], - "transit_agency_id": 1 - } - }, { "model": "core.transitprocessor", "pk": 1, From a880802c964b86ddea32dcaf879b8aaddf15a1ca Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 26 Sep 2024 00:56:35 +0000 Subject: [PATCH 09/20] feat(admin): display agency, supported methods on enrollmentflow list --- benefits/core/admin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benefits/core/admin.py b/benefits/core/admin.py index f9bf8adf2..80c51aea1 100644 --- a/benefits/core/admin.py +++ b/benefits/core/admin.py @@ -44,6 +44,8 @@ def get_readonly_fields(self, request, obj=None): @admin.register(models.EnrollmentFlow) class SortableEnrollmentFlowAdmin(SortableAdminMixin, admin.ModelAdmin): # pragma: no cover + list_display = ("label", "transit_agency", "supported_enrollment_methods") + def get_exclude(self, request, obj=None): if not request.user.is_superuser: return [ From 3bf128cc210f46d84eb17ed7b82405290e69a428 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 26 Sep 2024 16:23:52 +0000 Subject: [PATCH 10/20] test(views): fix last test w/ mocked flow+session --- tests/pytest/in_person/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pytest/in_person/test_views.py b/tests/pytest/in_person/test_views.py index 53d186af8..7d53d1729 100644 --- a/tests/pytest/in_person/test_views.py +++ b/tests/pytest/in_person/test_views.py @@ -50,7 +50,7 @@ def test_eligibility_logged_in(admin_client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_agency") +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_flow") def test_confirm_post_valid_form_eligibility_verified(admin_client): path = reverse(routes.IN_PERSON_ELIGIBILITY) From 6afab5482a5bf7275b62526cd70c1296480551e4 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Thu, 26 Sep 2024 18:05:36 +0000 Subject: [PATCH 11/20] feat(migration): add data migration so existing flows have an agency --- ..._transitagency_enrollment_flows_and_more.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py b/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py index f2af616bc..14b6ee321 100644 --- a/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py +++ b/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py @@ -4,6 +4,15 @@ from django.db import migrations, models +def migrate_data(apps, schema_editor): + TransitAgency = apps.get_model("core", "TransitAgency") + + for agency in TransitAgency.objects.all(): + for flow in agency.enrollment_flows.all(): + flow.transit_agency = agency + flow.save() + + class Migration(migrations.Migration): dependencies = [ @@ -11,14 +20,15 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RemoveField( - model_name="transitagency", - name="enrollment_flows", - ), migrations.AddField( model_name="enrollmentflow", name="transit_agency", field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to="core.transitagency"), preserve_default=False, ), + migrations.RunPython(migrate_data), + migrations.RemoveField( + model_name="transitagency", + name="enrollment_flows", + ), ] From fc2139d22be9572a5716d582d586f0b0542f4001 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Mon, 30 Sep 2024 05:58:56 +0000 Subject: [PATCH 12/20] refactor(migration): rename migration to a more descriptive title --- ...ansitagency_enrollmentflow_relationship.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py diff --git a/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py b/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py new file mode 100644 index 000000000..14b6ee321 --- /dev/null +++ b/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py @@ -0,0 +1,34 @@ +# Generated by Django 5.1.1 on 2024-09-25 16:47 + +import django.db.models.deletion +from django.db import migrations, models + + +def migrate_data(apps, schema_editor): + TransitAgency = apps.get_model("core", "TransitAgency") + + for agency in TransitAgency.objects.all(): + for flow in agency.enrollment_flows.all(): + flow.transit_agency = agency + flow.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0027_enrollmentflow_supported_methods"), + ] + + operations = [ + migrations.AddField( + model_name="enrollmentflow", + name="transit_agency", + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to="core.transitagency"), + preserve_default=False, + ), + migrations.RunPython(migrate_data), + migrations.RemoveField( + model_name="transitagency", + name="enrollment_flows", + ), + ] From 5f92ab3eb39dff68a7435f6101e10be48d26d89a Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Mon, 30 Sep 2024 06:01:47 +0000 Subject: [PATCH 13/20] fix(fixtures): set transitagency directly with object, not id --- benefits/core/migrations/local_fixtures.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index 05c1a5745..30f88e95b 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -87,7 +87,7 @@ "claims_scope": "verify:senior", "claims_claim": "senior", "supported_enrollment_methods": ["digital", "in_person"], - "transit_agency_id": 1 + "transit_agency": 1 } }, { @@ -105,7 +105,7 @@ "claims_scope": "verify:veteran", "claims_claim": "veteran", "supported_enrollment_methods": ["digital", "in_person"], - "transit_agency_id": 1 + "transit_agency": 1 } }, { @@ -131,7 +131,7 @@ "eligibility_unverified_template": "eligibility/unverified--cst-agency-card.html", "help_template": "core/includes/help--cst-agency-card.html", "supported_enrollment_methods": ["digital", "in_person"], - "transit_agency_id": 1 + "transit_agency": 1 } }, { @@ -154,7 +154,7 @@ "claims_scope": "verify:calfresh", "claims_claim": "calfresh", "supported_enrollment_methods": ["digital", "in_person"], - "transit_agency_id": 1 + "transit_agency": 1 } }, { @@ -173,7 +173,7 @@ "claims_scope": "verify:medicare", "claims_claim": "medicare", "supported_enrollment_methods": ["digital", "in_person"], - "transit_agency_id": 1 + "transit_agency": 1 } }, { From b73826d200331518f1463ce09a7dc48c5faec2c4 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Mon, 30 Sep 2024 06:06:40 +0000 Subject: [PATCH 14/20] fix(tests): assign transit_agency directly, not by id --- tests/pytest/conftest.py | 12 +++++++----- tests/pytest/core/test_models.py | 6 +++--- tests/pytest/core/test_views.py | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index 9690ffc13..780b23fd8 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -5,14 +5,16 @@ from django.utils import timezone import pytest -from pytest_socket import disable_socket + +# from pytest_socket import disable_socket from benefits.core import session from benefits.core.models import ClaimsProvider, EnrollmentFlow, TransitProcessor, PemData, TransitAgency def pytest_runtest_setup(): - disable_socket() + # disable_socket() + pass @pytest.fixture @@ -82,7 +84,7 @@ def model_EnrollmentFlow(model_TransitAgency): label="Test flow label", group_id="group123", enrollment_success_template="enrollment/success.html", - transit_agency_id=model_TransitAgency.id, + transit_agency=model_TransitAgency, ) return flow @@ -111,9 +113,9 @@ def model_EnrollmentFlow_with_scope_and_claim(model_EnrollmentFlow, model_Claims @pytest.fixture -def model_EnrollmentFlow_with_claims_scheme(model_EnrollmentFlow_with_scope_and_claim): +def model_EnrollmentFlow_with_claims_scheme(model_EnrollmentFlow_with_scope_and_claim, model_TransitAgency): model_EnrollmentFlow_with_scope_and_claim.claims_scheme_override = "scheme" - model_EnrollmentFlow_with_scope_and_claim.transit_agency_id = 1 + model_EnrollmentFlow_with_scope_and_claim.transit_agency = model_TransitAgency model_EnrollmentFlow_with_scope_and_claim.save() return model_EnrollmentFlow_with_scope_and_claim diff --git a/tests/pytest/core/test_models.py b/tests/pytest/core/test_models.py index d52fb07ac..50aa34d2a 100644 --- a/tests/pytest/core/test_models.py +++ b/tests/pytest/core/test_models.py @@ -169,7 +169,7 @@ def test_EnrollmentFlow_supports_expiration(model_EnrollmentFlow_supports_expira @pytest.mark.django_db def test_EnrollmentFlow_enrollment_index_template(model_TransitAgency): - new_flow = EnrollmentFlow.objects.create(transit_agency_id=model_TransitAgency.id) + new_flow = EnrollmentFlow.objects.create(transit_agency=model_TransitAgency) assert new_flow.enrollment_index_template == "enrollment/index.html" @@ -181,14 +181,14 @@ def test_EnrollmentFlow_enrollment_index_template(model_TransitAgency): @pytest.mark.django_db def test_EnrollmentFlow_enrollment_success_template(model_TransitAgency): - new_flow = EnrollmentFlow.objects.create(transit_agency_id=model_TransitAgency.id) + new_flow = EnrollmentFlow.objects.create(transit_agency=model_TransitAgency) assert new_flow.enrollment_success_template == "enrollment/success.html" @pytest.mark.django_db def test_EnrollmentFlow_supported_enrollment_methods(model_TransitAgency): - new_flow = EnrollmentFlow.objects.create(transit_agency_id=model_TransitAgency.id) + new_flow = EnrollmentFlow.objects.create(transit_agency=model_TransitAgency) assert new_flow.supported_enrollment_methods == ["digital", "in_person"] diff --git a/tests/pytest/core/test_views.py b/tests/pytest/core/test_views.py index bea3cdaa4..57dfdf66e 100644 --- a/tests/pytest/core/test_views.py +++ b/tests/pytest/core/test_views.py @@ -130,7 +130,7 @@ def test_agency_card_with_multiple_eligibility_api_flows( new_flow.label = "New flow" new_flow.system_name = "new" new_flow.pk = None - new_flow.transit_agency_id = model_TransitAgency.id + new_flow.transit_agency = model_TransitAgency new_flow.save() url = reverse(routes.AGENCY_CARD, args=[model_TransitAgency.slug]) @@ -145,7 +145,7 @@ def test_agency_card_with_multiple_eligibility_api_flows( def test_agency_card_without_eligibility_api_flow( client, model_TransitAgency, model_EnrollmentFlow_with_scope_and_claim, mocked_session_update, mocked_session_reset ): - model_EnrollmentFlow_with_scope_and_claim.transit_agency_id = model_TransitAgency.id + model_EnrollmentFlow_with_scope_and_claim.transit_agency = model_TransitAgency model_EnrollmentFlow_with_scope_and_claim.save() url = reverse(routes.AGENCY_CARD, args=[model_TransitAgency.slug]) From 5d01deab7a64139f1d445b1aa9a5d4c1881d6205 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Mon, 30 Sep 2024 06:06:51 +0000 Subject: [PATCH 15/20] fix: remove old migration --- ...transitagency_enrollment_flows_and_more.py | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py diff --git a/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py b/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py deleted file mode 100644 index 14b6ee321..000000000 --- a/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 5.1.1 on 2024-09-25 16:47 - -import django.db.models.deletion -from django.db import migrations, models - - -def migrate_data(apps, schema_editor): - TransitAgency = apps.get_model("core", "TransitAgency") - - for agency in TransitAgency.objects.all(): - for flow in agency.enrollment_flows.all(): - flow.transit_agency = agency - flow.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ("core", "0027_enrollmentflow_supported_methods"), - ] - - operations = [ - migrations.AddField( - model_name="enrollmentflow", - name="transit_agency", - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to="core.transitagency"), - preserve_default=False, - ), - migrations.RunPython(migrate_data), - migrations.RemoveField( - model_name="transitagency", - name="enrollment_flows", - ), - ] From b2f0840ed885d09f92b54c273302251334d5bb41 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Mon, 30 Sep 2024 06:10:25 +0000 Subject: [PATCH 16/20] refactor(db): re-word helptext for enrollmentflow label --- .../0029_alter_enrollmentflow_label.py | 18 ++++++++++++++++++ benefits/core/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 benefits/core/migrations/0029_alter_enrollmentflow_label.py diff --git a/benefits/core/migrations/0029_alter_enrollmentflow_label.py b/benefits/core/migrations/0029_alter_enrollmentflow_label.py new file mode 100644 index 000000000..c31b80288 --- /dev/null +++ b/benefits/core/migrations/0029_alter_enrollmentflow_label.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1 on 2024-09-30 06:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0028_refactor_transitagency_enrollmentflow_relationship"), + ] + + operations = [ + migrations.AlterField( + model_name="enrollmentflow", + name="label", + field=models.TextField(help_text="A human readable label, used as the display text in Admin.", null=True), + ), + ] diff --git a/benefits/core/models.py b/benefits/core/models.py index 29baad8d9..c2cf07a07 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -350,7 +350,7 @@ class EnrollmentFlow(models.Model): ) label = models.TextField( null=True, - help_text="A human readable label, not shown to end-users. Used as the display text in Admin.", + help_text="A human readable label, used as the display text in Admin.", ) group_id = models.TextField(null=True, help_text="Reference to the TransitProcessor group for user enrollment") supports_expiration = models.BooleanField( From 4017f48e440c7cb192723b9a7f4cd973b4f9b799 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Mon, 30 Sep 2024 06:28:17 +0000 Subject: [PATCH 17/20] feat(db): allow enrollmentflow.transit_agency to be null --- .../0028_refactor_transitagency_enrollmentflow_relationship.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py b/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py index 14b6ee321..ea8049e78 100644 --- a/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py +++ b/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py @@ -23,7 +23,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="enrollmentflow", name="transit_agency", - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.PROTECT, to="core.transitagency"), + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="core.transitagency"), preserve_default=False, ), migrations.RunPython(migrate_data), From a230cbfbc1120c7467b6259a15da155e10511a9b Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Mon, 30 Sep 2024 22:46:43 +0000 Subject: [PATCH 18/20] fix(migration): allow enrollmentflow's transit agency to be null, allow blanks for admin --- ...0028_refactor_transitagency_enrollmentflow_relationship.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py b/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py index ea8049e78..680b18baf 100644 --- a/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py +++ b/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py @@ -23,7 +23,9 @@ class Migration(migrations.Migration): migrations.AddField( model_name="enrollmentflow", name="transit_agency", - field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to="core.transitagency"), + field=models.ForeignKey( + blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to="core.transitagency" + ), preserve_default=False, ), migrations.RunPython(migrate_data), From 2ffa3d13decf0b0073296a9ef6b4c515030fb7cd Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Mon, 30 Sep 2024 23:27:12 +0000 Subject: [PATCH 19/20] refactor(db): combine migration into 1 and add migrate_data() --- ...transitagency_enrollment_flows_and_more.py} | 9 ++++++--- .../0029_alter_enrollmentflow_label.py | 18 ------------------ benefits/core/models.py | 2 +- 3 files changed, 7 insertions(+), 22 deletions(-) rename benefits/core/migrations/{0028_refactor_transitagency_enrollmentflow_relationship.py => 0028_remove_transitagency_enrollment_flows_and_more.py} (77%) delete mode 100644 benefits/core/migrations/0029_alter_enrollmentflow_label.py diff --git a/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py b/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py similarity index 77% rename from benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py rename to benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py index 680b18baf..5f1f61a1f 100644 --- a/benefits/core/migrations/0028_refactor_transitagency_enrollmentflow_relationship.py +++ b/benefits/core/migrations/0028_remove_transitagency_enrollment_flows_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.1 on 2024-09-25 16:47 +# Generated by Django 5.1 on 2024-09-30 23:25 import django.db.models.deletion from django.db import migrations, models @@ -6,7 +6,6 @@ def migrate_data(apps, schema_editor): TransitAgency = apps.get_model("core", "TransitAgency") - for agency in TransitAgency.objects.all(): for flow in agency.enrollment_flows.all(): flow.transit_agency = agency @@ -26,11 +25,15 @@ class Migration(migrations.Migration): field=models.ForeignKey( blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to="core.transitagency" ), - preserve_default=False, ), migrations.RunPython(migrate_data), migrations.RemoveField( model_name="transitagency", name="enrollment_flows", ), + migrations.AlterField( + model_name="enrollmentflow", + name="label", + field=models.TextField(help_text="A human readable label, used as the display text in Admin.", null=True), + ), ] diff --git a/benefits/core/migrations/0029_alter_enrollmentflow_label.py b/benefits/core/migrations/0029_alter_enrollmentflow_label.py deleted file mode 100644 index c31b80288..000000000 --- a/benefits/core/migrations/0029_alter_enrollmentflow_label.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1 on 2024-09-30 06:09 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("core", "0028_refactor_transitagency_enrollmentflow_relationship"), - ] - - operations = [ - migrations.AlterField( - model_name="enrollmentflow", - name="label", - field=models.TextField(help_text="A human readable label, used as the display text in Admin.", null=True), - ), - ] diff --git a/benefits/core/models.py b/benefits/core/models.py index c2cf07a07..7b29a55f0 100644 --- a/benefits/core/models.py +++ b/benefits/core/models.py @@ -381,7 +381,7 @@ class EnrollmentFlow(models.Model): default=[EnrollmentMethods.DIGITAL, EnrollmentMethods.IN_PERSON], help_text="If the flow is supported by digital enrollment, in-person enrollment, or both", ) - transit_agency = models.ForeignKey(TransitAgency, on_delete=models.PROTECT) + transit_agency = models.ForeignKey(TransitAgency, on_delete=models.PROTECT, null=True, blank=True) class Meta: ordering = ["display_order"] From 4d7f09fc48a1a9b2898a6c494215547bc820bc86 Mon Sep 17 00:00:00 2001 From: Machiko Yasuda Date: Wed, 2 Oct 2024 17:58:02 +0000 Subject: [PATCH 20/20] chore(tests): remove socket-disabling code --- tests/pytest/conftest.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index 780b23fd8..67fdda3dc 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -6,15 +6,14 @@ import pytest -# from pytest_socket import disable_socket +from pytest_socket import disable_socket from benefits.core import session from benefits.core.models import ClaimsProvider, EnrollmentFlow, TransitProcessor, PemData, TransitAgency def pytest_runtest_setup(): - # disable_socket() - pass + disable_socket() @pytest.fixture