title: Flask SQLAlchemy upgrade failing after updating models. Need an explanation on how my fix works
body: <p>I'm following along with a book to try and create a Flask blog. I am new to Flask. I updated a model so the code looked like this:</p>
  
  <pre><code>class Entry(db.Model):
      STATUS_PUBLIC = 0
      STATUS_DRAFT = 1
      STATUS_DELETED = 2
  
      id = db.Column(db.Integer, primary_key=True)
      title = db.Column(db.String(100))
      slug = db.Column(db.String(100), unique=True)
      body = db.Column(db.Text)
      status = db.Column(db.SmallInteger, default=STATUS_PUBLIC)
      created_timestamp = db.Column(db.DateTime, default=datetime.datetime.now)
      modified_timestamp = db.Column(
          db.DateTime,
          default=datetime.datetime.now,
          onupdate=datetime.datetime.now)
      author_id = db.Column(db.Integer, db.ForeignKey("user.id"))
  </code></pre>
  
  <p>Adding the last line <code>author_id = db.Column(db.Integer, db.ForeignKey("user.id"))</code> is causing the database upgrade to fail with an error:</p>
  
  <pre><code>  File "manage.py", line 5, in &lt;module&gt;
      manager.run()
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/flask_script/__init__.py", line 412, in run
      result = self.handle(sys.argv[0], sys.argv[1:])
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/flask_script/__init__.py", line 383, in handle
      res = handle(*args, **config)
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/flask_script/commands.py", line 216, in __call__
      return self.run(*args, **kwargs)
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/flask_migrate/__init__.py", line 259, in upgrade
      command.upgrade(config, revision, sql=sql, tag=tag)
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/command.py", line 254, in upgrade
      script.run_env()
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/script/base.py", line 425, in run_env
      util.load_python_file(self.dir, 'env.py')
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/util/pyfiles.py", line 93, in load_python_file
      module = load_module_py(module_id, path)
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/util/compat.py", line 75, in load_module_py
      mod = imp.load_source(module_id, path, fp)
    File "migrations/env.py", line 87, in &lt;module&gt;
      run_migrations_online()
    File "migrations/env.py", line 80, in run_migrations_online
      context.run_migrations()
    File "&lt;string&gt;", line 8, in run_migrations
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/runtime/environment.py", line 836, in run_migrations
      self.get_context().run_migrations(**kw)
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/runtime/migration.py", line 330, in run_migrations
      step.migration_fn(**kw)
    File "/home/devblog/projects/blog/app/migrations/versions/6b49bdaf06ce_.py", line 22, in upgrade
      batch_op.create_foreign_key(None, 'user', ['author_id'], ['id'])
    File "/usr/lib64/python2.7/contextlib.py", line 24, in __exit__
      self.gen.next()
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/operations/base.py", line 299, in batch_alter_table
      impl.flush()
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/operations/batch.py", line 80, in flush
      fn(*arg, **kw)
    File "/home/devblog/projects/blog/venv/lib/python2.7/site-packages/alembic/operations/batch.py", line 337, in add_constraint
      raise ValueError("Constraint must have a name")
  ValueError: Constraint must have a name
  </code></pre>
  
  <p>The issue seemed to be with the migration script's upgrade function here:</p>
  
  <pre><code>def upgrade():
      # ### commands auto generated by Alembic - please adjust! ###
      with op.batch_alter_table('entry', schema=None) as batch_op:
          #batch_op.create_foreign_key(None, 'user', ['author_id'], ['id'])
          batch_op.create_foreign_key("author_id", 'user', ['author_id'], ['id'])
  </code></pre>
  
  <p>I "fixed" this by commenting out the original line, removing <code>None</code>, and using <code>'author_id'</code>.</p>
  
  <p>I used the documentation here <a href="http://alembic.zzzcomputing.com/en/latest/ops.html" rel="noreferrer">http://alembic.zzzcomputing.com/en/latest/ops.html</a> to come up with the fix. Relevant section:</p>
  
  <blockquote>
    <p>create_foreign_key(constraint_name, source_table, referent_table,
    local_cols, remote_cols, onupdate=None, ondelete=None,
    deferrable=None, initially=None, match=None, source_schema=None,
    referent_schema=None, **dialect_kw)</p>
    
    <p>This internally generates a Table object containing the necessary
    columns, then generates a new ForeignKeyConstraint object which it
    then associates with the Table. Any event listeners associated with
    this action will be fired off normally. The AddConstraint construct is
    ultimately used to generate the ALTER statement.</p>
    
    <p>Parameters:    name – Name of the foreign key constraint. The name is
    necessary so that an ALTER statement can be emitted. For setups that
    use an automated naming scheme such as that described at Configuring
    Constraint Naming Conventions, name here can be None, as the event
    listener will apply the name to the constraint object when it is
    associated with the table. source_table – String name of the source
    table. referent_table – String name of the destination table.
    local_cols – a list of string column names in the source table.
    remote_cols – a list of string column names in the remote table.
    onupdate – Optional string. If set, emit ON UPDATE  when
    issuing DDL for this constraint. Typical values include CASCADE,
    DELETE and RESTRICT. ondelete – Optional string. If set, emit ON
    DELETE  when issuing DDL for this constraint. Typical values
    include CASCADE, DELETE and RESTRICT. deferrable – optional bool. If
    set, emit DEFERRABLE or NOT DEFERRABLE when issuing DDL for this
    constraint. source_schema – Optional schema name of the source table.
    referent_schema¶ – Optional schema name of the destination table.</p>
  </blockquote>
  
  <p>I <em>think</em> I've added a constraint name instead of <code>None</code>, which has "fixed" the issue. Is this correct?</p>
  
  <p>Also, it looks like there are 5 arguments necessary for the function to work. How does it work with just 4?</p>
  
  <p>Any advice gratefully appreciated.</p>