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