Skip to content

Commit e580f93

Browse files
authored
Merge branch 'develop' into feature/routestream
2 parents 7a90029 + b886e7e commit e580f93

25 files changed

Lines changed: 381 additions & 361 deletions

File tree

.github/workflows/sdlc-version-create.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ jobs:
9292
uses: actions/checkout@v6
9393

9494
- name: Run Trivy vulnerability scanner
95-
uses: aquasecurity/trivy-action@0.33.1
95+
uses: aquasecurity/trivy-action@0.34.1
9696
with:
9797
image-ref: ${{ vars.DOCKERHUB_ORGANIZATION }}/${{ vars.DOCKERHUB_REPOSITORY }}:${{ needs.prepare-version.outputs.version }}
9898
format: 'sarif'

.pre-commit-config.yaml

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: end-of-file-fixer
1010
- id: trailing-whitespace
1111
- repo: https://github.com/python-jsonschema/check-jsonschema
12-
rev: 0.36.0
12+
rev: 0.37.0
1313
hooks:
1414
- id: check-github-workflows
1515
args: [ "--verbose" ]
@@ -19,24 +19,28 @@ repos:
1919
- id: tox-ini-fmt
2020
args: [ "-p", "lint" ]
2121
- repo: https://github.com/astral-sh/ruff-pre-commit
22-
rev: "v0.14.10"
22+
rev: "v0.15.4"
2323
hooks:
2424
- id: ruff-format
2525
- id: ruff
2626
args: [ "--fix", "--unsafe-fixes", "--exit-non-zero-on-fix" ]
2727
- repo: https://github.com/adamchainz/djade-pre-commit
28-
rev: "1.7.0"
28+
rev: "1.9.0"
2929
hooks:
3030
- id: djade
3131
args: [ --target-version, "5.1" ] # Replace with Django version
32-
32+
- repo: https://github.com/adamchainz/django-upgrade
33+
rev: "1.30.0" # replace with latest tag on GitHub
34+
hooks:
35+
- id: django-upgrade
36+
args: [--target-version, "5.2"]
3337
- repo: meta
3438
hooks:
3539
- id: check-hooks-apply
3640
- id: check-useless-excludes
3741

3842
- repo: https://github.com/saxix/pch
39-
rev: '79b0c0e'
43+
rev: '0.5'
4044
hooks:
4145

4246
- id: check-missed-migrations
@@ -63,8 +67,8 @@ repos:
6367
name: check tailwind CSS ready for production
6468
files: src/hope_live/theme/static_src/src/styles.scss
6569
args:
66-
- -o
67-
- src/hope_country_report/web/theme/static/css/styles.css
70+
- -d
71+
- src/hope_country_report/web/theme/static/css
6872

