Skip to content

Commit 4452b87

Browse files
chg ! RDI for people
1 parent a18e8c9 commit 4452b87

File tree

3 files changed

+79
-22
lines changed

3 files changed

+79
-22
lines changed

src/country_workspace/datasources/rdi.py

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from base64 import b64encode
33
from collections import defaultdict
44
from collections.abc import Iterable, Generator
5-
from typing import Any, Mapping, cast
5+
from typing import Any, Mapping, cast, NotRequired
66

77
import openpyxl
88
from PIL import Image
@@ -28,9 +28,12 @@
2828

2929

3030
class Config(BatchNameConfig, FailIfAlienConfig):
31-
household_pk_col: str
32-
master_column_label: str
33-
detail_column_label: str
31+
master_detail: bool
32+
household_pk_col: NotRequired[str]
33+
master_column_label: NotRequired[str]
34+
detail_column_label: NotRequired[str]
35+
people_column_prefix: NotRequired[str]
36+
first_line: int
3437

3538

3639
class ColumnConfigurationError(Exception):
@@ -129,6 +132,30 @@ def process_individuals(
129132
return processed
130133

131134

135+
def process_people_only(sheet: Sheet, job: AsyncJob, batch: Batch, config: Config) -> int:
136+
processed = 0
137+
138+
for i, row in enumerate(sheet, 1):
139+
cleaned_row = {k.removeprefix(config["people_column_prefix"]): v for k, v in row.items()}
140+
name_column = full_name_column(cleaned_row)
141+
name = get_value(cleaned_row, name_column) if name_column else None
142+
flex_fields = clean_field_names(cleaned_row)
143+
144+
try:
145+
job.program.individuals.create(
146+
batch=batch,
147+
name=name,
148+
household_id=None, # No household association in People scenario
149+
flex_fields=flex_fields,
150+
)
151+
except Exception as e:
152+
raise SheetProcessingError(INDIVIDUAL, i) from e
153+
154+
processed += 1
155+
156+
return processed
157+
158+
132159
def image_location(image: RDIImage) -> tuple[int, int]:
133160
return image.anchor._from.row, image.anchor._from.col
134161

@@ -166,13 +193,15 @@ def read_sheets(config: Config, filepath: str, *sheet_indices: int) -> Generator
166193
sheet_images = extract_images(filepath, *sheet_indices)
167194
for (_, sheet), images in zip(sheets, sheet_images, strict=False):
168195
sheet_with_images = merge_images(sheet, images)
169-
yield filter_rows_with_household_pk(config, sheet_with_images)
196+
if config["master_detail"]:
197+
yield filter_rows_with_household_pk(config, sheet_with_images)
198+
else:
199+
yield sheet_with_images
170200

171201

172202
def import_from_rdi(job: AsyncJob) -> dict[str, int]:
173203
with atomic():
174-
config: Config = job.config
175-
rdi = job.file
204+
config = job.config
176205
batch = Batch.objects.create(
177206
name=config["batch_name"],
178207
program=job.program,
@@ -181,14 +210,12 @@ def import_from_rdi(job: AsyncJob) -> dict[str, int]:
181210
source=Batch.BatchSource.RDI,
182211
)
183212

184-
household_sheet, individual_sheet = read_sheets(config, rdi, 0, 1)
185-
186-
household_mapping = process_households(household_sheet, job, batch, config)
187-
individuals_number = process_individuals(individual_sheet, household_mapping, job, batch, config)
188-
189-
validate_beneficiaries(config, household_mapping)
213+
if config["master_detail"]:
214+
household_sheet, individual_sheet = read_sheets(config, job.file, 0, 1)
215+
household_mapping = process_households(household_sheet, job, batch, config)
216+
individuals_count = process_individuals(individual_sheet, household_mapping, job, batch, config)
217+
validate_beneficiaries(config, household_mapping)
218+
return {"household": len(household_mapping), "individual": individuals_count}
190219

191-
return {
192-
"household": len(household_mapping),
193-
"individual": individuals_number,
194-
}
220+
(people_sheet,) = read_sheets(config, job.file, 0)
221+
return {"people": process_people_only(people_sheet, job, batch, config)}

src/country_workspace/workspaces/admin/forms.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from country_workspace.workspaces.admin.cleaners.base import BaseActionForm
66
from country_workspace.workspaces.validators import ValidatableFileValidator
7+
from country_workspace.models import BeneficiaryGroup
78

89
if TYPE_CHECKING:
910
from hope_flex_fields.models import DataChecker
@@ -54,6 +55,12 @@ class ImportFileForm(forms.Form):
5455
help_text="Which column should be used as label for the household. It can use interpolation",
5556
)
5657

