我想提供两种不同的序列化器,并且能够从ModelViewSet的所有功能中受益:
当查看对象列表时,我希望每个对象都有一个url,该url重定向到其详细信息,并且每个其他关系都使用目标模型的__unicode __出现;
例子:
{
"url": "http://127.0.0.1:8000/database/gruppi/2/",
"nome": "universitari",
"descrizione": "unitn!",
"creatore": "emilio",
"accesso": "CHI",
"membri": [
"emilio",
"michele",
"luisa",
"ivan",
"saverio"
]
}
在查看对象的详细信息时,我希望使用默认的HyperlinkedModelSerializer
例子:
{
"url": "http://127.0.0.1:8000/database/gruppi/2/",
"nome": "universitari",
"descrizione": "unitn!",
"creatore": "http://127.0.0.1:8000/database/utenti/3/",
"accesso": "CHI",
"membri": [
"http://127.0.0.1:8000/database/utenti/3/",
"http://127.0.0.1:8000/database/utenti/4/",
"http://127.0.0.1:8000/database/utenti/5/",
"http://127.0.0.1:8000/database/utenti/6/",
"http://127.0.0.1:8000/database/utenti/7/"
]
}
我通过以下方法做到了这一切:
serializers.py
# serializer to use when showing a list
class ListaGruppi(serializers.HyperlinkedModelSerializer):
membri = serializers.RelatedField(many = True)
creatore = serializers.RelatedField(many = False)
class Meta:
model = models.Gruppi
# serializer to use when showing the details
class DettaglioGruppi(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Gruppi
views.py
class DualSerializerViewSet(viewsets.ModelViewSet):
"""
ViewSet providing different serializers for list and detail views.
Use list_serializer and detail_serializer to provide them
"""
def list(self, *args, **kwargs):
self.serializer_class = self.list_serializer
return viewsets.ModelViewSet.list(self, *args, **kwargs)
def retrieve(self, *args, **kwargs):
self.serializer_class = self.detail_serializer
return viewsets.ModelViewSet.retrieve(self, *args, **kwargs)
class GruppiViewSet(DualSerializerViewSet):
model = models.Gruppi
list_serializer = serializers.ListaGruppi
detail_serializer = serializers.DettaglioGruppi
# etc.
基本上,我检测用户何时请求列表视图或详细视图,并更改serializer_class以满足我的需要。但我对这段代码并不满意,它看起来像一个肮脏的黑客,最重要的是,如果两个用户同时请求一个列表和一个细节怎么办?
是否有更好的方法来实现这个使用ModelViewSets或我必须返回使用GenericAPIView?
编辑:
下面是如何使用一个自定义的基础ModelViewSet:
class MultiSerializerViewSet(viewsets.ModelViewSet):
serializers = {
'default': None,
}
def get_serializer_class(self):
return self.serializers.get(self.action,
self.serializers['default'])
class GruppiViewSet(MultiSerializerViewSet):
model = models.Gruppi
serializers = {
'list': serializers.ListaGruppi,
'detail': serializers.DettaglioGruppi,
# etc.
}
对于所有提到的其他解决方案,我无法找到如何使用get_serializer_class函数实例化类,也无法找到自定义验证函数。对于那些像我一样仍然迷失并希望完全实现的人,请检查下面的答案。
views.py
from rest_framework.response import Response
from project.models import Project
from project.serializers import ProjectCreateSerializer, ProjectIDGeneratorSerializer
class ProjectViewSet(viewsets.ModelViewSet):
action_serializers = {
'generate_id': ProjectIDGeneratorSerializer,
'create': ProjectCreateSerializer,
}
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if hasattr(self, 'action_serializers'):
return self.action_serializers.get(self.action, self.serializer_class)
return super(ProjectViewSet, self).get_serializer_class()
# You can create custom function
def generate_id(self, request):
serializer = self.get_serializer_class()(data=request.GET)
serializer.context['user'] = request.user
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
def create(self, request, **kwargs):
serializer = self.get_serializer_class()(data=request.data)
serializer.context['user'] = request.user
serializer.is_valid(raise_exception=True)
return Response(serializer.validated_data, status=status.HTTP_200_OK)
serializers.py
import random
from rest_framework import serializers
from project.models import Project
class ProjectIDGeneratorSerializer(serializers.Serializer):
def update(self, instance, validated_data):
pass
def create(self, validated_data):
pass
projectName = serializers.CharField(write_only=True)
class Meta:
fields = ['projectName']
def validate(self, attrs):
project_name = attrs.get('projectName')
project_id = project_name.replace(' ', '-')
return {'projectID': project_id}
class ProjectCreateSerializer(serializers.Serializer):
def update(self, instance, validated_data):
pass
def create(self, validated_data):
pass
projectName = serializers.CharField(write_only=True)
projectID = serializers.CharField(write_only=True)
class Meta:
model = Project
fields = ['projectName', 'projectID']
def to_representation(self, instance: Project):
data = dict()
data['projectName'] = instance.name
data['projectID'] = instance.projectID
data['createdAt'] = instance.createdAt
data['updatedAt'] = instance.updatedAt
representation = {
'message': f'Project {instance.name} has been created.',
}
return representation
def validate(self, attrs):
print('attrs', dict(attrs))
project_name = attrs.get('projectName')
project_id = attrs.get('projectID')
if Project.objects.filter(projectID=project_id).first():
raise serializers.ValidationError(f'Project with ID {project_id} already exist')
project = Project.objects.create(projectID=project_id,
name=project_name)
print('user', self.context['user'])
project.user.add(self.context["user"])
project.save()
return self.to_representation(project)
urls . py
from django.urls import path
from .views import ProjectViewSet
urlpatterns = [
path('project/generateID', ProjectViewSet.as_view({'get': 'generate_id'})),
path('project/create', ProjectViewSet.as_view({'post': 'create'})),
]
models.py
# Create your models here.
from django.db import models
from authentication.models import User
class Project(models.Model):
id = models.AutoField(primary_key=True)
projectID = models.CharField(max_length=255, blank=False, db_index=True, null=False)
user = models.ManyToManyField(User)
name = models.CharField(max_length=255, blank=False)
createdAt = models.DateTimeField(auto_now_add=True)
updatedAt = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name