Django Rest Framework: désactiver la mise à jour du champ après la création de l'objet

j'essaie de rendre mon modèle Utilisateur RESTful via les appels D'API Rest de Django, afin de pouvoir créer des utilisateurs et mettre à jour leurs profils.

cependant, comme je passe par un processus de vérification particulier avec mes utilisateurs, Je ne veux pas que les utilisateurs aient la possibilité de mettre à jour le nom d'utilisateur après que leur compte est créé. J'ai essayé d'utiliser read_only_fields, mais cela a semblé désactiver ce champ dans les opérations POST, donc je n'ai pas été en mesure de spécifier un nom d'utilisateur quand la création de l'objet utilisateur.

Comment puis-je m'y prendre? Le code pertinent pour L'API telle qu'elle existe maintenant est ci-dessous.

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

    def restore_object(self, attrs, instance=None):
        user = super(UserSerializer, self).restore_object(attrs, instance)
        user.set_password(attrs['password'])
        return user


class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    serializer_class = UserSerializer
    model = User

    def get_permissions(self):
        if self.request.method == 'DELETE':
            return [IsAdminUser()]
        elif self.request.method == 'POST':
            return [AllowAny()]
        else:
            return [IsStaffOrTargetUser()]

Merci!

46
demandé sur Brad Reardon 2014-03-02 10:19:47

10 réponses

il semble que vous ayez besoin de différents sérialiseurs pour les méthodes POST et PUT. Dans la méthode serializer for PUT vous pouvez juste excepté le champ username (ou définir le champ username comme Lu seulement).

class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    serializer_class = UserSerializer
    model = User

    def get_serializer_class(self):
        serializer_class = self.serializer_class

        if self.request.method == 'PUT':
            serializer_class = SerializerWithoutUsernameField

        return serializer_class

    def get_permissions(self):
        if self.request.method == 'DELETE':
            return [IsAdminUser()]
        elif self.request.method == 'POST':
            return [AllowAny()]
        else:
            return [IsStaffOrTargetUser()]

Cochez cette question django-repos-cadre: indépendant OBTENIR et de METTRE dans la même URL mais différents médicaments génériques vue

49
répondu Andrei Kaigorodov 2017-05-23 12:18:24

une autre option (DRF3 seulement)

class MySerializer(serializers.ModelSerializer):
    ...
    def get_extra_kwargs(self):
        extra_kwargs = super(MySerializer, self).get_extra_kwargs()
        action = self.context['view'].action

        if action in ['create']:
            kwargs = extra_kwargs.get('ro_oncreate_field', {})
            kwargs['read_only'] = True
            extra_kwargs['ro_oncreate_field'] = kwargs

        elif action in ['update', 'partial_update']:
            kwargs = extra_kwargs.get('ro_onupdate_field', {})
            kwargs['read_only'] = True
            extra_kwargs['ro_onupdate_field'] = kwargs

        return extra_kwargs
15
répondu VoSi 2016-09-09 15:50:52

mon approche consiste à modifier la méthode perform_update lorsqu'on utilise des classes de vue génériques. Je supprime le champ lorsque la mise à jour est effectuée.

class UpdateView(generics.UpdateAPIView):
    ...
    def perform_update(self, serializer):
        #remove some field
        rem_field = serializer.validated_data.pop('some_field', None)
        serializer.save()
3
répondu Gooshan 2017-06-16 10:33:10

mise à jour:

S'avère que Rest Framework est déjà équipé de cette fonctionnalité. La façon correcte d'avoir un champ "Créer seulement" est en utilisant l'option CreateOnlyDefault() .

je suppose que la seule chose qui reste à dire est de lire les Docs!!! http://www.django-rest-framework.org/api-guide/validators/#createonlydefault

Ancienne Réponse:

on dirait que je suis en retard à la fête mais voilà mes deux cents de toute façon.

pour moi ça n'a pas de sens d'avoir deux sérialiseurs différents juste parce que vous voulez empêcher un champ d'être mis à jour. J'ai eu ce même problème et l'approche que j'ai utilisée était de mettre en œuvre ma propre méthode validate dans la classe Serializer. Dans mon cas, le champ que je ne veux pas mettre à jour s'appelle owner . Voici le code correspondant:

class BusinessSerializer(serializers.ModelSerializer):

    class Meta:
        model = Business
        pass

    def validate(self, data):
        instance = self.instance

        # this means it's an update
        # see also: http://www.django-rest-framework.org/api-guide/serializers/#accessing-the-initial-data-and-instance
        if instance is not None: 
            originalOwner = instance.owner

            # if 'dataOwner' is not None it means they're trying to update the owner field
            dataOwner = data.get('owner') 
            if dataOwner is not None and (originalOwner != dataOwner):
                raise ValidationError('Cannot update owner')
        return data
    pass
pass

et voici un test unitaire pour le valider:

