Skip to content

Commit

Permalink
Merge pull request #569 from Gary-Community-Ventures/dev
Browse files Browse the repository at this point in the history
Release 10/2/2024
  • Loading branch information
CalebPena authored Oct 10, 2024
2 parents 962d977 + 803930b commit 14f70c8
Show file tree
Hide file tree
Showing 76 changed files with 1,440 additions and 1,067 deletions.
5 changes: 5 additions & 0 deletions benefits/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@
"icon": "tune",
"link": reverse_lazy("admin:configuration_configuration_changelist"),
},
{
"title": _("Translation Overrides"),
"icon": "letter_switch",
"link": reverse_lazy("admin:programs_translationoverride_changelist"),
},
],
},
{
Expand Down
3 changes: 2 additions & 1 deletion integrations/util/cache.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Any
from sentry_sdk import capture_exception
import datetime

Expand Down Expand Up @@ -31,7 +32,7 @@ def should_update(self):

return datetime.datetime.now() > self.last_update + datetime.timedelta(seconds=self.expire_time)

def fetch(self):
def fetch(self) -> Any:
if self.should_update():
self._update_cache()

Expand Down
32 changes: 32 additions & 0 deletions programs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
County,
NavigatorLanguage,
Document,
TranslationOverride,
)


Expand Down Expand Up @@ -270,6 +271,36 @@ class WebHookFunctionsAdmin(ModelAdmin):
search_fields = ("name",)


class TranslationOverrideAdmin(ModelAdmin):
search_fields = ("external_name",)
list_display = ["get_str", "calculator", "action_buttons"]
filter_horizontal = ("counties",)

def get_str(self, obj):
return str(obj)

get_str.admin_order_field = "external_name"
get_str.short_description = "Name"

def action_buttons(self, obj):
message = obj.translation

return format_html(
"""
<div class="dropdown">
<span class="dropdown-btn material-symbols-outlined"> menu </span>
<div class="dropdown-content">
<a href="{}">Translation Override</a>
</div>
</div>
""",
reverse("translation_admin_url", args=[message.id]),
)

action_buttons.short_description = "Translate:"
action_buttons.allow_tags = True


admin.site.register(LegalStatus, LegalStatusAdmin)
admin.site.register(Program, ProgramAdmin)
admin.site.register(County, CountiesAdmin)
Expand All @@ -283,3 +314,4 @@ class WebHookFunctionsAdmin(ModelAdmin):
admin.site.register(Document, DocumentAdmin)
admin.site.register(Referrer, ReferrerAdmin)
admin.site.register(WebHookFunction, WebHookFunctionsAdmin)
admin.site.register(TranslationOverride, TranslationOverrideAdmin)
10 changes: 10 additions & 0 deletions programs/co_county_zips.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from screener.models import Screen


def counties_from_zip(lookup_zip):
matches = []

Expand All @@ -9,6 +12,13 @@ def counties_from_zip(lookup_zip):
return matches


def counties_from_screen(screen: Screen):
if screen.county is not None:
return [screen.county]

return counties_from_zip(screen.zipcode)


zipcodes = {
"Adams County": [
"80023",
Expand Down
56 changes: 56 additions & 0 deletions programs/migrations/0088_translationoverride.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 4.2.14 on 2024-09-24 16:41

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("translations", "0004_translation_no_auto"),
("programs", "0087_remove_program_warning"),
]

operations = [
migrations.CreateModel(
name="TranslationOverride",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("calculator", models.CharField(max_length=120)),
(
"external_name",
models.CharField(blank=True, max_length=120, null=True, unique=True),
),
("field", models.CharField(max_length=64)),
("active", models.BooleanField(blank=True, default=True)),
(
"counties",
models.ManyToManyField(related_name="translation_overrides", to="programs.county"),
),
(
"program",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="translation_overrides",
to="programs.program",
),
),
(
"translation",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name="translation_overrides",
to="translations.translation",
),
),
],
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.2.14 on 2024-09-30 20:42

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("programs", "0088_translationoverride"),
]

operations = [
migrations.AlterField(
model_name="translationoverride",
name="counties",
field=models.ManyToManyField(blank=True, related_name="translation_overrides", to="programs.county"),
),
migrations.AlterField(
model_name="translationoverride",
name="program",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="translation_overrides",
to="programs.program",
),
),
]
153 changes: 138 additions & 15 deletions programs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from translations.model_data import ModelDataController
from translations.models import Translation
from programs.programs import calculators
from programs.util import Dependencies, DependencyError
from programs.util import Dependencies
import requests
from integrations.util.cache import Cache
from typing import Optional, TypedDict
from typing import Optional, Type, TypedDict
from programs.programs.translation_overrides import warning_calculators


class FplCache(Cache):
Expand Down Expand Up @@ -77,7 +78,12 @@ def get_limit(self, household_size: int):
return limits[self.MAX_DEFINED_SIZE] + limits["additional"] * additional_member_count

def as_dict(self):
return self.fpl_cache.fetch()[self.period]
try:
return self.fpl_cache.fetch()[self.period]
except KeyError:
# the year is not cached, so invalidate the cache
self.fpl_cache.invalid = True
return self.fpl_cache.fetch()[self.period]

