Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fc01117
Enhance batch administration functionality and templates
Dec 22, 2025
addcf78
Enhance BatchAdmin and CountryBatchAdmin with new button functionalities
Dec 23, 2025
e1125b7
Refactor BatchAdmin button functionalities for improved navigation
Dec 23, 2025
826b82e
add styles for adminextrabuttons select
vitali-yanushchyk-valor Dec 24, 2025
0f32f3d
Add tests for BatchAdmin button behavior with beneficiary groups
Dec 24, 2025
588a278
Refactor BatchAdmin and enhance batch reprocessing functionality
Dec 29, 2025
8b06971
Merge branch 'develop' into feature/291348
arsen-vs Dec 29, 2025
47bb098
Update tests for batch reprocessing to conditionally assert household…
Dec 30, 2025
61a8ccf
Refactor household assertions in batch reprocessing tests
Dec 30, 2025
2d3506f
Enhance batch reprocessing logic and add comprehensive tests
Dec 30, 2025
5381609
Update reprocess confirmation form and enhance batch reprocessing tests
Dec 30, 2025
08564ef
Update assertions in batch reprocessing tests to improve accuracy
Dec 30, 2025
66a3e25
Update assertion for skipped households in batch reprocessing tests
Dec 30, 2025
f40e19d
Add tests for BatchAdmin functionality related to households
Dec 30, 2025
16bc6e1
Refactor BatchAdmin test cases for improved clarity and accuracy
Dec 30, 2025
e952708
Refactor beneficiary group checks in BatchAdmin and batch reprocessing
Dec 30, 2025
5d6a720
Update test setup for batch reprocessing to include raw data
Dec 31, 2025
df15cfa
Enhance batch reprocessing tests to include beneficiary group setup
Dec 31, 2025
511b248
Update assertions for skipped individuals in batch reprocessing tests
Dec 31, 2025
64d949f
Merge branch 'develop' into feature/291348
domdinicola Dec 31, 2025
1d8fdf9
Merge branch 'develop' into feature/291348
domdinicola Dec 31, 2025
320e6d7
Add comprehensive tests for batch reprocessing mapping functionality
Jan 3, 2026
604bae7
Add alien individual fields to program configuration in batch reproce…
Jan 5, 2026
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
14 changes: 13 additions & 1 deletion src/country_workspace/admin/base.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
from admin_extra_buttons.mixins import ExtraButtonsMixin
from adminfilters.mixin import AdminAutoCompleteSearchMixin, AdminFiltersMixin
from django.contrib import admin
from django import forms

from adminactions import actions


class BaseModelAdmin(ExtraButtonsMixin, AdminAutoCompleteSearchMixin, AdminFiltersMixin, admin.ModelAdmin):
pass
@property
def media(self) -> forms.Media:
base = super().media
return base + forms.Media(
js=[],
css={
"screen": [
"admin/admin_extra.css",
],
},
)


actions.add_to_site(admin.site)
58 changes: 53 additions & 5 deletions src/country_workspace/admin/batch.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from admin_extra_buttons.buttons import LinkButton
from admin_extra_buttons.decorators import link
from admin_extra_buttons.decorators import button, link
from adminfilters.autocomplete import AutoCompleteFilter, LinkedAutoCompleteFilter
from django.contrib import admin
from django.http import HttpRequest
from django.db.models import QuerySet
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext as _

from ..models import Batch
from .base import BaseModelAdmin
Expand All @@ -24,14 +27,59 @@ class BatchAdmin(BaseModelAdmin):
search_fields = ("name",)
ordering = ("-import_date",)

def get_queryset(self, request: HttpRequest) -> QuerySet[Batch]:
return super().get_queryset(request).select_related("program", "program__beneficiary_group", "country_office")

def has_add_permission(self, request: HttpRequest) -> bool:
return False

