J'essaie de rendre mon modèle utilisateur RESTful via Django Appels API Rest Framework, 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 la création de leur compte. J'ai essayé d'utiliser read_only_fields, mais cela semblait désactiver ce champ dans les opérations POST, donc je n'ai pas pu spécifier de nom d'utilisateur lors de la création de l'objet utilisateur.
Comment puis-je procéder pour l'implémenter? Le code pertinent pour l'API tel qu'il 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!
Il semble que vous ayez besoin de différents sérialiseurs pour les méthodes POST et PUT. Dans le sérialiseur pour la méthode PUT, vous pouvez simplement exclure le champ du nom d'utilisateur (ou définir le champ du nom d'utilisateur en lecture seule).
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-rest-framework: GET et PUT indépendants dans la même URL mais vue générique différente
Une autre option (DRF3 uniquement)
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 est de modifier le perform_update
méthode lors de l'utilisation de 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:
Il s'avère que Rest Framework est déjà équipé de cette fonctionnalité. La façon correcte d'avoir un champ "créer uniquement" consiste à utiliser l'option CreateOnlyDefault()
.
Je suppose que la seule chose qui reste à dire est de lire les documents !!! http://www.Django-rest-framework.org/api-guide/validators/#createonlydefault
Ancienne réponse:
On dirait que je suis assez tard pour la fête mais voici mes deux cents quand même.
Pour moi, cela n'a pas de sens d'avoir deux sérialiseurs différents simplement parce que vous voulez empêcher la mise à jour d'un champ. J'ai eu exactement le même problème et l'approche que j'ai utilisée était d'implémenter ma propre méthode validate
dans la classe Serializer. Dans mon cas, le champ que je ne souhaite pas mettre à jour s'appelle owner
. Voici le code pertinent:
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 invalide. Si vous ne souhaitez pas le faire et que vous souhaitez autoriser l'opération à se terminer sans mettre à jour le champ à la place, procédez comme suit:
supprimer la ligne:
raise ValidationError('Cannot update owner')
et remplacez-le par:
data.update({'owner': originalOwner})
J'espère que cela t'aides!
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
class UserUpdateSerializer(UserSerializer):
class Meta(UserSerializer.Meta):
fields = ('username', 'email')
class UserViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
return UserUpdateSerializer if self.action == 'update' else super().get_serializer_class()
djangorestframework == 3.8.2
Une autre méthode serait d'ajouter une méthode de validation, mais de renvoyer une erreur de validation si l'instance existe déjà et que 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: Validation de champ de niveau dans Django rest framework 3.1 - accès à l'ancienne valeur
Une autre solution (en dehors de la création d'un sérialiseur séparé) serait de faire apparaître le nom d'utilisateur dans 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 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 l'équivalent de getattr (). Les arguments sont un objet, une chaîne et une valeur arbitraire. La chaîne peut nommer un attribut existant ou un nouvel attribut. La fonction attribue la valeur à l'attribut, à condition que l'objet le permette. Par exemple, setattr (x, 'foobar', 123) est équivalent à x.foobar = 123.
This post mentionne quatre façons différentes d'atteindre cet objectif.
C'était la façon la plus propre, je pense: [la collection ne doit pas être modifiée]
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)
Plus manière universelle à "Désactiver la mise à jour des champs après la création de l'objet" - ajustez read_only_fields per View.action
1) ajouter une méthode à Serializer (mieux utiliser vos propres cls de base)
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 à la méta du dict du sérialiseur nommé actions_readonly_fields
class Meta:
model = YourModel
fields = '__all__'
actions_readonly_fields = {
('update', 'partial_update'): ('client', )
}
Dans l'exemple ci-dessus, le champ client
deviendra en lecture seule pour les actions: 'update', 'partial_update' (c'est-à-dire pour les méthodes PUT, PATCH)