web-dev-qa-db-fra.com

Filtrage à l'aide de vues dans le cadre de repos Django

Veuillez considérer ces trois modèles:

class Movie(models.Model):
    name = models.CharField(max_length=254, unique=True)
    language = models.CharField(max_length=14)
    synopsis = models.TextField()

class TimeTable(models.Model):
    date = models.DateField()

class Show(models.Model):
    day = models.ForeignKey(TimeTable)
    time = models.TimeField(choices=CHOICE_TIME)
    movie = models.ForeignKey(Movie)

    class Meta:
        unique_together = ('day', 'time')

Et chacun d'entre eux a son sérialiseur:

class MovieSerializer(serializers.HyperlinkedModelSerializer):
    movie_id = serializers.IntegerField(read_only=True, source="id")

    class Meta:
        model = Movie
        fields = '__all__'    

class TimeTableSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = TimeTable
        fields = '__all__'


class ShowSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Show
        fields = '__all__'

Et leurs routeurs

router.register(r'movie-list', views.MovieViewSet)
router.register(r'time-table', views.TimeTableViewSet)
router.register(r'show-list', views.ShowViewSet)        

Maintenant, j'aimerais obtenir tous les objets TimeTable (c'est-à-dire une liste de dates) en filtrant tous les objets Afficher par un objet de film spécifique. Ce code semble être le travail et obtenir la liste comme je le veux

m = Movie.objects.get(id=request_id)
TimeTable.objects.filter(show__movie=m).distinct()

Mais je ne sais pas comment utiliser cela dans Django Rest Framework. J'ai essayé de faire de cette façon (ce dont je suis sûr que c'est faux), et j'obtiens une erreur:

views.py:

class DateListViewSet(viewsets.ModelViewSet, movie_id):
    movie = Movie.objects.get(id=movie_id)
    queryset = TimeTable.objects.filter(show__movie=movie).distinct()
    serializer_class = TimeTableSerializer

urls.py:

router.register(r'date-list/(?P<movie_id>.+)/', views.DateListViewSet)

erreur:

classe DateListViewSet (viewsets.ModelViewSet, movie_id): NameError: le nom 'movie_id' n'est pas défini

Comment puis-je filtrer en utilisant des ensembles de vues dans Django Rest Framework? Ou s'il existe un autre moyen préféré que de le lister s'il vous plaît. Je vous remercie.

14

ModelViewSet de par la conception suppose que vous souhaitez implémenter un CRUD (créer, mettre à jour, supprimer)
Il existe également une ReadOnlyModelViewSet qui implémente uniquement la méthode GET pour lire uniquement les points de terminaison.
Pour les modèles Movie et Show, une ModelViewSet ou ReadOnlyModelViewSet est un bon choix que vous souhaitiez ou non implémenter CRUD.
Mais une ViewSet distincte pour une requête connexe d'une TimeTable qui décrit la planification d'un modèle Movie ne semble pas si bonne.
Une meilleure approche consisterait à attribuer directement ce paramètre à une MovieViewSet. DRF l'a fourni par les décorateurs @detail_route et @list_route

from rest_framework.response import Response
from rest_framework.decorators import detail_route

class MovieViewSet(viewsets.ModelViewset):

    queryset = Movie.objects.all()
    serializer_class = MovieSerializer

    @detail_route()
    def date_list(self, request, pk=None):
        movie = self.get_object() # retrieve an object by pk provided
        schedule = TimeTable.objects.filter(show__movie=movie).distinct()
        schedule_json = TimeTableSerializer(schedule, many=True)
        return Response(schedule_json.data)

Ce point de terminaison sera disponible avec un movie-list/:id/date_list url
Documents sur les itinéraires supplémentaires

12
Ivan Semochkin

L'erreur

classe DateListViewSet (viewsets.ModelViewSet, movie_id): NameError: le nom 'movie_id' n'est pas défini.

se produit parce que movie_id est transmis en tant que classe parent de DataListViewSet et non en tant que paramètre comme vous l'imaginiez

Cet exemple dans documentation devrait correspondre à ce que vous recherchez.

Ajustez votre URL:

url(r'date-list/(?P<movie_id>.+)/', views.DateListView.as_view())

Ajustez votre modèle:

class Show(models.Model):
   day = models.ForeignKey(TimeTable, related_name='show')
   time = models.TimeField(choices=CHOICE_TIME)
   movie = models.ForeignKey(Movie)

