diff --git a/benefits/core/admin.py b/benefits/core/admin.py
index cb468b629..6a06fee20 100644
--- a/benefits/core/admin.py
+++ b/benefits/core/admin.py
@@ -5,6 +5,7 @@
import logging
import requests
+from adminsortable2.admin import SortableAdminMixin
from django.conf import settings
from django.contrib import admin
from . import models
@@ -17,7 +18,6 @@
for model in [
models.EligibilityType,
- models.EligibilityVerifier,
models.PaymentProcessor,
models.PemData,
models.TransitAgency,
@@ -26,6 +26,11 @@
admin.site.register(model)
+@admin.register(models.EligibilityVerifier)
+class SortableEligibilityVerifierAdmin(SortableAdminMixin, admin.ModelAdmin):
+ pass
+
+
def pre_login_user(user, request):
logger.debug(f"Running pre-login callback for user: {user.username}")
token = request.session.get("google_sso_access_token")
diff --git a/benefits/core/migrations/0004_alter_eligibilityverifier_display_order.py b/benefits/core/migrations/0004_alter_eligibilityverifier_display_order.py
new file mode 100644
index 000000000..f455e8e24
--- /dev/null
+++ b/benefits/core/migrations/0004_alter_eligibilityverifier_display_order.py
@@ -0,0 +1,30 @@
+# Generated by Django 5.0.3 on 2024-03-19 20:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("core", "0003_eligibilitytype_expiration"),
+ ]
+
+ # see https://django-admin-sortable2.readthedocs.io/en/latest/usage.html#initial-data
+ def set_initial_display_order(apps, schema_editor):
+ EligibilityVerifier = apps.get_model("core", "EligibilityVerifier")
+ for order, item in enumerate(EligibilityVerifier.objects.all(), 1):
+ item.display_order = order
+ item.save(update_fields=["display_order"])
+
+ operations = [
+ migrations.AlterModelOptions(
+ name="eligibilityverifier",
+ options={"ordering": ["display_order"]},
+ ),
+ migrations.AddField(
+ model_name="eligibilityverifier",
+ name="display_order",
+ field=models.PositiveSmallIntegerField(default=0),
+ ),
+ migrations.RunPython(set_initial_display_order, reverse_code=migrations.RunPython.noop),
+ ]
diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json
index 1e51dad7a..0dc81799b 100644
--- a/benefits/core/migrations/local_fixtures.json
+++ b/benefits/core/migrations/local_fixtures.json
@@ -117,11 +117,21 @@
"group_id": "group123"
}
},
+ {
+ "model": "core.eligibilitytype",
+ "pk": 7,
+ "fields": {
+ "name": "calfresh",
+ "label": "CalFresh",
+ "group_id": "group123"
+ }
+ },
{
"model": "core.eligibilityverifier",
"pk": 1,
"fields": {
"name": "(MST) oauth claims via Login.gov",
+ "display_order": 1,
"active": true,
"api_url": null,
"api_auth_header": null,
@@ -142,6 +152,7 @@
"pk": 2,
"fields": {
"name": "(MST) VA.gov - veteran",
+ "display_order": 3,
"active": true,
"api_url": null,
"api_auth_header": null,
@@ -162,6 +173,7 @@
"pk": 3,
"fields": {
"name": "(MST) eligibility server verifier",
+ "display_order": 4,
"active": true,
"api_url": "http://server:8000/verify",
"api_auth_header": "X-Server-API-Key",
@@ -182,6 +194,7 @@
"pk": 4,
"fields": {
"name": "(SacRT) oauth claims via Login.gov",
+ "display_order": 5,
"active": false,
"api_url": null,
"api_auth_header": null,
@@ -202,6 +215,7 @@
"pk": 5,
"fields": {
"name": "(SBMTD) oauth claims via Login.gov",
+ "display_order": 6,
"active": false,
"api_url": null,
"api_auth_header": null,
@@ -222,6 +236,7 @@
"pk": 6,
"fields": {
"name": "(SBMTD) eligibility server verifier",
+ "display_order": 7,
"active": true,
"api_url": "http://server:8000/verify",
"api_auth_header": "X-Server-API-Key",
@@ -237,6 +252,27 @@
"form_class": "benefits.eligibility.forms.SBMTDMobilityPass"
}
},
+ {
+ "model": "core.eligibilityverifier",
+ "pk": 7,
+ "fields": {
+ "name": "(MST) CalFresh oauth claims via Login.gov",
+ "display_order": 2,
+ "active": true,
+ "api_url": null,
+ "api_auth_header": null,
+ "api_auth_key_secret_name": null,
+ "eligibility_type": 7,
+ "public_key": null,
+ "jwe_cek_enc": null,
+ "jwe_encryption_alg": null,
+ "jws_signing_alg": null,
+ "auth_provider": 1,
+ "selection_label_template": "eligibility/includes/selection-label--calfresh.html",
+ "start_template": "",
+ "form_class": null
+ }
+ },
{
"model": "core.paymentprocessor",
"pk": 1,
@@ -299,8 +335,8 @@
"eligibility_index_template": "eligibility/index--mst.html",
"enrollment_success_template": "enrollment/success--mst.html",
"help_template": "core/includes/help--mst.html",
- "eligibility_types": [1, 2, 3],
- "eligibility_verifiers": [1, 2, 3]
+ "eligibility_types": [1, 7, 2, 3],
+ "eligibility_verifiers": [1, 7, 2, 3]
}
},
{
diff --git a/benefits/core/models.py b/benefits/core/models.py
index ec3c3499b..36010414a 100644
--- a/benefits/core/models.py
+++ b/benefits/core/models.py
@@ -158,6 +158,7 @@ class EligibilityVerifier(models.Model):
id = models.AutoField(primary_key=True)
name = models.TextField()
+ display_order = models.PositiveSmallIntegerField(default=0, blank=False, null=False)
active = models.BooleanField(default=False)
api_url = models.TextField(null=True)
api_auth_header = models.TextField(null=True)
@@ -177,6 +178,9 @@ class EligibilityVerifier(models.Model):
# reference to a form class used by this Verifier, e.g. benefits.app.forms.FormClass
form_class = models.TextField(null=True)
+ class Meta:
+ ordering = ["display_order"]
+
def __str__(self):
return self.name
diff --git a/benefits/eligibility/templates/eligibility/includes/modal--calfresh.html b/benefits/eligibility/templates/eligibility/includes/modal--calfresh.html
new file mode 100644
index 000000000..3098b39ac
--- /dev/null
+++ b/benefits/eligibility/templates/eligibility/includes/modal--calfresh.html
@@ -0,0 +1,42 @@
+{% extends "core/includes/modal.html" %}
+{% load i18n %}
+{% load static %}
+
+{% block modal-content %}
+
{% translate "Learn more about the transit benefit for CalFresh Cardholders" %}
+
+
{% translate "How do I know if I'm eligible for the transit benefit for CalFresh Cardholders?" %}
+
+ {% blocktranslate trimmed %}
+ We verify your eligibility as a CalFresh Cardholder by confirming you have received funds in your
+ CalFresh account at any point in the last three months. This means you are eligible for a transit
+ benefit even if you did not receive funds in your CalFresh account this month or last month.
+ {% endblocktranslate %}
+
+
+
{% translate "Will this transit benefit change my CalFresh account?" %}
+
+ {% blocktranslate trimmed %}
+ No. Your monthly CalFresh allotment will not change.
+ {% endblocktranslate %}
+
+
{% translate "Do I need my Golden State Advantage card to enroll?" %}
+
+ {% blocktranslate trimmed %}
+ No, you do not need your physical EBT card to enroll. We use information from Login.gov and the California
+ Department of Social Services to enroll you in the benefit.
+ {% endblocktranslate %}
+
+
{% translate "Can I use my Golden State Advantage card to pay for transit rides?" %}
+
+ {% blocktranslate trimmed %}
+ No. You can not use your EBT or P-EBT card to pay for public transportation. When you tap to ride, use your personal
+ contactless debit or credit card to pay for public transportation.
+ {% endblocktranslate %}
+
+
+
+ {% translate "Go back" %}
+
+
+{% endblock modal-content %}
diff --git a/benefits/eligibility/templates/eligibility/includes/selection-label--calfresh.html b/benefits/eligibility/templates/eligibility/includes/selection-label--calfresh.html
new file mode 100644
index 000000000..e44fe3376
--- /dev/null
+++ b/benefits/eligibility/templates/eligibility/includes/selection-label--calfresh.html
@@ -0,0 +1,18 @@
+{% extends "eligibility/includes/selection-label.html" %}
+{% load i18n %}
+
+{% block label %}
+ {% translate "CalFresh Cardholder" %}
+{% endblock label %}
+
+{% block description %}
+ {% translate "You must have" %}
+ {% translate "recently received CalFresh funds" as calfresh_modal_link %}
+ {% include "core/includes/modal-trigger.html" with modal="modal--calfresh" text=calfresh_modal_link period=True %}
+ {% include "eligibility/includes/modal--calfresh.html" with id="modal--calfresh" size="modal-lg" header="p-md-2 p-3" body="pb-md-3 mb-md-3 mx-md-3 py-0 pt-0 absolute-top" %}
+
+ {% translate "This transit benefit will remain active for one year. You will need to verify your identity with" %}
+ {% include "core/includes/modal-trigger.html" with classes="border-0 bg-transparent p-0 login" modal="modal--login-gov-veteran" login=True period=True %}
+ {% include "eligibility/includes/modal--login-gov-help.html" with id="modal--login-gov-calfresh" size="modal-lg" header="p-md-2 p-3" body="pb-md-3 mb-md-3 mx-md-3 py-0 pt-0 absolute-top" %}
+
+{% endblock description %}
diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po
index 76ab9d419..cf2d22f61 100644
--- a/benefits/locale/en/LC_MESSAGES/django.po
+++ b/benefits/locale/en/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n"
-"POT-Creation-Date: 2024-03-13 22:40+0000\n"
+"POT-Creation-Date: 2024-03-20 18:12+0000\n"
"Language: English\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -368,6 +368,45 @@ msgstr ""
msgid "A phone number with a phone plan associated with your name"
msgstr ""
+msgid "Learn more about the transit benefit for CalFresh Cardholders"
+msgstr ""
+
+msgid ""
+"How do I know if I'm eligible for the transit benefit for CalFresh "
+"Cardholders?"
+msgstr ""
+
+msgid ""
+"We verify your eligibility as a CalFresh Cardholder by confirming you have "
+"received funds in your CalFresh account at any point in the last three "
+"months. This means you are eligible for a transit benefit even if you did "
+"not receive funds in your CalFresh account this month or last month."
+msgstr ""
+
+msgid "Will this transit benefit change my CalFresh account?"
+msgstr ""
+
+msgid "No. Your monthly CalFresh allotment will not change."
+msgstr ""
+
+msgid "Do I need my Golden State Advantage card to enroll?"
+msgstr ""
+
+msgid ""
+"No, you do not need your physical EBT card to enroll. We use information "
+"from Login.gov and the California Department of Social Services to enroll "
+"you in the benefit."
+msgstr ""
+
+msgid "Can I use my Golden State Advantage card to pay for transit rides?"
+msgstr ""
+
+msgid ""
+"No. You can not use your EBT or P-EBT card to pay for public transportation. "
+"When you tap to ride, use your personal contactless debit or credit card to "
+"pay for public transportation."
+msgstr ""
+
msgid "Go back"
msgstr ""
@@ -386,6 +425,20 @@ msgid ""
"the transit benefit you selected."
msgstr ""
+msgid "CalFresh Cardholder"
+msgstr ""
+
+msgid "You must have"
+msgstr ""
+
+msgid "recently received CalFresh funds"
+msgstr ""
+
+msgid ""
+"This transit benefit will remain active for one year. You will need to "
+"verify your identity with"
+msgstr ""
+
msgid "MST Courtesy Card"
msgstr ""
diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po
index fbfdfce87..f290aa3c4 100644
--- a/benefits/locale/es/LC_MESSAGES/django.po
+++ b/benefits/locale/es/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n"
-"POT-Creation-Date: 2024-03-13 22:40+0000\n"
+"POT-Creation-Date: 2024-03-20 18:12+0000\n"
"Language: Español\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -469,6 +469,45 @@ msgid "A phone number with a phone plan associated with your name"
msgstr ""
"Un número de teléfono en el cual pueda recibir llamadas o mensajes de texto"
+msgid "Learn more about the transit benefit for CalFresh Cardholders"
+msgstr ""
+
+msgid ""
+"How do I know if I'm eligible for the transit benefit for CalFresh "
+"Cardholders?"
+msgstr ""
+
+msgid ""
+"We verify your eligibility as a CalFresh Cardholder by confirming you have "
+"received funds in your CalFresh account at any point in the last three "
+"months. This means you are eligible for a transit benefit even if you did "
+"not receive funds in your CalFresh account this month or last month."
+msgstr ""
+
+msgid "Will this transit benefit change my CalFresh account?"
+msgstr ""
+
+msgid "No. Your monthly CalFresh allotment will not change."
+msgstr ""
+
+msgid "Do I need my Golden State Advantage card to enroll?"
+msgstr ""
+
+msgid ""
+"No, you do not need your physical EBT card to enroll. We use information "
+"from Login.gov and the California Department of Social Services to enroll "
+"you in the benefit."
+msgstr ""
+
+msgid "Can I use my Golden State Advantage card to pay for transit rides?"
+msgstr ""
+
+msgid ""
+"No. You can not use your EBT or P-EBT card to pay for public transportation. "
+"When you tap to ride, use your personal contactless debit or credit card to "
+"pay for public transportation."
+msgstr ""
+
msgid "Go back"
msgstr "Volver"
@@ -489,6 +528,20 @@ msgstr ""
"Utilizamos Login.gov para verificar su identidad para asegurarnos de que "
"seas elegible para el beneficio de tránsito que seleccionaste."
+msgid "CalFresh Cardholder"
+msgstr ""
+
+msgid "You must have"
+msgstr ""
+
+msgid "recently received CalFresh funds"
+msgstr ""
+
+msgid ""
+"This transit benefit will remain active for one year. You will need to "
+"verify your identity with"
+msgstr ""
+
msgid "MST Courtesy Card"
msgstr "Tarjeta de cortesía de MST"
diff --git a/benefits/settings.py b/benefits/settings.py
index 35163a339..7e19d3e74 100644
--- a/benefits/settings.py
+++ b/benefits/settings.py
@@ -49,6 +49,7 @@ def RUNTIME_ENVIRONMENT():
"django.contrib.messages",
"django.contrib.sessions",
"django.contrib.staticfiles",
+ "adminsortable2",
"django_google_sso",
"benefits.core",
"benefits.enrollment",
diff --git a/pyproject.toml b/pyproject.toml
index 1b2323b94..71c529665 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,6 +12,7 @@ dependencies = [
"azure-identity==1.15.0",
"Django==5.0.3",
"django-csp==3.8",
+ "django-admin-sortable2==2.1.5",
"django-google-sso==6.0.2",
"eligibility-api==2023.9.1",
"calitp-littlepay==2024.3.1",
diff --git a/tests/cypress/specs/benefit-select.cy.js b/tests/cypress/specs/benefit-select.cy.js
index 1d4f9c5ad..9ee369c5a 100644
--- a/tests/cypress/specs/benefit-select.cy.js
+++ b/tests/cypress/specs/benefit-select.cy.js
@@ -8,20 +8,20 @@ describe("Benefit selection", () => {
helpers.selectAgency();
});
- it("User sees 3 radio buttons", () => {
- cy.get("input:radio").should("have.length", 3);
+ it("User sees 4 radio buttons", () => {
+ cy.get("input:radio").should("have.length", 4);
cy.contains("Courtesy Card");
cy.contains("65 years");
});
it("User must select a radio button, or else see a validation message", () => {
- cy.get("input:radio").should("have.length", 3);
+ cy.get("input:radio").should("have.length", 4);
cy.get("input:radio:checked").should("have.length", 0);
cy.get("#form-verifier-selection").submit();
cy.url().should("include", verifier_selection_url);
cy.get("input:radio:checked").should("have.length", 0);
- cy.get("input:invalid").should("have.length", 3);
+ cy.get("input:invalid").should("have.length", 4);
cy.get("input:radio")
.first()
.invoke("prop", "validationMessage")