If I recall correctly the real significance of virtual_only is that fields added with this flag are copied to child models. Generic relations need this as they need to know the model they are used on. Normal fields are not copied, instead they are fetched directly from the parent model when _meta is queried.
So, it might be possible to rename virtual_only to something like copy_to_childs, and then virtuality of a field would be determined only on it having a column.
Even better would be if fields had a get_migration_operations(from_state, to_state) method. Then migrations wouldn't have to know anything else than to call that method. This would also be very useful method for 3rd party fields.
- Anssi