diff --git a/src/country_workspace/config/fragments/constance.py b/src/country_workspace/config/fragments/constance.py
index 0ddac09..8b0c45d 100644
--- a/src/country_workspace/config/fragments/constance.py
+++ b/src/country_workspace/config/fragments/constance.py
@@ -3,22 +3,36 @@
CONSTANCE_BACKEND = "constance.backends.database.DatabaseBackend"
-# CONSTANCE_CONFIG_FIELDSETS = {
-# "User settings": {
-# "fields": ("NEW_USER_IS_STAFF", "NEW_USER_DEFAULT_GROUP"),
-# "collapse": False,
-# }
-# }
-
CONSTANCE_ADDITIONAL_FIELDS = {
"email": [
"django.forms.EmailField",
{},
],
"group_select": [
- "country_workspace.utils.constance.GroupSelect",
+ "country_workspace.utils.constance.GroupChoiceField",
{"initial": NEW_USER_DEFAULT_GROUP},
],
+ "read_only_text": [
+ "django.forms.fields.CharField",
+ {
+ "required": False,
+ "widget": "country_workspace.utils.constance.ObfuscatedInput",
+ },
+ ],
+ "write_only_text": [
+ "django.forms.fields.CharField",
+ {
+ "required": False,
+ "widget": "country_workspace.utils.constance.WriteOnlyTextarea",
+ },
+ ],
+ "write_only_input": [
+ "django.forms.fields.CharField",
+ {
+ "required": False,
+ "widget": "country_workspace.utils.constance.WriteOnlyInput",
+ },
+ ],
}
CONSTANCE_CONFIG = {
@@ -28,5 +42,10 @@
"Group to assign to any new user",
"group_select",
),
- "HOPE_API_URL": ("https://hope.unicef.org/api/rest/", "", str),
+ "HOPE_API_URL": ("https://hope.unicef.org/api/rest/", "HOPE API Server address", str),
+ "HOPE_API_TOKEN": ("", "HOPE API Access Token", "write_only_input"),
+ "AURORA_API_URL": ("https://register.unicef.org/api/", "Aurora API Server address", str),
+ "AURORA_API_TOKEN": ("", "Aurora API Access Token", "write_only_input"),
+ "KOBO_API_URL": ("", "Kobo API Server address", str),
+ "KOBO_API_TOKEN": ("", "Kobo API Access Token", "write_only_input"),
}
diff --git a/src/country_workspace/utils/constance.py b/src/country_workspace/utils/constance.py
index 996c504..68de33d 100644
--- a/src/country_workspace/utils/constance.py
+++ b/src/country_workspace/utils/constance.py
@@ -1,12 +1,47 @@
import logging
from typing import Any
-from django.forms import ChoiceField
+from django.forms import ChoiceField, HiddenInput, Textarea, TextInput
+from django.template import Context, Template
+from django.utils.safestring import mark_safe
+
+from constance import config
logger = logging.getLogger(__name__)
-class GroupSelect(ChoiceField):
+class ObfuscatedInput(HiddenInput):
+
+ def render(self, name, value, attrs=None, renderer=None):
+ context = self.get_context(name, value, attrs)
+ context["value"] = str(value)
+ context["label"] = "Set" if value else "Not Set"
+
+ tpl = Template('{{ label }}')
+ return mark_safe(tpl.render(Context(context))) # nosec B308 B703
+
+
+class WriteOnlyWidget:
+ def format_value(self, value):
+ value = "***"
+ return super().format_value(value)
+
+ def value_from_datadict(self, data, files, name):
+ value = data.get(name)
+ if value == "***":
+ return getattr(config, name)
+ return value
+
+
+class WriteOnlyTextarea(WriteOnlyWidget, Textarea):
+ pass
+
+
+class WriteOnlyInput(WriteOnlyWidget, TextInput):
+ pass
+
+
+class GroupChoiceField(ChoiceField):
def __init__(self, **kwargs: Any) -> None:
from django.contrib.auth.models import Group
diff --git a/src/country_workspace/web/templates/workspace/includes/program_title.html b/src/country_workspace/web/templates/workspace/includes/program_title.html
index 33df093..519db17 100644
--- a/src/country_workspace/web/templates/workspace/includes/program_title.html
+++ b/src/country_workspace/web/templates/workspace/includes/program_title.html
@@ -21,5 +21,7 @@
{% endif %}
{% endif %}
+ {% else %}
+
{% endif %}
diff --git a/src/country_workspace/workspaces/admin/__init__.py b/src/country_workspace/workspaces/admin/__init__.py
index 9cc9ecb..2ebd84f 100644
--- a/src/country_workspace/workspaces/admin/__init__.py
+++ b/src/country_workspace/workspaces/admin/__init__.py
@@ -1,4 +1,5 @@
from .batch import CountryBatchAdmin # noqa
from .household import CountryHouseholdAdmin # noqa
from .individual import CountryIndividualAdmin # noqa
+from .job import CountryJobAdmin # noqa
from .program import CountryProgramAdmin # noqa
diff --git a/src/country_workspace/workspaces/admin/batch.py b/src/country_workspace/workspaces/admin/batch.py
index 996e97a..a07e03d 100644
--- a/src/country_workspace/workspaces/admin/batch.py
+++ b/src/country_workspace/workspaces/admin/batch.py
@@ -1,5 +1,6 @@
from typing import TYPE_CHECKING
+from django.contrib.admin import register
from django.db.models import QuerySet
from django.http import HttpRequest
from django.urls import reverse
@@ -11,6 +12,7 @@
from ..filters import CWLinkedAutoCompleteFilter
from ..models import CountryBatch
from ..options import WorkspaceModelAdmin
+from ..sites import workspace
from .hh_ind import SelectedProgramMixin
if TYPE_CHECKING:
@@ -26,6 +28,7 @@ def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet:
return queryset
+@register(CountryBatch, site=workspace)
class CountryBatchAdmin(SelectedProgramMixin, WorkspaceModelAdmin):
list_display = ["name", "program", "country_office"]
search_fields = ("label",)
diff --git a/src/country_workspace/workspaces/admin/household.py b/src/country_workspace/workspaces/admin/household.py
index 02bfc1a..29c9437 100644
--- a/src/country_workspace/workspaces/admin/household.py
+++ b/src/country_workspace/workspaces/admin/household.py
@@ -11,7 +11,13 @@
if TYPE_CHECKING:
from ..models import CountryProgram
+from django.contrib.admin import register
+from ..models import CountryHousehold
+from ..sites import workspace
+
+
+@register(CountryHousehold, site=workspace)
class CountryHouseholdAdmin(BeneficiaryBaseAdmin):
list_display = ["name", "batch"]
search_fields = ("name",)
diff --git a/src/country_workspace/workspaces/admin/individual.py b/src/country_workspace/workspaces/admin/individual.py
index 0bc8077..39f4ea5 100644
--- a/src/country_workspace/workspaces/admin/individual.py
+++ b/src/country_workspace/workspaces/admin/individual.py
@@ -1,15 +1,17 @@
from typing import Any, Optional
-from django.contrib.admin import AdminSite
+from django.contrib.admin import AdminSite, register
from django.db.models import Model
from django.http import HttpRequest
from ...state import state
from ..filters import HouseholdFilter, ProgramFilter
from ..models import CountryHousehold, CountryIndividual, CountryProgram
+from ..sites import workspace
from .hh_ind import BeneficiaryBaseAdmin
+@register(CountryIndividual, site=workspace)
class CountryIndividualAdmin(BeneficiaryBaseAdmin):
list_display = [
"name",
@@ -30,7 +32,7 @@ class CountryIndividualAdmin(BeneficiaryBaseAdmin):
change_form_template = "workspace/individual/change_form.html"
ordering = ("name",)
- def __init__(self, model: Model, admin_site: AdminSite):
+ def __init__(self, model: Model, admin_site: "AdminSite"):
self._selected_household = None
super().__init__(model, admin_site)
diff --git a/src/country_workspace/workspaces/admin/job.py b/src/country_workspace/workspaces/admin/job.py
new file mode 100644
index 0000000..7a2898e
--- /dev/null
+++ b/src/country_workspace/workspaces/admin/job.py
@@ -0,0 +1,21 @@
+from django.contrib.admin import register
+
+from ..models import CountryJob
+from ..options import WorkspaceModelAdmin
+from ..sites import workspace
+
+
+@register(CountryJob, site=workspace)
+class CountryJobAdmin(WorkspaceModelAdmin):
+ list_display = (
+ "name",
+ "sector",
+ "status",
+ "active",
+ )
+
+ def has_add_permission(self, request):
+ return False
+
+
+# workspace.register(CountryJob, CountryJobAdmin)
diff --git a/src/country_workspace/workspaces/admin/program.py b/src/country_workspace/workspaces/admin/program.py
index 7ddac45..082b26d 100644
--- a/src/country_workspace/workspaces/admin/program.py
+++ b/src/country_workspace/workspaces/admin/program.py
@@ -1,6 +1,7 @@
from typing import Any
from django import forms
+from django.contrib.admin import register
from django.db.models import QuerySet
from django.db.transaction import atomic
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
@@ -21,6 +22,7 @@
from ...sync.office import sync_programs
from ..models import CountryProgram
from ..options import WorkspaceModelAdmin
+from ..sites import workspace
from .forms import ImportFileForm
@@ -55,6 +57,7 @@ def clean_field_name(v):
return v.replace("_h_c", "").replace("_h_f", "").replace("_i_c", "").replace("_i_f", "").lower()
+@register(CountryProgram, site=workspace)
class CountryProgramAdmin(WorkspaceModelAdmin):
list_display = (
"name",
diff --git a/src/country_workspace/workspaces/apps.py b/src/country_workspace/workspaces/apps.py
index 71625bb..d43fefb 100644
--- a/src/country_workspace/workspaces/apps.py
+++ b/src/country_workspace/workspaces/apps.py
@@ -8,13 +8,8 @@
class Config(AppConfig):
name = __name__.rpartition(".")[0]
- def ready(self) -> None:
- from country_workspace.workspaces import models
-
- from . import admin
- from .sites import workspace
-
- workspace.register(models.CountryBatch, admin.CountryBatchAdmin)
- workspace.register(models.CountryHousehold, admin.CountryHouseholdAdmin)
- workspace.register(models.CountryIndividual, admin.CountryIndividualAdmin)
- workspace.register(models.CountryProgram, admin.CountryProgramAdmin)
+ # def ready(self) -> None:
+ # from country_workspace.workspaces import models
+ #
+ # from . import admin
+ # from .sites import workspace
diff --git a/src/country_workspace/workspaces/models.py b/src/country_workspace/workspaces/models.py
index 115aaef..a0633ac 100644
--- a/src/country_workspace/workspaces/models.py
+++ b/src/country_workspace/workspaces/models.py
@@ -5,7 +5,7 @@
from hope_flex_fields.models import DataChecker
-from country_workspace.models import Batch, Household, Individual, Office, Program
+from country_workspace.models import AsyncJob, Batch, Household, Individual, Office, Program
__all__ = ["CountryProgram", "CountryHousehold", "CountryIndividual", "CountryBatch"]
@@ -45,5 +45,10 @@ class Meta:
class CountryChecker(DataChecker):
country_office = models.ForeignKey(Office, on_delete=models.CASCADE)
+
+class CountryJob(AsyncJob):
+ class Meta:
+ proxy = True
+
# class Meta:
# app_label = "workspaces"
diff --git a/tests/utils/test_utils_constance.py b/tests/utils/test_utils_constance.py
new file mode 100644
index 0000000..a9f1fbf
--- /dev/null
+++ b/tests/utils/test_utils_constance.py
@@ -0,0 +1,31 @@
+from constance.test import override_config
+
+from country_workspace.utils.constance import GroupChoiceField, ObfuscatedInput, WriteOnlyInput, WriteOnlyTextarea
+
+
+def test_utils_groupchoicefield():
+ field = GroupChoiceField()
+ assert field
+
+
+# LdapDNField
+
+
+# ObfuscatedInput
+def test_obfuscatedinput():
+ field = ObfuscatedInput()
+ assert field.render("name", "value") == 'Set'
+
+
+# WriteOnlyTextarea
+def test_writeonlytextarea():
+ field = WriteOnlyTextarea()
+ assert field.render("name", "value") == ''
+
+
+@override_config(HOPE_API_TOKEN="abc")
+def test_writeonlyinput():
+ field = WriteOnlyInput()
+ assert field.render("name", "value")
+ assert field.value_from_datadict({"HOPE_API_TOKEN": "***"}, {}, "HOPE_API_TOKEN") == "abc"
+ assert field.value_from_datadict({"HOPE_API_TOKEN": "123"}, {}, "HOPE_API_TOKEN") == "123"
diff --git a/tests/test_flags.py b/tests/utils/test_utils_flags.py
similarity index 100%
rename from tests/test_flags.py
rename to tests/utils/test_utils_flags.py
diff --git a/tests/test_utils_http.py b/tests/utils/test_utils_http.py
similarity index 100%
rename from tests/test_utils_http.py
rename to tests/utils/test_utils_http.py
diff --git a/tests/workspace/test_ws_autocmplete.py b/tests/workspace/test_ws_autocmplete.py
new file mode 100644
index 0000000..8691357
--- /dev/null
+++ b/tests/workspace/test_ws_autocmplete.py
@@ -0,0 +1,35 @@
+from django.urls import reverse
+
+import pytest
+from django_webtest import DjangoTestApp
+from django_webtest.pytest_plugin import MixinWithInstanceVariables
+from responses import RequestsMock
+from testutils.utils import select_office
+
+
+@pytest.fixture()
+def program():
+ from testutils.factories import CountryProgramFactory
+
+ return CountryProgramFactory()
+
+
+@pytest.fixture()
+def app(django_app_factory: "MixinWithInstanceVariables", mocked_responses: "RequestsMock") -> "DjangoTestApp":
+ from testutils.factories.user import SuperUserFactory
+
+ django_app = django_app_factory(csrf_checks=False)
+ admin_user = SuperUserFactory(username="superuser")
+ django_app.set_user(admin_user)
+ django_app._user = admin_user
+ return django_app
+
+
+def test_project_autocomplete(app: DjangoTestApp, program) -> None:
+ url = reverse("workspace:autocomplete")
+ with select_office(app, program.country_office):
+ res = app.get(url, expect_errors=True)
+ assert res.status_code == 403
+
+ res = app.get(f"{url}?app_label=country_workspace&model_name=batch&field_name=program")
+ assert res.status_code == 200