Skip to content

Commit 02ed82e

Browse files
add ! beneficiary_group
1 parent 22ef82c commit 02ed82e

File tree

9 files changed

+173
-28
lines changed

9 files changed

+173
-28
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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from django.contrib import admin
2+
from django.http import HttpRequest
3+
4+
from admin_extra_buttons.api import button
5+
6+
from country_workspace.models import BeneficiaryGroup
7+
from .base import BaseModelAdmin
8+
9+
10+
@admin.register(BeneficiaryGroup)
11+
class BeneficiaryGroupAdmin(BaseModelAdmin):
12+
list_display = ("name", "group_label", "group_label_plural", "member_label", "member_label_plural", "master_detail")
13+
search_fields = (
14+
"name",
15+
"group_label",
16+
"group_label_plural",
17+
"member_label",
18+
"member_label_plural",
19+
)
20+
ordering = ("name",)
21+
22+
def has_add_permission(self, request: HttpRequest) -> bool:
23+
return False
24+
25+
def has_delete_permission(self, request: HttpRequest, obj: BeneficiaryGroup = None) -> bool:
26+
return False
27+
28+
def has_change_permission(self, request: HttpRequest, obj: BeneficiaryGroup = None) -> bool:
29+
return False
30+
31+
@button()
32+
def sync(self, request: HttpRequest) -> None:
33+
from country_workspace.contrib.hope.sync.office import sync_beneficiary_groups
34+
35+
totals = sync_beneficiary_groups()
36+
self.message_user(request, f"{totals['add']} created - {totals['upd']} updated")

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: 29 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,8 @@ 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+
# TODO: beneficiary group
55+
beneficiary_group = BeneficiaryGroup.objects.get(pk=3)
5356
p, created = Program.objects.get_or_create(
5457
hope_id=record["id"],
5558
defaults={
@@ -58,6 +61,7 @@ def sync_programs(limit_to_office: "Office | None" = None, stdout: TextIOBase |
5861
"status": record["status"],
5962
"sector": record["sector"],
6063
"country_office": office,
64+
"beneficiary_group": beneficiary_group,
6165
},
6266
)
6367
if created:
@@ -73,6 +77,29 @@ def sync_programs(limit_to_office: "Office | None" = None, stdout: TextIOBase |
7377
return totals
7478

7579

80+
def sync_beneficiary_groups(stdout: TextIOBase | None = None) -> bool:
81+
totals = {"add": 0, "upd": 0}
82+
client = HopeClient()
83+
if stdout:
84+
stdout.write("Fetching Beneficiary Groups data from HOPE...")
85+
with cache.lock("sync-beneficiary-groups"):
86+
for record in client.get("beneficiary-groups"):
87+
__, created = BeneficiaryGroup.objects.get_or_create(
88+
hope_id=record["id"],
89+
defaults={
90+
"name": record["name"],
91+
"group_label": record["group_label"],
92+
"group_label_plural": record["group_label_plural"],
93+
"member_label": record["member_label"],
94+
"member_label_plural": record["member_label_plural"],
95+
"master_detail": record["master_detail"],
96+
},
97+
)
98+
totals["add" if created else "upd"] += 1
99+
SyncLog.objects.register_sync(BeneficiaryGroup)
100+
return totals
101+
102+
76103
def sync_all(stdout: TextIOBase | None = None) -> bool:
77104
sync_offices(stdout=stdout)
78105
sync_programs(stdout=stdout)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Generated by Django 5.1.7 on 2025-03-21 08:42
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+
("hope_id", models.CharField(blank=True, max_length=100, null=True, unique=True)),
18+
("name", models.CharField(max_length=255, unique=True)),
19+
("group_label", models.CharField(max_length=255)),
20+
("group_label_plural", models.CharField(max_length=255)),
21+
("member_label", models.CharField(max_length=255)),
22+
("member_label_plural", models.CharField(max_length=255)),
23+
("master_detail", models.BooleanField(default=True)),
24+
],
25+
options={
26+
"verbose_name": "Beneficiary Group",
27+
"verbose_name_plural": "Beneficiary Groups",
28+
"ordering": ("name",),
29+
},
30+
),
31+
migrations.AlterField(
32+
model_name="office",
33+
name="kobo_country_code",
34+
field=models.CharField(blank=True, max_length=3, null=True),
35+
),
36+
migrations.AddField(
37+
model_name="program",
38+
name="beneficiary_group",
39+
field=models.ForeignKey(
40+
blank=True,
41+
help_text="Beneficiary group to which this program belongs",
42+
null=True,
43+
on_delete=django.db.models.deletion.PROTECT,
44+
related_name="programs",
45+
to="country_workspace.beneficiarygroup",
46+
),
47+
),
48+
]

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
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)
6+
name = models.CharField(max_length=255, unique=True)
7+
group_label = models.CharField(max_length=255)
8+
group_label_plural = models.CharField(max_length=255)
9+
member_label = models.CharField(max_length=255)
10+
member_label_plural = models.CharField(max_length=255)
11+
master_detail = models.BooleanField(default=True)
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/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)

