Django-rest-framework: Une contrainte unique empêche les sérialiseurs imbriqués de se mettre à jour

Créé le 2 juin 2015  ·  21Commentaires  ·  Source: encode/django-rest-framework

Si vous avez un sérialiseur imbriqué dont le modèle a une contrainte unique sur un champ, vous ne pouvez pas référencer une instance existante.

class Task(serializers.ModelSerializer):
    class Meta:
        model = models.Task
        fields = ('id', 'name')


class Category(serializers.ModelSerializer):
    tasks = Task(many=True)
    class Meta:
        model = models.Category
        fields = ('id', 'name', 'tasks')

Disons que j'ai créé une catégorie et une tâche que je mets à jour avec les mêmes données qu'elles ont déjà :

c = Category(data={"id": 1, "name": "Django", "tasks": [{"id": 1, "name": "Demo", "owner": "admin"}]})
c.is_valid()  # Returns False
c.errors  # Returns {'tasks': [{'name': ['This field must be unique.']}]}

Le fait est name reste unique car il correspond à son propre id

Documentation

Commentaire le plus utile

Les gars, j'ai aussi rencontré ce problème.

Je voulais juste dire que (malheureusement) sans un WritableNestedModelSerializer générique prêt à l'emploi, le framework Django REST n'est guère utile. Avec les API hyperliens uniquement, vous devez souvent créer des chaînes de 3-4-5 requêtes ajax POST/PUT consécutives, ce qui est une perte de temps. En écrire un à partir de zéro est problématique, répétitif et nécessite une bonne connaissance des composants internes de DRF. Plus important encore, si vous souhaitez exposer votre API à des spécialistes du domaine (par exemple, des bioinformaticiens), ils n'achèteront pas d'hyperlien. Ils veulent une API claire avec des structures de données imbriquées et, pour être honnête, ils ont raison.

J'étudie les détails de cela depuis quelques jours maintenant, je suis resté bloqué sur cette erreur et je ne sais pas si je dois continuer à jouer avec les composants internes de DRF ou arrêter (en brisant les délais) d'investir du temps dans cela et commencer à apprendre Loopback.

Je suis presque sûr qu'avec l'avènement de la fonction update_or_create dans Django 1.7, il est possible de penser à une manière générique d'imbriquer des sérialiseurs inscriptibles.

Tous les 21 commentaires

Il se peut que nous devions simplement documenter cela comme une limitation dans validators .
Il n'y aura pas vraiment de bons moyens automatiques pour gérer cela.

Je suis un peu mal à l'aise avec ça.
Disons que notre ressource imbriquée est soit créée, soit extraite de la base de données ala get_or_create .
Si nous supprimons la validation, elle risque de s'exécuter dans des numéros ultérieurs.
Si nous le gardons, il sera impossible d'obtenir un élément qui existe déjà sans le modifier.

Nous ne prenons pas en charge la sérialisation imbriquée en écriture (automatique), il n'est donc pas totalement déraisonnable de dire également que les "validateurs" ne sont pas appropriés pour ce cas, voici ce que vous devez faire. Cependant, s'il existe une alternative, cela vaudrait la peine d'être envisagé.

@arw180 en effet. Merci d'avoir signalé ce problème, je l'avais complètement oublié.
Ajout d'une référence à #3312 puisque c'est la même chose.

Arrgh, j'ai cherché pendant un jour pourquoi ça ne fonctionnait pas !!!

C'est un problème vraiment déroutant, car il est courant de jouer avec des modèles imbriqués, et je ne sais pas comment contourner ce problème ...

J'ai ça :

class Contact(models.Model):
    account = models.OneToOneField(Account, null=True)
    first_name = models.CharField(max_length=40, blank=True)
    last_name = models.CharField(max_length=40, blank=True)
    phone = PhoneNumberField(blank=True, null=True)
    phone_alt = PhoneNumberField(blank=True, null=True)
    birthday = models.DateField(blank=True, null=True)
    comment = models.TextField(blank=True, null=True)
    created = models.DateTimeField(auto_now_add=True)
    modified = models.DateTimeField(auto_now=True)
    is_musician = models.BooleanField(default=False)
    is_volunteer = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
class Account(AbstractBaseUser):
    email = models.EmailField(unique=True)
    tagline = models.CharField(max_length=140, blank=True)
    is_admin = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    objects = AccountManager()

    USERNAME_FIELD = 'email'
class ContactSerializer(serializers.ModelSerializer):
    account = AccountContactSerializer()

    class Meta:
        model = Contact
        read_only_fields = ('created', 'modified',)

def create(self, validated_data):
        account_data = validated_data.pop('account')
        account = Account.objects.create_user(**account_data)
        contact = Contact.objects.create(account=account, **validated_data)

        return contact
class AccountContactSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        fields = ('id', 'email', 'password',)

    def create(self, validated_data):
        return Account.objects.create(**validated_data)

Et je dois enregistrer un contact avec un compte, ils sont indissociables. Comment puis-je faire cela sans ce message de validateur unique ennuyeux ?

Le groupe de discussion est le meilleur endroit pour mener cette discussion et d'autres questions d'utilisation. Merci!

Alors, quelle est la sortie? Cela me semble être une limitation majeure.

  1. Écriture d'un sérialiseur non modèle personnalisé. - Presque tous mes terminaux auraient alors des sérialiseurs personnalisés.
  2. Ignorer 'id' de la méthode 'to_internal_value'.* - Peut rencontrer des problèmes plus tard à cause de cela.
  3. Un moyen d'utiliser le sérialiseur de modèle mais de modifier la règle de validation pour le sérialiseur imbriqué ? une sorte de mixin .. Ce serait un moyen idéal, je ne sais pas comment le faire.
  4. vérifier le numéro 2403.

