Skip to content

Commit 69a13ba

Browse files
AB#241769: add ! beneficiary_group (#60)
* add ! beneficiary_group * chg ! beneficiary group
1 parent 85cdc76 commit 69a13ba

File tree

21 files changed

+1307
-934
lines changed

21 files changed

+1307
-934
lines changed

src/country_workspace/admin/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from ..cache.smart_panel import panel_cache
88
from .batch import BatchAdmin # noqa
9+
from .beneficiary_group import BeneficiaryGroupAdmin # noqa
910
from .constance import ConstanceAdmin # noqa
1011
from .household import HouseholdAdmin # noqa
1112
from .individual import IndividualAdmin # noqa
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from django.contrib import admin
2+
from django.http import HttpRequest
3+
4+
from country_workspace.models import BeneficiaryGroup
5+
from .base import BaseModelAdmin
6+
7+
8+
@admin.register(BeneficiaryGroup)
9+
class BeneficiaryGroupAdmin(BaseModelAdmin):
10+
list_display = ("name", "group_label", "group_label_plural", "member_label", "member_label_plural", "master_detail")
11+
search_fields = (
12+
"name",
13+
"group_label",
14+
"group_label_plural",
15+
"member_label",
16+
"member_label_plural",
17+
)
18+
ordering = ("name",)
19+
20+
def has_add_permission(self, request: HttpRequest) -> bool:
21+
return False
22+
23+
def has_delete_permission(self, request: HttpRequest, obj: BeneficiaryGroup = None) -> bool:
24+
return False
25+
26+
def has_change_permission(self, request: HttpRequest, obj: BeneficiaryGroup = None) -> bool:
27+
return False

src/country_workspace/admin/program.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class ProgramAdmin(BaseModelAdmin):
2020
list_display = (
2121
"name",
2222
"sector",
23+
"beneficiary_group",
2324
"status",
2425
"active",
2526
"beneficiary_validator",
@@ -32,6 +33,7 @@ class ProgramAdmin(BaseModelAdmin):
3233
"status",
3334
"active",
3435
"sector",
36+
"beneficiary_group",
3537
"beneficiary_validator",
3638
"household_checker",
3739
"individual_checker",

src/country_workspace/contrib/hope/sync/office.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.core.cache import cache
44
from hope_flex_fields.models import DataChecker
55

6-
from country_workspace.models import Office, Program, SyncLog
6+
from country_workspace.models import BeneficiaryGroup, Office, Program, SyncLog
77

88
from .. import constants
99
from ..client import HopeClient
@@ -30,10 +30,11 @@ def sync_offices(stdout: TextIOBase | None = None) -> dict[str, int]:
3030
)
3131
totals["add" if created else "upd"] += 1
3232
SyncLog.objects.register_sync(Office)
33-
return totals
33+
return totals
3434

3535

