Поле SerializerClass в Сериализаторе сохранить с первичного ключа

Я работаю над разработкой API с Django-rest-framework и потреблением его из веб-приложения. Он имеет модель врача с Fk из модели пользователя django.auth. Я хочу отправить сообщение из формы в модель врача, но сериализатор возвращает это сообщение:

{ "user": { "non_field_errors": [ "Недопустимые данные. Ожидаемый словарь, но получил unicode." ]}}

Я посылаю первичный ключ объекта пользователя. Что является правильным (или только одним способом) для хранения внешнего ключа на DRF. Я попытался переопределить get_validation_exclusions на сериализаторе и переопределить метод execute_create в представлении.

Api и веб-приложение развязаны. API разработан с помощью django и веб-приложения с угловыми функциями.

Моя модель

class Physician(models.Model):
    medical_office_number = models.CharField(max_length = 15)
    fiscal_id_number = models.CharField(max_length = 20)
    user = models.OneToOneField(User)

    def __unicode__(self):
        return self.user.first_name +' '+ self.user.last_name

Serializer:

class PhysicianSerializer(serializers.ModelSerializer):
    user = AccountSerializer()
    class Meta:
        model = Physician
        fields = ('id', 'user', 'medical_office_number', 'fiscal_id_number')
        read_only_fields = ('id')
        depth = 1
    def get_validation_exclusions(self, *args, **kwargs):
        exclusions = super(PhysicianSerializer, self).get_validation_exclusions()
        return exclusions + ['user']

* Редактировать Это мой сериализатор учетных записей, который основан на этой реализации и с предложением @Kevin Brown

class PrimaryKeyNestedMixin(serializers.RelatedField, serializers.ModelSerializer):

    def to_internal_value(self, data):
        return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)
    def to_representation(self, data):
        return serializers.ModelSerializer.to_representation(self, data)

class AccountSerializer(PrimaryKeyNestedMixin):
    password = serializers.CharField(write_only=True, required=False)
    confirm_password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        fields = ('id', 'email', 'username', 'created_at', 'updated_at',
                  'first_name', 'last_name', 'password',
                  'confirm_password', 'is_admin',)
        read_only_fields = ('created_at', 'updated_at',)

Viewset

class AccountViewSet(viewsets.ModelViewSet):
    lookup_field = 'username'
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

Когда я пытаюсь выполнить сериализацию этого объекта, он вызывает ошибку.

Поэтому я могу опубликовать любого пользователя из элемента <select>. Но я не могу проверить решение. Что-то мне не хватает?

Ошибка Stacktrace

TypeError at /api/v1/accounts/

__init__() takes exactly 1 argument (5 given)

Exception Location:     /home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py in many_init, line 68
Python Executable:  /home/jlromeroc/workspace/asclepios/venv/bin/python
Python Version:     2.7.3

