Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: EligibilityType model fields for expiration #1955

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion benefits/core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
import logging
import requests

from django import forms
from django.conf import settings
from django.contrib import admin
from django.core.exceptions import ValidationError
from . import models

logger = logging.getLogger(__name__)
Expand All @@ -16,7 +18,6 @@


for model in [
models.EligibilityType,
models.EligibilityVerifier,
models.PaymentProcessor,
models.PemData,
Expand All @@ -26,6 +27,33 @@
admin.site.register(model)


class EligibilityTypeForm(forms.ModelForm):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hoping this could be done without a custom admin form?

I think implementing clean() in the model class should be enough?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see now that it could be done that way too and that we don't need the custom admin form. I'll put this back in draft and move the logic to be done on the model

class Meta:
model = models.EligibilityType
exclude = []

def clean(self):
cleaned_data = super().clean()
supports_expiration = cleaned_data.get("supports_expiration")
expiration_days = cleaned_data.get("expiration_days")
expiration_reenrollment_days = cleaned_data.get("expiration_reenrollment_days")

if supports_expiration:
message = "When support_expiration is True, this value must be greater than 0."
if expiration_days is None or expiration_days <= 0:
self.add_error("expiration_days", ValidationError(message))
if expiration_reenrollment_days is None or expiration_reenrollment_days <= 0:
self.add_error("expiration_reenrollment_days", ValidationError(message))


class EligibilityTypeAdmin(admin.ModelAdmin):
form = EligibilityTypeForm


logger.debug(f"Register {models.EligibilityType.__name__} with custom admin {EligibilityTypeAdmin.__name__}")
admin.site.register(models.EligibilityType, EligibilityTypeAdmin)


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")
Expand Down
28 changes: 28 additions & 0 deletions benefits/core/migrations/0003_eligibilitytype_expiration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 5.0.2 on 2024-03-14 20:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("core", "0002_paymentprocessor_backoffice_api"),
]

operations = [
migrations.AddField(
model_name="eligibilitytype",
name="expiration_days",
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name="eligibilitytype",
name="expiration_reenrollment_days",
field=models.PositiveSmallIntegerField(blank=True, null=True),
),
migrations.AddField(
model_name="eligibilitytype",
name="supports_expiration",
field=models.BooleanField(default=False),
),
]
3 changes: 3 additions & 0 deletions benefits/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ class EligibilityType(models.Model):
name = models.TextField()
label = models.TextField()
group_id = models.TextField()
supports_expiration = models.BooleanField(default=False)
expiration_days = models.PositiveSmallIntegerField(null=True, blank=True)
expiration_reenrollment_days = models.PositiveSmallIntegerField(null=True, blank=True)

def __str__(self):
return self.label
Expand Down
140 changes: 139 additions & 1 deletion tests/pytest/core/test_admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest
from django.contrib.auth.models import User
import benefits.core.admin
from benefits.core.admin import GOOGLE_USER_INFO_URL, pre_login_user
from benefits.core.admin import GOOGLE_USER_INFO_URL, pre_login_user, EligibilityTypeForm


@pytest.fixture
Expand Down Expand Up @@ -62,3 +62,141 @@ def test_pre_login_user_no_session_token(mocker, model_AdminUser):
assert model_AdminUser.last_name == ""
assert model_AdminUser.username == ""
logger_spy.warning.assert_called_once()


def eligibility_type_form_data(supports_expiration=False, expiration_days=None, expiration_reenrollment_days=None):
form_data = {
"name": "calfresh",
"label": "CalFresh recipients",
"group_id": "123",
"supports_expiration": supports_expiration,
}

if expiration_days:
form_data.update(expiration_days=expiration_days)

if expiration_reenrollment_days:
form_data.update(expiration_reenrollment_days=expiration_reenrollment_days)

return form_data


@pytest.mark.django_db
def test_EligibilityTypeForm_supports_expiration_False():
form_data = eligibility_type_form_data(supports_expiration=False)
form = EligibilityTypeForm(form_data)
assert form.is_valid()


