Подтвердить что ты не робот

Django Rest Framework: отключить обновление поля после создания объекта

Я пытаюсь создать свою модель User RESTful через вызовы API Django Rest Framework, чтобы я мог создавать пользователей, а также обновлять их профили.

Однако, когда я просматриваю отдельный процесс проверки с моими пользователями, я не хочу, чтобы пользователи имели возможность обновлять имя пользователя после создания учетной записи. Я попытался использовать read_only_fields, но это, казалось, отключило это поле в POST-операциях, поэтому я не смог указать имя пользователя при создании пользовательского объекта.

Как я могу это реализовать? Соответствующий код API, который существует сейчас, ниже.

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()]

Спасибо!

4b9b3361

Ответ 1

Кажется, вам нужны разные сериализаторы для методов POST и PUT. В сериализаторе для метода PUT вы можете просто исключить поле имени пользователя (или указать поле имени пользователя только как прочитанное).

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()]

Проверьте этот вопрос django-rest-framework: независимые GET и PUT в том же URL-адресе, но разные общие представления

Ответ 2

Другая опция (только для 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

Ответ 3

UPDATE:

Оказывается, что Rest Framework уже оснащена этой функциональностью. Правильный способ создания поля "только для создания" - это использовать параметр CreateOnlyDefault().

Я предполагаю, что осталось только сказать, что прочитал Документы!!! http://www.django-rest-framework.org/api-guide/validators/#createonlydefault

Старый ответ:

Похоже, я опаздываю на вечеринку, но все равно мои два цента.

Для меня нет смысла иметь два разных сериализатора только потому, что вы хотите предотвратить обновление поля. У меня была такая же проблема, и подход, который я использовал, заключался в реализации моего собственного метода validate в классе Serializer. В моем случае поле, которое я не хочу обновлять, называется owner. Вот соответствующий код:

class BusinessSerializer(serializers.ModelSerializer):

    class Meta:
        model = Business
        pass

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

        # this means it 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

И вот unit test, чтобы проверить его:

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

Я поднимаю ValidationError, потому что не хочу скрывать, что кто-то пытался выполнить недопустимую операцию. Если вы не хотите этого делать, и вы хотите разрешить выполнение операции без обновления поля, сделайте следующее:

удалите строку:

raise ValidationError('Cannot update owner')

и замените его на:

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

Надеюсь, это поможет!

Ответ 4

Другим решением (помимо создания отдельного сериализатора) было бы вывести имя пользователя из attrs в методе restore_object, если экземпляр установлен (что означает его метод 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

Ответ 5

Я использовал этот подход:

def get_serializer_class(self):
    if getattr(self, 'object', None) is None:
        return super(UserViewSet, self).get_serializer_class()
    else:
        return SerializerWithoutUsernameField

Ответ 6

Мой подход заключается в изменении метода perform_update при использовании классов представления generics. Я удаляю поле, когда выполняется обновление.

class UpdateView(generics.UpdateAPIView):
    ...
    def perform_update(self, serializer):
        #remove some field
        rem_field = serializer.validated_data.pop('some_field', None)
        serializer.save()

Ответ 7

Если вы не хотите создавать другой сериализатор, вы можете попробовать настроить get_serializer_class() внутри MyViewSet. Это было полезно для меня для простых проектов.

# 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 (объект, имя, значение)

Это аналог getattr(). Аргументы - это объект, строка и произвольное значение. Струна может называть существующий атрибут или новый атрибут. Функция присваивает значение атрибуту, если объект разрешает его. Для Например, setattr (x, 'foobar', 123) эквивалентно x.foobar = 123.