Ich versuche, mein Benutzermodell über Django Rest Framework-API-Aufrufe REST-fähig zu machen, damit ich Benutzer erstellen und deren Profile aktualisieren kann.
Da ich jedoch mit meinen Benutzern einen bestimmten Überprüfungsprozess durchlaufe, möchte ich nicht, dass die Benutzer den Benutzernamen aktualisieren können, nachdem ihr Konto erstellt wurde. Ich habe versucht, read_only_fields zu verwenden, aber das schien dieses Feld in POST-Vorgängen zu deaktivieren, sodass ich beim Erstellen des Benutzerobjekts keinen Benutzernamen angeben konnte.
Wie kann ich dies umsetzen? Relevanter Code für die API, wie sie jetzt existiert, ist unten.
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()]
Vielen Dank!
django
rest
django-rest-framework
Brad Reardon
quelle
quelle
Antworten:
Es scheint, dass Sie unterschiedliche Serializer für POST- und PUT-Methoden benötigen. In der Serializer for PUT-Methode können Sie nur das Feld Benutzername ausnehmen (oder das Feld Benutzername als schreibgeschützt festlegen).
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()]
Überprüfen Sie diese Frage django-rest-framework: unabhängiges GET und PUT in derselben URL, aber unterschiedlicher generischer Ansicht
quelle
Eine weitere Option (nur DRF3)
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
quelle
if self.instance is None:
anstelle von view.action check verwenden. Auf diese Weise können Sie den Serializer außerhalb der Standard-DRF-Ansichten verwenden. Dh ich habe ein Szenario, in dem ich den Serializer in einer Sellerie-Aufgabe verwende, während ich Daten aus einer externen CSV-Datei importiere. Das view-Attribut ist in diesem Fall null, aber die self.instance-Prüfung funktioniert weiterhin.action
in ViewSet.Eine andere Methode wäre, eine Validierungsmethode hinzuzufügen, aber einen Validierungsfehler auszulösen, wenn die Instanz bereits vorhanden ist und sich der Wert geändert hat:
def validate_foo(self, value): if self.instance and value != self.instance.foo: raise serializers.ValidationError("foo is immutable once set.") return value
In meinem Fall wollte ich, dass ein Fremdschlüssel niemals aktualisiert wird:
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
Siehe auch: Level-Field-Validierung im Django Rest Framework 3.1 - Zugriff auf den alten Wert
quelle
Mein Ansatz besteht darin, die
perform_update
Methode bei Verwendung von generischen Ansichtsklassen zu ändern . Ich entferne das Feld, wenn die Aktualisierung durchgeführt wird.class UpdateView(generics.UpdateAPIView): ... def perform_update(self, serializer): #remove some field rem_field = serializer.validated_data.pop('some_field', None) serializer.save()
quelle
AKTUALISIEREN:
Es stellt sich heraus, dass Rest Framework bereits mit dieser Funktionalität ausgestattet ist. Die richtige Art, ein "Nur erstellen" -Feld zu haben, ist die Verwendung der
CreateOnlyDefault()
Option.Ich denke, das einzige, was noch zu sagen ist, ist Read the Docs !!! http://www.django-rest-framework.org/api-guide/validators/#createonlydefault
Alte Antwort:
Sieht so aus, als wäre ich ziemlich spät zur Party, aber hier sind trotzdem meine zwei Cent.
Für mich ist es nicht sinnvoll, zwei verschiedene Serializer zu haben, nur weil Sie verhindern möchten, dass ein Feld aktualisiert wird. Ich hatte genau das gleiche Problem und der Ansatz, den ich verwendete, bestand darin, meine eigene
validate
Methode in der Serializer-Klasse zu implementieren . In meinem Fall wird das Feld aufgerufen, das nicht aktualisiert werden sollowner
. Hier ist der relevante Code: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
Und hier ist ein Unit-Test, um es zu validieren:
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
Ich erhebe eine,
ValidationError
weil ich nicht verbergen möchte, dass jemand versucht hat, eine ungültige Operation auszuführen. Wenn Sie dies nicht möchten und zulassen möchten, dass der Vorgang abgeschlossen wird, ohne stattdessen das Feld zu aktualisieren, gehen Sie wie folgt vor:Entfernen Sie die Zeile:
raise ValidationError('Cannot update owner')
und ersetzen Sie es durch:
data.update({'owner': originalOwner})
Hoffe das hilft!
quelle
CreateOnlyDefault
gilt nicht für von der API übermittelte Daten, sondern für Dinge, die bei der ersten Erstellung auf dem Server generiert werden, z. B. das Erstellungsdatum. Für den Benutzer ist es standardmäßig schreibgeschützt.Ich habe diesen Ansatz verwendet:
def get_serializer_class(self): if getattr(self, 'object', None) is None: return super(UserViewSet, self).get_serializer_class() else: return SerializerWithoutUsernameField
quelle
Weitere universelle Art und Weise zu „nach Objekt - Update deaktivieren Feld erzeugt“ - einstellen read_only_fields pro View.action
1) füge dem Serializer eine Methode hinzu (besser, du verwendest deine eigenen Basis-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) Fügen Sie zu Meta des Serializer- Dikts den Namen action_readonly_fields hinzu
class Meta: model = YourModel fields = '__all__' actions_readonly_fields = { ('update', 'partial_update'): ('client', ) }
Im obigen Beispiel ist das
client
Feld für Aktionen schreibgeschützt: 'update', 'teilweise_update' (dh für PUT, PATCH-Methoden)quelle
In diesem Beitrag werden vier verschiedene Möglichkeiten zur Erreichung dieses Ziels erwähnt.
Dies war der sauberste Weg, den ich denke: [Sammlung darf nicht bearbeitet werden]
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)
quelle
Eine andere Lösung (abgesehen vom Erstellen eines separaten Serializers) besteht darin, den Benutzernamen aus attrs in der restore_object-Methode einzufügen, wenn die Instanz festgelegt ist (was bedeutet, dass es sich um eine PATCH / PUT-Methode handelt):
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
quelle
restore_object
: django-rest-framework.org/topics/3.0-announcementWenn Sie keinen weiteren Serializer erstellen möchten, können Sie versuchen, das
get_serializer_class()
Innere anzupassenMyViewSet
. Dies hat mir bei einfachen Projekten geholfen.# 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
quelle
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
quelle
Ich würde vorschlagen, auch anzuschauen Django Pgtrigger zu betrachten
Auf diese Weise können Sie Trigger zur Validierung installieren. Ich habe damit angefangen und war sehr zufrieden mit seiner Einfachheit:
Hier ist eines ihrer Beispiele, das verhindert, dass ein veröffentlichter Beitrag aktualisiert wird:
import pgtrigger from django.db import models @pgtrigger.register( pgtrigger.Protect( operation=pgtrigger.Update, condition=pgtrigger.Q(old__status='published') ) ) class Post(models.Model): status = models.CharField(default='unpublished') content = models.TextField()
Der Vorteil dieses Ansatzes besteht darin,
.update()
dass Sie auch vor umgangenen Anrufen geschützt sind.save()
quelle