jmrivas86 / django-json-widget

An alternative widget that makes it easy to edit the new Django's field JSONField (PostgreSQL specific model fields)
MIT License
433 stars 88 forks source link

Widget doesn't appear to support "disabled" behavior #38

Open pneudecorb opened 4 years ago

pneudecorb commented 4 years ago

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"] })}, }

m0hithreddy commented 2 years ago

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.

elonzh commented 2 years ago

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))