Serializer上的SerializerClass字段从主键保存

时间:2015-01-18 14:16:49

标签: python django serialization django-rest-framework django-serializer

我正在使用Django-rest-framework开发API并从Web应用程序中使用它。它有一个医生模型,带有来自django.auth用户模型的Fk。我想从表单发布到Physician Model,但序列化程序返回此消息:

  

{" user":{" non_field_errors":["无效数据。期待一本字典,但得到了unicode。"]}}

我发送用户对象的主键。哪个是在DRF上存储外键的权利(或只是一种方式)。我已尝试在序列化程序上覆盖get_validation_exclusions并覆盖视图集上的perform_create方法。

api和web应用程序是分离的。 API是使用django和带有angularjs的web应用程序开发的。

我的模特

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

串行:

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',)

视图集

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尝试使用null user_id存储模型。如何在不尝试插入相关对象的情况下保存模型?

5 个答案:

答案 0 :(得分:3)

这里的问题是,对于嵌套的序列化程序,Django REST框架期望输入和输出都是嵌套表示。 DRF将自动验证输入以确保它与嵌套的序列化程序匹配,允许您在单个请求中创建主对象和任何关系。

您希望拥有一个带PrimaryKeyRelatedField输入的嵌套输出。对于那些不需要在同一个请求中创建关系的人来说,这是很常见的,但是他们总是在他们的关系中使用现有的对象。您必须这样做的方法基本上是在PrimaryKeyRelatedField中接收主键(就像to_internal_value),但在to_representation中输出序列化器。这样的事情(未经测试)应该有效

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在你的情况下,它应该做你想要的。

答案 1 :(得分:1)

我从SO那里听到了这个答案。 Disable creating nested objects in django rest framework它有点乱,但有效。无论哪种方式,它都缺乏DRF。

答案 2 :(得分:0)

我通过使用不同的视图处理获取单个项目和发布以及获取嵌套列表来解决此问题。 get单项和get列表使用嵌套的序列化程序,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'),

答案 3 :(得分:0)

我遇到了类似的问题(想要POST对象的id / FK,但期望GET中的序列化对象)。我成功地为我的案例实施了Kevin Brown's solution。适应你的问题(为时已晚,但希望其他人,包括未来的我,偶然发现并发现它有用)。

SET @sql = 'INSERT INTO #actual (jobnumber,firstNameCounts,lastNameCounts , 
address1Counts, address2Counts, cityCounts, stateCounts, zipCounts, 
inHomeDateCounts) '
SET @sql = @sql + ' Select s.jobnumber, count(s.firstName) AS 
[firstNameCounts], Count (s.lastName) AS [lastNameCounts], Count (s.Address1) 
As [address1Counts], Count (s.address2)-Count (address2) AS '
SET @sql = @sql + ' [address2Counts], Count (s.City) AS [cityCounts], Count 
(s.State) AS [stateCounts], Count (s.Zip) AS [zipCounts], Count 
(jb.inHomeDate) AS [inHomeDateCounts] '
SET @sql = @sql + ' From [s-portaldb1].[tdis_417133) + ].[dbo].
[tblStandardFinal] s '
SET @sql = @sql + ' INNER JOIN [s-printstream].[tdSchedule2].[dbo].
[tblJobTicketActions] jb '
SET @sql = @sql + ' ON jb.psFlagJobNumber = s.jobNumber '
SET @sql = @sql + ' where jobNumber = @jobNumber '
SET @sql = @sql + ' group by jobNumber '

PRINT @SQL

EXEC (@sql)

当您拥有自定义序列化程序字段(限制基于request.user的访问权限)时,INSERT INTO #actual (jobnumber,firstNameCounts,lastNameCounts , address1Counts, address2Counts, cityCounts, stateCounts, zipCounts, inHomeDateCounts) Select s.jobnumber, count(s.firstName) AS [firstNameCounts], Count (s.lastName) AS [lastNameCounts], Count (s.Address1) As [address1Counts], Count (s.address2)-Count (address2) AS [address2Counts], Count (s.City) AS [cityCounts], Count (s.State) AS [stateCounts], Count (s.Zip) AS [zipCounts], Count (jb.inHomeDate) AS [inHomeDateCounts] From [s-portaldb1].[tdis_417133) + ].[dbo]. [tblStandardFinal] s INNER JOIN [s-printstream].[tdSchedule2].[dbo].[tblJobTicketActions] jb ON jb.psFlagJobNumber = s.jobNumber where jobNumber = @jobNumber group by jobNumber 生成器非常方便。

答案 4 :(得分:0)

我创建了一个Field类型,该类型试图解决带有Integer中的ForeignKey的数据保存请求以及读取带有嵌套数据的数据的请求

这是课程:

class NestedRelatedField(serializers.PrimaryKeyRelatedField):
"""
    Model identical to PrimaryKeyRelatedField but its
    representation will be nested and its input will
    be a primary key.
"""

def __init__(self, **kwargs):
    self.pk_field = kwargs.pop('pk_field', None)
    self.model = kwargs.pop('model', None)
    self.serializer_class = kwargs.pop('serializer_class', None)
    super().__init__(**kwargs)

def to_representation(self, data):
    pk = super(NestedRelatedField, self).to_representation(data)
    try:
        return self.serializer_class(self.model.objects.get(pk=pk)).data
    except self.model.DoesNotExist:
        return None

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

因此将被使用:

class PostModelSerializer(serializers.ModelSerializer):

    message = NestedRelatedField(
         queryset=MessagePrefix.objects.all(),
         model=MessagePrefix,
         serializer_class=MessagePrefixModelSerializer
   )

希望对您有帮助。