Search code examples
python-2.7flaskflask-sqlalchemyflask-admin

Flask Admin Display Enum Value Instead of Name


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)

Solution

  • 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),
        }
    
        # ...