Pergunta

Ao tentar desserializar alguns dados em um objeto, se eu incluir um campo que é único e dê a ele o valor que já está atribuído a um objeto no banco de dados, eu recebo um erro de restrição de chave.Isso faz sentido, já que ele está tentando criar um objeto com um valor único, que já está em uso.

Existe uma maneira de ter uma get_or_create tipo de funcionalidade para um ModelSerializer?Eu quero ser capaz de dar o Serializador de alguns dados, e se existe um objeto que tem dado campo exclusivo, em seguida, basta devolver o objeto.

Foi útil?

Solução

Na minha experiência nmgeek a solução não vai funcionar na DRF 3+ como serializador.is_valid() corretamente honras do modelo unique_together restrição.Você pode contornar isto removendo a UniqueTogetherValidator e substituindo o serializador do método de criar.

class MyModelSerializer(serializers.ModelSerializer):

    def run_validators(self, value):
        for validator in self.validators:
            if isinstance(validator, validators.UniqueTogetherValidator):
                self.validators.remove(validator)
        super(MyModelSerializer, self).run_validators(value)

    def create(self, validated_data):
        instance, _ = models.MyModel.objects.get_or_create(**validated_data)
        return instance

    class Meta:
        model = models.MyModel

Outras dicas

O Serializador restore_object método foi removido começando com a versão 3.0 do RESTO do Quadro.

Uma maneira simples de adicionar get_or_create funcionalidade é a seguinte:

class MyObjectSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyObject
        fields = (
                  'unique_field',
                  'other_field',
                  )

    def get_or_create(self):
        defaults = self.validated_data.copy()
        identifier = defaults.pop('unique_field')
        return MyObject.objects.get_or_create(unique_field=identifier, defaults=defaults)

def post(self, request, format=None):
    serializer = MyObjectSerializer(data=request.data)
    if serializer.is_valid():
        instance, created = serializer.get_or_create()
        if not created:
            serializer.update(instance, serializer.validated_data)
        return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

No entanto, não me parece que o código resultante é mais compacto e fácil de entender do que se consultar se a instância existe, em seguida, actualizar ou salvar dependendo do resultado da consulta.

Há um par de cenários onde um serializador pode precisar de ser capaz de obter ou criar Objetos, com base em dados recebidos por uma visão - onde não é lógico para o modo de exibição para fazer a pesquisa / criar a funcionalidade - que eu corri para esta semana.

Sim, é possível ter get_or_create a funcionalidade de um Serializador.Há uma dica sobre isto na documentação aqui: http://www.django-rest-framework.org/api-guide/serializers#specifying-which-fields-should-be-write-only onde:

  • restore_object método: foi escrito para instanciar novos usuários.
  • O instance atributo é fixa como None para garantir que este método não é utilizado para atualizar os Usuários.

Eu acho que você pode ir mais longe com isso para colocar toda a get_or_create no restore_object - neste caso, o carregamento de Utilizadores do seu endereço de e-mail que foi postado a um modo de exibição:

class UserFromEmailSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()
        fields = [
            'email',
        ]

    def restore_object(self, attrs, instance=None):
        assert instance is None, 'Cannot update users with UserFromEmailSerializer'

        (user_object, created) = get_user_model().objects.get_or_create(
            email=attrs.get('email')
        )

        # You can extend here to work on `user_object` as required - update etc.

        return user_object

Agora você pode usar o serializador em um modo de exibição post o método, por exemplo:

def post(self, request, format=None):

    # Serialize "new" member's email
    serializer = UserFromEmailSerializer(data=request.DATA)

    if not serializer.is_valid():
        return Response(serializer.errors,
                        status=status.HTTP_400_BAD_REQUEST)

    # Loaded or created user is now available in the serializer object:
    person=serializer.object
    # Save / update etc.

@Groady a resposta de obras, mas agora você perdeu a sua capacidade de validar a singularidade quando a criação de novos objetos (UniqueValidator foi removido da sua lista de validadores, independentemente do cicumstance).A idéia de usar um serializador é que você tem uma maneira abrangente para criar um novo objeto que valida a integridade dos dados que você deseja usar para criar o objeto.A remoção de validação não é o que você deseja.Você quer esta validação para estar presente quando da criação de novos objetos, você gostaria de ser capaz de lançar os dados no seu serializador e obter o direito de comportamento sob o capô (get_or_create), de validação e de tudo incluído.

Eu recomendo a substituição do is_valid() método de serializador em vez disso.Com o código abaixo você primeiro verifique para ver se o objeto existe no seu banco de dados, se não você continuar com uma validação completa, como de costume.Se ela não existir, você simplesmente anexar este objeto para o serializador e, em seguida, prosseguir com a validação, como de costume, como se você tivesse instanciado o serializador com o objeto associado e de dados.Em seguida, quando você acertar o serializador.salvar (), você poderá obter de volta o seu já objecto criado, e você pode ter o mesmo código padrão em um nível alto:instanciar o serializador com dados, ligue para .is_valid(), e , em seguida, chamar .save() e ter retornado o seu modelo de instância (la get_or_create).Não há necessidade de substituir .create() ou .update().

O truque aqui é que você vai ter uma desnecessários UPDATE transação no banco de dados quando você acertar .save(), mas o custo de um extra chamada de banco de dados para ter um limpo API para programadores com uma validação completa ainda estão no lugar, parece que vale a pena.Ele também permite a extensibilidade do uso de modelos personalizados.Gerenciador de modelos.QuerySet para identificar exclusivamente o seu modelo a partir de alguns campos (o que quer que o principal identificar os campos de dados) e, em seguida, usando o restante dos dados initial_data sobre o Serializador como uma atualização para o objeto em questão, permitindo assim que você agarrar objetos exclusivos a partir de um subconjunto dos campos de dados e tratar os demais campos como atualizações para o objeto (no caso, o UPDATE chamada não seria extra).

Note que as chamadas para super() estão em Python3 sintaxe.Se estiver usando Python 2, você gostaria de usar o antigo estilo: super(MyModelSerializer, self).is_valid(**kwargs)

from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned


class MyModelSerializer(serializers.ModelSerializer):

    def is_valid(self, raise_exception=False):
        if hasattr(self, 'initial_data'):
            # If we are instantiating with data={something}
            try:
                # Try to get the object in question
                obj = Security.objects.get(**self.initial_data)
            except (ObjectDoesNotExist, MultipleObjectsReturned):
                # Except not finding the object or the data being ambiguous
                # for defining it. Then validate the data as usual
                return super().is_valid(raise_exception)
            else:
                # If the object is found add it to the serializer. Then
                # validate the data as usual
                self.instance = obj
                return super().is_valid(raise_exception)
        else:
            # If the Serializer was instantiated with just an object, and no
            # data={something} proceed as usual 
            return super().is_valid(raise_exception)

    class Meta:
        model = models.MyModel

Uma melhor maneira de fazer isso é usar o PUT verbo, em vez disso, em seguida, substituir o get_object() método na ModelViewSet.Eu respondi isso aqui: https://stackoverflow.com/a/35024782/3025825.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top