webentwicklung-frage-antwort-db.com.de

Django Rest Framework: Deaktiviert die Feldaktualisierung, nachdem das Objekt erstellt wurde

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 durchführe, 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 dies schien das Feld in POST) - Vorgängen zu deaktivieren, sodass ich beim Erstellen des Benutzerobjekts keinen Benutzernamen angeben konnte.

Wie kann ich das 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!

56
Brad Reardon

Es scheint, dass Sie verschiedene Serializer für POST und PUT-Methoden benötigen. In der Serializer für 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 Generics-Ansicht

52

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
22
VoSi

Mein Ansatz ist es, die perform_update -Methode bei Verwendung von generischen Ansichtsklassen. Ich entferne das Feld, wenn ein Update 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()
4
Gooshan

UPDATE:

Es stellt sich heraus, dass Rest Framework bereits mit dieser Funktionalität ausgestattet ist. Die korrekte Art, ein "Nur-Erstellen" -Feld zu haben, ist die Verwendung der Option CreateOnlyDefault().

Ich denke, das einzige, was noch zu sagen bleibt, ist Read the Docs !!! http://www.Django-rest-framework.org/api-guide/validators/#createonlydefault

Alte Antwort:

Ich komme ziemlich spät zur Party, aber hier sind 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 den Ansatz, den ich benutzte, bestand darin, meine eigene validate -Methode in der Serializer-Klasse zu implementieren. In meinem Fall heißt das Feld, das nicht aktualisiert werden soll, owner. 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 hebe ein ValidationError an, 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 das Feld zu aktualisieren, gehen Sie wie folgt vor:

entferne die Linie:

raise ValidationError('Cannot update owner')

und ersetze es durch:

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

Hoffe das hilft!

3
LuisCien

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
2
Alex Rothberg
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

1
gzerone

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-Feld-Validierung in Django Rest Framework 3.1 - Zugriff auf den alten Wert

1
rrauenza

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
1
Pawel Kozela

Wenn Sie keinen weiteren Serializer erstellen möchten, können Sie versuchen, get_serializer_class() in MyViewSet anzupassen. 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

setattr (Objekt, Name, Wert)

Dies ist das Gegenstück zu getattr (). Die Argumente sind ein Objekt, eine Zeichenfolge und ein beliebiger Wert. Die Zeichenfolge kann ein vorhandenes Attribut oder ein neues Attribut benennen. Die Funktion weist dem Attribut den Wert zu, sofern das Objekt dies zulässt. Beispielsweise entspricht setattr (x, 'foobar', 123) x.foobar = 123.

1
Nogurenn

This post nennt vier verschiedene Wege, um dieses Ziel zu erreichen.

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)
1
Hojat Modaresi

Mehr niverseller Weg zu "Feldaktualisierung deaktivieren, nachdem Objekt erstellt wurde" - anpassen read_only_fields per View.action

1) füge eine Methode zu Serializer hinzu (besser deine eigenen Basis-Cls verwenden)

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 der Meta des Serializer-Dikts den Namen actions_readonly_fields hinzu

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

Im obigen Beispiel ist das Feld client für folgende Aktionen schreibgeschützt: 'update', 'partial_update' (dh für PUT- und PATCH-Methoden)

1
pymen