You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 20:40:25