Flask-admin: Flask-Admin doesn't populates correct value from Enum column

Created on 20 Jul 2016  路  5Comments  路  Source: flask-admin/flask-admin

I have a working example here: https://github.com/citizen-stig/flask_admin_enum

I investigated it a little bit and maybe problem is in SQLalchemy and how it provides information about Enum column.

Most helpful comment

We employed a similar workaround:

import sqlalchemy
from flask_admin.form.fields import Select2Field
from flask_admin.model.form import converts

class EnumField(Select2Field):
    def __init__(self, column, **kwargs):
        assert isinstance(column.type, sqlalchemy.sql.sqltypes.Enum)

        def coercer(value):
            # coerce incoming value into an enum value
            if isinstance(value, column.type.enum_class):
                return value
            elif isinstance(value, basestring):
                return column.type.enum_class[value]
            else:
                assert False

        super(EnumField, self).__init__(
            choices=[(v, v) for v in column.type.enums],
            coerce=coercer,
            **kwargs)

    def pre_validate(self, form):
        # we need to override the default SelectField validation because it
        # apparently tries to directly compare the field value with the choice
        # key; it is not clear how that could ever work in cases where the
        # values and choice keys must be different types

        for (v, _) in self.choices:
            if self.data == self.coerce(v):
                break
        else:
            raise ValueError(self.gettext('Not a valid choice'))


class CustomAdminConverter(flask_admin.contrib.sqla.form.AdminModelConverter):
    @converts("sqlalchemy.sql.sqltypes.Enum")
    def conv_enum(self, field_args, **extra):
        return EnumField(column=extra["column"], **field_args)


class DefaultView(flask_admin.contrib.sqla.ModelView):
    model_form_converter = CustomAdminConverter

All 5 comments

Since I use https://github.com/spoqa/sqlalchemy-enum34 I created custom enum fields.
Maybe it could work with the default enum column:

from enum import EnumMeta, Enum
from wtforms import SelectMultipleField
from flask_admin.form import Select2Field
from wtforms.compat import text_type


class EnumField:

    @staticmethod
    def get_name(enum_instance):
        return enum_instance.name

    @staticmethod
    def get_value(enum_instance):
        return enum_instance.value

    default_label_function = get_value

    def apply_choices(self, enum_class: EnumMeta, label_function=default_label_function, kwargs=None):
        if not kwargs:
            kwargs = dict()
        choices = [(member.value, label_function(member)) for name, member in enum_class.__members__.items()]
        kwargs['choices'] = choices
        kwargs['coerce'] = self.coerce
        return kwargs

    @staticmethod
    def coerce(data):
        if isinstance(data, Enum):
            return data.value
        return text_type(data)


class SelectEnumField(Select2Field, EnumField):
    def __init__(self, *args, enum_class: EnumMeta, label_function=EnumField.default_label_function, **kwargs):
        kwargs = self.apply_choices(enum_class, label_function, kwargs)
        super().__init__(*args, **kwargs)


class SelectMultipleEnumField(SelectMultipleField, EnumField):
    def __init__(self, *args, enum_class: EnumMeta, label_function=EnumField.default_label_function, **kwargs):
        kwargs = self.apply_choices(enum_class, label_function, kwargs)
        super().__init__(*args, **kwargs)

You could of course edit the default_label_function to any function you want to create the label (there are two predefined ones - using the enum value: get_value and its name get_name. You could do better like getting the name and calling enum_instance.name.title() on it etc.

Is there a fix in place that hasn't been published yet or is a workaround the only short-term way forward. Is there an official workaround that is published and known to work?

We employed a similar workaround:

import sqlalchemy
from flask_admin.form.fields import Select2Field
from flask_admin.model.form import converts

class EnumField(Select2Field):
    def __init__(self, column, **kwargs):
        assert isinstance(column.type, sqlalchemy.sql.sqltypes.Enum)

        def coercer(value):
            # coerce incoming value into an enum value
            if isinstance(value, column.type.enum_class):
                return value
            elif isinstance(value, basestring):
                return column.type.enum_class[value]
            else:
                assert False

        super(EnumField, self).__init__(
            choices=[(v, v) for v in column.type.enums],
            coerce=coercer,
            **kwargs)

    def pre_validate(self, form):
        # we need to override the default SelectField validation because it
        # apparently tries to directly compare the field value with the choice
        # key; it is not clear how that could ever work in cases where the
        # values and choice keys must be different types

        for (v, _) in self.choices:
            if self.data == self.coerce(v):
                break
        else:
            raise ValueError(self.gettext('Not a valid choice'))


class CustomAdminConverter(flask_admin.contrib.sqla.form.AdminModelConverter):
    @converts("sqlalchemy.sql.sqltypes.Enum")
    def conv_enum(self, field_args, **extra):
        return EnumField(column=extra["column"], **field_args)


class DefaultView(flask_admin.contrib.sqla.ModelView):
    model_form_converter = CustomAdminConverter

No official fix pushed yet ?

FYI - Using this solution to declare the field does not conflict with the ENUM type https://stackoverflow.com/a/33042283/50348

And now I do not have to supply the form_override list either in Flask-Admin.

sale_status = db.Column(db.Enum('ACTIVE, 'SOLD, 'PENDING', name='sale_status_enum'))

From psql:

CREATE TYPE sale_status_enum AS ENUM (
    'ACTIVE',
    'SOLD',
    'PENDING',
);
Was this page helpful?
0 / 5 - 0 ratings

Related issues

macfire picture macfire  路  3Comments

coreybrett picture coreybrett  路  4Comments

nickretallack picture nickretallack  路  6Comments

pmazurek picture pmazurek  路  6Comments

fwiersENO picture fwiersENO  路  6Comments