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!
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
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
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()
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!
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
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
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.
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)
ce billet mentionne quatre façons différentes d'atteindre cet objectif.
C'était la façon la plus propre je pense: [collection ne doit pas être édité]
class DocumentSerializer(serializers.ModelSerializer):
def update(self, instance, validated_data):
if 'collection' in validated_data:
raise serializers.ValidationError({
'collection': 'You must not change this field.',
})
return super().update(instance, validated_data)
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