File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response 111. response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view 57. return view_func(*args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/viewsets.py" in view 85. return self.dispatch(request, *args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch 407. response = self.handle_exception(exc) File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch 404. response = handler(request, *args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/mixins.py" in list 45. serializer = self.get_serializer(instance, many=True)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/generics.py" in get_serializer 90. instance, data=data, many=many, partial=partial, context=context File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py" in __new__ 48. return cls.many_init(*args, **kwargs)
File "/home/jlromeroc/workspace/asclepios/venv/local/lib/python2.7/site-packages/rest_framework/relations.py" in many_init 68. list_kwargs = {'child_relation': cls(*args, **kwargs)}

Exception Type: TypeError at /api/v1/accounts/
Exception Value: __init__() takes exactly 1 argument (5 given)

Изменить ** Я решил переопределить функцию create в представлении и включить объект в запрос, чтобы его можно было проверить, но затем сериализатор пытается вставить новый объект для модели Account. Как я могу предотвратить это поведение? Я попытался установить сериализатор класса PhysicianSerializer как read_only, но затем django пытается сохранить модель с нулевым user_id. Как сохранить модель без попытки вставки связанного объекта?

Ответ 1

Проблема здесь в том, что с вложенными сериализаторами инфраструктура Django REST ожидает, что вход и выход будут вложенными представлениями. DRF автоматически проверит ввод, чтобы убедиться, что он соответствует вложенному сериализатору, что позволяет вам создать основной объект и любые отношения в одном запросе.

Вы хотите иметь вложенный результат с входом PrimaryKeyRelatedField. Это очень распространено для тех, кому не нужно создавать отношения в одном запросе, но вместо этого всегда будет использовать существующие объекты в своих отношениях. То, как вы собираетесь это делать, в основном принимает первичный ключ (как и PrimaryKeyRelatedField) в to_internal_value, но выводит сериализатор в to_representation. Что-то вроде этого (untested) должно работать

class PrimaryKeyNestedMixin(serializers.PrimaryKeyRelatedField, serializers.ModelSerializer):

    def to_internal_value(self, data):
        return serializers.PrimaryKeyRelatedField.to_internal_value(self, data)

    def to_representation(self, data):
        return serializers.ModelSerializer.to_representation(self, data)

Вам нужно будет использовать это как mixin для вложенного сериализатора AccountSerializer в вашем случае, и он должен делать то, что вы ищете.

Ответ 3

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

class JobAlertList(APIView):
    """
    List all job alerts or create a new job alert
    """
    def get(self, request, format=None):
        job_alerts = JobAlert.objects.all()
        serializer = JobAlertNestedSerializer(job_alerts, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = JobAlertSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class JobAlertDetail(APIView):
    """
    Retrieve or delete a job alert instance.
    """
    def get_object(self, pk):
        try:
            return JobAlert.objects.get(pk=pk)
        except JobAlert.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        job_alert = self.get_object(pk)
        serializer = JobAlertNestedSerializer(job_alert)
        return Response(serializer.data)

    def delete(self, request, pk, format=None):
        job_alert = self.get_object(pk)
        job_alert.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

class JobAlertSerializer(serializers.ModelSerializer):

    class Meta:
        model = JobAlert
        fields = ('job', 'user')
        depth = 0

    def create(self, validated_data):
        user = validated_data.pop('user')
        job = validated_data.pop('job')
        job_alert = JobAlert.objects.create(user=user, job=job)
        return job_alert


class JobAlertNestedSerializer(serializers.ModelSerializer):

    class Meta:
        model = JobAlert
        fields = ('id', 'job', 'user')
        depth = 1

url(r'^job_alerts/$', views.JobAlertList.as_view(), name='job-alerts-list'),
url(r'^job_alerts/(?P<pk>[0-9]+)/$', views.JobAlertDetail.as_view(), name='job-alerts-detail'),

Ответ 4

Я столкнулся с подобной проблемой (желая POST id/FK объекта, но ожидая сериализованного объекта в GET). Я реализовал решение Кевина Брауна для моего случая. Адаптируйте это к вашей проблеме (слишком поздно, но надеюсь, что кто-то другой, включая будущее меня, наткнется на это и сочтет это полезным).

def get_primary_key_related_model(model_class, **kwargs):
    """
    Nested serializers are a mess. /info/332523/serializerclass-field-on-serializer-save-from-primary-key/1584679#1584679
    This lets us accept ids when saving / updating instead of nested objects.
    Representation would be into an object (depending on model_class).
    """
    class PrimaryKeyNestedMixin(model_class):

        def to_internal_value(self, data):
            try:
                return model_class.Meta.model.objects.get(pk=data)
            except model_class.Meta.model.DoesNotExist:
                self.fail('does_not_exist', pk_value=data)
            except (TypeError, ValueError):
                self.fail('incorrect_type', data_type=type(data).__name__)

        def to_representation(self, data):
            return model_class.to_representation(self, data)

    return PrimaryKeyNestedMixin(**kwargs)


class AccountSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=False)
    confirm_password = serializers.CharField(write_only=True, required=False)

    class Meta:
        model = Account
        # ...


class PhysicianSerializer(serializers.ModelSerializer):
    user = get_primary_key_related_model(AccountSerializer)

    class Meta:
        model = Physician
        # ...

Генератор class очень удобен, когда у вас есть настраиваемые поля сериализатора (ограничение доступа на основе request.user).