使用具有外键的模型的序列化程序在 Django Rest Framework 中对父 table 执行 CRUD
Use serializer of model having foreign key to do CRUD on parent table in Django Rest Framework
在我的API中,我有两个模型Question
和Option
如下图
class Question(models.Model):
body = models.TextField()
class Options(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
option = models.CharField(max_length=100)
is_correct = models.SmallIntegerField()
在创建问题的同时创建选项会更好。并且不应创建已经存在的问题,但如果选项与以前的选项不同,则可以更改选项。
我正在使用 ModelSerializer
和 ModelViewSet
。我为 Question
和 Option
使用不同的 url 和视图。
serializers.py
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = '__all__'
class OptionReadSerializer(serializers.ModelSerializer):
question = QuestionSerializer(read_only=True)
class Meta:
model = Option
fields = ('question', 'option', 'is_correct')
class OptionWriteSerializer(serializer.ModelSerializer):
class Meta:
model = Option
fields = ('question', 'option', 'is_correct')
views.py
class QuestionViewSet(ModelViewSet):
seriaizer_class = QuestionSerializer
queryset = Question.objects.all()
class OptionViewSet(ModelViewSet):
queryset = Option.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST':
return OptionWriteSerializer
return OptionReadSerializer
urls.py
from django.urls import include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('api/question', QuestionViewset, base_name='question')
router.register('api/option', OptionViewSet, base_name='option')
urlpatterns = [
path('', include(router.urls))
]
这样一来,我总是必须先创建问题,然后才能为该问题单独添加选项。我认为这可能不是一个实用的方法。
如果问题和选项可以同时添加并且类似于所有 CRUD 操作,那就更好了。
JSON格式的预期结果和发布数据如下所示:
{
"body": "Which country won the FIFA world cup 2018",
"options": [
{
"option": "England",
"is_correct": 0
},
{
"option": "Germany",
"is_correct": 0
},
{
"option": "France",
"is_correct": 1
}
]
}
我们可以使用PrimaryKeyRelatedField.
tldr;
我相信一个 Question
可以附加多个 Options
。而不是将 Option
挂接到 Question
.
像这样:
class Question(models.Model):
body = models.TextField()
options = models.ManyToManyField(Option)
class Options(models.Model):
text = models.CharField(max_length=100)
is_correct = models.BooleanField()
然后我们可以使用PrimaryKeyRelatedField这样的东西:
class QuestionSerializer(serializers.ModelSerializer):
options = serializers.PrimaryKeyRelatedField(queryset=Options.objects.all(), many=True, read_only=False)
class Meta:
model = Question
fields = '__all__'
参考:https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
在 models
中,我在 Option
模型的外键字段中添加了 related_name='options'
models.py
class Question(models.Model):
body = models.TextField()
class Options(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='options')
option = models.CharField(max_length=100)
is_correct = models.SmallIntegerField()
在 QuestionWriteSerializer
中,我覆盖了 update()
和 create()
方法。创建和更新逻辑是从 QuestionWriteSerialzer
处理的。
serializers.py
class OptionSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
class Meta:
model = Option
fields = ('id', 'question', 'option', 'is_correct')
class QuestionReadSerializer(serializers.ModelSerializer):
options = OptionSerializer(many=True, read_only=True)
class Meta:
model = Question
fields = ('id', 'body', 'options')
class QuestionWriteSerializers(serializers.ModelSerializer):
options = OptionSerializer(many=True)
class Meta:
model = Question
fields = ('id', 'body', 'options')
def create(self, validated_data):
options_data = validated_data.pop('options')
question_created = Questions.objects.update_or_create(**validated_data)
option_query = Options.objects.filter(question=question_created[0])
if len(option_query) > 1:
for existeding_option in option_query:
option_query.delete()
for option_data in options_data:
Options.objects.create(question=question_created[0], **option_data)
return question_created[0]
def update(self, instance, validated_data):
options = validated_data.pop('options')
instance.body = validated_data.get('body', instance.body)
instance.save()
keep_options = []
for option_data in options:
if 'id' in option_data.keys():
if Options.objects.filter(id=option_data['id'], question_id=instance.id).exists():
o = Options.objects.get(id=option_data['id'])
o.option = option_data.get('option', o.option)
o.is_correct = option_data.get('is_correct', o.is_correct)
o.save()
keep_options.append(o.id)
else:
continue
else:
o = Options.objects.create(**option_data, question=instance)
keep_options.append(o.id)
for option_data in instance.options.all():
if option_data.id not in keep_options:
Options.objects.filter(id=option_data.id).delete()
return instance
QuestionViewSet
几乎一样,我去掉了OptionViewSet
,控制了QuestionViewSet
的所有东西
views.py
class QuestionViewSet(ModelViewSet):
queryset = Question.objects.all()
def get_serializer_class(self) or self.request.method == 'PUT' or self.request.method == 'PATCH':
if self.request.method == 'POST':
return QuestionWriteSerializer
return QuestionReadSerializer
def create(self, request, *args, **kwargs):
"""
Overriding create() method to change response format
"""
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({
'message': 'Successfully created question',
'data': serializer.data,
'status': 'HTTP_201_CREATED',
}, status=status.HTTP_201_CREATED, headers=headers)
else:
return Response({
'message': 'Can not create',
'data': serializer.errors,
'status': 'HT',
}, status=status.HTTP_400_BAD_REQUEST)
在我的API中,我有两个模型Question
和Option
如下图
class Question(models.Model):
body = models.TextField()
class Options(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
option = models.CharField(max_length=100)
is_correct = models.SmallIntegerField()
在创建问题的同时创建选项会更好。并且不应创建已经存在的问题,但如果选项与以前的选项不同,则可以更改选项。
我正在使用 ModelSerializer
和 ModelViewSet
。我为 Question
和 Option
使用不同的 url 和视图。
serializers.py
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
fields = '__all__'
class OptionReadSerializer(serializers.ModelSerializer):
question = QuestionSerializer(read_only=True)
class Meta:
model = Option
fields = ('question', 'option', 'is_correct')
class OptionWriteSerializer(serializer.ModelSerializer):
class Meta:
model = Option
fields = ('question', 'option', 'is_correct')
views.py
class QuestionViewSet(ModelViewSet):
seriaizer_class = QuestionSerializer
queryset = Question.objects.all()
class OptionViewSet(ModelViewSet):
queryset = Option.objects.all()
def get_serializer_class(self):
if self.request.method == 'POST':
return OptionWriteSerializer
return OptionReadSerializer
urls.py
from django.urls import include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('api/question', QuestionViewset, base_name='question')
router.register('api/option', OptionViewSet, base_name='option')
urlpatterns = [
path('', include(router.urls))
]
这样一来,我总是必须先创建问题,然后才能为该问题单独添加选项。我认为这可能不是一个实用的方法。
如果问题和选项可以同时添加并且类似于所有 CRUD 操作,那就更好了。
JSON格式的预期结果和发布数据如下所示:
{
"body": "Which country won the FIFA world cup 2018",
"options": [
{
"option": "England",
"is_correct": 0
},
{
"option": "Germany",
"is_correct": 0
},
{
"option": "France",
"is_correct": 1
}
]
}
我们可以使用PrimaryKeyRelatedField.
tldr;
我相信一个 Question
可以附加多个 Options
。而不是将 Option
挂接到 Question
.
像这样:
class Question(models.Model):
body = models.TextField()
options = models.ManyToManyField(Option)
class Options(models.Model):
text = models.CharField(max_length=100)
is_correct = models.BooleanField()
然后我们可以使用PrimaryKeyRelatedField这样的东西:
class QuestionSerializer(serializers.ModelSerializer):
options = serializers.PrimaryKeyRelatedField(queryset=Options.objects.all(), many=True, read_only=False)
class Meta:
model = Question
fields = '__all__'
参考:https://www.django-rest-framework.org/api-guide/relations/#primarykeyrelatedfield
在 models
中,我在 Option
模型的外键字段中添加了 related_name='options'
models.py
class Question(models.Model):
body = models.TextField()
class Options(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name='options')
option = models.CharField(max_length=100)
is_correct = models.SmallIntegerField()
在 QuestionWriteSerializer
中,我覆盖了 update()
和 create()
方法。创建和更新逻辑是从 QuestionWriteSerialzer
处理的。
serializers.py
class OptionSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
class Meta:
model = Option
fields = ('id', 'question', 'option', 'is_correct')
class QuestionReadSerializer(serializers.ModelSerializer):
options = OptionSerializer(many=True, read_only=True)
class Meta:
model = Question
fields = ('id', 'body', 'options')
class QuestionWriteSerializers(serializers.ModelSerializer):
options = OptionSerializer(many=True)
class Meta:
model = Question
fields = ('id', 'body', 'options')
def create(self, validated_data):
options_data = validated_data.pop('options')
question_created = Questions.objects.update_or_create(**validated_data)
option_query = Options.objects.filter(question=question_created[0])
if len(option_query) > 1:
for existeding_option in option_query:
option_query.delete()
for option_data in options_data:
Options.objects.create(question=question_created[0], **option_data)
return question_created[0]
def update(self, instance, validated_data):
options = validated_data.pop('options')
instance.body = validated_data.get('body', instance.body)
instance.save()
keep_options = []
for option_data in options:
if 'id' in option_data.keys():
if Options.objects.filter(id=option_data['id'], question_id=instance.id).exists():
o = Options.objects.get(id=option_data['id'])
o.option = option_data.get('option', o.option)
o.is_correct = option_data.get('is_correct', o.is_correct)
o.save()
keep_options.append(o.id)
else:
continue
else:
o = Options.objects.create(**option_data, question=instance)
keep_options.append(o.id)
for option_data in instance.options.all():
if option_data.id not in keep_options:
Options.objects.filter(id=option_data.id).delete()
return instance
QuestionViewSet
几乎一样,我去掉了OptionViewSet
,控制了QuestionViewSet
views.py
class QuestionViewSet(ModelViewSet):
queryset = Question.objects.all()
def get_serializer_class(self) or self.request.method == 'PUT' or self.request.method == 'PATCH':
if self.request.method == 'POST':
return QuestionWriteSerializer
return QuestionReadSerializer
def create(self, request, *args, **kwargs):
"""
Overriding create() method to change response format
"""
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({
'message': 'Successfully created question',
'data': serializer.data,
'status': 'HTTP_201_CREATED',
}, status=status.HTTP_201_CREATED, headers=headers)
else:
return Response({
'message': 'Can not create',
'data': serializer.errors,
'status': 'HT',
}, status=status.HTTP_400_BAD_REQUEST)