Skip to content

Commit 75aa560

Browse files
authored
Merge pull request #47 from unicef/feature/storage
chg ! use media storage for files instead of default
2 parents 0ca8e53 + 37c62f7 commit 75aa560

File tree

7 files changed

+64
-91
lines changed

7 files changed

+64
-91
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.3 on 2025-03-13 09:04
2+
3+
from django.db import migrations, models
4+
from country_workspace.storages import MEDIA_STORAGE
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("country_workspace", "0004_kobosubmission_delete_koboasset"),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name="asyncjob",
15+
name="file",
16+
field=models.FileField(blank=True, null=True, storage=MEDIA_STORAGE, upload_to="updates"),
17+
),
18+
]

src/country_workspace/models/jobs.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from django.utils.module_loading import import_string
77
from django_celery_boost.models import CeleryTaskModel
88

9+
from country_workspace.storages import MEDIA_STORAGE
10+
911

1012
class AsyncJob(CeleryTaskModel, models.Model):
1113
class JobType(models.TextChoices):
@@ -16,7 +18,7 @@ class JobType(models.TextChoices):
1618
type = models.CharField(max_length=50, choices=JobType.choices)
1719
program = models.ForeignKey("Program", related_name="jobs", on_delete=models.CASCADE, null=True, blank=True)
1820
batch = models.ForeignKey("Batch", related_name="jobs", on_delete=models.CASCADE, null=True, blank=True)
19-
file = models.FileField(upload_to="updates", null=True, blank=True)
21+
file = models.FileField(storage=MEDIA_STORAGE, upload_to="updates", null=True, blank=True)
2022
config = models.JSONField(default=dict, blank=True)
2123
action = models.CharField(max_length=500, blank=True, null=True)
2224
description = models.CharField(max_length=255, blank=True, null=True)

src/country_workspace/storages.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from storages.backends.azure_storage import AzureStorage
2+
from django.conf import settings
3+
4+
MEDIA_STORAGE: AzureStorage = AzureStorage(**settings.STORAGES.get("media").get("OPTIONS"))

src/country_workspace/workspaces/admin/cleaners/bulk_update.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from django import forms
66
from django.apps import apps
77
from django.core.exceptions import ObjectDoesNotExist
8-
from django.core.files.storage import default_storage
98
from xlsxwriter import Workbook
109

1110
from constance import config as constance_config
@@ -15,6 +14,7 @@
1514
from hope_smart_import.readers import open_xls
1615

1716
from country_workspace.models import AsyncJob, Program
17+
from country_workspace.storages import MEDIA_STORAGE
1818

1919
if TYPE_CHECKING:
2020
from django.db.models import QuerySet
@@ -174,7 +174,7 @@ def bulk_update_export_template(job: AsyncJob) -> bytes:
174174
queryset = model.objects.filter(pk__in=job.config["pks"])
175175
filename = "bulk_update_export_template/%s/%s/%s.xlsx" % (job.program.pk, job.owner.pk, job.config["model_name"])
176176
out, __ = create_xls_importer(queryset, job.program, job.config["columns"])
177-
path = default_storage.save(filename, out)
177+
path = MEDIA_STORAGE.save(filename, out)
178178
job.file = path
179179
job.save()
180180
return path

tests/workspace/actions/test_ws_bulk.py

Lines changed: 37 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.urls import reverse
99
from testutils.factories import FlexFieldFactory
1010
from testutils.utils import select_office
11-
from webtest import Checkbox, Upload
11+
from unittest import mock
1212

1313
from country_workspace.state import state
1414
from country_workspace.workspaces.admin.cleaners.bulk_update import TYPES, create_xls_importer
@@ -20,6 +20,7 @@
2020
from django_webtest import DjangoTestApp
2121
from django_webtest.pytest_plugin import MixinWithInstanceVariables
2222
from pytest_django.fixtures import SettingsWrapper
23+
from webtest import Checkbox
2324

2425
from country_workspace.models import AsyncJob
2526
from country_workspace.workspaces.models import CountryHousehold
@@ -38,6 +39,18 @@ def app(django_app_factory: "MixinWithInstanceVariables") -> "DjangoTestApp":
3839
return django_app
3940

4041

