Django数据库迁移失败:因forms.py调用未创建的blog_category表
你遇到的这个问题本质是Django执行migrate命令时的加载顺序冲突:
当你运行python manage.py migrate时,Django会先加载项目的URL配置,而你的URL配置导入了blog_post_create_view,进而触发了BlogPostForm的导入。在BlogPostForm的定义里,你直接调用了category_list()函数生成下拉框选项——这个函数会立刻去查询Category表,但此时migrate还没来得及创建这张表,自然就抛出了no such table: blog_category的错误。
简单说:模块加载阶段就执行了数据库查询,但目标数据库表还不存在。
针对这个问题,有几种实用的解决方式,按推荐程度排序:
1. 改用Django自带的ModelChoiceField(最推荐)
因为你的category字段是关联Category模型的外键,ModelForm默认会自动使用ModelChoiceField,它会延迟数据库查询,直到表单实际被实例化时才去获取选项,而不是在模块加载阶段就执行查询。
你只需要修改forms.py,去掉手动指定的category widget配置,让ModelForm自动处理,同时通过重写__init__方法保留你需要的CSS类:
from django import forms from .models import BlogPost class BlogPostForm(forms.ModelForm): class Meta: model = BlogPost fields = ['title', 'category', 'content', 'image', 'private', 'tags'] widgets ={ 'title': forms.TextInput(attrs={'class': 'form-control'}), # 删掉手动生成choices的这一行 # 'category': forms.Select(choices=category_list(), attrs={'class': 'form-control'}), 'content': forms.Textarea(attrs={'class': 'form-control'}), 'image': forms.FileInput(attrs={'class': 'form-control-file'}), 'private': forms.CheckboxInput(attrs={'class': 'form-check-label'}), 'tags': forms.TextInput(attrs={'class': 'form-control'}), } # 重写__init__给category字段添加样式类 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['category'].widget.attrs.update({'class': 'form-control'})
这样既保留了你需要的UI样式,又彻底避免了模块加载时的数据库查询问题。
2. 让category_list延迟执行查询
如果你坚持要手动生成choices,可以用Django的lazy函数包装你的查询逻辑,让它延迟到实际需要使用时才执行:
首先修改utils.py:
from django.utils.functional import lazy from .models import Category def get_category_list(): choice_list = [] # 修正原逻辑的判断错误:存在分类则添加,否则显示空选项 categories = Category.objects.all().values_list('name', 'name') if categories.exists(): choice_list.extend(categories) else: choice_list.append(('', '暂无分类,请先添加')) return choice_list # 用lazy包装,让函数延迟执行 category_list = lazy(get_category_list, list)
然后修改forms.py中调用的地方(去掉括号,因为lazy返回的是可调用对象):
'category': forms.Select(choices=category_list, attrs={'class': 'form-control'}),
这样category_list只会在表单被实例化时才会触发数据库查询,不会影响migrate的执行。
3. 临时注释代码执行migrate(临时方案)
这就是你已经尝试过的方法:先注释掉forms.py中调用category_list()的行,执行migrate创建表后再取消注释。但这只是临时 workaround,每次部署新环境都要重复操作,不推荐长期使用。
原utils.py中的逻辑有个小错误:if not Category.objects.all().values_list('name', 'name').exists()的判断是反向的,会导致永远不会进入else分支。上面的解决方案已经修正了这个逻辑,你可以根据实际需求调整空选项的显示文本。
内容的提问来源于stack exchange,提问作者Dragos Neata