58+
people_column_prefix = forms.CharField(
59+
required=False,
60+
initial="pp_",
61+
help_text="People' column group prefix",
62+
)
63+
5764
first_line = forms.IntegerField(required=True, initial=0, help_text="First line to process")
5865

5966
check_before = forms.BooleanField(
@@ -63,3 +70,14 @@ class ImportFileForm(forms.Form):
6370
required=False, help_text="Fails if it finds fields which do not exists in data checker."
6471
)
6572
file = forms.FileField(validators=[ValidatableFileValidator()])
73+
74+
def __init__(self, *args: Any, beneficiary_group: BeneficiaryGroup | None = None, **kwargs: Any) -> None:
75+
super().__init__(*args, **kwargs)
76+
if beneficiary_group:
77+
exclude_fields = (
78+
("people_column_prefix",)
79+
if beneficiary_group.master_detail
80+
else ("pk_column_name", "master_column_label", "detail_column_label")
81+
)
82+
for field_name in exclude_fields:
83+
self.fields.pop(field_name, None)

src/country_workspace/workspaces/admin/program.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def import_data(self, request: HttpRequest, pk: str) -> "HttpResponse":
324324
if not (form_kobo := self.import_kobo(request, program)):
325325
return HttpResponseRedirect(reverse("workspace:workspaces_countryasyncjob_changelist"))
326326
else:
327-
form_rdi = ImportFileForm(prefix="rdi")
327+
form_rdi = ImportFileForm(prefix="rdi", beneficiary_group=program.beneficiary_group)
328328
form_aurora = ImportAuroraForm(prefix="aurora", program=program)
329329
form_kobo = ImportKoboForm(prefix="kobo", kobo_country_code=program.country_office.kobo_country_code)
330330

@@ -335,15 +335,27 @@ def import_data(self, request: HttpRequest, pk: str) -> "HttpResponse":
335335
return render(request, "workspace/program/import.html", context)
336336

337337
def import_rdi(self, request: HttpRequest, program: CountryProgram) -> "ImportFileForm | None":
338-
form = ImportFileForm(request.POST, request.FILES, prefix="rdi")
338+
form = ImportFileForm(request.POST, request.FILES, prefix="rdi", beneficiary_group=program.beneficiary_group)
339339
if form.is_valid():
340340
config: RDIConfig = {
341+
"master_detail": (
342+
master_detail := (program.beneficiary_group.master_detail if program.beneficiary_group else False)
343+
),
341344
"batch_name": form.cleaned_data["batch_name"] or batch_name_default(),
342-
"household_pk_col": form.cleaned_data["pk_column_name"],
343-
"master_column_label": form.cleaned_data["master_column_label"],
344-
"detail_column_label": form.cleaned_data["detail_column_label"],
345+
"first_line": form.cleaned_data["first_line"],
345346
"check_before": (check_before := form.cleaned_data.get("check_before", False)),
346347
"fail_if_alien": form.cleaned_data.get("fail_if_alien", False) if check_before else False,
348+
**(
349+
{
350+
"household_pk_col": form.cleaned_data.get("pk_column_name"),
351+
"master_column_label": form.cleaned_data.get("master_column_label"),
352+
"detail_column_label": form.cleaned_data.get("detail_column_label"),
353+
}
354+
if master_detail
355+
else {
356+
"people_column_prefix": form.cleaned_data.get("people_column_prefix"),
357+
}
358+
),
347359
}
348360
job: AsyncJob = AsyncJob.objects.create(
349361
description="RDI import",

0 commit comments

Comments
 (0)