miguelgrinberg / microblog

The microblogging application developed in my Flask Mega-Tutorial series. This version maps to the 2024 Edition of the tutorial.
http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world
MIT License
4.52k stars 1.62k forks source link

Question regarding Flask-WTF SelectMultipleField and Migration scripts #80

Closed tripdes closed 6 years ago

tripdes commented 6 years ago

Hi,

I've had alot of fun finishing your book and consider it a great refresh of the Oreilly book, but in my attempts to branch out I have encountered a minor issue when attempting to implement a Flask-WTF SelectMultipleField form with choices bound to a SQLAlchemy query.

Specifically when creating a SelectMultipleField in my main blueprint forms.py file:

from flask_wtf import FlaskForm
from wtforms import (StringField, PasswordField, BooleanField, \
TextAreaField, HiddenField, SelectMultipleField)
from app.models import db, Category

app = create_app()
app.app_context().push()

class ComposeForm(FlaskForm):
    categories = SelectMultipleField('Categories', choices=[(category.id, category.name.title()) for category in db.session.query(Category).all()], coerce=int)

Now if I comment out the categories column and run flask db init my migrations folder is created successfully, however if I leave it I encounter the following error:

Traceback (most recent call last):
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
    context)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
    cursor.execute(statement, parameters)
sqlite3.OperationalError: no such table: category

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/tripdes/Projects/microblog/venv/bin/flask", line 11, in <module>
    sys.exit(main())
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/flask/cli.py", line 513, in main
    cli.main(args=args, prog_name=name)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/flask/cli.py", line 380, in main
    return AppGroup.main(self, *args, **kwargs)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/flask/cli.py", line 256, in decorator
    with __ctx.ensure_object(ScriptInfo).load_app().app_context():
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/flask/cli.py", line 237, in load_app
    rv = locate_app(self.app_import_path)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/flask/cli.py", line 90, in locate_app
    __import__(module)
  File "/Users/tripdes/Projects/microblog/microblog.py", line 4, in <module>
    app = create_app()
  File "/Users/tripdes/Projects/microblog/app/__init__.py", line 50, in create_app
    from app.main import bp as main_bp
  File "/Users/tripdes/Projects/microblog/app/main/__init__.py", line 5, in <module>
    from app.main import routes
  File "/Users/tripdes/Projects/microblog/app/main/routes.py", line 8, in <module>
    from app.main.forms import EditProfileForm, PostForm, SearchForm, MessageForm
  File "/Users/tripdes/Projects/microblog/app/main/forms.py", line 12, in <module>
    class EditProfileForm(FlaskForm):
  File "/Users/tripdes/Projects/microblog/app/main/forms.py", line 17, in EditProfileForm
    categories = SelectMultipleField('Category', choices=[(category.id, category.name) for category in db.session.query(Category).all()], coerce=int)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2703, in all
    return list(self)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2855, in __iter__
    return self._execute_and_instances(context)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 2878, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 945, in execute
    return meth(self, multiparams, params)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/sql/elements.py", line 263, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1053, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1189, in _execute_context
    context)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1402, in _handle_dbapi_exception
    exc_info
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 203, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 186, in reraise
    raise value.with_traceback(tb)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1182, in _execute_context
    context)
  File "/Users/tripdes/Projects/microblog/venv/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 470, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: category [SQL: 'SELECT category.id AS category_id, category.name AS category_name \nFROM category']

The form is instantiated in my view but I'm trying to wrap my head around why the query is being executed now?

Any help would be greatly appreciated.

Sam

miguelgrinberg commented 6 years ago

The problem is that the query for your categories runs in the global scope (i.e. when Python parses your script prior to running it), not when you create an instance of the form class.

You have two options to do this. You can either use the QuerySelectMultipleField from the wtforms-sqlalchemy extension package, or else you have to move the initialization of the choices to the __init__() function, so that it happens when the form instance is created. That would be more or less like this:

class ComposeForm(FlaskForm):
    categories = SelectMultipleField('Categories', coerce=int)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.categories.choices=[(category.id, category.name.title()) for category in db.session.query(Category).all()]
tripdes commented 6 years ago

@miguelgrinberg That's interesting regarding the categories running in the global scope. I came dangerously close to the example above but retreated as I thought I must be doing something wrong.

Thank you @miguelgrinberg, I really appreciate you taking the time in getting back to me! Hopefully this helps someone else as I could find very little online. Again, thanks for a great book, flask-migrate and I look forward to following your future projects.

Sam