web-dev-qa-db-fra.com

Django Rest Framework donne l'impression que la relation OnetoOne est un modèle

J'ai mon User enregistré dans deux modèles différents, UserProfile et User. Du point de vue API, personne ne se soucie vraiment que ces deux-là soient différents.

Alors là, j'ai:

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'first_name', 'last_name', 'email')

et

class UserPSerializer(serializers.HyperlinkedModelSerializer):
    full_name = Field(source='full_name')
    class Meta:
        model = UserProfile
        fields = ('url', 'mobile', 'user','favourite_locations')

Ainsi, dans UserPSerializer, le champ user n'est qu'un lien vers cette ressource. Mais du point de vue de l'utilisateur, il n'a vraiment aucune raison de connaître User.

Existe-t-il des astuces avec lesquelles je peux simplement les mélanger et les présenter à l'utilisateur comme un modèle ou dois-je le faire manuellement d'une manière ou d'une autre.

39
nickik

Vous pouvez POST et PUT avec approche @ kahlo si vous remplacez également les méthodes de création et de mise à jour sur votre sérialiseur.

Étant donné un modèle de profil comme celui-ci:

class Profile(models.Model):
    user = models.OneToOneField(User)
    avatar_url = models.URLField(default='', blank=True)  # e.g.

Voici un sérialiseur utilisateur qui lit et écrit les champs de profil supplémentaires:

class UserSerializer(serializers.HyperlinkedModelSerializer):
    # A field from the user's profile:
    avatar_url = serializers.URLField(source='profile.avatar_url', allow_blank=True)

    class Meta:
        model = User
        fields = ('url', 'username', 'avatar_url')

    def create(self, validated_data):
        profile_data = validated_data.pop('profile', None)
        user = super(UserSerializer, self).create(validated_data)
        self.update_or_create_profile(user, profile_data)
        return user

    def update(self, instance, validated_data):
        profile_data = validated_data.pop('profile', None)
        self.update_or_create_profile(instance, profile_data)
        return super(UserSerializer, self).update(instance, validated_data)

    def update_or_create_profile(self, user, profile_data):
        # This always creates a Profile if the User is missing one;
        # change the logic here if that's not right for your app
        Profile.objects.update_or_create(user=user, defaults=profile_data)

L'API résultante présente une ressource utilisateur plate, comme souhaité:

GET /users/5/
{
    "url": "http://localhost:9090/users/5/", 
    "username": "test", 
    "avatar_url": "http://example.com/avatar.jpg"
}

et vous pouvez inclure le profil du avatar_url champ dans les deux POST et PUT. (Et DELETE sur la ressource utilisateur supprimera également son modèle de profil, bien que ce ne soit que la cascade de suppression normale de Django.)

La logique ici va toujours créer un modèle de profil pour l'utilisateur s'il est manquant (sur toute mise à jour). Avec les utilisateurs et les profils, c'est probablement ce que vous voulez. Pour d'autres relations, il se peut que ce ne soit pas le cas et vous devrez modifier la logique de mise à jour ou de création. (C'est pourquoi DRF n'écrit pas automatiquement via une relation imbriquée pour vous.)

33
medmunds

Je viens de rencontrer ça; Je n'ai pas encore trouvé de bonne solution, en particulier pour réécrire sur mes modèles User et UserProfile. J'aplatis actuellement mes sérialiseurs manuellement en utilisant le SerializerMethodField, ce qui est extrêmement irritant, mais cela fonctionne:

class UserSerializer(serializers.HyperlinkedModelSerializer):

    mobile = serializers.SerializerMethodField('get_mobile')
    favourite_locations = serializers.SerializerMethodField('get_favourite_locations')

    class Meta:
        model = User
        fields = ('url', 'username', 'first_name', 'last_name', 'email', 'mobile', 'favourite_locations')

    def get_mobile(self, obj):
        return obj.get_profile().mobile

    def get_favourite_locations(self, obj):
        return obj.get_profile().favourite_locations

C'est horriblement manuel, mais vous vous retrouvez avec:

{
    "url": "http://example.com/api/users/1",
    "username": "jondoe",
    "first_name": "Jon",
    "last_name": "Doe",
    "email": "[email protected]",
    "mobile": "701-680-3095",
    "favourite_locations": [
        "Paris",
        "London",
        "Tokyo"
    ]
}

Je suppose que c'est ce que vous cherchez.

12
bbengfort

Je voudrais implémenter les modifications sur le UserPSerializer car les champs ne vont pas augmenter:

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('url', 'username', 'first_name', 'last_name', 'email')

class UserPSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.CharField(source='user.url')
    username = serializers.CharField(source='user.username')
    first_name = serializers.CharField(source='user.first_name')
    last_name = serializers.CharField(source='user.last_name')
    email = serializers.CharField(source='user.email')

    class Meta:
        model = UserProfile
        fields = ('mobile', 'favourite_locations',
                  'url', 'username', 'first_name', 'last_name', 'email')
6
kahlo