diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b9d3325..c20fa32 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -171,15 +171,16 @@ jobs:
redis1:
image: redis:7.4.0
ports:
- - 26379:6379
+ - 5379:6379
db1:
image: postgres:14
env:
+ POSTGRES_HOST: db1
POSTGRES_DATABASE: country_workspace
POSTGRES_PASSWORD: postgres
POSTGRES_USERNAME: postgres
ports:
- - 25432:5432
+ - 4432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
@@ -188,9 +189,9 @@ jobs:
env:
DOCKER_DEFAULT_PLATFORM: linux/amd64
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
- DATABASE_URL: postgres://postgres:postgres@localhost:25432/country_workspace
- CELERY_BROKER_URL: redis://localhost:26379/1
- CACHE_URL: redis://localhost:26379/2
+ DATABASE_URL: postgres://postgres:postgres@localhost:4432/country_workspace
+ CELERY_BROKER_URL: redis://localhost:5379/1
+ CACHE_URL: redis://localhost:5379/2
DOCKER_BUILDKIT: 1
steps:
- name: Checkout code
@@ -206,27 +207,26 @@ jobs:
--target dist \
--cache-from "type=gha" \
--cache-to "type=gha,mode=max" \
- --build-arg "VERSION=${{needs.test.outputs.commit}}" \
--build-arg "GIT_SHA=${{needs.test.outputs.commit}}" \
--build-arg "BUILD_DATE=${{needs.test.outputs.build_date}}" \
--build-arg "BRANCH=${{needs.test.outputs.branch}}" \
-t ${{needs.test.outputs.image}} \
-f docker/Dockerfile .
-
- - name: Docker Integrity Check
- run: |
- docker run --rm \
- --network host \
- -e DATABASE_URL=${DATABASE_URL} \
- -e CELERY_BROKER_URL=${CELERY_BROKER_URL} \
- -e CACHE_URL=${CACHE_URL} \
- -e SECRET_KEY=super-secret-key-just-for-testing \
- -e HOPE_API_URL="https://dev-hope.unitst.org/api/rest/" \
- -e HOPE_API_TOKEN=${{ secrets.HOPE_API_TOKEN }} \
- -e AURORA_API_URL="https://uni-hope-ukr-sr-dev.unitst.org/api/" \
- -e AURORA_API_TOKEN=${{ secrets.AURORA_API_TOKEN }} \
- -t ${{needs.test.outputs.image}} \
- django-admin upgrade
+#
+# - name: Docker Integrity Check
+# run: |
+# docker run --rm \
+# --network host \
+# -e DATABASE_URL=${DATABASE_URL} \
+# -e CELERY_BROKER_URL=${CELERY_BROKER_URL} \
+# -e CACHE_URL=${CACHE_URL} \
+# -e SECRET_KEY=super-secret-key-just-for-testing \
+# -e HOPE_API_URL="https://dev-hope.unitst.org/api/rest/" \
+# -e HOPE_API_TOKEN=${{ secrets.HOPE_API_TOKEN }} \
+# -e AURORA_API_URL="https://uni-hope-ukr-sr-dev.unitst.org/api/" \
+# -e AURORA_API_TOKEN=${{ secrets.AURORA_API_TOKEN }} \
+# -t ${{needs.test.outputs.image}} \
+# django-admin upgrade
- name: Publish images
run: |
diff --git a/docker/Dockerfile b/docker/Dockerfile
index bbed587..79a5a4d 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -62,7 +62,7 @@ RUN pip install uv uwsgi
# ------- tests -------
FROM builder AS tests
ARG BUILD_DATE
-ARG VERSION
+ARG GIT_SHA
LABEL distro="tests"
LABEL org.opencontainers.image.created="$BUILD_DATE"
@@ -111,7 +111,6 @@ RUN --mount=type=cache,target=/root/.uv-cache \
# ------- dist -------
FROM base_os AS dist
ARG BUILD_DATE
-ARG VERSION
ARG GIT_SHA
ARG BRANCH
@@ -120,7 +119,6 @@ ENV PATH=/app/.venv/bin:/usr/local/bin/:/usr/bin:/bin \
GIT_SHA=$GIT_SHA \
VERSION=$VERSION \
BRANCH=$BRANCH \
- DJANGO_SETTINGS_MODULE=country_workspace.config.settings \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
STATIC_URL="/static/" \
@@ -150,7 +148,7 @@ RUN --mount=type=cache,target=/root/.uv-cache \
--mount=type=bind,source=MANIFEST.in,target=/app/MANIFEST.in \
--mount=type=bind,source=README.md,target=/app/README.md \
--mount=type=bind,source=./src/country_workspace,target=/app/src/country_workspace \
- uv --cache-dir=/root/.uv-cache pip install --no-deps .
+ uv --cache-dir=/root/.uv-cache pip install --link-mode=copy --no-deps .
EXPOSE 8000
diff --git a/docker/bin/docker-entrypoint.sh b/docker/bin/docker-entrypoint.sh
index dda3791..dcad279 100755
--- a/docker/bin/docker-entrypoint.sh
+++ b/docker/bin/docker-entrypoint.sh
@@ -4,7 +4,7 @@
export MEDIA_ROOT="${MEDIA_ROOT:-/var/run/app/media}"
export STATIC_ROOT="${STATIC_ROOT:-/var/run/app/static}"
export UWSGI_PROCESSES="${UWSGI_PROCESSES:-"4"}"
-export DJANGO_SETTINGS_MODULE="${DJANGO_SETTINGS_MODULE:-country_workspace.config.settings}"
+export DJANGO_SETTINGS_MODULE="country_workspace.config.settings"
case "$1" in
run)
diff --git a/docs/src/license.md b/docs/src/license.md
new file mode 100644
index 0000000..acd6294
--- /dev/null
+++ b/docs/src/license.md
@@ -0,0 +1,4 @@
+# License
+
+
+--8<-- "LICENSE.md"
diff --git a/pyproject.toml b/pyproject.toml
index 145ff0f..316bf4b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -16,7 +16,7 @@ dependencies = [
"deprecation>=2.1.0",
"dictdiffer>=0.9.0",
"django-adminactions>=2.3.0",
- "django-adminfilters==2.5.1",
+ "django-adminfilters>=2.5.2",
"django-cacheops>=7.1",
"django-celery-beat>=2.6.0",
"django-celery-boost>=0.5.0",
diff --git a/src/country_workspace/admin/__init__.py b/src/country_workspace/admin/__init__.py
index 0fed349..16d872b 100644
--- a/src/country_workspace/admin/__init__.py
+++ b/src/country_workspace/admin/__init__.py
@@ -5,13 +5,13 @@
from smart_admin.console import panel_migrations, panel_redis, panel_sentry, panel_sysinfo
from smart_admin.smart_auth.admin import ContentTypeAdmin, PermissionAdmin
+from ..cache.smart_panel import panel_cache
from .batch import BatchAdmin # noqa
from .household import HouseholdAdmin # noqa
from .individual import IndividualAdmin # noqa
from .job import AsyncJobAdmin # noqa
from .locations import AreaAdmin, AreaTypeAdmin, CountryAdmin # noqa
from .office import OfficeAdmin # noqa
-from .panels.cache import panel_cache
from .program import ProgramAdmin # noqa
from .role import UserRoleAdmin # noqa
from .sync import SyncLog # noqa
diff --git a/src/country_workspace/admin/individual.py b/src/country_workspace/admin/individual.py
index d5a8c92..69acd46 100644
--- a/src/country_workspace/admin/individual.py
+++ b/src/country_workspace/admin/individual.py
@@ -21,7 +21,10 @@ class IndividualAdmin(BaseModelAdmin):
("batch", LinkedAutoCompleteFilter.factory(parent="batch__program")),
IsValidFilter,
)
- autocomplete_fields = ("batch",)
+ autocomplete_fields = (
+ "batch",
+ "household",
+ )
@link(change_list=True, change_form=False)
def view_in_workspace(self, btn: "LinkButton") -> None:
diff --git a/src/country_workspace/admin/job.py b/src/country_workspace/admin/job.py
index 0e389d9..0b95326 100644
--- a/src/country_workspace/admin/job.py
+++ b/src/country_workspace/admin/job.py
@@ -14,7 +14,7 @@
@admin.register(AsyncJob)
class AsyncJobAdmin(CeleryTaskModelAdmin, BaseModelAdmin):
list_display = ("program", "type", "verbose_status", "owner")
- autocomplete_fields = ("program", "owner")
+ autocomplete_fields = ("program", "owner", "batch", "content_type")
list_filter = (
("program__country_office", LinkedAutoCompleteFilter.factory(parent=None)),
("program", LinkedAutoCompleteFilter.factory(parent="program__country_office")),
diff --git a/src/country_workspace/admin/program.py b/src/country_workspace/admin/program.py
index d7a652c..d2009e4 100644
--- a/src/country_workspace/admin/program.py
+++ b/src/country_workspace/admin/program.py
@@ -22,6 +22,7 @@ class ProgramAdmin(BaseModelAdmin):
search_fields = ("name",)
list_filter = (("country_office", AutoCompleteFilter), "status", "active", "sector")
ordering = ("name",)
+ autocomplete_fields = ("country_office",)
@button()
def invalidate_cache(self, request: HttpRequest, pk: str) -> None:
diff --git a/src/country_workspace/cache/manager.py b/src/country_workspace/cache/manager.py
index 3bbbf40..a4e96b5 100644
--- a/src/country_workspace/cache/manager.py
+++ b/src/country_workspace/cache/manager.py
@@ -23,7 +23,6 @@
class CacheManager:
def __init__(self, prefix="cache"):
self.prefix = prefix
- self.active = True
self.cw_version = "-"
self.cache_timeout = 86400
self.cache_by_version = False
@@ -59,6 +58,8 @@ def retrieve(self, key):
def store(self, key: str, value: Any, timeout: int = 0, **kwargs):
cache_set.send(self.__class__, key=key)
+ if not self.active:
+ timeout = 1
self.cache.set(key, value, timeout=timeout or self.cache_timeout, **kwargs)
def _get_version_key(self, office: "Optional[Office]" = None, program: "Optional[Program]" = None):
@@ -92,12 +93,23 @@ def incr_cache_version(self, *, office: "Optional[Office]" = None, program: "Opt
except ValueError:
return self.cache.set(key, 2)
+ @property
+ def active(self) -> bool:
+ return not bool(self.cache.get(f"{self.prefix}:cache_disabled"))
+
+ @active.setter
+ def active(self, value: bool):
+ if not value:
+ self.cache.set(f"{self.prefix}:cache_disabled", True)
+ else:
+ self.cache.delete(f"{self.prefix}:cache_disabled")
+
def build_key(self, prefix, *parts):
tenant = "t"
version = "v"
program = "p"
ts = "ts"
- if self.cache.get("cache_disabled"):
+ if not self.active:
ts = str(timezone.now().toordinal())
if state.tenant and state.program:
diff --git a/src/country_workspace/admin/panels/cache.py b/src/country_workspace/cache/smart_panel.py
similarity index 68%
rename from src/country_workspace/admin/panels/cache.py
rename to src/country_workspace/cache/smart_panel.py
index 8164ff1..b5ebf8f 100644
--- a/src/country_workspace/admin/panels/cache.py
+++ b/src/country_workspace/cache/smart_panel.py
@@ -1,4 +1,5 @@
from django import forms
+from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
@@ -6,7 +7,7 @@
class CacheManagerForm(forms.Form):
- pattern = forms.CharField()
+ pattern = forms.CharField(required=False, initial="*")
def panel_cache(self, request):
@@ -21,10 +22,18 @@ def _get_keys():
form = CacheManagerForm(request.POST)
if form.is_valid():
limit_to = form.cleaned_data["pattern"]
- if "_delete" in request.POST:
+ if "_disable" in request.POST:
+ cache_manager.active = False
+ return HttpResponseRedirect(".")
+ elif "_enable" in request.POST:
+ cache_manager.active = True
+ return HttpResponseRedirect(".")
+ elif "_delete" in request.POST:
to_delete = list(_get_keys())
if to_delete:
client.delete(*to_delete)
+ return HttpResponseRedirect(".")
+
else:
form = CacheManagerForm()
@@ -32,6 +41,7 @@ def _get_keys():
context["form"] = form
cache_data = _get_keys()
context["cache_data"] = cache_data
+ context["active"] = cache_manager.active
return render(request, "smart_admin/panels/cache.html", context)
diff --git a/src/country_workspace/cache/templates/smart_admin/panels/cache.html b/src/country_workspace/cache/templates/smart_admin/panels/cache.html
new file mode 100644
index 0000000..5179e26
--- /dev/null
+++ b/src/country_workspace/cache/templates/smart_admin/panels/cache.html
@@ -0,0 +1,32 @@
+{% extends "smart_admin/console.html" %}{% load i18n static %}
+{% block left %}
+
+{% endblock left %}
diff --git a/src/country_workspace/config/__init__.py b/src/country_workspace/config/__init__.py
index f20e3a0..2a4523c 100644
--- a/src/country_workspace/config/__init__.py
+++ b/src/country_workspace/config/__init__.py
@@ -19,7 +19,7 @@ def setting(anchor: str) -> str:
def celery_doc(anchor: str) -> str:
- return f"@see https://docs.celeryq.dev/en/stable/" f"userguide/configuration.html#{anchor}"
+ return f"@see https://docs.celeryq.dev/en/stable/userguide/configuration.html#{anchor}"
class Group(Enum):
@@ -55,6 +55,13 @@ class Group(Enum):
"AURORA_API_TOKEN": (str, "", "", False, "Aurora API token"),
"AURORA_API_URL": (str, "", "", False, "Aurora API url"),
"CACHE_URL": (str, "", "redis://localhost:6379/0", True, setting("cache-url")),
+ "SELECT2_CACHE": (
+ str,
+ "",
+ "redis://localhost:6379/9",
+ False,
+ "https://django-select2.readthedocs.io/en/latest/django_select2.html#module-django_select2.conf",
+ ),
"CELERY_BROKER_URL": (
str,
"",
diff --git a/src/country_workspace/config/settings.py b/src/country_workspace/config/settings.py
index 4c51211..fb033e8 100644
--- a/src/country_workspace/config/settings.py
+++ b/src/country_workspace/config/settings.py
@@ -131,6 +131,7 @@
USE_TZ = True
CACHE_URL = env("CACHE_URL")
+SELECT2_CACHE = env("SELECT2_CACHE")
# REDIS_URL = urlparse(CACHE_URL).hostname
CACHES = {
"default": {
@@ -140,7 +141,7 @@
},
"select2": {
"BACKEND": "django_redis.cache.RedisCache",
- "LOCATION": CACHE_URL,
+ "LOCATION": SELECT2_CACHE or CACHE_URL,
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
diff --git a/src/country_workspace/web/templates/admin/base_site.html b/src/country_workspace/web/templates/admin/base_site.html
index 241e16d..5bb3170 100644
--- a/src/country_workspace/web/templates/admin/base_site.html
+++ b/src/country_workspace/web/templates/admin/base_site.html
@@ -2,6 +2,8 @@
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
+{% block extrastyle %}{{ block.super }}{{ media }}{% endblock %}
+
{% block userlinks %}{{ block.super }}
{# {% if sysinfo %} / System Info {% endif %} #}
{% if profile_link %} / Profile {% endif %}
diff --git a/src/country_workspace/web/templates/smart_admin/panels/cache.html b/src/country_workspace/web/templates/smart_admin/panels/cache.html
deleted file mode 100644
index e2cefc7..0000000
--- a/src/country_workspace/web/templates/smart_admin/panels/cache.html
+++ /dev/null
@@ -1,20 +0,0 @@
-{% extends "smart_admin/console.html" %}{% load i18n static %}
-{% block left %}
-
-{% endblock left %}
diff --git a/tests/admin/panels/test_cache_panel.py b/tests/admin/panels/test_cache_panel.py
deleted file mode 100644
index b4530c2..0000000
--- a/tests/admin/panels/test_cache_panel.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from unittest.mock import MagicMock
-
-from country_workspace.admin import panel_cache
-from country_workspace.cache.manager import cache_manager
-
-
-def test_cache_panel(rf):
- req = rf.get("/")
- res = panel_cache(MagicMock(each_context=lambda s: {}), req)
- assert res.status_code == 200
-
-
-def test_cache_panel_invalid(rf):
- req = rf.post("/", {})
- res = panel_cache(MagicMock(each_context=lambda s: {}), req)
- assert res.status_code == 200
-
-
-def test_cache_panel_filter(rf):
- k = cache_manager.build_key("test_cache_panel_entry")
- cache_manager.store(k, 1)
- req = rf.post("/", {"pattern": "test_cache_panel_entry"})
- res = panel_cache(MagicMock(each_context=lambda s: {}), req)
- assert res.status_code == 200
- assert b"test_cache_panel_entry" in res.content
-
-
-def test_cache_panel_delete(rf):
- k = cache_manager.build_key("test_cache_panel_delete")
- cache_manager.store(k, 1)
- req = rf.post("/", {"pattern": "*cache_panel_delete*", "_delete": "Delete"})
- res = panel_cache(MagicMock(each_context=lambda s: {}), req)
- assert res.status_code == 200
-
- req = rf.post("/", {"pattern": "xx", "_delete": "Delete"})
- res = panel_cache(MagicMock(each_context=lambda s: {}), req)
- assert res.status_code == 200
diff --git a/tests/admin/test_admin_individual.py b/tests/admin/test_admin_individual.py
index 026f2fa..d59bfb9 100644
--- a/tests/admin/test_admin_individual.py
+++ b/tests/admin/test_admin_individual.py
@@ -44,3 +44,10 @@ def test_individual_changelist(app, individual: "CountryIndividual"):
assert res.status_code == 200
res = res.click(individual.name)
assert res.status_code == 200
+
+
+@pytest.mark.parametrize("valid", ["v", "i", "u"])
+def test_individual_filter_by_valid(app, individual: "CountryIndividual", valid):
+ base_url = reverse("admin:country_workspace_individual_changelist")
+ res = app.get(f"{base_url}?valid={valid}")
+ assert res.status_code == 200
diff --git a/tests/admin/test_admin_job.py b/tests/admin/test_admin_job.py
new file mode 100644
index 0000000..4de0eb2
--- /dev/null
+++ b/tests/admin/test_admin_job.py
@@ -0,0 +1,37 @@
+from typing import TYPE_CHECKING
+
+from django.urls import reverse
+
+import pytest
+from django_webtest.pytest_plugin import MixinWithInstanceVariables
+
+from country_workspace.models import User
+
+if TYPE_CHECKING:
+ from testutils.types import CWTestApp
+
+ from country_workspace.workspaces.models import AsyncJob
+
+
+@pytest.fixture()
+def job():
+ from testutils.factories import AsyncJobFactory
+
+ return AsyncJobFactory()
+
+
+@pytest.fixture()
+def app(django_app_factory: "MixinWithInstanceVariables", admin_user: "User") -> "CWTestApp":
+ django_app = django_app_factory(csrf_checks=False)
+ django_app.set_user(admin_user)
+ yield django_app
+
+
+def test_job_filtering(app, job: "AsyncJob"):
+ base_url = reverse("admin:country_workspace_asyncjob_changelist")
+ app.get(base_url)
+
+ app.get(f"{base_url}?program={job.program.pk}")
+ app.get(f"{base_url}?program={job.program.pk}&failed=f")
+ app.get(f"{base_url}?program={job.program.pk}&failed=s")
+ app.get(f"{base_url}?program={job.program.pk}&failed=x")
diff --git a/tests/cache/test_cache_panel.py b/tests/cache/test_cache_panel.py
new file mode 100644
index 0000000..065c228
--- /dev/null
+++ b/tests/cache/test_cache_panel.py
@@ -0,0 +1,63 @@
+from unittest import mock
+from unittest.mock import MagicMock
+
+import pytest
+
+from country_workspace.admin import panel_cache
+from country_workspace.cache.manager import CacheManager
+
+
+@pytest.fixture
+def manager(worker_id):
+ m = CacheManager(f"cache{worker_id}")
+ m.init()
+ m.active = True
+ yield m
+
+
+def test_cache_panel(rf):
+ req = rf.get("/")
+ res = panel_cache(MagicMock(each_context=lambda s: {}), req)
+ assert res.status_code == 200
+
+
+def test_cache_panel_invalid(rf):
+ req = rf.post("/", {})
+ res = panel_cache(MagicMock(each_context=lambda s: {}), req)
+ assert res.status_code == 200
+
+
+def test_cache_panel_filter(rf, manager):
+ with mock.patch("country_workspace.cache.smart_panel.cache_manager", manager):
+ k = manager.build_key("test_cache_panel_entry")
+ manager.store(k, 1)
+ req = rf.post("/", {"pattern": "test_cache_panel_entry"})
+ res = panel_cache(MagicMock(each_context=lambda s: {}), req)
+ assert res.status_code == 200
+ assert b"test_cache_panel_entry" in res.content
+
+
+def test_cache_panel_delete(rf, manager):
+ with mock.patch("country_workspace.cache.smart_panel.cache_manager", manager):
+ k = manager.build_key("test_cache_panel_delete")
+ manager.store(k, 1)
+ req = rf.post("/", {"pattern": "*cache_panel_delete*", "_delete": "Delete"})
+ res = panel_cache(MagicMock(each_context=lambda s: {}), req)
+ assert res.status_code == 302
+
+ req = rf.post("/", {"pattern": "xx", "_delete": "Delete"})
+ res = panel_cache(MagicMock(each_context=lambda s: {}), req)
+ assert res.status_code == 302
+
+
+def test_cache_panel_toggle(rf, manager):
+ with mock.patch("country_workspace.cache.smart_panel.cache_manager", manager):
+ req = rf.post("/", {"pattern": "*", "_disable": "D"})
+ res = panel_cache(MagicMock(each_context=lambda s: {}), req)
+ assert res.status_code == 302
+ assert not manager.active
+
+ req = rf.post("/", {"pattern": "*", "_enable": "E"})
+ res = panel_cache(MagicMock(each_context=lambda s: {}), req)
+ assert res.status_code == 302
+ assert manager.active
diff --git a/tests/conftest.py b/tests/conftest.py
index e1e8960..8b711c5 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -98,7 +98,7 @@ def mocked_responses():
@pytest.fixture()
-def user(db):
+def user(db, worker_id, settings):
from testutils.factories import UserFactory
return UserFactory()
diff --git a/tests/extras/testutils/factories/household.py b/tests/extras/testutils/factories/household.py
index 247404e..f203527 100644
--- a/tests/extras/testutils/factories/household.py
+++ b/tests/extras/testutils/factories/household.py
@@ -60,9 +60,16 @@ def get_hh_fields(household: "CountryHousehold"):
}
+def get_name(instance, num):
+ name = fake.last_name()
+ return f"{name} #{num}"
+
+
class HouseholdFactory(AutoRegisterModelFactory):
batch = factory.SubFactory(CountryBatchFactory)
- name = factory.Faker("last_name")
+ # name = factory.Faker("last_name")
+ name = factory.LazyAttributeSequence(get_name)
+
flex_fields = factory.LazyAttribute(get_hh_fields)
class Meta:
diff --git a/tests/versioning/test_ver.py b/tests/versioning/test_ver.py
index e56ed35..db4de81 100644
--- a/tests/versioning/test_ver.py
+++ b/tests/versioning/test_ver.py
@@ -1,11 +1,15 @@
from pathlib import Path
from typing import TYPE_CHECKING
+import pytest
+
from country_workspace import VERSION
if TYPE_CHECKING:
from country_workspace.versioning.management.manager import Manager
+pytestmark = pytest.mark.xdist_group("versioning")
+
def test_manager_1(manager: "Manager", scripts: list[Path]) -> None:
assert manager.max_version == 3
diff --git a/uv.lock b/uv.lock
index 4417aab..9139a25 100644
--- a/uv.lock
+++ b/uv.lock
@@ -551,11 +551,11 @@ sdist = { url = "https://files.pythonhosted.org/packages/a2/63/4428328cc0ca60ee0
[[package]]
name = "django-adminfilters"
-version = "2.5.1"
+version = "2.5.2"
source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/09/5d/a8f22bedd8d17ca0b6fa90bac5492840f16fdd7d463f4e81c68dab4a5cb9/django_adminfilters-2.5.1.tar.gz", hash = "sha256:affaf0614a1939ad523f7a5ee1427f929f18f0df3a61b63d92eab6bdfcfab3a2", size = 57830 }
+sdist = { url = "https://files.pythonhosted.org/packages/43/e6/bcf3341161b2d363281d0ddb9924ce27e31f7e8b370564b63c7f200e398c/django_adminfilters-2.5.2.tar.gz", hash = "sha256:2d4982490631cf198734e83337280ca831d5f559995198843103b30202104a29", size = 58865 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/25/4e/af6b7a97a570acea30bb446ba58794336df44d351069918460e10d6def69/django_adminfilters-2.5.1-py2.py3-none-any.whl", hash = "sha256:f568f3009886d74463c0aef7d54acd10bb2c6374e4ce2be1f8642695dcdc8e53", size = 47514 },
+ { url = "https://files.pythonhosted.org/packages/f0/eb/2965d0ae94edc46e8a3ec0328c3ec71f580afd57084a785a8e99d39c0a55/django_adminfilters-2.5.2-py2.py3-none-any.whl", hash = "sha256:c1f19c8215b4573159359eaa1231ecdb5f5d9edbfca93f44e4dce27636630596", size = 49246 },
]
[[package]]
@@ -1222,7 +1222,7 @@ requires-dist = [
{ name = "dictdiffer", specifier = ">=0.9.0" },
{ name = "django", specifier = ">=5.1" },
{ name = "django-adminactions", specifier = ">=2.3.0" },
- { name = "django-adminfilters", specifier = "==2.5.1" },
+ { name = "django-adminfilters", specifier = ">=2.5.2" },
{ name = "django-cacheops", specifier = ">=7.1" },
{ name = "django-celery-beat", specifier = ">=2.6.0" },
{ name = "django-celery-boost", specifier = ">=0.5.0" },