3636
def sync_programs(limit_to_office: "Office | None" = None, stdout: TextIOBase | None = None) -> dict[str, int]:
37+
sync_beneficiary_groups(stdout=stdout)
3738
if stdout:
3839
stdout.write("Fetching Programs data from HOPE...")
3940
client = HopeClient()
@@ -50,6 +51,7 @@ def sync_programs(limit_to_office: "Office | None" = None, stdout: TextIOBase |
5051
office = Office.objects.get(code=record["business_area_code"])
5152
if record["status"] not in [Program.ACTIVE, Program.DRAFT]:
5253
continue
54+
beneficiary_group = BeneficiaryGroup.objects.get(hope_id=record["beneficiary_group"])
5355
p, created = Program.objects.get_or_create(
5456
hope_id=record["id"],
5557
defaults={
@@ -58,6 +60,7 @@ def sync_programs(limit_to_office: "Office | None" = None, stdout: TextIOBase |
5860
"status": record["status"],
5961
"sector": record["sector"],
6062
"country_office": office,
63+
"beneficiary_group": beneficiary_group,
6164
},
6265
)
6366
if created:
@@ -73,6 +76,29 @@ def sync_programs(limit_to_office: "Office | None" = None, stdout: TextIOBase |
7376
return totals
7477

7578

79+
def sync_beneficiary_groups(stdout: TextIOBase | None = None) -> bool:
80+
totals = {"add": 0, "upd": 0}
81+
client = HopeClient()
82+
if stdout:
83+
stdout.write("Fetching Beneficiary Groups data from HOPE...")
84+
with cache.lock("sync-beneficiary-groups"):
85+
for record in client.get("beneficiary-groups"):
86+
__, created = BeneficiaryGroup.objects.get_or_create(
87+
hope_id=record["id"],
88+
defaults={
89+
"name": record["name"],
90+
"group_label": record["group_label"],
91+
"group_label_plural": record["group_label_plural"],
92+
"member_label": record["member_label"],
93+
"member_label_plural": record["member_label_plural"],
94+
"master_detail": record["master_detail"],
95+
},
96+
)
97+
totals["add" if created else "upd"] += 1
98+
SyncLog.objects.register_sync(BeneficiaryGroup)
99+
return totals
100+
101+
76102
def sync_all(stdout: TextIOBase | None = None) -> bool:
77103
sync_offices(stdout=stdout)
78104
sync_programs(stdout=stdout)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Generated by Django 5.1.7 on 2025-03-25 13:44
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("country_workspace", "0008_alter_individual_options"),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name="BeneficiaryGroup",
15+
fields=[
16+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
17+
(
18+
"hope_id",
19+
models.CharField(
20+
blank=True, help_text="Unique ID from HOPE", max_length=100, null=True, unique=True
21+
),
22+
),
23+
("name", models.CharField(help_text="Name of the beneficiary group", max_length=255, unique=True)),
24+
("group_label", models.CharField(help_text="Label for the group", max_length=255)),
25+
("group_label_plural", models.CharField(help_text="Plural label for the group", max_length=255)),
26+
("member_label", models.CharField(help_text="Label for the member", max_length=255)),
27+
("member_label_plural", models.CharField(help_text="Plural label for the member", max_length=255)),
28+
(
29+
"master_detail",
30+
models.BooleanField(default=True, help_text="Indicates if this is a master-detail group"),
31+
),
32+
],
33+
options={
34+
"verbose_name": "Beneficiary Group",
35+
"verbose_name_plural": "Beneficiary Groups",
36+
"ordering": ("name",),
37+
},
38+
),
39+
migrations.AlterModelOptions(
40+
name="asyncjob",
41+
options={
42+
"permissions": (("debug_job", "Can debug background jobs"),),
43+
"verbose_name": "Async Job",
44+
"verbose_name_plural": "Async Jobs",
45+
},
46+
),
47+
migrations.AlterModelOptions(
48+
name="batch",
49+
options={"verbose_name": "Batch", "verbose_name_plural": "Batches"},
50+
),
51+
migrations.AlterField(
52+
model_name="office",
53+
name="kobo_country_code",
54+
field=models.CharField(blank=True, max_length=3, null=True),
55+
),
56+
migrations.AddField(
57+
model_name="program",
58+
name="beneficiary_group",
59+
field=models.ForeignKey(
60+
blank=True,
61+
help_text="Beneficiary group to which this program belongs",
62+
null=True,
63+
on_delete=django.db.models.deletion.PROTECT,
64+
related_name="programs",
65+
to="country_workspace.beneficiarygroup",
66+
),
67+
),
68+
]

src/country_workspace/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .batch import Batch # noqa
2+
from .beneficiary_group import BeneficiaryGroup # noqa
23
from .household import Household # noqa
34
from .individual import Individual # noqa
45
from .jobs import AsyncJob # noqa

src/country_workspace/models/batch.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from django.db import models
2+
from django.utils.translation import gettext as _
23

34
from .base import BaseModel
45
from .user import User
@@ -19,6 +20,8 @@ class BatchSource(models.TextChoices):
1920

2021
class Meta:
2122
unique_together = (("import_date", "name"),)
23+
verbose_name = _("Batch")
24+
verbose_name_plural = _("Batches")
2225

2326
def __str__(self) -> str:
2427
return self.name or f"Batch self.pk ({self.country_office})"
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from django.db import models
2+
3+
4+
class BeneficiaryGroup(models.Model):
5+
hope_id = models.CharField(max_length=100, blank=True, null=True, unique=True, help_text="Unique ID from HOPE")
6+
name = models.CharField(max_length=255, unique=True, help_text="Name of the beneficiary group")
7+
group_label = models.CharField(max_length=255, help_text="Label for the group")
8+
group_label_plural = models.CharField(max_length=255, help_text="Plural label for the group")
9+
member_label = models.CharField(max_length=255, help_text="Label for the member")
10+
member_label_plural = models.CharField(max_length=255, help_text="Plural label for the member")
11+
master_detail = models.BooleanField(default=True, help_text="Indicates if this is a master-detail group")
12+
13+
class Meta:
14+
verbose_name = "Beneficiary Group"
15+
verbose_name_plural = "Beneficiary Groups"
16+
ordering = ("name",)
17+
18+
def __str__(self) -> str:
19+
return self.name

src/country_workspace/models/jobs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ class JobType(models.TextChoices):
2727

2828
class Meta:
2929
permissions = (("debug_job", "Can debug background jobs"),)
30+
verbose_name = "Async Job"
31+
verbose_name_plural = "Async Jobs"
3032

3133
def __str__(self) -> str:
3234
return self.description or f"Background Job #{self.pk}"

src/country_workspace/models/program.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from strategy_field.utils import fqn
88

99
from country_workspace.models.office import Office
10+
from country_workspace.models.beneficiary_group import BeneficiaryGroup
1011

1112
from ..validators.registry import NoopValidator, beneficiary_validator_registry
1213
from .base import BaseModel, Validable
@@ -44,6 +45,14 @@ class Program(BaseModel):
4445
(WASH, _("WASH")),
4546
)
4647
hope_id = models.CharField(max_length=200, unique=True, editable=False)
48+
beneficiary_group = models.ForeignKey(
49+
BeneficiaryGroup,
50+
on_delete=models.PROTECT,
51+
related_name="programs",
52+
null=True,
53+
blank=True,
54+
help_text="Beneficiary group to which this program belongs",
55+
)
4756
country_office = models.ForeignKey(Office, on_delete=models.CASCADE, related_name="programs")
4857
name = models.CharField(max_length=255)
4958
code = models.CharField(max_length=255, blank=True, null=True)

0 commit comments

Comments
 (0)