DRF多对多创建用户报错:GrupoOrganizacional无bulk_create属性
问题:Django用户注册时关联已存在组织组失败,报错
GrupoOrganizacional无bulk_create属性 开发用户API时,注册用户并关联已存在的GrupoOrganizacional(组织组)时遇到错误:
type object 'GrupoOrganizacional' has no attribute 'bulk_create'
用户能成功注册,但组织组没有关联上,需求是将已存在的组织组关联给新用户。
models.py
class Cliente(models.Model): nome = models.CharField(max_length=100, unique=True, null=True) documento = models.CharField(max_length=30, blank=True, default='00.000.000/0001-00') logo = models.CharField(max_length=255, blank=True, null=True) data_de_criacao = models.DateField(default=date.today) cliente_ativo = models.BooleanField(default=True) background_img = models.CharField(max_length=255, blank=True, null=True) cor_primaria = models.CharField(max_length=10, blank=True, null=True) cor_secundaria = models.CharField(max_length=10, blank=True, null=True) def __str__(self): return self.nome class GrupoOrganizacional(models.Model): id_grupo = models.BigAutoField(primary_key=True, unique=True) nome_grupo = models.CharField(max_length=100, blank=False, null=False) cliente = models.ForeignKey(Cliente, blank=True, null=True ,on_delete=models.CASCADE) def __str__(self): return self.nome_grupo class Usuario(AbstractUser): objects = CustomUserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = [] cliente = models.ForeignKey(Cliente, blank=True, null=True ,on_delete=models.CASCADE) first_name = models.CharField(max_length=100, blank=False, null=False) last_name = models.CharField(max_length=100, blank=False, null=False) username = models.CharField(max_length=50, blank=True, null=True) email = models.EmailField(max_length=100, blank=False, null=False, unique=True, error_messages={'unique': "O email cadastrado já existe."}) password = models.CharField(max_length=100, blank=False, null=False) usuario_ativo = models.BooleanField(default=True) grupo = models.ManyToManyField(GrupoOrganizacional, related_name='grupos') is_staff = models.BooleanField(default=True) is_superuser = models.BooleanField(default=False) def __str__(self): return "%s (%s)" %(self.first_name, self.cliente)
views.py
class GruposViewSet(viewsets.ModelViewSet): ''' Lista todos os grupos cadastrados ''' queryset = GrupoOrganizacional.objects.all() serializer_class = ListaGruposSerializer permission_classes = [IsAuthenticated, IsAdminUser] class UsuariosViewSet(viewsets.ModelViewSet): ''' Lista todos os usuarios cadastrados''' queryset = Usuario.objects.all() serializer_class = UsuarioSerializer permission_classes = [IsAuthenticated, IsAdminUser]
serializers.py
class GruposDoUsuarioSerializer(PrimaryKeyRelatedField ,serializers.ModelSerializer): '''Serializer para listar todos os grupos organizacionais cadastrados''' #grupo_id = serializers.PrimaryKeyRelatedField( many=True, read_only=True ) class Meta: model = GrupoOrganizacional fields = ['id_grupo'] class UsuarioSerializer(serializers.ModelSerializer): '''Serializer para listar, criar e atualizar os usuarios''' grupo = GruposDoUsuarioSerializer(many=True, queryset=GrupoOrganizacional.objects.all()) password = serializers.CharField( style={'input_type': 'password'}, write_only=True, label="Senha" ) password_confirm = serializers.CharField( style={'input_type': 'password'}, write_only=True, label="Confirme a senha" ) is_staff = serializers.BooleanField( label="Membro da Equipe", help_text="Indica que usuário consegue acessar o site de administração." ) is_superuser = serializers.BooleanField( label="SuperUsuário", help_text="Indica que este usuário tem todas as permissões sem atribuí-las explicitamente." ) class Meta: model = Usuario fields = ('id','cliente', 'first_name', 'last_name', 'username', 'email', 'password', 'password_confirm', 'usuario_ativo', 'grupo', 'is_staff', 'is_superuser') extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): '''Permite caradastrar e atualizar contas''' grupo_data = validated_data.pop('grupo') password = self.validated_data.get('password') password_confirm = self.validated_data.get('password_confirm') if password != password_confirm: raise serializers.ValidationError({'password': 'As senhas não são iguais.'}) validated_data.pop('password_confirm') usuario = Usuario.objects.create(**validated_data) grupo_organizacional_list = [] for grupo_data in grupo_data: grupo_organizacional_list.append(GrupoOrganizacional(grupo_data)) # failed attempts # grupo_organizacional_list.append(GrupoOrganizacional(**grupo_data)) # error ModelBase object argument after ** must be a mapping, not GrupoOrganizacional # grupo_organizacional_list.append(GrupoOrganizacional(**grupo_data['id_grupo'])) # error ModelBase object argument after ** must be a mapping, not GrupoOrganizacional # grupo_organizacional_list.append(Usuario(**grupo_data)) # error ModelBase object argument after ** must be a mapping, not GrupoOrganizacional # grupo_organizacional_list.append(**grupo_data['id_grupo']) # error GrupoOrganizacional' object is not subscriptable grupo_organizacional_list = GrupoOrganizacional.bulk_create(grupo_organizacional_list) # error type object 'GrupoOrganizacional' has no attribute 'bulk_create' # failed attempts # grupo_organizacional_list = GrupoOrganizacional.objects.create(grupo_organizacional_list) # error: create() takes 1 positional argument but 2 were given usuario.grupo.set(*grupo_organizacional_list) usuario.set_password(password) usuario.save() return usuario
报错堆栈信息
Internal Server Error: /usuarios/ Traceback (most recent call last): File "/home/my-pc/.local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 55, in inner response = get_response(request) File "/home/my-pc/.local/lib/python3.8/site-packages/django/core/handlers/base.py", line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/home/my-pc/.local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view return view_func(*args, **kwargs) File "/home/my-pc/.local/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view return self.dispatch(request, *args, **kwargs) File "/home/my-pc/.local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch response = self.handle_exception(exc) File "/home/my-pc/.local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception self.raise_uncaught_exception(exc) File "/home/my-pc/.local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception raise exc File "/home/my-pc/.local/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch response = handler(request, *args, **kwargs) File "/home/my-pc/.local/lib/python3.8/site-packages/rest_framework/mixins.py", line 19, in create self.perform_create(serializer) File "/home/my-pc/.local/lib/python3.8/site-packages/rest_framework/mixins.py", line 24, in perform_create serializer.save() File "/home/my-pc/.local/lib/python3.8/site-packages/rest_framework/serializers.py", line 212, in save self.instance = self.create(validated_data) File "/home/my-pc/sistema-web-beanalytic/be-api/clientes/serializers.py", line 89, in create grupo_organizacional_list = GrupoOrganizacional.bulk_create(grupo_organizacional_list) AttributeError: type object 'GrupoOrganizacional' has no attribute 'bulk_create'
问题分析与解决方案
1. 核心错误原因
bulk_create是Django模型的管理器方法,必须通过GrupoOrganizacional.objects.bulk_create()调用,而非直接在模型类上调用。- 更关键的是:你的需求是关联已存在的组织组,完全不需要使用
bulk_create——之前的代码错误地尝试创建新组织组,而非关联已有数据。
2. 序列化器冗余设计问题
自定义的GruposDoUsuarioSerializer同时继承PrimaryKeyRelatedField和ModelSerializer是错误的。对于多对多关联的已存在对象,直接使用PrimaryKeyRelatedField即可,无需额外封装。
3. 具体修复步骤
步骤1:简化序列化器
删除冗余的GruposDoUsuarioSerializer,修改UsuarioSerializer中的grupo字段定义:
class UsuarioSerializer(serializers.ModelSerializer): '''Serializer para listar, criar e atualizar os usuarios''' # 直接用PrimaryKeyRelatedField关联已存在的GrupoOrganizacional grupo = serializers.PrimaryKeyRelatedField( many=True, queryset=GrupoOrganizacional.objects.all() ) # 其余密码、权限字段代码保持不变... class Meta: model = Usuario fields = ('id','cliente', 'first_name', 'last_name', 'username', 'email', 'password', 'password_confirm', 'usuario_ativo', 'grupo', 'is_staff', 'is_superuser') extra_kwargs = {'password': {'write_only': True}}
步骤2:修改create方法逻辑
直接从验证后的数据中取出组织组ID,关联给新用户,无需创建新实例:
def create(self, validated_data): '''Permite cadastrar e atualizar contas''' # 取出组织组ID列表 grupo_ids = validated_data.pop('grupo') password = self.validated_data.get('password') password_confirm = self.validated_data.get('password_confirm') if password != password_confirm: raise serializers.ValidationError({'password': 'As senhas não são iguais.'}) validated_data.pop('password_confirm') usuario = Usuario.objects.create(**validated_data) # 用set方法直接关联已存在的组织组,传入ID列表即可 usuario.grupo.set(grupo_ids) usuario.set_password(password) usuario.save() return usuario
4. 错误逻辑复盘
- 之前循环创建
GrupoOrganizacional实例,是在尝试新建组织组,完全违背了“关联已有组织组”的需求。 - 错误调用
GrupoOrganizacional.bulk_create(),即使修正为objects.bulk_create(),也属于多余操作,因为不需要新增数据。 - 自定义序列化器导致
grupo字段的数据格式异常,增加了不必要的处理复杂度。
内容的提问来源于stack exchange,提问作者Rafael Vinicius
相关产品推荐
相关产品推荐