def _get_beneficiary_labels(self, batch: Batch) -> tuple[str, str]:
if batch.program.beneficiary_group:
beneficiary_group = batch.program.beneficiary_group
group_label = beneficiary_group.group_label_plural or beneficiary_group.group_label or _("Household")
member_label = beneficiary_group.member_label_plural or beneficiary_group.member_label or _("Individual")
return group_label, member_label
return _("Household"), _("Individual")

@button(change_list=False, label="All Beneficiaries")
def beneficiaries(self, request: HttpRequest, pk: str) -> HttpResponse:
batch: Batch = self.get_object(request, pk)
households = batch.household_set.all()
individuals = batch.individual_set.all()
context = {
"batch": batch,
"households": households,
"individuals": individuals,
**self.admin_site.each_context(request),
"opts": self.model._meta,
}
return render(request, "admin/country_workspace/batch_beneficiaries.html", context)

@link(change_list=False)
def households(self, button: LinkButton) -> None:
obj: Batch = button.context["original"]

if not obj.household_set.exists():
button.visible = False
return

if obj.program and obj.program.beneficiary_group:
if not obj.program.beneficiary_group.master_detail:
button.visible = False
return

group_label, _ = self._get_beneficiary_labels(obj)
button.label = group_label
base = reverse("admin:country_workspace_household_changelist")
button.href = f"{base}?batch__exact={obj.pk}"

@link(change_list=False)
def members(self, button: LinkButton) -> None:
def individuals(self, button: LinkButton) -> None:
obj: Batch = button.context["original"]
_, member_label = self._get_beneficiary_labels(obj)
button.label = member_label
base = reverse("admin:country_workspace_individual_changelist")
obj = button.context["original"]
button.href = f"{base}?household__exact={obj.pk}"
button.href = f"{base}?batch__exact={obj.pk}"

@link(change_list=True, change_form=False)
def view_in_workspace(self, btn: "LinkButton") -> None:
Expand Down
3 changes: 2 additions & 1 deletion src/country_workspace/admin/household.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from admin_extra_buttons.buttons import LinkButton
from admin_extra_buttons.decorators import button, link
from adminfilters.filters import LinkedAutoCompleteFilter
from adminfilters.filters import LinkedAutoCompleteFilter, AutoCompleteFilter
from django.contrib import admin, messages
from django.urls import reverse
from django.utils.translation import gettext as _
Expand All @@ -20,6 +20,7 @@ class HouseholdAdmin(BaseModelAdmin):
("batch__country_office", LinkedAutoCompleteFilter.factory(parent=None)),
("batch__program", LinkedAutoCompleteFilter.factory(parent="batch__country_office")),
("batch", LinkedAutoCompleteFilter.factory(parent="batch__program")),
("batch", AutoCompleteFilter),
IsValidFilter,
"removed",
)
Expand Down
3 changes: 2 additions & 1 deletion src/country_workspace/admin/individual.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import TYPE_CHECKING

from admin_extra_buttons.decorators import link
from adminfilters.autocomplete import LinkedAutoCompleteFilter
from adminfilters.autocomplete import LinkedAutoCompleteFilter, AutoCompleteFilter
from django.contrib import admin
from django.urls import reverse

Expand All @@ -23,6 +23,7 @@ class IndividualAdmin(BaseModelAdmin):
("batch__country_office", LinkedAutoCompleteFilter.factory(parent=None)),
("batch__program", LinkedAutoCompleteFilter.factory(parent="batch__country_office")),
("batch", LinkedAutoCompleteFilter.factory(parent="batch__program")),
("batch", AutoCompleteFilter),
IsValidFilter,
"removed",
)
Expand Down
26 changes: 26 additions & 0 deletions src/country_workspace/web/static/admin/admin_extra.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.object-tools select.aeb-button {
margin: 0;
padding:.2rem .1rem .2rem .3rem; /* top right bottom left */
height: 1.6rem;
line-height: 1.25rem;

background: var(--object-tools-bg);
color: var(--object-tools-fg);

font: 400 .7rem/1.25rem var(--font-family-primary);
text-transform: uppercase;

border: 0;
border-radius: 1rem;
cursor: pointer;
vertical-align: middle;
}