La solution consiste à supprimer la contrainte unique du sérialiseur et à la déplacer vers la partie validation de la logique métier, car le sérialiseur imbriqué ne peut pas dire - à ce jour - s'il met à jour ou crée un nouvel objet.

Les gars, j'ai aussi rencontré ce problème.

Je voulais juste dire que (malheureusement) sans un WritableNestedModelSerializer générique prêt à l'emploi, le framework Django REST n'est guère utile. Avec les API hyperliens uniquement, vous devez souvent créer des chaînes de 3-4-5 requêtes ajax POST/PUT consécutives, ce qui est une perte de temps. En écrire un à partir de zéro est problématique, répétitif et nécessite une bonne connaissance des composants internes de DRF. Plus important encore, si vous souhaitez exposer votre API à des spécialistes du domaine (par exemple, des bioinformaticiens), ils n'achèteront pas d'hyperlien. Ils veulent une API claire avec des structures de données imbriquées et, pour être honnête, ils ont raison.

J'étudie les détails de cela depuis quelques jours maintenant, je suis resté bloqué sur cette erreur et je ne sais pas si je dois continuer à jouer avec les composants internes de DRF ou arrêter (en brisant les délais) d'investir du temps dans cela et commencer à apprendre Loopback.

Je suis presque sûr qu'avec l'avènement de la fonction update_or_create dans Django 1.7, il est possible de penser à une manière générique d'imbriquer des sérialiseurs inscriptibles.

mais le sérialiseur imbriqué ne peut-il pas déterminer s'il met à jour une instance en vérifiant parent.instance ?

@dedsm bien sûr, mais cela briserait également les cas où la contrainte d'unicité est brisée sur le sérialiseur "principal" et se retrouverait dans 500 erreurs avec des erreurs de base de données.

Désolé, je ne peux pas vraiment dire à partir de ce fil; ce problème a-t-il été résolu ?

J'essaie de mettre à jour un champ ManyToMany via un sérialiseur imbriqué et il reste bloqué sur run_validators() car l'objet de relation que j'essaie d'attacher à mon objet principal existe déjà.

Ce serait bien de mettre au moins la solution de contournement recommandée (avec quelques détails) pour un cas d'utilisation assez courant comme celui-ci.

La solution consiste à supprimer la contrainte unique du sérialiseur et à la déplacer vers la partie validation de la logique métier, car le sérialiseur imbriqué ne peut pas dire - à ce jour - s'il met à jour ou crée un nouvel objet.

Je suis récemment tombé sur ce problème (un objet utilisateur attaché à un objet compte via une relation intermédiaire).

Dans le contexte du compte, je voulais pouvoir mettre à jour à la fois les détails de l'utilisateur et les détails de la relation en tant que point de terminaison unique. Le problème étant que le champ e-mail sur le modèle utilisateur est unique et est tombé en panne du problème décrit ici.

Je n'étais pas satisfait de casser une validation très nécessaire, j'ai donc adopté une approche plus directe. Au moment de l'initialisation du sérialiseur, si l'e-mail fourni dans les données est _déjà le même_ que l'e-mail de l'utilisateur, je l'ai simplement supprimé des données. Aucun changement = aucune validation requise*.

def __init__(self, instance, data=empty, **kwargs):
    if (
        data is not empty and
        'details.email' in data and
        instance.user is not None and
        instance.user.email == data['details.email']
    ):
        del data['details.email']
    super(AccountUserSerializer, self).__init__(instance, data=data, **kwargs)

    • D'accord, techniquement, vous pouvez modifier le validateur d'e-mail et demander aux utilisateurs dont les e-mails ne sont plus valides de les modifier, mais cela semble une possibilité lointaine à l'extrême.

@xordoquy , est-ce que ça marcherait ?

class MySerializer(serializers.ModelSerializer):
    nested = NestedSerializer()
    ...

    def get_fields(self):
        fields = super().get_fields()

        if self.instance is not None:
            nested_serializer = copy.deepcopy(fields.pop('nested'))
            nested_serializer.instance = self.instance.nested
            fields['nested'] = nested_serializer
        return fields

Le groupe de discussion est le meilleur endroit pour approfondir cette discussion et d'autres questions d'utilisation. Merci!

Je ne sais pas s'il existe une documentation pour cela maintenant, mais j'ai récemment résolu ce problème avec un validateur zéro de base sur le terrain qui cause le problème.
J'ai mis beaucoup de temps à découvrir cela, alors j'espère que quelqu'un d'autre m'aidera
youruniquefield = serializers.CharField(max_length=255, min_length=4, validators=[])
Avant cela, je soulève toujours une erreur déjà existante même si je corrige le problème avec les gestionnaires pour utiliser ger_or_create.

J'ai écrit il y a quelque temps un article à ce sujet si cela peut aider : https://medium.com/django-rest-framework/dealing-with-unique-constraints-in-nested-serializers-dade33b831d9

il y a cette bibliothèque tierce https://github.com/beda-software/drf-writable-nested , elle a un UniqueFieldsMixin qui peut faire la magie pour gérer des contraintes uniques.

pour ceux qui finissent ici => cette réponse SO aide
https://stackoverflow.com/a/32452603/2559785

Cette page vous a été utile?
0 / 5 - 0 notes