Added missing logic to reset multi select (#659)
Some checks failed
Docs / docs (push) Has been cancelled
Test / ruff-format (push) Has been cancelled
Test / ruff-lint (push) Has been cancelled
Test / build (3.10) (push) Has been cancelled
Test / build (3.11) (push) Has been cancelled
Test / build (3.12) (push) Has been cancelled
Test / build (3.13) (push) Has been cancelled
Test / build (3.14) (push) Has been cancelled
Test / build (3.8) (push) Has been cancelled
Test / build (3.9) (push) Has been cancelled

This commit is contained in:
Philipp Thumfart 2026-03-14 17:27:39 +01:00 committed by GitHub
parent 7e75db3ebc
commit c908b05740
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 0 deletions

View file

@ -1,3 +1,4 @@
import json
from collections import OrderedDict from collections import OrderedDict
from datetime import date from datetime import date
from datetime import datetime from datetime import datetime
@ -62,8 +63,13 @@ class ConstanceAdmin(admin.ModelAdmin):
"is_date": isinstance(default, date), "is_date": isinstance(default, date),
"is_datetime": isinstance(default, datetime), "is_datetime": isinstance(default, datetime),
"is_checkbox": isinstance(form_field.field.widget, forms.CheckboxInput), "is_checkbox": isinstance(form_field.field.widget, forms.CheckboxInput),
"is_multi_select": isinstance(
form_field.field.widget, (forms.SelectMultiple, forms.CheckboxSelectMultiple)
),
"is_file": isinstance(form_field.field.widget, forms.FileInput), "is_file": isinstance(form_field.field.widget, forms.FileInput),
} }
if config_value["is_multi_select"]:
config_value["json_default"] = json.dumps(default if isinstance(default, list) else [default])
if field_type and field_type in settings.ADDITIONAL_FIELDS: if field_type and field_type in settings.ADDITIONAL_FIELDS:
serialized_default = form[name].field.prepare_value(default) serialized_default = form[name].field.prepare_value(default)
config_value["default"] = serialized_default config_value["default"] = serialized_default

View file

@ -12,6 +12,17 @@
if (fieldType === 'checkbox') { if (fieldType === 'checkbox') {
field.prop('checked', this.dataset.default === 'true'); field.prop('checked', this.dataset.default === 'true');
} else if (fieldType === 'multi-select') {
const defaults = JSON.parse(this.dataset.default);
const stringDefaults = defaults.map(function(v) { return String(v); });
// CheckboxSelectMultiple: individual checkboxes inside a wrapper
field.find('input[type="checkbox"]').each(function() {
$(this).prop('checked', stringDefaults.indexOf($(this).val()) !== -1);
});
// SelectMultiple: <select multiple> element
field.find('option').each(function() {
$(this).prop('selected', stringDefaults.indexOf($(this).val()) !== -1);
});
} else if (fieldType === 'date') { } else if (fieldType === 'date') {
const defaultDate = new Date(this.dataset.default * 1000); const defaultDate = new Date(this.dataset.default * 1000);
$('#' + this.dataset.fieldId).val(defaultDate.strftime(get_format('DATE_INPUT_FORMATS')[0])); $('#' + this.dataset.fieldId).val(defaultDate.strftime(get_format('DATE_INPUT_FORMATS')[0]));

View file

@ -31,12 +31,14 @@
data-field-id="{{ item.form_field.auto_id }}" data-field-id="{{ item.form_field.auto_id }}"
data-field-type="{% spaceless %} data-field-type="{% spaceless %}
{% if item.is_checkbox %}checkbox {% if item.is_checkbox %}checkbox
{% elif item.is_multi_select %}multi-select
{% elif item.is_datetime %}datetime {% elif item.is_datetime %}datetime
{% elif item.is_date %}date {% elif item.is_date %}date
{% endif %} {% endif %}
{% endspaceless %}" {% endspaceless %}"
data-default="{% spaceless %} data-default="{% spaceless %}
{% if item.is_checkbox %}{% if item.raw_default %} true {% else %} false {% endif %} {% if item.is_checkbox %}{% if item.raw_default %} true {% else %} false {% endif %}
{% elif item.is_multi_select %}{{ item.json_default }}
{% elif item.is_date %}{{ item.raw_default|date:"U" }} {% elif item.is_date %}{{ item.raw_default|date:"U" }}
{% elif item.is_datetime %}{{ item.raw_default|date:"U" }} {% elif item.is_datetime %}{{ item.raw_default|date:"U" }}
{% else %}{{ item.default }} {% else %}{{ item.default }}

View file

@ -266,6 +266,62 @@ class TestAdmin(TestCase):
content_str = response.content.decode() content_str = response.content.decode()
self.assertGreater(content_str.find("STRING_VALUE"), content_str.find("INT_VALUE")) self.assertGreater(content_str.find("STRING_VALUE"), content_str.find("INT_VALUE"))
@mock.patch(
"constance.settings.ADDITIONAL_FIELDS",
{
"language_select": [
"django.forms.fields.TypedMultipleChoiceField",
{
"widget": "django.forms.CheckboxSelectMultiple",
"choices": (("en", "English"), ("de", "German"), ("fr", "French")),
"coerce": str,
},
],
},
)
@mock.patch(
"constance.settings.CONFIG",
{
"LANGUAGES": (["en", "de"], "Supported languages", "language_select"),
},
)
def test_reset_to_default_multi_select(self):
"""
Test that multi-select config values render with data-field-type='multi-select'
and a JSON-encoded data-default attribute.
"""
# Re-parse additional fields so the mock is picked up by the form
from constance.forms import FIELDS
from constance.forms import parse_additional_fields
FIELDS.update(
parse_additional_fields(
{
"language_select": [
"django.forms.fields.TypedMultipleChoiceField",
{
"widget": "django.forms.CheckboxSelectMultiple",
"choices": (("en", "English"), ("de", "German"), ("fr", "French")),
"coerce": str,
},
]
}
)
)
try:
self.client.login(username="admin", password="nimda")
request = self.rf.get("/admin/constance/config/")
request.user = self.superuser
response = self.options.changelist_view(request, {})
response.render()
content = response.content.decode()
self.assertIn('data-field-type="multi-select"', content)
self.assertIn('data-default="[&quot;en&quot;, &quot;de&quot;]"', content)
finally:
# Clean up FIELDS to avoid leaking into other tests
FIELDS.pop("language_select", None)
def test_labels(self): def test_labels(self):
self.assertEqual(type(self.model._meta.label), str) self.assertEqual(type(self.model._meta.label), str)
self.assertEqual(type(self.model._meta.label_lower), str) self.assertEqual(type(self.model._meta.label_lower), str)