[PYTHON] Implémenter des URL hiérarchiques avec des routeurs imbriqués drf dans le framework Django REST

Par exemple, lorsque vous souhaitez obtenir une liste d'éléments appartenant à une catégorie spécifique, vous voulez que l'URL ressemble à / categories / <category> / items / drf-nested-routers. alanjds / drf-nested-routers) J'ai essayé de l'utiliser.

Essayez de mettre en œuvre avec drf-nested-routers

Tout en regardant README, implémentez une URL imbriquée afin que l'élément relève d'une catégorie spécifique comme l'URL ci-dessous.

/categories
/categories/{pk}
/categories/{category_pk}/items
/categories/{category_pk}/items/{pk}

models.py Tout d'abord, implémentez la catégorie et le modèle d'élément.

class Category(models.Model):
    name = models.CharField(max_length=30)
    slug = models.SlugField(unique=True)


class Item(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category)
    display_order = models.IntegerField(default=0, help_text='Ordre d'affichage')

serializers.py Il implémente également des sérialiseurs de catégories et d'articles.

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = (
            'pk',
            'name',
            'slug',
        )


class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = (
            'pk',
            'name',
            'display_order',
            'category',
        )

views.py Comme cela sera décrit plus loin, bien que le routeur soit utilisé pour générer l'URL, il ne peut pas être spécifié de sorte que «pk» et «category_pk» soient des nombres. Donc, vous devez faire attention que si vous entrez par inadvertance quelque chose comme / category / hoge / items ou / category / 1 / items / hoge, vous obtiendrez une Value Error et une Internal Server Error.

Pour éviter ValueError, retrieve () ʻutilise rest_framework.generics.get_object_or_404au lieu dedjango.shortcuts.get_object_or_404. Cependant, malheureusement, list () n'existe pas dans rest_framework.generics.get_list_or_404, donc si TypeError et ValuError se produisent après rest_framework.generics.get_object_or_404`, augmentez Http404.

from rest_framework.generics import get_object_or_404

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer


class ItemViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = ItemSerializer
    queryset = Item.objects.all()

    def retrieve(self, request, pk=None, category_pk=None):
        item = get_object_or_404(self.queryset, pk=pk, category__pk=category_pk)
        serializer = self.get_serializer(item)
        return Response(serializer.data)

    def list(self, request, category_pk=None):
        try:
            items = get_list_or_404(self.queryset, category__pk=category_pk)
        except (TypeError, ValueError):
            raise Http404
        else:
            serializer = self.get_serializer(items, many=True)
            return Response(serializer.data)

urls.py Route à l'aide de NestedSimpleRouter.

from rest_framework_nested import routers

router = routers.SimpleRouter(trailing_slash=False)
router.register(r'categories', CategoryViewSet)

categories_router = routers.NestedSimpleRouter(
    router, r'categories', lookup='category', trailing_slash=False)
categories_router.register(r'items', ItemViewSet)

urlpatterns = [
    url(r'^', include(router.urls)),
    url(r'^', include(categories_router.urls)),
]

L'URL enregistrée dans l'implémentation ci-dessus est la suivante.

categories$ [name='category-list']
categories/(?P<pk>[^/.]+)$ [name='category-detail']
categories/(?P<categoary_pk>[^/.]+)/items$ [name='item-list']
categories/(?P<categoary_pk>[^/.]+)/items/(?P<pk>[^/.]+)$ [name='item-detail']

Mise en place de méthodes de mise à jour

En regardant README, il n'y avait pas d'exemple d'implémentation autre que list () et retrieve () dans le ViewSet du côté enfant. Alors j'ai essayé.

create Il y a deux points à prendre en compte. Le premier point est d'ignorer l'existence de `` catégorie '' dans les données de requête reçues du client et d'utiliser category_pk (ou jouer avec une erreur). Le deuxième point est de ne pas oublier de vérifier l'existence d'une catégorie. Cela ressemble à ceci une fois mis en œuvre en y prêtant attention.

    def create(self, request, category_pk=None):
        category = get_object_or_404(Category.objects, pk=category_pk)
        request.data['category'] = category.pk
        return super(ItemViewSet, self).create(request)

update En gros, soyez prudent comme avec create. Cependant, le traitement lorsque la catégorie est spécifiée dans les données reçues du client est gênant, mais il se joue par validation.

views.py:

    def update(self, request, category_pk=None, *args, **kwargs):
        category = get_object_or_404(Category.objects, pk=category_pk)
        request.data['category'] = category.pk
        return super(ItemViewSet, self).update(request, *args, **kwargs)

serializers.py:

class ItemSerializer(serializers.ModelSerializer):
    class Meta:
        model = Item
        fields = (
            'pk',
            'name',
            'display_order',
            'category',
        )

    def validate_category(self, category):
        if self.instance and self.instance.category_id != category.id:
            raise serializers.ValidationError("can not update to category.")

        return category

Je pense que ce serait bien de pouvoir changer la catégorie, mais je me demande si le code d'état dans ce cas devrait être autour de status.HTTP_204_NO_CONTENT. La ressource n'existe pas dans l'URL lorsque PUT est terminé.

destroy Fondamentalement, détruire équivaut à créer. Cependant, comme il n'est pas nécessaire d'obtenir la catégorie, implémentez-la pour que l'élément soit obtenu avec get_object_or_404.

    def destroy(self, request, pk=None, category_pk=None):
        item = get_object_or_404(self.queryset, pk=pk, category__pk=category_pk)
        self.perform_destroy(item)
        return Response(status=status.HTTP_204_NO_CONTENT)

Trier les enfants

Il est facile de trier les résultats de l'accès à / categories / {category_pk} / items. Modifiez simplement le jeu de requêtes ViewSet.

class ItemViewSet(viewsets.ModelViewSet):
    serializer_class = ItemSerializer
    queryset = Item.objects.all().order_by('display_order')

Cependant, avec la méthode ci-dessus, il n'est pas possible de mettre des éléments dans le résultat de / categories / {category_pk} et de les trier. Dans ce cas, il ne semble pas y avoir d'autre moyen que de le spécifier dans l'ordre du modèle.

serializers.py:

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = (
            'pk',
            'name',
            'slug',
            'item_set'
        )

    item_set = ItemSerializer(many=True, read_only=True)

models.py:

class Item(models.Model):
    class Meta(object):
        ordering = ('display_order', 'pk')

    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category)
    display_order = models.IntegerField(default=0, help_text='Ordre d'affichage')

Utiliser un slug pour l'URL

Si vous voulez utiliser categories / some-slug / items au lieu de categories / 1 / items, spécifiez simplement lookup_field dans ViewSet.

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    lookup_field = 'slug'

L'URL enregistrée dans ce cas est la suivante.

categories$ [name='category-list']
categories/(?P<slug>[^/.]+)$ [name='category-detail']
categories/(?P<category_slug>[^/.]+)/items$ [name='item-list']
categories/(?P<category_slug>[^/.]+)/items/(?P<pk>[^/.]+)$ [name='item-detail']

Puisque l'argument passé à ItemViewSet est category_slug, les arguments de diverses méthodes doivent y correspondre.

class ItemViewSet(viewsets.ModelViewSet):
    serializer_class = ItemSerializer
    queryset = Item.objects.all().order_by('display_order')

    def retrieve(self, request, pk=None, category_slug=None):
        item = get_object_or_404(self.queryset, pk=pk, category__slug=category_slug)
        serializer = self.get_serializer(item)
        return Response(serializer.data)

list_ruote list_route peut également être utilisé normalement. Par exemple, si vous souhaitez trier les éléments dans l'ordre spécifié, implémentez une méthode pour mettre à jour display_order dans categories / 1 / items / sort.

class ItemViewSet(viewsets.ModelViewSet):
    (...réduction)

    @list_route(methods=['patch'])
    @transaction.atomic
    def sort(self, request, category_pk=None):
        items = self.queryset.filter(category__pk=category_pk)

        for i, pk in enumerate(request.data.get('item_pks', [])):
            items.filter(pk=pk).update(display_order=i + 1)

        return Response()

Code source

Si vous en avez envie, générez l'exemple de code quelque part.

Différentes versions

Python==3.6 Django==1.10.6 djangorestframework==3.6.2 drf-nested-routers==0.90.0

Site de référence

https://github.com/alanjds/drf-nested-routers

Recommended Posts

Implémenter des URL hiérarchiques avec des routeurs imbriqués drf dans le framework Django REST
Implémenter la fonctionnalité de connexion JWT dans le framework Django REST
Implémentation de la fonction d'authentification dans Django REST Framework à l'aide de djoser
Framework Django REST avec Vue.js
Connectez-vous avec Django Rest Framework
[Django] Utiliser MessagePack avec le framework Django REST
Créer une API RESTful avec Django Rest Framework
Suppression logique dans Django, DRF (Django REST Framework)
CRUD GET avec Nuxt & Django REST Framework ②
CRUD POST avec Nuxt & Django REST Framework
CRUD GET avec Nuxt & Django REST Framework ①
Implémentation de la fonction d'authentification du modèle utilisateur personnalisé dans Django REST Framework à l'aide de djoser
Comment gérer les caractères déformés dans json de Django REST Framework
CRUD PUT, DELETE avec Nuxt & Django REST Framework
Bases du framework Django REST
Astuces Django Rest Framework
Créer une application Todo avec Django REST Framework + Angular
Plus de nouvelles méthodes d'authentification des utilisateurs avec Django REST Framework
Essayez de créer une application Todo avec le framework Django REST
Créer une API autour de l'authentification des utilisateurs avec Django REST Framework
Lorsque vous souhaitez filtrer avec le framework Django REST
List, méthode pour les ressources imbriquées dans le framework Django REST
Implémentez l'API à une vitesse explosive en utilisant Django REST Framework
Implémenter la fonction de suivi dans Django
Bloc d'achoppement du framework Django REST
Comment écrire une validation personnalisée dans Django REST Framework
recharger dans le shell django avec ipython
Implémentation de la fonction d'authentification JWT dans Django REST Framework à l'aide de djoser
Implémentez rapidement l'API REST en Python
API GraphQL utilisant graphene_django dans Django
Créer une API REST pour faire fonctionner dynamodb avec le Framework Django REST
Un outil administratif qui peut être créé immédiatement avec le framework ng-admin + Django REST
Notes diverses sur le framework Django REST
Django REST Framework + Considération de conception d'architecture propre
Implémenter un modèle utilisateur personnalisé dans Django
Je souhaite créer une API qui retourne un modèle avec une relation récursive dans Django REST Framework
Comment vous permettre d'essayer les fonctionnalités du framework django rest dans un seul fichier
Comment implémenter la fonctionnalité de type helper Rails dans Django
Enregistrez plusieurs modèles sous un seul formulaire avec Django
Démarrez Django dans un environnement virtuel à l'aide de Pipenv
Créez un environnement Django avec Vagrant en 5 minutes
Comment implémenter "named_scope" de RubyOnRails avec Django
Supprimer les chaînes supplémentaires dans l'URL avec une expression canonique
Framework Django REST Un peu utile à savoir.
Configurer un module avec plusieurs fichiers dans Django
Comment créer une API Rest dans Django
Parfois, vous souhaitez accéder aux informations de vue depuis Serializer avec DRF (Django REST Framework)