src/country_workspace/workspaces/templates/workspace/_base.html

100755100644
Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,34 @@
2828
</li>
2929
{% if active_program %}
3030
<li class="flex {% if modeladmin_name == 'CountryProgramAdmin' %}selected{% endif %}">
31-
<a class="flex w-full px-4"
32-
href="{% url "workspace:workspaces_countryprogram_change" active_program.pk %}">
31+
<a class="flex w-full px-4" href="{% url "workspace:workspaces_countryprogram_change" active_program.pk %}">
3332
<span class="flex text-2xl pt-2 pr-4 icon icon-equalizer"></span>
3433
<div class="flex-grow pt-3 display-open">{% translate "Programme" %}</div>
3534
</a>
3635
</li>
37-
<li class="{% if modeladmin_name == 'CountryHouseholdAdmin' %}selected{% endif %}">
38-
<a class="flex w-full px-4" href="{% url "workspace:workspaces_countryhousehold_changelist" %}">
39-
<span class="flex text-2xl pt-2 pr-4 icon icon-members"></span>
40-
<div class="flex-grow pt-3 display-open">{% translate "Households" %}</div>
41-
</a>
42-
</li>
43-
<li class="pl-5 {% if modeladmin_name == 'CountryIndividualAdmin' %}selected{% endif %}">
44-
<a class="flex w-full px-4" href="{% url "workspace:workspaces_countryindividual_changelist" %}">
45-
<span class="flex text-2xl pt-2 pr-4 icon icon-user"></span>
46-
<div class="flex-grow pt-3 display-open">{% translate "Individuals" %}</div>
47-
</a>
48-
</li>
36+
{% if active_program.beneficiary_group.master_detail %}
37+
<li class="{% if modeladmin_name == 'CountryHouseholdAdmin' %}selected{% endif %}">
38+
<a class="flex w-full px-4" href="{% url "workspace:workspaces_countryhousehold_changelist" %}">
39+
<span class="flex text-2xl pt-2 pr-4 icon icon-members"></span>
40+
<div class="flex-grow pt-3 display-open">{% translate "Households" %}</div>
41+
</a>
42+
</li>
43+
<li class="pl-5 {% if modeladmin_name == 'CountryIndividualAdmin' %}selected{% endif %}">
44+
<a class="flex w-full px-4" href="{% url "workspace:workspaces_countryindividual_changelist" %}">
45+
<span class="flex text-2xl pt-2 pr-4 icon icon-user"></span>
46+
<div class="flex-grow pt-3 display-open">{% translate "Individuals" %}</div>
47+
</a>
48+
</li>
49+
{% else %}
50+
51+
<li class="flex {% if modeladmin_name == 'CountryIndividualAdmin' %}selected{% endif %}">
52+
<a class="flex w-full px-4" href="{% url "workspace:workspaces_countryindividual_changelist" %}">
53+
<span class="flex text-2xl pt-2 pr-4 icon icon-user"></span>
54+
<div class="flex-grow pt-3 display-open">{{ active_program.beneficiary_group.member_label_plural }}</div>
55+
</a>
56+
</li>
57+
58+
{% endif %}
4959
<li class="flex {% if modeladmin_name == 'CountryBatchAdmin' %}selected{% endif %}">
5060
<a class="flex w-full px-4" href="{% url "workspace:workspaces_countrybatch_changelist" %}">
5161
<span class="flex text-2xl pt-2 pr-4 icon icon-sign"></span>
@@ -59,7 +69,6 @@
5969
</a>
6070
</li>
6171
{% endif %}
62-
6372
<li class="flex">
6473
<form method="post" action="{% url "admin:logout" %}">
6574
{% csrf_token %}
@@ -75,22 +84,15 @@
7584
<li class="flex">
7685
<a class="flex w-full px-4" target="_admin" href="{% url "admin:index" %}">
7786
<span class="flex text-2xl pt-2 pr-4 icon icon-shield1"></span>
78-
<div class="flex-grow pt-3 display-open">{% translate "Admin" %}</div>
87+
<div class="flex-grow pt-3 display-open">{% translate "Admin" %}</div>
7988
</a>
8089
</li>
8190
{% endif %}
82-
{# <li class="flex items-center border-t-black display-open"> #}
83-
{# <div class="m-auto text-gray-400 "> #}
84-
{# <span>HOPE Workspace</span> #}
85-
{# <span>{{ app.version }}</span> #}
86-
{# <span>{{ app.release_date }}</span> #}
87-
{# </div> #}
88-
{# </li> #}
8991
</ul>
9092
{% endblock menu %}
9193

9294
{% block page-top %}
93-
<div class="title-top">
94-
<h2>{% block page-title %}{{ title|default_if_none:"" }}{% endblock %}</h2>
95-
</div>
95+
<div class="title-top">
96+
<h2>{% block page-title %}{{ title|default_if_none:"" }}{% endblock %}</h2>
97+
</div>
9698
{% endblock page-top %}

0 commit comments

Comments
 (0)