def test_owner_cant_be_updated(self):
    harry = User.objects.get(username='harry')
    jack = User.objects.get(username='jack')

    # create object
    serializer = BusinessSerializer(data={'name': 'My Company', 'owner': harry.id})
    self.assertTrue(serializer.is_valid())
    serializer.save()

    # retrieve object
    business = Business.objects.get(name='My Company')
    self.assertIsNotNone(business)

    # update object
    serializer = BusinessSerializer(business, data={'owner': jack.id}, partial=True)

    # this will be False! owners cannot be updated!
    self.assertFalse(serializer.is_valid())
    pass

je lève un ValidationError parce que je ne veux pas cacher le fait que quelqu'un a essayé d'effectuer une opération non valide. Si vous ne voulez pas faire cela et que vous voulez permettre que l'opération soit complétée sans mettre à jour le champ à la place, faites ce qui suit:

supprimer la ligne:

raise ValidationError('Cannot update owner')

et le remplacer par:

data.update({'owner': originalOwner})

Espérons que cette aide!

3
répondu LuisCien 2017-11-07 23:22:18

j'ai utilisé cette approche:

def get_serializer_class(self):
    if getattr(self, 'object', None) is None:
        return super(UserViewSet, self).get_serializer_class()
    else:
        return SerializerWithoutUsernameField
2
répondu Alex Rothberg 2015-02-12 18:37:22

une autre solution (en dehors de la création d'un sérialiseur séparé) serait de pop le nom d'utilisateur d'attrs dans la méthode restore_object si l'instance est définie (ce qui signifie qu'il s'agit d'une méthode PATCH / PUT):

def restore_object(self, attrs, instance=None):
    if instance is not None:
        attrs.pop('username', None)
    user = super(UserSerializer, self).restore_object(attrs, instance)
    user.set_password(attrs['password'])
    return user
1
répondu Pawel Kozela 2014-03-17 08:51:24

si vous ne voulez pas créer un autre sérialiseur, vous pouvez essayer de personnaliser get_serializer_class() à l'intérieur de MyViewSet . Cela m'a été utile pour des projets simples.

# Your clean serializer
class MySerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = '__all__'

# Your hardworking viewset
class MyViewSet(MyParentViewSet):
    serializer_class = MySerializer
    model = MyModel

    def get_serializer_class(self):
        serializer_class = self.serializer_class
        if self.request.method in ['PUT', 'PATCH']:
            # setting `exclude` while having `fields` raises an error
            # so set `read_only_fields` if request is PUT/PATCH
            setattr(serializer_class.Meta, 'read_only_fields', ('non_updatable_field',))
            # set serializer_class here instead if you have another serializer for finer control
        return serializer_class

setattr(objet, nom, valeur)

c'est le pendant de getattr(). Le les arguments sont un objet, une chaîne et une valeur arbitraire. Chaîne peut-nom d'un attribut existant ou un nouvel attribut. Le fonction affecte la valeur de l'attribut, à condition que l'objet le permet. Pour exemple, setattr (x, 'foobar', 123) est équivalent à X. fobar = 123.

1
répondu Nogurenn 2017-06-21 06:24:39

Plus de façon universelle à "Désactiver la mise à jour du champ après la création de l'objet" - ajuster read_only_fields par View.action

1) Ajouter la méthode à Serializer (mieux d'utiliser votre propre base cls)

def get_extra_kwargs(self):
    extra_kwargs = super(BasePerTeamSerializer, self).get_extra_kwargs()
    action = self.context['view'].action
    actions_readonly_fields = getattr(self.Meta, 'actions_readonly_fields', None)
    if actions_readonly_fields:
        for actions, fields in actions_readonly_fields.items():
            if action in actions:
                for field in fields:
                    if extra_kwargs.get(field):
                        extra_kwargs[field]['read_only'] = True
                    else:
                        extra_kwargs[field] = {'read_only': True}
    return extra_kwargs

2) Ajouter au Meta de serializer dict nommé actions_readonly_fields

class Meta:
    model = YourModel
    fields = '__all__'
    actions_readonly_fields = {
        ('update', 'partial_update'): ('client', )
    }

Dans l'exemple ci-dessus client le champ deviendra en lecture seule pour les actions: 'update', 'partial_update' (c'est-à-dire pour les méthodes PUT, PATCH)

0
répondu pymen 2018-01-28 14:57:04

une Autre méthode serait d'ajouter une méthode de validation, mais jeter une erreur de validation si l'instance existe déjà et la valeur a changé:

def validate_foo(self, value):                                     
    if self.instance and value != self.instance.foo:
        raise serializers.ValidationError("foo is immutable once set.")
    return value         

dans mon cas, je voulais qu'une clé étrangère ne soit jamais mise à jour:

def validate_foo_id(self, value):                                     
    if self.instance and value.id != self.instance.foo_id:            
        raise serializers.ValidationError("foo_id is immutable once set.")
    return value         

Voir aussi: Niveau de validation de champ dans django repos cadre 3.1 - l'accès à la vieille valeur

0
répondu rrauenza 2018-06-13 16:50:10