Question

Lorsque j'essaie de désérialiser certaines données dans un objet, si j'inclus un champ unique et que je lui donne une valeur déjà attribuée à un objet dans la base de données, j'obtiens une erreur de contrainte clé.Cela a du sens, car il s’agit de créer un objet avec une valeur unique déjà utilisée.

Existe-t-il un moyen d'avoir une fonctionnalité de type get_or_create pour un ModelSerializer ?Je veux pouvoir donner certaines données au sérialiseur, et s'il existe un objet possédant le champ unique donné, renvoyez simplement cet objet.

Était-ce utile?

La solution

Dans mon expérience La solution de NMGEEK ne fonctionnera pas dans la DRF 3+ comme Serializer.is_valid () honore correctement la contrainte unique du modèle.Vous pouvez contourner cela en supprimant l'UNIQUETOGETHERVALIDADOR et en remplaçant la méthode de votre sériologiste.

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

Autres conseils

La méthode Serializer Restore_Object a été supprimée à partir de la version 3.0 du cadre de repos.

Un moyen simple d'ajouter get_or_create La fonctionnalité est la suivante:

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)

Cependant, il ne me semble pas que le code résultant soit plus compact ou facile à comprendre que si vous interrogez si l'instance existe, puis mettez à jour ou économisez en fonction du résultat de la requête.

Il existe quelques scénarios dans lesquels un sérialiseur pourrait devoir être en mesure d'obtenir ou de créer des objets basés sur les données reçues par une vue - où il n'est pas logique que la vue effectue la fonctionnalité de recherche/création - j'ai rencontré cela cette semaine.

Oui, il est possible d'avoir get_or_create fonctionnalité dans un sérialiseur.Il y a un indice à ce sujet dans la documentation ici : http://www.django-rest-framework.org/api-guide/serializers#specifying-which-fields-should-be-write-only où:

  • restore_object La méthode a été écrite pour instancier de nouveaux utilisateurs.
  • Le instance l'attribut est corrigé comme None pour garantir que cette méthode n'est pas utilisée pour mettre à jour les utilisateurs.

Je pense que tu peux aller plus loin avec ça pour mettre plein get_or_create dans le restore_object - dans ce cas, chargement des utilisateurs à partir de leur adresse e-mail qui a été publiée dans une vue :

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

Vous pouvez désormais utiliser le sérialiseur dans une vue post méthode, par exemple :

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.

La réponse de @Groady fonctionne, mais vous avez maintenant perdu votre capacité à valider l'unicité lors de la création de nouveaux objets (UniqueValidator a été supprimé de votre liste de validateurs quelles que soient les circonstances).L'idée générale de l'utilisation d'un sérialiseur est que vous disposez d'un moyen complet de créer un nouvel objet qui valide l'intégrité des données que vous souhaitez utiliser pour créer l'objet.Supprimer la validation n'est pas ce que vous voulez.Vous voulez que cette validation soit présente lors de la création de nouveaux objets, vous aimeriez simplement pouvoir envoyer des données à votre sérialiseur et obtenir le bon comportement sous le capot (get_or_create), la validation et tout inclus.

Je recommanderais d'écraser votre is_valid() méthode sur le sérialiseur à la place.Avec le code ci-dessous, vous vérifiez d'abord si l'objet existe dans votre base de données, sinon vous procédez à une validation complète comme d'habitude.S'il existe, attachez simplement cet objet à votre sérialiseur, puis procédez à la validation comme d'habitude comme si vous aviez instancié le sérialiseur avec l'objet et les données associés.Ensuite, lorsque vous appuyez sur serializer.save(), vous récupérerez simplement votre objet déjà créé et vous pourrez avoir le même modèle de code à un niveau élevé :instanciez votre sérialiseur avec des données, appelez .is_valid(), puis appelle .save() et récupérez votre instance de modèle (à la get_or_create).Pas besoin d'écraser .create() ou .update().

La mise en garde ici est que vous obtiendrez un inutile UPDATE transaction sur votre base de données lorsque vous appuyez sur .save(), mais le coût d'un appel de base de données supplémentaire pour disposer d'une API de développeur propre avec une validation complète toujours en place semble rentable.Il vous permet également d'utiliser des modèles personnalisés.Manager et des modèles personnalisés.QuerySet pour identifier de manière unique votre modèle à partir de quelques champs uniquement (quels que soient les champs d'identification principaux), puis d'utiliser le reste des données dans initial_data sur le sérialiseur comme mise à jour de l'objet en question, vous permettant ainsi de récupérer des objets uniques à partir d'un sous-ensemble de champs de données et de traiter les champs restants comme des mises à jour de l'objet (auquel cas le UPDATE l'appel ne serait pas en supplément).

Notez que les appels à super() sont dans la syntaxe Python3.Si vous utilisez Python 2, vous souhaiterez utiliser l'ancien style : 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

Une meilleure façon de procéder consiste à utiliser le PUT verbe à la place, puis remplacez le get_object() méthode dans le ModelViewSet.J'ai répondu ici : https://stackoverflow.com/a/35024782/3025825.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top