Django配置ManyToManyField后新增UserSearchLocation报user_id不存在错误
问题根因
column base_usersearchlocation.user_id does not exist报错的核心原因是模型关联逻辑完全错位:
- 定义的
UserSearchLocation表仅包含city_name字段,没有任何和User的外键关联,但Django admin渲染该模型操作逻辑时,会尝试从这张表查询不存在的user_id字段,本质是多对多中间表、搜索记录表、城市基础信息表的职责完全混淆。 City模型已经通过through='UsersLocations'指定和User的多对多中间表为UsersLocations,又单独定义UserSearchLocation模型,既不关联用户也不关联城市,完全没有接入多对多关联链路,属于冗余定义。- 结合「存储用户搜索过的城市、登录用户仅可查看自己搜索过的城市」的业务需求,根本不需要单独的
UserSearchLocation模型,现有UsersLocations中间表完全可以承担搜索记录存储职责,重复建表只会导致关联逻辑混乱。
修正步骤
1. 清理冗余模型,修正关联定义
直接删除无用的UserSearchLocation模型,把搜索时间这类扩展字段加到中间表UsersLocations上即可,修正后的模型代码如下:
from django.db import models from django.contrib.auth.models import User class City(models.Model): id = models.AutoField(primary_key=True, editable=False) location = models.CharField(max_length=85) country = models.CharField(max_length=85, blank=True) country_code = models.CharField(max_length=2, blank=True) latitude = models.DecimalField(max_digits=6, decimal_places=4, null=True, blank=True) longitude = models.DecimalField(max_digits=6, decimal_places=4, null=True, blank=True) zip_code = models.PositiveIntegerField(default=0) # 多对多字段放到普通字段后定义,避免引用顺序问题 user_searched_locations = models.ManyToManyField( User, through='UsersLocations', through_fields=('city', 'user'), related_name="my_cities", blank=True ) def __str__(self): return f'{self.location}, {self.country_code}' def save(self, *args, **kwargs): self.location = self.location.capitalize() self.country = self.country.capitalize() self.country_code = self.country_code.capitalize() return super(City, self).save(*args, **kwargs) class Meta: verbose_name_plural = 'cities' unique_together = ("location", "country_code") class UsersLocations(models.Model): id = models.AutoField(primary_key=True, editable=False) user = models.ForeignKey(User, on_delete=models.CASCADE, null=False) city = models.ForeignKey( City, on_delete=models.CASCADE, related_name='locations_by_users', null=False ) # 可新增搜索时间字段,方便后续做搜索历史排序 search_time = models.DateTimeField(auto_now_add=True) class Meta: # 增加唯一约束,避免同一用户重复绑定同一城市产生冗余数据 unique_together = ('user', 'city')
注意:多对多字段不要放在模型主键前面定义,容易触发模型引用顺序问题;业务要求必须关联用户和城市的场景下,外键不要设null=True,避免产生无归属的脏数据。
2. 执行数据库迁移
因为删除了冗余模型、调整了字段约束,必须重新生成迁移文件并同步到数据库:
python manage.py makemigrations python manage.py migrate
如果迁移时提示旧的UserSearchLocation表存在冲突,先备份该表数据后删除对应旧迁移记录,再重新执行迁移即可。
3. 实现数据权限控制逻辑
不需要在模型层做特殊改造,直接在API视图层过滤查询集即可。如果使用DRF框架,重写视图的get_queryset方法就能实现「用户只能看到自己搜索过的城市」的要求:
from rest_framework import generics from rest_framework.permissions import IsAuthenticated # 引入自己定义的序列化器和模型 from .serializers import CitySerializer from .models import City class CityListView(generics.ListAPIView): serializer_class = CitySerializer # JWT认证会自动将当前登录用户注入request.user permission_classes = [IsAuthenticated] def get_queryset(self): return City.objects.filter(locations_by_users__user=self.request.user)
用户触发城市搜索时,按以下逻辑写入数据即可:
# 先查询城市是否存在,不存在则创建基础城市数据 city, created = City.objects.get_or_create( location=search_location_name.capitalize(), country_code=search_country_code.upper(), defaults={ "country": search_country_name.capitalize(), # 经纬度、邮编等字段从第三方位置接口返回值填充 } ) # 绑定当前用户和城市的关联,加ignore_conflicts避免重复插入报错 UsersLocations.objects.get_or_create(user=request.user, city=city)
该逻辑下,其他用户搜索同一个城市时,只会新增自己和该城市的关联记录,不会修改城市基础数据,同时每个用户只能查询到自己关联过的城市,完全匹配业务要求。
报错补充说明
如果清理冗余模型后仍然触发同类错误,大概率是之前给UserSearchLocation加过指向User的外键,后续删除字段时没有执行数据库迁移,旧表结构残留或admin缓存逻辑仍在尝试读取不存在的user_id字段,清空冗余表重新迁移后问题会完全消失。
内容的提问来源于stack exchange,提问作者vhij
相关产品推荐
相关产品推荐

