<p>您正在处理<strong><a href="http://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects" rel="noreferrer">nested serialization</a></strong>的问题。请在继续之前阅读链接的文档。</p>
<p>您的问题涉及DRF中问题的一个复杂领域,因此需要一些解释和讨论,以了解序列化程序和视图集是如何工作的。</p>
<p>我将讨论通过同一个端点通过不同HTTP方法使用不同的数据表示来表示您的<code>Subject</code>和<code>Class</code>数据的问题,因为这通常是人们希望以嵌套格式表示其数据时的问题;他们希望为用户界面提供足够的信息以供干净使用,e、 通过下拉选择。</p>
<p>默认情况下,Django和Django REST Framework(DRF)通过主键</em>引用相关对象(您的<code>Subject</code>和<code>Class</code>)。默认情况下,这些是用Django自动递增整数键。如果要通过其他方式引用它们,则必须为此编写重写。有几个不同的选择。</p>
<ol>
<li>第一个选项是专门化您的创建和更新逻辑:</strong>通过一些其他属性引用您的类,并自己手动编写创建的查找,或者将您通过引用的键设置为类的<strong><a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.Field.primary_key" rel="noreferrer">primary key</a></strong>。您可以将类的名称、UUID或任何其他属性设置为主数据库键,只要它是唯一的,<strong><a href="https://code.djangoproject.com/wiki/MultipleColumnPrimaryKeys" rel="noreferrer">single field</a></strong>(我之所以提到这一点,是因为您目前正在使用由复合(数字、字母)搜索项组成的复合搜索查找<code>Class</code>模型)。例如,您可以重写视图方法(对于POST)中的相关对象查找,但是您还必须处理视图方法(对于PUT和PATCH)中的类似查找。</li>
<li>其次,在我看来,最好的选择是专门化对象表示:</strong>通常通过主键引用类,<em>创建一个序列化器来读取对象,并创建和更新对象。这可以通过序列化程序类继承和重写表示来轻松实现。使用POST、PUT、PATCH等请求中的主键来更新类引用和外键。</li>
</ol>
<hr/>
<p><strong>选项1:在“创建和更新”中使用任意属性查找类和主题:</strong></p>
<p>将嵌套类序列化程序设置为只读:</p>
<pre><code>class ExamSerializer(serializers.ModelSerializer):
subject = SubjectSerializer(read_only=True)
clazz = ClassSerializer(read_only=True)
</code></pre>
<p>重写视图的create以查找自由形式属性上的相关类。另外,请查看<strong><a href="https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py#L14" rel="noreferrer">how DRF implements this with mixins</a></strong>。您还必须重写您的<code>update</code>方法才能正确处理这些问题,如果您采用以下方法,除了<code>PUT</code>(update)之外,还必须考虑<code>PATCH</code>(部分更新)支持:</p>
<pre><code>def create(self, request):
# Look up objects by arbitrary attributes.
# You can check here if your students are participating
# the classes and have taken the subjects they sign up for.
subject = get_object_or_404(Subject, title=request.data.get('subject'))
clazz = get_object_or_404(
Class,
number=request.data.get('clazz_number')
letter=request.data.get('clazz_letter')
)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(clazz=clazz, subject=subject)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
</code></pre>
<hr/>
<p><strong>选项2:专门化读写序列化程序并使用主键;这是惯用的方法:</strong></p>
<p>首先定义希望用于正常操作(POST、PUT、PATCH)的默认ModelSerializer:</p>
<pre><code>class ExamSerializer(serializers.ModelSerializer)
class Meta:
model = Exam
fields = ('id', 'subject', 'clazz', 'topic', 'date', 'details')
</code></pre>
<p>然后,使用要为其提供的用于读取数据的表示形式(GET)覆盖必要的字段:</p>
<pre><code>class ExamReadSerializer(ExamSerializer):
subject = SubjectSerializer(read_only=True)
clazz = ClassSerializer(read_only=True)
</code></pre>
<p>然后为您的视图集<strong><a href="https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/generics.py#L104" rel="noreferrer">specify the serializer you wish to use for different operations</a></strong>。在这里,我们返回用于读取操作的嵌套主题和类数据,但只将它们的主键用于更新操作(简单得多):</p>
<pre><code>class ExamViewSet(viewsets.ModelViewSet):
queryset = Exam.objects.all()
def get_serializer_class(self):
# Define your HTTP method-to-serializer mapping freely.
# This also works with CoreAPI and Swagger documentation,
# which produces clean and readable API documentation,
# so I have chosen to believe this is the way the
# Django REST Framework author intended things to work:
if self.request.method in ['GET']:
# Since the ReadSerializer does nested lookups
# in multiple tables, only use it when necessary
return ExamReadSerializer
return ExamSerializer
</code></pre>
<p>如您所见,选项2看起来相当不复杂,也不容易出错,它只在DRF(get_serializer_类实现)之上包含3行手写代码。只需让框架的逻辑为您计算对象的表示、创建和更新。</p>
<p>我见过许多其他的方法,但是到目前为止,这些方法产生了最少的代码来维护我,并且以干净的方式利用DRF的设计。</p>