42+
@pytest.fixture
43+
def mock_media_storage():
44+
return mock.MagicMock(
45+
**{
46+
"save.return_value": "mocked/path/to/file",
47+
"exists.return_value": False,
48+
"get_available_name.return_value": "mocked/path/to/file",
49+
"open.return_value": io.BytesIO(b"mocked file content"),
50+
}
51+
)
52+
53+
4154
@pytest.fixture(scope="session")
4255
def celery_config():
4356
return {"broker_url": os.environ["CELERY_BROKER_URL"], "result_backend": os.environ["CELERY_BROKER_URL"]}
@@ -142,56 +155,30 @@ def test_create_xls_importer(household: "CountryHousehold", force_migrated_recor
142155

143156

144157
def test_bulk_update_export(
145-
app: "DjangoTestApp", force_migrated_records, settings: "SettingsWrapper", household: "CountryHousehold"
146-
) -> None:
147-
url = reverse("workspace:workspaces_countryindividual_changelist")
148-
settings.CELERY_TASK_ALWAYS_EAGER = True
149-
selected_fields = stub.header_add["ind"]
150-
with select_office(app, household.country_office, household.program):
151-
res = app.get(url)
152-
form = res.forms["changelist-form"]
153-
form["action"] = "bulk_update_export"
154-
form.set("_selected_action", True, index=0)
155-
res = form.submit()
156-
157-
form = res.forms["bulk-update-form"]
158-
for i in range(len(form.fields.get("fields"))):
159-
target: Checkbox = form.fields.get("fields")[i]
160-
if target._value in selected_fields:
161-
target.checked = True
162-
res = form.submit("_export")
163-
164-
assert res.status_code == 302
165-
job: AsyncJob = household.program.jobs.first()
166-
job.queue()
167-
168-
169-
def test_bulk_update_import(
170158
app: "DjangoTestApp",
171159
force_migrated_records,
172160
settings: "SettingsWrapper",
173-
data: tuple[io.BytesIO, "CountryHousehold", str],
161+
household: "CountryHousehold",
162+
mock_media_storage,
174163
) -> None:
175-
buff, household, target = data
176-
url = reverse("workspace:workspaces_countryprogram_change", args=[household.program.pk])
177-
settings.CELERY_TASK_ALWAYS_EAGER = True
178-
179-
with select_office(app, household.country_office, household.program):
180-
res = app.get(url)
181-
res = res.click("Update Records")
182-
res.forms["bulk-update-form"]["description"] = f"Bulk update from {target}"
183-
res.forms["bulk-update-form"]["target"] = target
184-
res.forms["bulk-update-form"]["file"] = Upload(f"{target}.xlsx", buff.read())
185-
res = res.forms["bulk-update-form"].submit("_import")
186-
household.refresh_from_db()
187-
job: AsyncJob = household.program.jobs.first()
188-
189-
assert res.status_code == 302
190-
assert job
191-
192-
if target == "hh":
193-
admin1_v = f"admin1_{stub.header_add['hh'].index('admin1') + 2}"
194-
assert household.flex_fields.get("admin1") == admin1_v
195-
elif target == "ind":
196-
given_name = f"given_name_{stub.header_add['ind'].index('given_name') + 2}"
197-
assert household.members.filter(flex_fields__given_name=given_name).exists()
164+
with mock.patch("country_workspace.workspaces.admin.cleaners.bulk_update.MEDIA_STORAGE", mock_media_storage):
165+
url = reverse("workspace:workspaces_countryindividual_changelist")
166+
settings.CELERY_TASK_ALWAYS_EAGER = True
167+
selected_fields = stub.header_add["ind"]
168+
with select_office(app, household.country_office, household.program):
169+
res = app.get(url)
170+
form = res.forms["changelist-form"]
171+
form["action"] = "bulk_update_export"
172+
form.set("_selected_action", True, index=0)
173+
res = form.submit()
174+
175+
form = res.forms["bulk-update-form"]
176+
for i in range(len(form.fields.get("fields"))):
177+
target: Checkbox = form.fields.get("fields")[i]
178+
if target._value in selected_fields:
179+
target.checked = True
180+
res = form.submit("_export")
181+
182+
assert res.status_code == 302
183+
job: AsyncJob = household.program.jobs.first()
184+
job.queue()

tests/workspace/test_ws_import.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
from pathlib import Path
21
from typing import TYPE_CHECKING, Any
32
import responses
43
import pytest
54
import re
65
from django.urls import reverse
7-
from webtest import Upload
86

97
from country_workspace.state import state
108
from constance import config
@@ -56,31 +54,6 @@ def app(django_app_factory: "MixinWithInstanceVariables") -> "DjangoTestApp":
5654
return django_app
5755

5856

59-
def test_import_data_rdi(force_migrated_records, app, program):
60-
# NOTE: This test is linked to the content of `data/rdi_one.xlsx`
61-
res = app.get("/").follow()
62-
res.forms["select-tenant"]["tenant"] = program.country_office.pk
63-
res.forms["select-tenant"].submit()
64-
65-
url = reverse("workspace:workspaces_countryprogram_import_data", args=[program.pk])
66-
data = (Path(__file__).parent.parent / "data/rdi_one.xlsx").read_bytes()
67-
68-
res = app.get(url)
69-
70-
res.forms["import-file"]["_selected_tab"] = "rdi"
71-
res.forms["import-file"]["rdi-file"] = Upload("rdi_one.xlsx", data)
72-
res.forms["import-file"]["rdi-detail_column_label"] = "full_name"
73-
res = res.forms["import-file"].submit()
74-
assert res.status_code == 302
75-
assert program.households.count() == 1
76-
assert program.individuals.count() == 5
77-
78-
hh: "CountryHousehold" = program.households.first()
79-
assert hh.members.count() == 5
80-
assert (head := hh.heads().first())
81-
assert head.name == "Edward Jeffrey Rogers"
82-
83-
8457
@pytest.mark.django_db(transaction=True)
8558
@pytest.mark.parametrize(
8659
("stub_data", "error_expected", "error_message"),

tests/workspace/test_ws_individual.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,6 @@ def app(django_app_factory: "MixinWithInstanceVariables") -> "DjangoTestApp":
5656
return django_app
5757

5858

59-
def test_ind_changelist(app: "DjangoTestApp", individual: "CountryIndividual") -> None:
60-
url = reverse("workspace:workspaces_countryindividual_changelist")
61-
with select_office(app, individual.country_office, individual.program):
62-
res = app.get(url)
63-
assert res.status_code == 200, res.location
64-
assert f"Add {individual._meta.verbose_name}" not in res.text
65-
# filter by program
66-
res = app.get(url)
67-
assert res.status_code == 200, res.location
68-
69-
7059
def test_ind_change(app: "DjangoTestApp", individual: "CountryIndividual") -> None:
7160
url = reverse("workspace:workspaces_countryindividual_changelist")
7261
with select_office(app, individual.country_office, individual.program):

0 commit comments

Comments
 (0)