Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enum TypeError: object of type 'enumVar' has no len() #162

Open
adorsett opened this issue Mar 8, 2023 · 1 comment
Open

enum TypeError: object of type 'enumVar' has no len() #162

adorsett opened this issue Mar 8, 2023 · 1 comment

Comments

@adorsett
Copy link

adorsett commented Mar 8, 2023

Using Flask, SQLAlchemy, WTForms against an MySQL DB.

Imports

from flask import Flask, render_template, request, url_for, flash, redirect
from waitress import serve
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms_alchemy import model_form_factory, ModelFieldList
from wtforms.fields import FormField, SelectField
from sqlalchemy import Column, Integer, String, ForeignKey, Date, Enum, Table
from sqlalchemy.orm import declarative_base, relationship, sessionmaker, aliased, backref, Query
import enum

Have an Enumerated Type Field that's stored in MySQL as a native Enum Column

class Category(enum.Enum):
    Cat1 = 'Cat1'
    Cat2 = 'Cat2'
    Cat3 = 'Cat3'

Model Definition

class Label(Base):
    __tablename__ = "labels"
    id = Column(Integer, primary_key=True)
    abbreviation = Column(String(10), nullable=False)
    label = Column(String(50), nullable=False)
    category = Column(Enum(Category))

Setting up Globals

db = SQLAlchemy(app)
Base = db.Model
BaseModelForm = model_form_factory(FlaskForm)

class ModelForm(BaseModelForm):
    @classmethod
    def get_session(self):
        return db.session

Use ModelForm to Auto-Build Form

class LabelForm(ModelForm):
    class Meta:
        model = Label
        strip_string_fields = True
        include_foreign_keys = True

Flask Handler Snippet

    label = Label()
    success = False

    if request.method == 'POST':
        form = LabelForm(request.form, obj=label)
        print(request.form)
        if form.validate():
            form.populate_obj(label)
            db.session.add(label)
            db.session.commit()
            success = True
            flash("Label Saved", 'notice')
        else:
            flash(f"Form Failed Validation: {form.errors}", 'error')

The Form in the HTML Template

    <form method="post">
        {{ form.hidden_tag() }}
        {% for field in form %}
          {% if field.widget.input_type != 'hidden' %} {{ field.label }} {% endif %}
          {{ field }}
        {% endfor %}

        <input type="submit" value="Save">
        <input type="reset" value="Reset">

The Form will show perfectly using the above template when selecting a pre-existing DB entry by ID. If I try to "Edit" that entry by simply clicking "Save" I get the following traceback.

adminui  | Traceback (most recent call last):
adminui  |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 2528, in wsgi_app
adminui  |     response = self.full_dispatch_request()
adminui  |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1825, in full_dispatch_request
adminui  |     rv = self.handle_user_exception(e)
adminui  |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1823, in full_dispatch_request
adminui  |     rv = self.dispatch_request()
adminui  |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1799, in dispatch_request
adminui  |     return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
adminui  |   File "/src/src/app.py", line 246, in clearancedetail
adminui  |     if form.validate():
adminui  |   File "/usr/local/lib/python3.8/site-packages/wtforms/form.py", line 329, in validate
adminui  |     return super().validate(extra)
adminui  |   File "/usr/local/lib/python3.8/site-packages/wtforms/form.py", line 146, in validate
adminui  |     if not field.validate(self, extra):
adminui  |   File "/usr/local/lib/python3.8/site-packages/wtforms/fields/core.py", line 242, in validate
adminui  |     stop_validation = self._run_validation_chain(form, chain)
adminui  |   File "/usr/local/lib/python3.8/site-packages/wtforms/fields/core.py", line 262, in _run_validation_chain
adminui  |     validator(form, self)
adminui  |   File "/usr/local/lib/python3.8/site-packages/wtforms/validators.py", line 138, in __call__
adminui  |     length = field.data and len(field.data) or 0
adminui  | TypeError: object of type 'Category' has no len()

I added a len() method to the Enum object and that cleared the Traceback but then I get a data validation error when calling form.validate().

This works but I want my Enum definitions stored in a central place and not in the middle of my Model definition:

class Label(Base):
    # ...
    category = Column(Enum("Cat1", "Cat2", "Cat3", name="Category", default="Cat1"))
@adorsett
Copy link
Author

adorsett commented Mar 8, 2023

This workaround worked. https://stackoverflow.com/a/51858425/15231085

Once I defined enum_field_options() I had to manually add the field using SelectField.
category = SelectField("Category", **enum_field_options(Category))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant