pallets-eco / flask-admin

Simple and extensible administrative interface framework for Flask
BSD 3-Clause "New" or "Revised" License
5.69k stars 1.56k forks source link

Unable to Render Flask-Admin Edit Form Fields for Variables Stored as JSON #2420

Closed OpenSourceSimon closed 3 months ago

OpenSourceSimon commented 3 months ago

Hey GitHub community šŸ‘‹, I'm encountering an issue with rendering Flask-Admin edit form fields for variables stored as JSON in a PostgreSQL database. I have a table called variables where each row represents a variable, and its attributes are stored as JSON in the label column. I'm using Flask-Admin to provide an interface for users to edit these variables.

This is a simplified version of my database table: Variable Value
User1 {"name": "Name", "email": "test@example.com"}
Region1 {"region": "Europe", "name": "DHL", "color": "blue"}

Here's many of the versions of code I've tried:

import json
from flask_admin.form import BaseForm
from wtforms import StringField
from wtforms.validators import InputRequired
from flask_admin.contrib.sqla import ModelView

class VariableDatabase(db_user.Model):
    __tablename__ = "variables"
    name = db_user.Column(db_user.Integer, primary_key=True)
    label = db_user.Column(db_user.String, nullable=False)

class VariableForm(BaseForm):
    def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
        super(VariableForm, self).__init__(formdata=formdata, obj=obj, prefix=prefix, **kwargs)
        if obj is not None:
            try:
                print("Label:", obj.label)
                value = json.loads(obj.label)
                print("Decoded JSON:", value)
                for key in value.keys():
                    field_name = key
                    field_label = key.capitalize()  # Set field label
                    setattr(self, key, StringField(field_label, validators=[InputRequired()], default=value[key]))
                    print("Field:", getattr(self, key))
            except json.JSONDecodeError as e:
                print("JSON Decode Error:", e)
                # Just render the form with the default fields
                pass

    def populate_obj(self, obj):
        super(VariableForm, self).populate_obj(obj)
        obj.label = json.dumps({field: getattr(self, field).data for field in self._fields})

class VariableView(ModelView):
    form = VariableForm

# Register the view
admin.add_view(VariableView(VariableDatabase, db_user.session, name="Variabelen"))

The VariableForm dynamically creates form fields based on the JSON attributes stored in the label column of the VariableDatabase model. However, when I visit the edit page in Flask-Admin, the edit form doesn't render any fields.

I've verified that the form fields are being created correctly in the VariableForm class, but they are not appearing in the Flask-Admin interface.

Could you please provide guidance on how to resolve this issue? I'd like to ensure that users can edit the variables directly in Flask-Admin without needing knowledge of JSON.

OpenSourceSimon commented 3 months ago

Finally found a solution after months:

Variable = Base.classes.variables

class VariableView(ModelView):
    list_columns = ["variable", "value"]
    form_columns = ["variable", "value"]
    column_default_sort = ("variable", False)
    column_filters = ["variable"]
    column_sortable_list = ["variable"]
    column_searchable_list = ["variable"]
    column_formatters = {
        "variable": lambda view, context, model, name: model.variable.replace(
            "_", " "
        ).capitalize(),
    }
    def edit_form(self, obj):
        class VariableForm(FlaskForm):
            pass

        setattr(
            VariableForm,
            "variable",
            StringField("Variable", validators=[Optional()], default=obj.variable),
        )
        try:
            json_data = json.loads(obj.value)
            for key in json_data:
                setattr(
                    VariableForm,
                    key,
                    StringField(
                        key.capitalize(),
                        validators=[Optional()],
                        default=json_data[key],
                        ),
                    )
        except json.decoder.JSONDecodeError:
            setattr(
                VariableForm,
                "Value",
                StringField("Label", validators=[Optional()], default=obj.value),
            )
        return VariableForm(obj=obj)

    def on_model_change(self, form, model, is_created):
        # Update the model with the data from the form
        json_output = {}
        for field in form:
            if field.name == "value":
                return model
            if field.name != "variable" and field.name != "csrf_token":
                json_output[field.name] = field.data
        model.value = json.dumps(json_output)
        return model

admin.add_view(VariableView(Variable, user.session, name="Variables"))