Commit 45ec950f authored by Éloi Rivard's avatar Éloi Rivard
Browse files

Fixed FieldList readonly state

parent 005c806b
Loading
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -111,6 +111,11 @@ class HTMXFormMixin:
            if not fieldlist or not isinstance(fieldlist, wtforms.FieldList):
                abort(400)

            if fieldlist.render_kw and (
                "readonly" in fieldlist.render_kw or "disabled" in fieldlist.render_kw
            ):
                abort(403)

            if request_is_htmx():
                self.validate_field(fieldlist)

@@ -128,6 +133,11 @@ class HTMXFormMixin:
            if not fieldlist or not isinstance(fieldlist, wtforms.FieldList):
                abort(400)

            if fieldlist.render_kw and (
                "readonly" in fieldlist.render_kw or "disabled" in fieldlist.render_kw
            ):
                abort(403)

            if request_is_htmx():
                self.validate_field(fieldlist)

+20 −15
Original line number Diff line number Diff line
@@ -11,12 +11,23 @@ add_button=false,
del_button=false
) -%}
{% set field_visible = field.type != 'HiddenField' and field.type !='CSRFTokenField' %}
{% set disabled = kwargs.get("disabled") or (field.render_kw and "disabled" in field.render_kw) %}
{% set readonly = kwargs.get("readonly") or (field.render_kw and "readonly" in field.render_kw) %}
{% set required = "required" in field.flags %}
{% set lock_indicator = readonly or disabled %}
{% set corner_indicator = not noindicator and (indicator_icon or lock_indicator or required) %}
{% set inline_validation = field.validators and field.type not in ("FileField", "MultipleFileField") %}
{% if inline_validation %}
    {% set ignore_me = kwargs.update({"hx-post": ""}) %}
    {% set ignore_me = kwargs.update({"hx-indicator": "closest .input"}) %}
{% endif %}

{% if container and field_visible %}
    <div class="field {{ kwargs.pop('class_', '') }}
                {%- if field.errors %} error{% endif -%}
                {%- if field.render_kw and "disabled" in field.render_kw %} disabled{% endif -%}"
                {%- if disabled %} disabled{% endif -%}"
         {% if not display %}style="display: none"{% endif %}
         {% if field.validators and field.type not in ("FileField", "MultipleFileField") %}hx-target="this" hx-swap="outerHTML"{% endif %}
         {% if inline_validation %}hx-target="this" hx-swap="outerHTML"{% endif %}
    >
{% endif %}

@@ -24,18 +35,11 @@ del_button=false
    {{ field.label() }}
{% endif %}

{% set lock_indicator = field.render_kw and ("readonly" in field.render_kw or "disabled" in field.render_kw) %}
{% set required_indicator = "required" in field.flags %}
{% set corner_indicator = not noindicator and (indicator_icon or lock_indicator or required_indicator) %}
{% if field.validators and field.type not in ("FileField", "MultipleFileField") %}
    {% set ignore_me = kwargs.update({"hx-post": ""}) %}
    {% set ignore_me = kwargs.update({"hx-indicator": "closest .input"}) %}
{% endif %}
{% if field_visible %}
    <div class="ui
                {%- if corner_indicator %} corner labeled{% endif -%}
                {%- if icon or field.description %} left icon{% endif -%}
                {%- if add_button or del_button %} action{% endif -%}
                {%- if field_visible and not readonly and not disabled and (add_button or del_button) %} action{% endif -%}
                {%- if field.type not in ("BooleanField", "RadioField") %} input{% endif -%}
               ">
{% endif %}
@@ -44,11 +48,11 @@ del_button=false

{% if field.type not in ("SelectField", "SelectMultipleField") %}
    {{ field(**kwargs) }}
{% elif field.type == "SelectMultipleField" and field.render_kw and "readonly" in field.render_kw %}
{% elif field.type == "SelectMultipleField" and readonly %}
    {{ field(class_="ui fluid dropdown multiple read-only", **kwargs) }}
{% elif field.type == "SelectMultipleField" %}
    {{ field(class_="ui fluid dropdown multiple", **kwargs) }}
{% elif field.type == "SelectField" and field.render_kw and "readonly" in field.render_kw %}
{% elif field.type == "SelectField" and readonly %}
    {{ field(class_="ui fluid dropdown read-only", **kwargs) }}
{% elif field.type == "SelectField" %}
    {{ field(class_="ui fluid dropdown", **kwargs) }}
@@ -63,7 +67,7 @@ del_button=false
        <div class="ui corner label" title="{{ _("This field is not editable") }}">
            <i class="lock icon"></i>
        </div>
    {% elif required_indicator %}
    {% elif required %}
        <div class="ui corner label" title="{{ _("This field is required") }}">
            <i class="asterisk icon"></i>
        </div>
@@ -71,7 +75,7 @@ del_button=false
{% endif %}

{% if field_visible %}
    {% if del_button %}
    {% if not readonly and not disabled and del_button %}
        <button
            class="ui teal icon button"
            title="{{ _("Remove this field") }}"
@@ -86,7 +90,7 @@ del_button=false
            <i class="minus icon"></i>
        </button>
    {% endif %}
    {% if add_button %}
    {% if not readonly and not disabled and add_button %}
        <button
            class="ui teal icon button"
            title="{{ _("Add another field") }}"
@@ -147,6 +151,7 @@ del_button=false
        {# Strangely enough, translations are not rendered when using field.label() #}
        {{ field[0].label() }}
        {% for subfield in field %}
            {% set ignore_me = kwargs.update(**field.render_kw or {}) %}
            {{ render_field(
            subfield,
            parent_list=field,
+36 −0
Original line number Diff line number Diff line
@@ -405,6 +405,42 @@ def test_fieldlist_remove_field_htmx(testclient, logged_admin):
    assert 'name="redirect_uris-1' not in response.text


def test_fieldlist_add_readonly(testclient, logged_user, configuration):
    configuration["ACL"]["DEFAULT"]["WRITE"].remove("phone_numbers")
    configuration["ACL"]["DEFAULT"]["READ"].append("phone_numbers")

    res = testclient.get("/profile/user")
    assert res.form["phone_numbers-0"].attrs["readonly"]
    assert "phone_numbers-1" not in res.form.fields

    data = {
        "csrf_token": res.form["csrf_token"].value,
        "family_name": res.form["family_name"].value,
        "phone_numbers-0": res.form["phone_numbers-0"].value,
        "fieldlist_add": "phone_numbers-0",
    }
    testclient.post("/profile/user", data, status=403)


def test_fieldlist_remove_readonly(testclient, logged_user, configuration):
    configuration["ACL"]["DEFAULT"]["WRITE"].remove("phone_numbers")
    configuration["ACL"]["DEFAULT"]["READ"].append("phone_numbers")
    logged_user.phone_numbers = ["555-555-000", "555-555-111"]
    logged_user.save()

    res = testclient.get("/profile/user")
    assert res.form["phone_numbers-0"].attrs["readonly"]
    assert res.form["phone_numbers-1"].attrs["readonly"]

    data = {
        "csrf_token": res.form["csrf_token"].value,
        "family_name": res.form["family_name"].value,
        "phone_numbers-0": res.form["phone_numbers-0"].value,
        "fieldlist_remove": "phone_numbers-1",
    }
    testclient.post("/profile/user", data, status=403)


def test_fieldlist_inline_validation(testclient, logged_admin):
    res = testclient.get("/admin/client/add")
    data = {