En Django Rest Framework, ¿cómo se filtra un serializador cuando está anidado en otro serializador?
Mis filtros se imponen en los conjuntos de vistas DRF, pero cuando llama a un serializador desde dentro de otro serializador, nunca se llama al conjunto de vistas del serializador anidado, por lo que los resultados anidados aparecen sin filtrar.
Intenté agregar un filtro en el conjunto de vistas de origen, pero no parece filtrar los resultados anidados porque los resultados anidados se llaman como una consulta previa separada. (El serializador anidado es una búsqueda inversa, como ve).
¿Es posible agregar una anulación de get_queryset () en el serializador anidado en sí (moviéndolo fuera del conjunto de vistas), para agregar el filtro allí? Yo también lo intenté, sin suerte.
Esto es lo que intenté, pero ni siquiera parece que lo llamen:
class QuestionnaireSerializer(serializers.ModelSerializer):
edition = EditionSerializer(read_only=True)
company = serializers.StringRelatedField(read_only=True)
class Meta:
model = Questionnaire
def get_queryset(self):
query = super(QuestionnaireSerializer, self).get_queryset(instance)
if not self.request.user.is_staff:
query = query.filter(user=self.request.user, edition__hide=False)
return query
get_queryset
es una clase enModelViewSet
, no en el serializador, por lo que no se llamaRespuestas:
Puede crear una subclase de ListSerializer y sobrescribir el
to_representation
método.De forma predeterminada, el
to_representation
método llamadata.all()
al conjunto de consultas anidado. Por lo tanto, debe realizardata = data.filter(**your_filters)
antes de llamar al método. Luego, debe agregar su ListSerializer subclasificado como list_serializer_class en el meta del serializador anidado.to_representation
y luego llamando superlist_serializer_class
en el serializador anidadoAquí está el código relevante para su muestra.
class FilteredListSerializer(serializers.ListSerializer): def to_representation(self, data): data = data.filter(user=self.context['request'].user, edition__hide=False) return super(FilteredListSerializer, self).to_representation(data) class EditionSerializer(serializers.ModelSerializer): class Meta: list_serializer_class = FilteredListSerializer model = Edition class QuestionnaireSerializer(serializers.ModelSerializer): edition = EditionSerializer(read_only=True) company = serializers.StringRelatedField(read_only=True) class Meta: model = Questionnaire
fuente
QuestionnaireSerializer
ListSerializer? Para aproximar, necesito filtrar por ID de edición y por ID de cuestionario.'FilteredListSerializer' object has no attribute 'request'
Alguien más obtiene lo mismo?Probé muchas soluciones de SO y otros lugares.
Encontré solo una solución de trabajo para Django 2.0 + DRF 3.7.7.
Defina un método en el modelo que tenga una clase anidada. Elabore un filtro que se adapte a sus necesidades.
class Channel(models.Model): name = models.CharField(max_length=40) number = models.IntegerField(unique=True) active = models.BooleanField(default=True) def current_epg(self): return Epg.objects.filter(channel=self, end__gt=datetime.now()).order_by("end")[:6] class Epg(models.Model): start = models.DateTimeField() end = models.DateTimeField(db_index=True) title = models.CharField(max_length=300) description = models.CharField(max_length=800) channel = models.ForeignKey(Channel, related_name='onair', on_delete=models.CASCADE)
.
class EpgSerializer(serializers.ModelSerializer): class Meta: model = Epg fields = ('channel', 'start', 'end', 'title', 'description',) class ChannelSerializer(serializers.ModelSerializer): onair = EpgSerializer(many=True, read_only=True, source="current_epg") class Meta: model = Channel fields = ('number', 'name', 'onair',)
Preste atención
source="current_epg"
y entenderá el punto.fuente
Si bien todas las respuestas anteriores funcionan, creo que el uso del
Prefetch
objeto de Django es la forma más fácil de todas.Supongamos que un
Restaurant
obj tiene muchosMenuItem
s, algunos de los cuales lo sonis_remove == True
, y solo desea los que no se eliminan.En
RestaurantViewSet
, haz algo comofrom django.db.models import Prefetch queryset = Restaurant.objects.prefetch_related( Prefetch('menu_items', queryset=MenuItem.objects.filter(is_removed=False), to_attr='filtered_menu_items') )
En
RestaurantSerializer
, haz algo comoclass RestaurantSerializer(serializers.ModelSerializer): menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)
fuente
class UserSerializer(serializers.ModelSerializer): """ Here many=True is passed, So a ListSerializer instance will be created""" system = SystemSerializer(many=True, read_only=True) class Meta: model = UserProfile fields = ('system', 'name') class FilteredListSerializer(serializers.ListSerializer): """Serializer to filter the active system, which is a boolen field in System Model. The value argument to to_representation() method is the model instance""" def to_representation(self, data): data = data.filter(system_active=True) return super(FilteredListSerializer, self).to_representation(data) class SystemSerializer(serializers.ModelSerializer): mac_id = serializers.CharField(source='id') system_name = serializers.CharField(source='name') serial_number = serializers.CharField(source='serial') class Meta: model = System list_serializer_class = FilteredListSerializer fields = ( 'mac_id', 'serial_number', 'system_name', 'system_active', )
En vista:
class SystemView(viewsets.GenericViewSet, viewsets.ViewSet): def retrieve(self, request, email=None): data = get_object_or_404(UserProfile.objects.all(), email=email) serializer = UserSerializer(data) return Response(serializer.data)
fuente
Me resulta más fácil, y más sencillo, usar un
SerializerMethodField
en el campo del serializador que desea filtrar.Entonces harías algo como esto.
class CarTypesSerializer(serializers.ModelSerializer): class Meta: model = CarType fields = '__all__' class CarSerializer(serializers.ModelSerializer): car_types = serializers.SerializerMethodField() class Meta: model = Car fields = '__all__' def get_car_types(self, instance): # Filter using the Car model instance and the CarType's related_name # (which in this case defaults to car_types_set) car_types_instances = instance.car_types_set.filter(brand="Toyota") return CarTypesSerializer(car_types_instances, many=True).data
Esto le evita tener que crear muchas modificaciones
serializers.ListSerializer
si necesita diferentes criterios de filtrado para diferentes serializadores.También tiene el beneficio adicional de ver exactamente lo que hace el filtro dentro del serializador en lugar de sumergirse en una definición de subclase.
Por supuesto, la desventaja es que si tiene un serializador con muchos objetos anidados, todos deben filtrarse de alguna manera. Podría hacer que el código del serializador aumente considerablemente. Depende de usted cómo le gustaría filtrar.
¡Espero que esto ayude!
fuente