From 5847140ee1178afa8c3e196e70ed50587c97a25f Mon Sep 17 00:00:00 2001 From: Luc Merceron Date: Tue, 26 Aug 2025 16:00:06 +0200 Subject: [PATCH 1/2] fix(conditional): disabled on change --- .../unfold/helpers/fieldset_row.html | 2 +- tests/conftest.py | 3 +- tests/factories.py | 12 +++++++- tests/server/example/admin.py | 18 ++++++++++- .../0005_conditionalfieldstestmodel.py | 23 ++++++++++++++ tests/server/example/models.py | 22 ++++++++++++++ tests/test_conditional_fields.py | 30 +++++++++++++++++++ 7 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 tests/server/example/migrations/0005_conditionalfieldstestmodel.py create mode 100644 tests/test_conditional_fields.py diff --git a/src/unfold/templates/unfold/helpers/fieldset_row.html b/src/unfold/templates/unfold/helpers/fieldset_row.html index 1722b84f4..a19f4c230 100644 --- a/src/unfold/templates/unfold/helpers/fieldset_row.html +++ b/src/unfold/templates/unfold/helpers/fieldset_row.html @@ -3,7 +3,7 @@
{% for field in line %} {% with adminform.model_admin.conditional_fields|index:field.field.name as conditional_display %} -
+
{% if has_conditional_display %} {% with field|changeform_condition as field %} {% if field.is_checkbox %} diff --git a/tests/conftest.py b/tests/conftest.py index 40b983e3b..b3b23a21f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,8 @@ from pytest_factoryboy import register -from .factories import TagFactory, UserFactory +from .factories import ConditionalFieldsTestModelFactory, TagFactory, UserFactory from .fixtures import * # noqa: F403 register(TagFactory) register(UserFactory) +register(ConditionalFieldsTestModelFactory) diff --git a/tests/factories.py b/tests/factories.py index 89ce270ae..dd433fdda 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -1,4 +1,5 @@ -from example.models import Tag, User +import factory +from example.models import ConditionalFieldsTestModel, Tag, User from factory.django import DjangoModelFactory @@ -10,3 +11,12 @@ class Meta: class TagFactory(DjangoModelFactory): class Meta: model = Tag + + +class ConditionalFieldsTestModelFactory(DjangoModelFactory): + class Meta: + model = ConditionalFieldsTestModel + + name = factory.Faker("name") + conditional_field_active = factory.Faker("name") + conditional_field_inactive = factory.Faker("name") diff --git a/tests/server/example/admin.py b/tests/server/example/admin.py index f3958d689..e61e2d320 100644 --- a/tests/server/example/admin.py +++ b/tests/server/example/admin.py @@ -10,7 +10,7 @@ from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm from unfold.sections import TableSection, TemplateSection -from .models import ActionUser, SectionUser, Tag, User +from .models import ActionUser, ConditionalFieldsTestModel, SectionUser, Tag, User admin.site.unregister(Group) @@ -52,6 +52,22 @@ class SectionUserAdmin(UserAdmin): ] +@admin.register(ConditionalFieldsTestModel) +class ConditionalFieldsModelAdmin(ModelAdmin): + list_display = ["name", "status"] + fields = [ + "name", + "status", + "conditional_field_active", + "conditional_field_inactive", + ] + + conditional_fields = { + "conditional_field_active": 'status === "ACTIVE"', + "conditional_field_inactive": 'status === "INACTIVE"', + } + + @admin.register(ActionUser) class ActionsUserAdmin(BaseUserAdmin, ModelAdmin): form = UserChangeForm diff --git a/tests/server/example/migrations/0005_conditionalfieldstestmodel.py b/tests/server/example/migrations/0005_conditionalfieldstestmodel.py new file mode 100644 index 000000000..ddb314959 --- /dev/null +++ b/tests/server/example/migrations/0005_conditionalfieldstestmodel.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.22 on 2025-08-26 07:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('example', '0004_actionuser_sectionuser'), + ] + + operations = [ + migrations.CreateModel( + name='ConditionalFieldsTestModel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('status', models.CharField(choices=[('ACTIVE', 'Active'), ('INACTIVE', 'Inactive')], default='ACTIVE', max_length=10)), + ('conditional_field_active', models.CharField(blank=True, help_text='This field is only visible when status is ACTIVE', max_length=100)), + ('conditional_field_inactive', models.CharField(blank=True, help_text='This field is only visible when status is INACTIVE', max_length=100)), + ], + ), + ] diff --git a/tests/server/example/models.py b/tests/server/example/models.py index ac43dc16c..939f53ad9 100644 --- a/tests/server/example/models.py +++ b/tests/server/example/models.py @@ -24,3 +24,25 @@ class Tag(models.Model): def __str__(self): return self.name + + +class ConditionalFieldsTestModel(models.Model): + STATUS_CHOICES = ( + ("ACTIVE", "Active"), + ("INACTIVE", "Inactive"), + ) + name = models.CharField(max_length=100) + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="ACTIVE") + conditional_field_active = models.CharField( + max_length=100, + blank=True, + help_text="This field is only visible when status is ACTIVE", + ) + conditional_field_inactive = models.CharField( + max_length=100, + blank=True, + help_text="This field is only visible when status is INACTIVE", + ) + + def __str__(self): + return self.name diff --git a/tests/test_conditional_fields.py b/tests/test_conditional_fields.py new file mode 100644 index 000000000..4afea605c --- /dev/null +++ b/tests/test_conditional_fields.py @@ -0,0 +1,30 @@ +import re +from http import HTTPStatus + +import pytest +from django.urls import reverse + +from tests.factories import ConditionalFieldsTestModelFactory + + +@pytest.mark.django_db +def test_conditional_fields_in_context(admin_client): + """Test that conditional fields are properly included in the admin form context.""" + conditional_fields_test_instance = ConditionalFieldsTestModelFactory( + status="ACTIVE", + conditional_field_active="Active Value", + conditional_field_inactive="Inactive Value" + ) + change_url = reverse( + "admin:example_conditionalfieldstestmodel_change", + args=[conditional_fields_test_instance.pk] + ) + + response = admin_client.get(change_url) + + assert response.status_code == HTTPStatus.OK + content = response.content.decode() + # Test that the x-bind:disabled directive is present to disable hidden fields + assert re.search(r'x-bind:disabled="!\(status\s*===\s*"ACTIVE"\)"', content) + assert re.search(r'x-bind:disabled="!\(status\s*===\s*"INACTIVE"\)"', content) + # TODO: test that the fields are disabled (at init & on update) \ No newline at end of file From 0d00f99d208da75bcefce24791fb597fc6ae4002 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 14:02:39 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../0005_conditionalfieldstestmodel.py | 44 +++++++++++++++---- tests/test_conditional_fields.py | 14 +++--- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/tests/server/example/migrations/0005_conditionalfieldstestmodel.py b/tests/server/example/migrations/0005_conditionalfieldstestmodel.py index ddb314959..852428501 100644 --- a/tests/server/example/migrations/0005_conditionalfieldstestmodel.py +++ b/tests/server/example/migrations/0005_conditionalfieldstestmodel.py @@ -4,20 +4,48 @@ class Migration(migrations.Migration): - dependencies = [ - ('example', '0004_actionuser_sectionuser'), + ("example", "0004_actionuser_sectionuser"), ] operations = [ migrations.CreateModel( - name='ConditionalFieldsTestModel', + name="ConditionalFieldsTestModel", fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('status', models.CharField(choices=[('ACTIVE', 'Active'), ('INACTIVE', 'Inactive')], default='ACTIVE', max_length=10)), - ('conditional_field_active', models.CharField(blank=True, help_text='This field is only visible when status is ACTIVE', max_length=100)), - ('conditional_field_inactive', models.CharField(blank=True, help_text='This field is only visible when status is INACTIVE', max_length=100)), + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100)), + ( + "status", + models.CharField( + choices=[("ACTIVE", "Active"), ("INACTIVE", "Inactive")], + default="ACTIVE", + max_length=10, + ), + ), + ( + "conditional_field_active", + models.CharField( + blank=True, + help_text="This field is only visible when status is ACTIVE", + max_length=100, + ), + ), + ( + "conditional_field_inactive", + models.CharField( + blank=True, + help_text="This field is only visible when status is INACTIVE", + max_length=100, + ), + ), ], ), ] diff --git a/tests/test_conditional_fields.py b/tests/test_conditional_fields.py index 4afea605c..db7d73d61 100644 --- a/tests/test_conditional_fields.py +++ b/tests/test_conditional_fields.py @@ -13,11 +13,11 @@ def test_conditional_fields_in_context(admin_client): conditional_fields_test_instance = ConditionalFieldsTestModelFactory( status="ACTIVE", conditional_field_active="Active Value", - conditional_field_inactive="Inactive Value" + conditional_field_inactive="Inactive Value", ) change_url = reverse( "admin:example_conditionalfieldstestmodel_change", - args=[conditional_fields_test_instance.pk] + args=[conditional_fields_test_instance.pk], ) response = admin_client.get(change_url) @@ -25,6 +25,10 @@ def test_conditional_fields_in_context(admin_client): assert response.status_code == HTTPStatus.OK content = response.content.decode() # Test that the x-bind:disabled directive is present to disable hidden fields - assert re.search(r'x-bind:disabled="!\(status\s*===\s*"ACTIVE"\)"', content) - assert re.search(r'x-bind:disabled="!\(status\s*===\s*"INACTIVE"\)"', content) - # TODO: test that the fields are disabled (at init & on update) \ No newline at end of file + assert re.search( + r'x-bind:disabled="!\(status\s*===\s*"ACTIVE"\)"', content + ) + assert re.search( + r'x-bind:disabled="!\(status\s*===\s*"INACTIVE"\)"', content + ) + # TODO: test that the fields are disabled (at init & on update)