@pytest.mark.django_db
@pytest.mark.parametrize(
"valid_expiration_reenrollment_days",
["1", "14", "30"],
ids=lambda n: f"negative expiration_days, valid expiration_enrollment_days ({n})",
)
def test_EligibilityTypeForm_supports_expiration_True_negative_expiration_days(valid_expiration_reenrollment_days):
form_data = eligibility_type_form_data(
supports_expiration=True, expiration_days=-20, expiration_reenrollment_days=valid_expiration_reenrollment_days
)
form = EligibilityTypeForm(form_data)

# assert state of the form
assert not form.is_valid()
assert len(form.errors) == 1

# assert state of specific field
errors = form.errors["expiration_days"]
assert len(errors) == 2

# error message coming from PositiveSmallIntegerField validation
assert errors[0] == "Ensure this value is greater than or equal to 0."
# our custom validation message for when supports_expiration is True
assert errors[1] == "When support_expiration is True, this value must be greater than 0."


@pytest.mark.django_db
@pytest.mark.parametrize(
"valid_expiration_reenrollment_days",
["1", "14", "30"],
ids=lambda n: f"zero expiration days, valid expiration_enrollment_days ({n})",
)
def test_EligibilityTypeForm_supports_expiration_True_zero_expiration_days(valid_expiration_reenrollment_days):
form_data = eligibility_type_form_data(
supports_expiration=True, expiration_days=0, expiration_reenrollment_days=valid_expiration_reenrollment_days
)
form = EligibilityTypeForm(form_data)

# assert state of the form
assert not form.is_valid()
assert len(form.errors) == 1

# assert state of specific field
errors = form.errors["expiration_days"]
assert len(errors) == 1

# our custom validation message for when supports_expiration is True
assert errors[0] == "When support_expiration is True, this value must be greater than 0."


@pytest.mark.django_db
@pytest.mark.parametrize(
"valid_expiration_days",
["1", "14", "30"],
ids=lambda n: f"valid expiration_days ({n}), negative expiration_reenrollment_days",
)
def test_EligibilityTypeForm_supports_expiration_True_negative_expiration_reenrollment_days(valid_expiration_days):
form_data = eligibility_type_form_data(
supports_expiration=True, expiration_days=valid_expiration_days, expiration_reenrollment_days=-20
)
form = EligibilityTypeForm(form_data)

# assert state of the form
assert not form.is_valid()
assert len(form.errors) == 1

# assert state of specific field
errors = form.errors["expiration_reenrollment_days"]
assert len(errors) == 2

# error message coming from PositiveSmallIntegerField validation
assert errors[0] == "Ensure this value is greater than or equal to 0."
# our custom validation message for when supports_expiration is True
assert errors[1] == "When support_expiration is True, this value must be greater than 0."


@pytest.mark.django_db
@pytest.mark.parametrize(
"valid_expiration_days",
["1", "14", "30"],
ids=lambda n: f"valid expiration_days ({n}), zero expiration_reenrollment_days",
)
def test_EligibilityTypeForm_supports_expiration_True_zero_expiration_reenrollment_days(valid_expiration_days):
form_data = eligibility_type_form_data(
supports_expiration=True, expiration_days=valid_expiration_days, expiration_reenrollment_days=0
)
form = EligibilityTypeForm(form_data)

# assert state of the form
assert not form.is_valid()
assert len(form.errors) == 1

# assert state of specific field
errors = form.errors["expiration_reenrollment_days"]
assert len(errors) == 1

# our custom validation message for when supports_expiration is True
assert errors[0] == "When support_expiration is True, this value must be greater than 0."


@pytest.mark.django_db
@pytest.mark.parametrize("expiration_days", ["1", "14", "3000"], ids=lambda n: f"expiration_days ({n})")
@pytest.mark.parametrize(
"expiration_reenrollment_days", ["1", "14", "3000"], ids=lambda n: f"expiration_reenrollment_days ({n})"
)
def test_EligibilityTypeForm_supports_expiration_True_valid(expiration_days, expiration_reenrollment_days):
form_data = eligibility_type_form_data(
supports_expiration=True, expiration_days=expiration_days, expiration_reenrollment_days=expiration_reenrollment_days
)
form = EligibilityTypeForm(form_data)

assert form.is_valid()
Loading