.object-tools select.aeb-button:hover {
background: var(--object-tools-hover-bg);
}

.object-tools select.aeb-button option {
background: var(--object-tools-bg);
color: var(--object-tools-fg);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls static %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'change' batch.pk %}">{{ batch|truncatewords:"18" }}</a>
&rsaquo; {% trans 'Beneficiaries' %}
</div>
{% endblock %}
{% block content %}
<div id="content-main">
<h1>
{% trans 'Beneficiaries for Batch' %}: {{ batch }}
</h1>
<div class="module">
<h2>
{% trans 'Households' %} ({{ households.count }})
</h2>
{% if households %}
<table style="width: 100%;">
<thead>
<tr>
<th>
{% trans 'Name' %}
</th>
<th>
{% trans 'ID' %}
</th>
<th>
{% trans 'Status' %}
</th>
</tr>
</thead>
<tbody>
{% for hh in households %}
<tr class="{% cycle 'row1' 'row2' %}">
<td>
<a href="{% url 'admin:country_workspace_household_change' hh.pk %}">{{ hh.name }}</a>
</td>
<td>
{{ hh.pk }}
</td>
<td>
{% if hh.removed %}
<span style="color: red;">{% trans 'Removed' %}</span>
{% else %}
<span style="color: green;">{% trans 'Active' %}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>
{% trans 'No households found.' %}
</p>
{% endif %}
</div>
<div class="module">
<h2>
{% trans 'Individuals' %} ({{ individuals.count }})
</h2>
{% if individuals %}
<table style="width: 100%;">
<thead>
<tr>
<th>
{% trans 'Name' %}
</th>
<th>
{% trans 'ID' %}
</th>
<th>
{% trans 'Household' %}
</th>
<th>
{% trans 'Status' %}
</th>
</tr>
</thead>
<tbody>
{% for ind in individuals %}
<tr class="{% cycle 'row1' 'row2' %}">
<td>
<a href="{% url 'admin:country_workspace_individual_change' ind.pk %}">{{ ind.name }}</a>
</td>
<td>
{{ ind.pk }}
</td>
<td>
{% if ind.household %}
<a href="{% url 'admin:country_workspace_household_change' ind.household.pk %}">{{ ind.household.name }}</a>
{% else %}
-
{% endif %}
</td>
<td>
{% if ind.removed %}
<span style="color: red;">{% trans 'Removed' %}</span>
{% else %}
<span style="color: green;">{% trans 'Active' %}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>
{% trans 'No individuals found.' %}
</p>
{% endif %}
</div>
</div>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</div>
{% endblock breadcrumbs %}
{% block content %}
<form action="" method="post">
<form action="" method="post" id="reprocess-form">
{% csrf_token %}
<div>
<p>
Expand Down
12 changes: 12 additions & 0 deletions src/country_workspace/workspaces/admin/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,18 @@ def imported_records(self, btn: LinkButton) -> None:
base = reverse("workspace:workspaces_countryhousehold_changelist")
obj = btn.context["original"]
btn.href = f"{base}?batch__exact={obj.pk}"
if obj.program.beneficiary_group:
btn.label = obj.program.beneficiary_group.group_label
if not obj.program.beneficiary_group.master_detail:
btn.visible = False

@link(change_list=False, html_attrs={"title": "Shows related Individual records."})
def imported_individuals(self, btn: LinkButton) -> None:
base = reverse("workspace:workspaces_countryindividual_changelist")
obj = btn.context["original"]
btn.href = f"{base}?batch__exact={obj.pk}"
if obj.program.beneficiary_group:
btn.label = obj.program.beneficiary_group.member_label

@button(
change_list=False,
Expand Down
Loading
Loading