class Meta:
    unique_together = ('day', 'time')

Votre vue ressemblerait à ceci:

class DateListView(generics.ListAPIView):
     serializer_class = TimeTableSerializer

     def get_queryset(self):

         movie = Movie.objects.get(id=self.kwargs['movie_id'])

         return TimeTable.objects.filter(show__movie=movie).distinct()

Une autre façon de le faire serait:

Ajustez votre URL:

router.register(r'date-list', views.DateListViewSet)

Ajustez votre modèle:

class Show(models.Model):
   day = models.ForeignKey(TimeTable, related_name='show')
   time = models.TimeField(choices=CHOICE_TIME)
   movie = models.ForeignKey(Movie)

class Meta:
    unique_together = ('day', 'time')

Votre vue ressemblerait à ceci:

class DateListViewSet(viewsets.ModelViewSet):
     serializer_class = TimeTableSerializer
     queryset = TimeTable.objects.all()
     filter_backends = (filters.DjangoFilterBackend,)
     filter_fields = ('show__movie_id')

Ce qui vous permettra de faire des demandes telles que:

http://example.com/api/date-list?show__movie_id=1

Voir documentation

5
Hugo Brilhante

Enregistrez votre itinéraire comme

router.register(r'date-list', views.DateListViewSet)

changez maintenant votre vue comme indiqué ci-dessous,

class DateListViewSet(viewsets.ModelViewSet):
    queryset = TimeTable.objects.all()
    serializer_class = TimeTableSerializer
    lookup_field = 'movie_id'

    def retrieve(self, request, *args, **kwargs):
        movie_id = kwargs.get('movie_id', None)
        movie = Movie.objects.get(id=movie_id)
        self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
        return super(DateListViewSet, self).retrieve(request, *args, **kwargs)

Utilisez une méthode de récupération, qui fera correspondre les demandes GET au noeud final/date-list/<id>/.

L'avantage est que vous n'avez pas à gérer explicitement la sérialisation et le renvoi de la réponse vous obligez ViewSet à effectuer cette partie difficile. Nous ne faisons que mettre à jour le jeu de requêtes pour qu'il soit sérialisé et le reste de l'infrastructure fait le reste.

ModelViewSet étant implémenté en tant que,

class ModelViewSet(mixins.CreateModelMixin,
                   mixins.RetrieveModelMixin,
                   mixins.UpdateModelMixin,
                   mixins.DestroyModelMixin,
                   mixins.ListModelMixin,
                   GenericViewSet):
    """
    A viewset that provides default `create()`, `retrieve()`, `update()`,
    `partial_update()`, `destroy()` and `list()` actions.
    """
    pass

Son implémentation inclut les méthodes suivantes (verbe HTTP et noeud final sur crochet) 

  • list() (GET /date-list/)  
  • create() (POST /date-list/)  
  • retrieve() (GET date-list/<id>/)
  • update() (PUT /date-list/<id>/)
  • partial_update() (PATCH, /date-list/<id>/
  • destroy() (DELETE /date-list/<id>/)

Si vous souhaitez uniquement implémenter retrieve() ( demandes GET au noeud final date-list/<id>/), vous pouvez le faire à la place de `ModelViewSet),

from rest_framework import mixins, views

class DateListViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
    queryset = TimeTable.objects.all()
    serializer_class = TimeTableSerializer
    lookup_field = 'movie_id'

    def retrieve(self, request, *args, **kwargs):
        movie_id = kwargs.get('movie_id', None)
        movie = Movie.objects.get(id=movie_id)
        self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
        return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
4

Pour améliorer la réponse @ all-is-vanity, vous pouvez explicitement utiliser movie_id en tant que paramètre de la fonction retrieve puisque vous redéfinissez la propriété de classe lookup_field:

def retrieve(self, request, movie_id=None):
    movie = Movie.objects.get(id=movie_id)
    self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
    return super(DateListViewSet, self).retrieve(request, *args, **kwargs)

Vous pouvez également appeler self.get_object() pour obtenir l'objet:

def retrieve(self, request, movie_id=None):
    movie = self.get_object()
    self.queryset = TimeTable.objects.filter(show__movie=movie).distinct()
    return super(DateListViewSet, self).retrieve(request, *args, **kwargs)
0
busla