W Django Rest Framework, jak filtrować serializator, gdy jest on zagnieżdżony w innym serializatorze?
Moje filtry są nakładane w zestawach widoków DRF, ale kiedy wywołujesz serializator z innego serializatora, zestaw widoków zagnieżdżonego serializatora nigdy nie jest wywoływany, więc zagnieżdżone wyniki są wyświetlane jako niefiltrowane.
Próbowałem dodać filtr na źródłowym zestawie widoków, ale nie wydaje się, aby filtrował zagnieżdżone wyniki, ponieważ zagnieżdżone wyniki są wywoływane jako oddzielne wstępnie przygotowane zapytanie. (Widzisz, zagnieżdżony serializator jest wyszukiwaniem wstecznym).
Czy można dodać przesłonięcie get_queryset () w samym zagnieżdżonym serializatorze (przenosząc go z zestawu widoków), aby dodać tam filtr? Też próbowałem, ale bez powodzenia.
To jest to, czego próbowałem, ale nawet nie wydaje się, żebym go wywołał:
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
to klasa włączonaModelViewSet
, a nie w Serializatorze, dlatego nie jest wywoływanaOdpowiedzi:
Możesz podklasować ListSerializer i nadpisać
to_representation
metodę.Domyślnie
to_representation
metoda wywołujedata.all()
zagnieżdżony zestaw zapytań. Więc musisz skutecznie zrobić,data = data.filter(**your_filters)
zanim metoda zostanie wywołana. Następnie musisz dodać podklasę ListSerializer jako list_serializer_class w meta zagnieżdżonego serializatora.to_representation
a następnie wywołując superlist_serializer_class
w zagnieżdżonym SerializatorzeOto odpowiedni kod dla Twojej próbki.
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
źródło
QuestionnaireSerializer
do ListSerializer? Dla przybliżenia muszę filtrować według ID wydania oraz ID kwestionariusza.'FilteredListSerializer' object has no attribute 'request'
Ktoś inny dostaje to samo?Przetestowałem wiele rozwiązań z SO i nie tylko.
Znalazłem tylko jedno działające rozwiązanie dla Django 2.0 + DRF 3.7.7.
Zdefiniuj metodę w modelu, który ma zagnieżdżoną klasę. Stwórz filtr, który będzie pasował do Twoich potrzeb.
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',)
Zwróć uwagę,
source="current_epg"
a zrozumiesz.źródło
Chociaż wszystkie powyższe odpowiedzi działają, uważam, że użycie obiektu Django jest
Prefetch
najłatwiejsze ze wszystkich.Powiedzmy, że obiekt
Restaurant
ma wieleMenuItem
s, z których niektóre sąis_remove == True
, a chcesz tylko te, które nie są usuwane.W
RestaurantViewSet
, zrób coś takiegofrom 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') )
W
RestaurantSerializer
, zrób coś takiegoclass RestaurantSerializer(serializers.ModelSerializer): menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)
źródło
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', )
Z uwagi:
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)
źródło
Uważam, że łatwiejsze i prostsze jest użycie
SerializerMethodField
znaku w polu serializatora, które chcesz filtrować.Więc zrobiłbyś coś takiego.
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
Dzięki temu nie musisz tworzyć wielu przesłonięć,
serializers.ListSerializer
jeśli potrzebujesz różnych kryteriów filtrowania dla różnych serializatorów.Ma również dodatkową zaletę polegającą na tym, że widzi dokładnie, co robi filtr w serializatorze, zamiast nurkować w definicji podklasy.
Oczywiście wadą jest to, że masz serializator z wieloma zagnieżdżonymi obiektami, które muszą być w jakiś sposób filtrowane. Może to spowodować znaczny wzrost kodu serializatora. To od Ciebie zależy, jak chcesz filtrować.
Mam nadzieję że to pomoże!
źródło