jmrivas86/django-json-widget

Widget doesn't appear to support "disabled" behavior

Opened this issue · 2 comments

  • django-json-widget version: 1.0
  • Django version: 3.0.4
  • Python version: 3.6.10
  • Operating System: Mac

Description

The widget doesn't appear to support the "disabled" html attribute. When I set associated Django field (e.g., JsonField) to disabled, it doesn't render as disabled and still allows the user to make edits in the widget.

If there is a way to render the widget, but in a "disabled" state, I would like to know how to specify that on the widget.

Work around

Set options to restrict mode to read-only text mode and prevent the user from selecting other modes.
Downside: This affects all JSONFields on the form and may not want ALL to be read-only.
formfield_overrides = {
fields.JSONField: {'widget': JSONEditorWidget(options={"mode": "text", "modes": ["text"] })},
}

I assume you are not adding the desired fields to readonly_fields list/tuple. Because, as soon as you add, they are rendered as plain TextArea. If you are doing it this way, your admin form is open to a security threat. Though at the editor level, there is no option to edit JSON data but think of the user submitting a different payload using raw endpoints. It accepts and modifies! The best way IMHO is:

Define a form for your payload

class PayloadForm(forms.ModelForm):
    """Form for viewing payload"""

    class Meta:
        model = Model
        fields = ('payload',)
        widgets = {
            'payload': JSONEditorWidget(options={
                'mode': 'view',
                'modes': ['view']
            }),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields.get('payload').disabled = True

Then in your admin

class ModelAdmin(admin.ModelAdmin):
    ""Model Admin"""

    model = Model
    form = PayloadForm
    readonly_fields = (....)  # Do not add 'payload' in this list.

The advantage of the above approach is two-fold, all JSONFields are not getting subjected to a similar setting. And, at the editor level, the user is presented with only the 'view' mode. Even if the user tampers and tries to submit a different payload, the changes are dropped in favor of existing data since we set disabled to True.

Here is my solution:

https://gist.github.com/elonzh/413df4532e491de27f9a51e9dbf7e8c1

Django admin render readonly_fields without widgets, So just remove the textarea input in JSONEditorWidget template and write a customized display function.

django/contrib/admin/templates/admin/includes/fieldset.html

...
{% if field.is_readonly %}
    <div class="readonly">{{ field.contents }}</div>
{% else %}
    {{ field.field }}
{% endif %}
...

JSONAdminMixin

class JSONAdminMixin:
    formfield_overrides = {
        models.JSONField: {"widget": JSONEditorWidget},
    }

    class ReadonlyJSONWidget(JSONEditorWidget):
        template_name = 'django_json_readonly_widget.html'

    @property
    def media(self):
        return super().media + self.ReadonlyJSONWidget().media

    @classmethod
    def render_readonly_json(cls, name, value, attrs=None, mode='code', options=None, width=None, height=None):
        attrs = attrs or {}
        attrs.setdefault("id", "id_" + name)
        widget = cls.ReadonlyJSONWidget(attrs, mode, options, width, height)
        return widget.render(name, value)

django_json_readonly_widget.html

<div {% if not widget.attrs.style %}style="height:{{widget.height|default:'500px'}};width:{{widget.width|default:'90%'}};display:inline-block;"{% endif %}{% include "django/forms/widgets/attrs.html" %}></div>

<script>
    (function() {
        var container = document.getElementById("{{ widget.attrs.id }}");

        var options = {{ widget.options|safe }};
        options.onModeChange = function (newMode, oldMode) {
            if (newMode === 'code') {
                editor.aceEditor.setReadOnly(true);
            }
        }

        var editor = new JSONEditor(container, options);
        if (editor.mode === 'code') {
            editor.aceEditor.setReadOnly(true);
        }
        var json = {{ widget.value|safe }};
        editor.set(json);
    })();
</script>

PayloadAdmin:

class PayloadAdmin(JSONAdminMixin, admin.ModelAdmin):
	fields = ["payload"]
    def payload_display(self, obj):
        return self.render_readonly_json("payload", json.dumps(obj.payload))