6973
- id: check-minimized
7074
name: check missing minimized javascript

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ dynamic = [ "version" ]
2121
dependencies = [
2222
"azure-devops",
2323
"celery",
24-
"django",
24+
"django>=5.1,<5.3",
2525
"django-admin-cursor-paginator",
2626
"django-admin-extra-buttons",
2727
"django-adminactions",

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ markers =
3636
python_files =test_*.py
3737
filterwarnings =
3838
ignore::DeprecationWarning
39-
ignore::django.utils.deprecation.RemovedInDjango60Warning
39+
ignore::django.utils.deprecation.RemovedInDjango61Warning
4040
ignore::coverage.exceptions.CoverageWarning

src/hope_country_report/apps/core/models.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import datetime
22
from typing import TYPE_CHECKING, Any
3-
from zoneinfo import ZoneInfo
43

54
from django.conf import settings
65
from django.contrib.auth.models import Group
@@ -156,7 +155,6 @@ def get_absolute_url(self) -> str:
156155

157156

158157
class User(TimeStampedModel, SecurityMixin, AbstractUser): # type: ignore
159-
timezone: ZoneInfo
160158
timezone = TimeZoneField(verbose_name=_("Timezone"), default="UTC")
161159
language = models.CharField(verbose_name=_("Language"), max_length=10, choices=settings.LANGUAGES, default="en")
162160
date_format = models.CharField(

src/hope_country_report/apps/hope/models/_inspect.py

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,8 +1298,8 @@ class Household(HopeModel):
12981298
unknown_sex_group_count = models.IntegerField(blank=True, null=True)
12991299
latitude = models.FloatField(blank=True, null=True)
13001300
longitude = models.FloatField(blank=True, null=True)
1301-
collision_flag = models.BooleanField(null=True)
13021301
identification_key = models.CharField(max_length=255, blank=True, null=True)
1302+
originating_id = models.CharField(unique=True, max_length=150, blank=True, null=True)
13031303

13041304
class Meta:
13051305
managed = False
@@ -1439,6 +1439,7 @@ class Individual(HopeModel):
14391439
biometric_deduplication_golden_record_results = models.JSONField(null=True)
14401440
biometric_deduplication_golden_record_status = models.CharField(max_length=50, null=True)
14411441
identification_key = models.CharField(max_length=255, blank=True, null=True)
1442+
originating_id = models.CharField(unique=True, max_length=150, blank=True, null=True)
14421443

14431444
class Meta:
14441445
managed = False
@@ -1868,7 +1869,7 @@ class Payment(HopeModel):
18681869
signature_hash = models.CharField(max_length=40, null=True)
18691870
status = models.CharField(max_length=255, null=True)
18701871
status_date = models.DateTimeField(null=True)
1871-
currency = models.CharField(max_length=4, blank=True, null=True)
1872+
currency = models.CharField(max_length=5, blank=True, null=True)
18721873
entitlement_quantity = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
18731874
entitlement_quantity_usd = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
18741875
delivered_quantity = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
@@ -1919,6 +1920,8 @@ class Payment(HopeModel):
19191920
parent_split = models.ForeignKey(
19201921
"Paymentplansplit", on_delete=models.DO_NOTHING, related_name="payment_parent_split", blank=True, null=True
19211922
)
1923+
extras = models.JSONField(null=True)
1924+
sent_to_fsp_date = models.DateTimeField(blank=True, null=True)
19221925

19231926
class Meta:
19241927
managed = False
@@ -1967,7 +1970,7 @@ class PaymentPlan(HopeModel):
19671970
total_undelivered_quantity_usd = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
19681971
status = models.CharField(max_length=50, null=True)
19691972
background_action_status = models.CharField(max_length=50, blank=True, null=True)
1970-
currency = models.CharField(max_length=4, blank=True, null=True)
1973+
currency = models.CharField(max_length=5, blank=True, null=True)
19711974
dispersion_start_date = models.DateField(blank=True, null=True)
19721975
dispersion_end_date = models.DateField(blank=True, null=True)
19731976
female_children_count = models.IntegerField(null=True)
@@ -2016,6 +2019,8 @@ class PaymentPlan(HopeModel):
20162019
flag_exclude_if_active_adjudication_ticket = models.BooleanField(null=True)
20172020
flag_exclude_if_on_sanction_list = models.BooleanField(null=True)
20182021
abort_comment = models.CharField(max_length=255, null=True)
2022+
flat_amount_value = models.DecimalField(max_digits=15, decimal_places=2, blank=True, null=True)
2023+
custom_exchange_rate = models.BooleanField(null=True)
20192024

20202025
class Meta:
20212026
managed = False
@@ -2292,6 +2297,7 @@ class Program(HopeModel):
22922297
slug = models.CharField(max_length=4, null=True)
22932298
reconciliation_window_in_days = models.IntegerField(null=True)
22942299
send_reconciliation_window_expiry_notifications = models.BooleanField(null=True)
2300+
identification_key_individual_label = models.CharField(max_length=255, blank=True, null=True)
22952301

22962302
class Meta:
22972303
managed = False
@@ -2513,34 +2519,6 @@ def __str__(self) -> str:
25132519
return str(self.name)
25142520

25152521

2516-
class DataRegistrationdataimportdatahub(HopeModel):
2517-
id = models.UUIDField(primary_key=True)
2518-
created_at = models.DateTimeField(null=True)
2519-
updated_at = models.DateTimeField(null=True)
2520-
name = models.CharField(max_length=255, null=True)
2521-
import_date = models.DateTimeField(null=True)
2522-
hct_id = models.UUIDField(blank=True, null=True)
2523-
import_done = models.CharField(max_length=15, null=True)
2524-
business_area_slug = models.CharField(max_length=250, null=True)
2525-
import_data = models.OneToOneField(
2526-
DataImportdata,
2527-
on_delete=models.DO_NOTHING,
2528-
related_name="dataregistrationdataimportdatahub_import_data",
2529-
blank=True,
2530-
null=True,
2531-
)
2532-
2533-
class Meta:
2534-
managed = False
2535-
db_table = "registration_data_registrationdataimportdatahub"
2536-
2537-
class Tenant:
2538-
tenant_filter_field: str = "__all__"
2539-
2540-
def __str__(self) -> str:
2541-
return str(self.name)
2542-
2543-
25442522
class ListSanctionlist(HopeModel):
25452523
id = models.BigAutoField(primary_key=True)
25462524
created_at = models.DateTimeField(null=True)

src/hope_country_report/apps/power_query/admin.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,20 @@ def get_queryset(self, request):
110110
return qs.filter(country_office=state.tenant)
111111
return qs
112112

113+
@admin.display(boolean=True)
113114
def success(self, obj: Query) -> bool:
114115
return not bool(obj.error_message)
115116

116-
success.boolean = True
117-
118117
def change_view(
119118
self, request: "HttpRequest", object_id: str, form_url: str = "", extra_context: "dict[str, Any] | None" = None
120119
) -> "HttpResponse":
121120
return super().change_view(request, object_id, form_url, extra_context)
122121

123-
@button(label="Inspect", icon="search")
122+
@button(
123+
label="Inspect",
124+
icon="search",
125+
permission=lambda r, o, handler: handler.model_admin.has_queue_permission("inspect", r, o),
126+
)
124127
def celery_inspect(self, request: HttpRequest, pk: int) -> HttpResponse:
125128
self.object = self.get_object(request, pk)
126129
ctx = self.get_common_context(request, pk=pk, object=self.object)
@@ -132,8 +135,15 @@ def celery_inspect(self, request: HttpRequest, pk: int) -> HttpResponse:
132135
)
133136

134137
def has_change_permission(self, request: HttpRequest, obj: "Any|None" = None) -> bool:
138+
if request.user.is_superuser:
139+
return True
140+
if obj and obj.owner == request.user:
141+
return True
135142
return super().has_change_permission(request, obj)
136143

144+
def has_queue_permission(self, perm, request: HttpRequest, o: "Query | None") -> bool:
145+
return self.has_change_permission(request, o)
146+
137147
@button()
138148
def notification(self, request: HttpRequest, pk: str) -> HttpResponse:
139149
obj = self.get_object(request, pk)
@@ -182,10 +192,15 @@ def datasets(self, request: HttpRequest, pk: int) -> HttpResponseRedirect:
182192
except Exception as e: # pragma: no cover
183193
self.message_user(request, f"{e.__class__.__name__}: {e}", messages.ERROR)
184194

185-
@button(visible=settings.DEBUG)
195+
@button(
196+
visible=settings.DEBUG,
197+
permission=lambda r, o, handler: handler.model_admin.has_queue_permission("run", r, o),
198+
)
186199
def run(self, request: HttpRequest, pk: int) -> HttpResponse:
187200
ctx = self.get_common_context(request, pk, title="Run results")
188201
query = self.get_object(request, str(pk))
202+
if query is None:
203+
return self._get_obj_does_not_exist_redirect(request, self.opts, str(pk))
189204
results = query.execute_matrix(persist=True)
190205
self.message_user(request, "Done", messages.SUCCESS)
191206
ctx["results"] = results
@@ -452,7 +467,14 @@ def get_queryset(self, request):
452467
return qs
453468

454469
def has_change_permission(self, request: HttpRequest, obj: "Any|None" = None) -> bool:
455-
return request.user.is_superuser or bool(obj and obj.owner == request.user)
470+
if request.user.is_superuser:
471+
return True
472+
if obj and obj.owner == request.user:
473+
return True
474+
return super().has_change_permission(request, obj)
475+
476+
def has_queue_permission(self, perm, request: HttpRequest, o: "ReportConfiguration | None") -> bool:
477+
return self.has_change_permission(request, o)
456478

457479
def get_changeform_initial_data(self, request: HttpRequest) -> "dict[str, Any]":
458480
kwargs: dict[str, Any] = {"owner": request.user}
@@ -463,7 +485,11 @@ def get_changeform_initial_data(self, request: HttpRequest) -> "dict[str, Any]":
463485
kwargs["notify_to"] = [request.user]
464486
return kwargs
465487

466-
@button(label="Inspect", icon="search")
488+
@button(
489+
label="Inspect",
490+
icon="search",
491+
permission=lambda r, o, handler: handler.model_admin.has_queue_permission("inspect", r, o),
492+
)
467493
def celery_inspect(self, request: HttpRequest, pk: int) -> HttpResponse:
468494
self.object = self.get_object(request, pk)
469495
ctx = self.get_common_context(request, pk=pk, object=self.object)

src/hope_country_report/apps/power_query/migrations/0006_query_valid_query.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class Migration(migrations.Migration):
1212
migrations.AddConstraint(
1313
model_name="query",
1414
constraint=models.CheckConstraint(
15-
check=models.Q(
15+
condition=models.Q(
1616
models.Q(
1717
("code__isnull", True),
1818
("country_office__isnull", False),

src/hope_country_report/apps/power_query/models/arguments.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,12 @@ def save(
7474
) -> None:
7575
if not self.code:
7676
self.code = slugify(self.name)
77-
super().save(force_insert, force_update, using, update_fields)
77+
super().save(
78+
force_insert=force_insert,
79+
force_update=force_update,
80+
using=using,
81+
update_fields=update_fields,
82+
)
7883

7984
def refresh(self) -> None:
8085
if self.source:

src/hope_country_report/apps/power_query/models/formatter.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
from django.db import models
77
from strategy_field.fields import StrategyField
88
from strategy_field.utils import fqn
9-
109
from ...core.models import CountryOffice
11-
from ..processors import TYPE_DETAIL, TYPE_LIST, TYPES, ProcessorStrategy, ToHTML, mimetype_map, registry
10+
from ..processors import TYPE_DETAIL, TYPE_LIST, TYPES, ToHTML, mimetype_map, registry
1211
from ._base import MIMETYPES
1312
from .report_template import ReportTemplate
1413

@@ -30,16 +29,14 @@ def batched(iterable, n):
3029

3130

3231
class Formatter(models.Model):
33-
processor: "ProcessorStrategy"
34-
32+
processor = StrategyField(registry=registry, default=fqn(ToHTML))
3533
country_office = models.ForeignKey(CountryOffice, on_delete=models.CASCADE, blank=True, null=True)
3634

3735
name = models.CharField(max_length=255, unique=True)
3836
code = models.TextField(blank=True, null=True)
3937
template = models.ForeignKey(ReportTemplate, on_delete=models.CASCADE, blank=True, null=True)
4038

4139
file_suffix = models.CharField(max_length=10, choices=MIMETYPES)
42-
processor = StrategyField(registry=registry, default=fqn(ToHTML))
4340
type = models.IntegerField(choices=TYPES, default=TYPE_LIST)
4441

4542
compress = models.BooleanField(default=False, blank=True)
@@ -88,4 +85,9 @@ def save(
8885
) -> None:
8986
if not self.file_suffix:
9087
self.file_suffix = self.processor.file_suffix
91-
super().save(force_insert, force_update, using, update_fields)
88+
super().save(
89+
force_insert=force_insert,
90+
force_update=force_update,
91+
using=using,
92+
update_fields=update_fields,
93+
)

0 commit comments

Comments
 (0)