def __str__(self):
return self.year
Expand Down Expand Up @@ -216,6 +222,8 @@ def from_model_data(self, data: DataType):
if fpl is not None:
try:
fpl_instance = FederalPoveryLimit.objects.get(year=fpl["year"])
fpl_instance.period = fpl["period"]
fpl_instance.save()
except FederalPoveryLimit.DoesNotExist:
fpl_instance = FederalPoveryLimit.objects.create(year=fpl["year"], period=fpl["period"])
program.fpl = fpl_instance
Expand Down Expand Up @@ -317,26 +325,34 @@ class Program(models.Model):
def eligibility(self, screen, data, missing_dependencies: Dependencies):
Calculator = calculators[self.name_abbreviated.lower()]

if not Calculator.can_calc(missing_dependencies):
raise DependencyError()
calculator = Calculator(screen, self, data, missing_dependencies)

calculator = Calculator(screen, self, data)
eligibility = calculator.calc()

eligibility = calculator.eligible()

eligibility.value = calculator.value(eligibility.eligible_member_count)

if Calculator.tax_unit_dependent and screen.has_members_outside_of_tax_unit():
eligibility.multiple_tax_units = True

return eligibility.to_dict()
return eligibility

def __str__(self):
return self.name.text

def __unicode__(self):
return self.name.text

def get_translation(self, screen, missing_dependencies: Dependencies, field: str):
if field not in Program.objects.translated_fields:
raise ValueError(f"translation with name {field} does not exist")

translation_overrides: list[TranslationOverride] = self.translation_overrides.filter(active=True)
for translation_override in translation_overrides:
if translation_override.field != field:
continue

Calculator = warning_calculators[translation_override.calculator]
calculator = Calculator(screen, translation_override, missing_dependencies)
if calculator.calc() is True:
return translation_override.translation

return getattr(self, field)


class UrgentNeedFunction(models.Model):
name = models.CharField(max_length=32)
Expand Down Expand Up @@ -650,7 +666,7 @@ def new_warning(self, calculator, external_name=None):
)

for [field, translation] in translations.items():
translation.label = f"navigator.{calculator}_{warning.id}-{field}"
translation.label = f"warning.{calculator}_{warning.id}-{field}"
translation.save()

return warning
Expand Down Expand Up @@ -742,3 +758,110 @@ class Referrer(models.Model):

def __str__(self):
return self.referrer_code


class TranslationOverrideManager(models.Manager):
translated_fields = ("translation",)

def new_translation_override(self, calculator: str, program_field: str, external_name: Optional[str] = None):
"""Make a new translation override with the calculator, field, and external_name"""

translations = {}
for field in self.translated_fields:
translations[field] = Translation.objects.add_translation(
f"translation_override.{calculator}_temporary_key-{field}"
)

if external_name is None:
external_name = calculator

# try to set the external_name to the name
external_name_exists = self.filter(external_name=external_name).count() > 0

translation_override = self.create(
external_name=external_name if not external_name_exists else None,
calculator=calculator,
field=program_field,
**translations,
)

for [field, translation] in translations.items():
translation.label = f"translation_override.{calculator}_{translation_override.id}-{field}"
translation.save()

return translation_override


class TranslationOverrideDataController(ModelDataController["TranslationOverride"]):
_model_name = "TranslationOverride"
dependencies = ["Program"]

CountiesType = list[TypedDict("CountyType", {"name": str})]
DataType = TypedDict(
"DataType", {"calculator": str, "field": str, "active": bool, "counties": CountiesType, "program": str}
)

def _counties(self) -> CountiesType:
return [{"name": c.name} for c in self.instance.counties.all()]

def to_model_data(self) -> DataType:
translation_override = self.instance
return {
"calculator": translation_override.calculator,
"field": translation_override.field,
"active": translation_override.active,
"counties": self._counties(),
"program": translation_override.program.external_name,
}

def from_model_data(self, data: DataType):
translation_override = self.instance

translation_override.calculator = data["calculator"]
translation_override.field = data["field"]
translation_override.active = data["active"]

# get or create counties
counties = []
for county in data["counties"]:
try:
county_instance = County.objects.get(name=county["name"])
except County.DoesNotExist:
county_instance = County.objects.create(name=county["name"])
counties.append(county_instance)
translation_override.counties.set(counties)

# get programs
translation_override.program = Program.objects.get(external_name=data["program"])

translation_override.save()

@classmethod
def create_instance(cls, external_name: str, Model: type["TranslationOverride"]) -> "TranslationOverride":
return Model.objects.new_translation_override("_show", "", external_name)


class TranslationOverride(models.Model):
external_name = models.CharField(max_length=120, blank=True, null=True, unique=True)
calculator = models.CharField(max_length=120, blank=False, null=False)
field = models.CharField(max_length=64, blank=False, null=False)
program = models.ForeignKey(
Program, related_name="translation_overrides", blank=False, null=True, on_delete=models.CASCADE
)
active = models.BooleanField(blank=True, null=False, default=True)
counties = models.ManyToManyField(County, related_name="translation_overrides", blank=True)
translation = models.ForeignKey(
Translation, related_name="translation_overrides", blank=False, null=False, on_delete=models.PROTECT
)

objects = TranslationOverrideManager()

TranslationExportBuilder = TranslationOverrideDataController

@property
def county_names(self) -> list[str]:
"""List of county names"""
return [c.name for c in self.counties.all()]

def __str__(self):
return self.external_name if self.external_name is not None else self.calculator
Loading

0 comments on commit 14f70c8

Please sign in to comment.