1

I have a model which uses an enum to define an access level as follows:

class DevelModelView(ModelView):
    edit_modal = True

    def is_accessible(self):
        return current_user.is_authenticated and current_user.access is AccessLevel.DEVEL

class DevelStaffModelView(DevelModelView):
    column_editable_list = ['access']
    column_filters = ['access']
    column_searchable_list = ['login', 'email']
    form_choices = {'access': [(AccessLevel.DEVEL.name, AccessLevel.DEVEL.value),
                               (AccessLevel.ADMIN.name, AccessLevel.ADMIN.value),
                               (AccessLevel.STAFF.name, AccessLevel.STAFF.value)]}

The enum definition is below...

class AccessLevel(Enum):
    DEVEL = 'Developer'
    ADMIN = 'Administrator'
    STAFF = 'Staff Member'

Using the form_choices attribute I was able to show in both the modal and the editable column choices in value form (IE: Developer) but unfortunately the display is still using the name (IE: Name).

To clarify, I'm essentially asking if there is anyway to have Flask Admin display the value of an enum as opposed to the name by default in the display table. Thank you in advance...

Also providing the Staff model just in case it is helpful...

class Staff(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    login = db.Column(db.String(64), unique=True)
    _password = db.Column(db.String(128))
    email = db.Column(db.String(100))
    access = db.Column('access', db.Enum(AccessLevel))

    @hybrid_property
    def password(self):
        return self._password

    @password.setter
    def password(self, plaintext):
        self._password = bcrypt.generate_password_hash(plaintext)

    def check_password(self, plaintext):
        return bcrypt.check_password_hash(self._password, plaintext)

    def __str__(self):
        return "%s: %s (%s)" % (self.access.name, self.login, self.email)
ThatTechGuy
  • 879
  • 1
  • 10
  • 29

2 Answers2

1

You need column_formatters functionality to display proper values in the table. Formatter function is defined like this:

def access_level_formatter(view, context, model, name):
    db_value = getattr(model, name)
    enum_value = getattr(AccessLevel, db_value)
    return enum_value.value

And you need to specify this formatter in the view class that it is used for the access column:

class DevelStaffModelView(DevelModelView):
    column_formatters = {
        'access': access_level_formatter,
    }
Sergey Shubin
  • 3,040
  • 4
  • 24
  • 36
1

If you have several enum types to display, rather than creating individual column_formatters you can update the column_type_formatters that Flask-Admin uses.

Example

from flask_admin.model import typefmt


class AccessLevel(Enum):
    DEVEL = 'Developer'
    ADMIN = 'Administrator'
    STAFF = 'Staff Member'

# ...

MY_DEFAULT_FORMATTERS = dict(typefmt.BASE_FORMATTERS)

MY_DEFAULT_FORMATTERS.update({
   AccessLevel: lambda view, access_level_enum: access_level_enum.value  # could use a function here
})


class DevelModelView(ModelView):

    column_type_formatters = MY_DEFAULT_FORMATTERS

    #  ...

Also consider setting up the AccessLevel choices as described in this SO answer. It means you don't have to repeat the enum name/values in your model view definition. Note the __str__ and __html__ methods in the AccessLevel class.

Example

from flask_admin.model import typefmt
from wtforms import SelectField


class AccessLevel(Enum):
    DEVEL = 'Developer'
    ADMIN = 'Administrator'
    STAFF = 'Staff Member'

    def __str__(self):
        return self.name  # value string

    def __html__(self):
        return self.value  # option labels


def enum_field_options(enum):
    """Produce WTForm Field instance configuration options for an Enum

    Returns a dictionary with 'choices' and 'coerce' keys, use this as
    **enum_fields_options(EnumClass) when constructing a field:

    enum_selection = SelectField("Enum Selection", **enum_field_options(EnumClass))

    Labels are produced from enum_instance.__html__() or
    str(eum_instance), value strings with str(enum_instance).

    """
    assert not {'__str__', '__html__'}.isdisjoint(vars(enum)), (
        "The {!r} enum class does not implement a __str__ or __html__ method")

    def coerce(name):
        if isinstance(name, enum):
            # already coerced to instance of this enum
            return name
        try:
            return enum[name]
        except KeyError:
            raise ValueError(name)

    return dict(choices=[(v, v) for v in enum], coerce=coerce)


class DevelModelView(ModelView):

    column_type_formatters = MY_DEFAULT_FORMATTERS

    #  ...

    form_overrides = {
        'access': SelectField,
    }

    form_args = {
        'access': enum_field_options(AccessLevel),
    }

    # ...
pjcunningham
  • 7,676
  • 1
  • 36
  • 49