Tutorial

Comment créer un raccourci d'URL avec Django et GraphQL

PythonDjangoPython FrameworksDevelopmentProgramming Project

L'auteur a sélectionné Girls Who Code pour recevoir un don dans le cadre de l'initiative Écrire pour des donations. 

Introduction

GraphQL est une norme d'API créée et mise à disposition par Facebook comme alternative à API REST. Contrairement aux API REST, GraphQL utilise un système typé pour définir sa structure de données où toutes les informations envoyées et reçues doivent être conformes à un schéma prédéfini. Il expose également un point d'accès unique pour toutes les communications au lieu de multiples URL pour différentes ressources et résout le problème épineux en renvoyant uniquement les données demandées par le client, ce qui génère des réponses plus petites et plus concises.

Dans ce tutoriel, vous allez créer un backend pour un raccourcisseur d'URL-un service qui prend n'importe quelle URL et génère une version plus courte et plus lisible- tout en plongeant dans les concepts de GraphQL, comme les interrogations et les mutations et les outils, comme l'interface GraphiQL. Vous avez peut-être déjà utilisé de tels services auparavant comme bit.ly.

Comme GraphQL est une technologie non basée sur le langage, elle est appliquée en plus de langages et cadres divers. Ici, vous utiliserez le langage de programmation général Python, le cadre web Django et la bibliothèque Graphene-Django comme implémentation Python GraphQL avec des intégrations spécifiques pour Django.

Conditions préalables

Étape 1 - Mise en place du projet Django

Dans cette étape, vous installerez tous les outils nécessaires à l'application et à la mise en place de votre projet Django.

Une fois que vous avez créé le répertoire de votre projet et lancé votre environnement virtuel comme prévu dans les conditions préalables, installez les paquets nécessaires en utilisant piple gestionnaire de paquets Python. Ce tutoriel permet d'installer la version 2.1.7 de Django et la version 2.2.0 ou supérieure de Graphene-Django :

  • pip install "django==2.1.7" "graphene-django>==2.2.0"

Vous avez maintenant tous les outils nécessaires dans votre ceinture à outils. Ensuite, vous allez créer un projet Django en utilisant la commande django-admin. Un projet est la plaque tournante par défaut de Django - un ensemble de dossiers et de fichiers avec tout le nécessaire pour démarrer le développement d'une application web. Dans ce cas, vous appellerez votre projet shorty et le créerez au sein de votre dossier actuel en spécifiant le . à la fin.

  • django-admin startproject shorty .

Après avoir créé votre projet, vous dirigerez les migrations de Django. Ces fichiers contiennent du code Python généré par Django et sont chargés de modifier la structure de l'application selon les modèles de Django. Les changements peuvent comprendre la création d'une table, par exemple. Par défaut, Django est livré avec son propre ensemble de migrations responsables de sous-systèmes comme Django Authentification. Il est donc nécessaire de les exécuter avec la commande suivante : 

  • python manage.py migrate

Cette commande utilise l'interpréteur Python pour invoquer un script Django appelé manage.py, chargé de gérer différents aspects de votre projet, comme la création d'applications ou l'exécution de migrations.

Cela donnera un résultat similaire à ce qui suit :

Output
Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying sessions.0001_initial... OK

Une fois que la base de données de Django est prête à fonctionner, démarrez son serveur de développement local :

  • python manage.py runserver

Il en résultera :

Output
Performing system checks... System check identified no issues (0 silenced). March 18, 2020 - 15:46:15 Django version 2.1.7, using settings 'shorty.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.

Cette commande supprime l'invite dans votre terminal et démarre le serveur.

Visitez http://127.0.0.1:8000 dans votre navigateur local. Vous verrez cette page :

Première page du serveur local de Django 

Pour arrêter le serveur et revenir à votre terminal, appuyez sur CTRL+C. Chaque fois que vous devez accéder au navigateur, assurez-vous que la commande précédente est exécutée.

Ensuite, vous terminerez cette étape en activant la bibliothèque Django-Graphene dans le projet. Django a le concept d’app, une application web avec une responsabilité spécifique. Un projet est composé d'une ou plusieurs applications. Pour l'instant, ouvrez le fichiershorty/settings.py dans l'éditeur de texte de votre choix. Ce tutoriel utilisera vim : 

  • vim shorty/settings.py

Le fichier settings.py gère tous les paramètres de votre projet. A l'intérieur, cherchez le INSTALLED_APPS et ajoutez l'entrée « graphene_django » : 

shorty/shorty/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'graphene_django',
]
...

Cet ajout indique à Django que vous utiliserez une application appelée graphene_django que vous avez installée à l'étape 1. 

Au bas du fichier, ajoutez la variable suivante :

shorty/shorty/settings.py
...
GRAPHENE = {
    'SCHEMA': 'shorty.schema.schema',
}

Cette dernière variable indique votre schéma principal, que vous créerez plus tard. Dans GraphQL, un schéma contient tous les types d'objets, tels que les ressources, les requêtes et les mutations. Considérez qu'il s'agit d'une documentation représentant toutes les données et fonctionnalités disponibles dans votre système.

Après les modifications, enregistrez et fermez le fichier.

Vous avez maintenant configuré le projet Django. Dans l'étape suivante, vous allez créer une application Django et ses modèles.

Étape 2 - Mise en place d'une application Django et de modèles

Une plateforme Django est généralement composée d'un projet et de nombreuses applications ou apps. Une app ou application décrit un ensemble de fonctionnalités à l'intérieur d'un projet, et, si elle est bien conçue, elle peut être réutilisée à travers les projets Django.

Dans cette étape, vous allez créer une application appelée shortenerresponsable de la fonction de raccourcissement des URL. Pour créer son squelette de base, tapez la commande suivante dans votre terminal :

  • python manage.py startapp shortener

Ici, vous avez utilisé les paramètres startapp app_name, en donnant des instructions à manage.py de créer une application appelée shortener. 

Pour terminer la création de l'application, ouvrez le fichier shorty/settings.py

  • vim shorty/settings.py

Ajouter le nom de l'application à la même entrée INSTALLED_APPS que vous avez modifiée auparavant :

shorty/shorty/settings.py
...
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'graphene_django'
    'shortener',
]
...

Enregistrez et fermez le fichier.

Avec votre raccourcisseur ajouté à shorty/settings.py, vous pouvez passer à la création des modèles pour votre projet. Les modèles sont l'un des éléments clés de Django. Ils sont utilisés pour représenter une base de données sous forme « Python », ce qui permet de gérer, d'interroger et de stocker des données à l'aide de code Python.

Avant d'ouvrir le fichier models.py pour les modifications, ce tutoriel donne un aperçu des modifications que vous allez apporter.

Votre fichier de modèle, models.py, contiendra le contenu suivant une fois que vous aurez remplacé le code existant :

shorty/shortener/models.py
from hashlib import md5

from django.db import models

Ici, vous importerez les paquets nécessaires à votre code. Vous ajouterez la ligne de hashlib import md5 en haut pour importer la bibliothèque standard Python qui sera utilisée pour créer un hachage de l'URL. La ligne de modèlesfrom django.db est une aide de Django pour la création de modèles. 

Avertissement : Ce tutoriel fait référence au hachage comme le résultat d'une fonction qui prend une entrée et renvoie toujours la même sortie. Ce tutoriel utilisera la fonction de hachage MD5 à des fins de démonstration. 

Notez que le MD5 présente des problèmes de collision et doit être évité en production.

Ensuite, vous ajouterez un modèle nommé URL avec les champs suivants :

  • full_url : l'URL à raccourcir.
  • url_hash : un court hachage représentant l'URL complète.
  • clics : nombre de fois que l'URL courte a été consultée.
  • created_at : la date et l'heure auxquelles l'URL a été créée.
shorty/shortener/models.py
...

class URL(models.Model):
    full_url = models.URLField(unique=True)
    url_hash = models.URLField(unique=True)
    clicks = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

Vous allez générer l’url_hash en appliquant l'algorithme de hachage MD5 au champ de saisie full_url et en utilisant uniquement les 10 premiers caractères renvoyés lors de la méthode save() du modèle, exécutée à chaque fois que Django enregistre une entrée dans la base de données. En outre, les raccourcisseurs d'URL suivent généralement le nombre de fois qu'un lien a été cliqué. Vous y parviendrez en appelant la méthode clicked() lorsque l'URL est visitée par un utilisateur.

Les opérations mentionnées seront ajoutées à l'intérieur de votre Modèle d’URL avec ce code :

shorty/shortener/models.py
...

    def clicked(self):
        self.clicks += 1
        self.save()

    def save(self, *args, **kwargs):
        if not self.id:
            self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]

        return super().save(*args, **kwargs)

Maintenant que vous avez examiné le code, ouvrez le fichier shortener/models.py :

  • vim shortener/models.py

Remplacez le code par le contenu suivant :

shorty/shortener/models.py
from hashlib import md5

from django.db import models


class URL(models.Model):
    full_url = models.URLField(unique=True)
    url_hash = models.URLField(unique=True)
    clicks = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    def clicked(self):
        self.clicks += 1
        self.save()

    def save(self, *args, **kwargs):
        if not self.id:
            self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]

        return super().save(*args, **kwargs)

Veillez à enregistrer et à fermer le fichier.

Pour appliquer ces changements dans la base de données, vous devrez créer les migrations en exécutant la commande suivante :

  • python manage.py makemigrations

Cela donnera la sortie suivante :

Output
Migrations for 'shortener': shortener/migrations/0001_initial.py - Create model URL

Ensuite, exécutez les migrations :

  • python manage.py migrate

Vous verrez la sortie suivante dans votre terminal :

Output
Operations to perform: Apply all migrations: admin, auth, contenttypes, sessions, shortener Running migrations: Applying shortener.0001_initial... OK

Maintenant que vous avez mis en place les modèles, l'étape suivante consiste à créer le point final GraphQL et une requête.

Étape 3 - Création de requêtes

L'architecture REST expose différentes ressources dans différents points finaux, chacun contenant une structure de données bien définie. Par exemple, vous pouvez obtenir une liste d'utilisateurs à l'adresse /api/usersqui s'attendent toujours aux mêmes domaines. GraphQL, en revanche, a un point final unique pour toutes les interactions, et utilise des Queries ou requêtes d'accès aux données. La différence principale - et la plus importante - est que vous pouvez utiliser une requête pour retrouver tous vos utilisateurs en une seule fois. 

Commencez par créer une requête pour récupérer toutes les URL. Vous aurez besoin de plusieurs choses :

  • Un type d'URL, lié à votre modèle préalablement défini.
  • Une déclaration de requête nommée urls. 
  • Une méthode pour résoudre votre requête, c'est-à-dire récupérer toutes les URL de la base de données et les renvoyer au client.

Créez un nouveau fichier appelé shortener/schema.py: 

  • vim shortener/schema.py

Commencez par ajouter les déclarations d’import Python :

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType

from .models import URL

La première ligne importe la principale bibliothèque de graphene qui contient les types de base de GraphQL, comme List. Le DjangoObjectType est une aide pour créer une définition de schéma à partir de n'importe quel modèle Django, et la troisième ligne importe votre modèle URL créé préalablement.

Ensuite, créez un nouveau type de GraphQL pour le modèle d’URL en ajoutant les lignes suivantes : 

shorty/shortener/schema.py
...
class URLType(DjangoObjectType):
    class Meta:
        model = URL

Enfin, ajoutez ces lignes pour créer un type de requête pour le modèle d’URL : 

shorty/shortener/schema.py
...
class Query(graphene.ObjectType):
    urls = graphene.List(URLType)

    def resolve_urls(self, info, **kwargs):
        return URL.objects.all()

Ce code crée une classe de Query avec un champ nommé urls qui est une liste du URLType défini précédemment. Lors de la résolution de la requête à l'aide des resolve_urlsvous renvoyez toutes les URL stockées dans la base de données. 

Le fichier complet shortener/schema.py est présenté ici :

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType

from .models import URL


class URLType(DjangoObjectType):
    class Meta:
        model = URL


class Query(graphene.ObjectType):
    urls = graphene.List(URLType)

    def resolve_urls(self, info, **kwargs):
        return URL.objects.all()

Enregistrez et fermez le fichier.

Toutes les requêtes doivent maintenant être ajoutées au schéma principal. Considérez-le comme le détenteur de toutes vos ressources.

Créez un nouveau dossier dans le shorty/schema.py et ouvrez-le avec votre éditeur : 

  • vim shorty/schema

Importez les paquets Python suivants en ajoutant les lignes suivantes. Le premier, comme déjà mentionné, contient les types GraphQL de base. La deuxième ligne importe le fichier Schema précédemment créé.

shorty/shorty/schema.py
import graphene

import shortener.schema

Ensuite, ajoutez la principale classe de Query. Elle conservera, par héritage, toutes les requêtes et les futures opérations créées :

shorty/shorty/schema.py
...
class Query(shortener.schema.Query, graphene.ObjectType):
    pass

Enfin, créez la variable schema : 

shorty/shorty/schema.py
...
schema = graphene.Schema(query=Query)

Le paramètre SCHEMA que vous avez défini à l'étape 2 renvoie à la variable schéma que vous venez de créer.

Le shorty/schema.py est affiché ici en entier : 

shorty/shorty/schema.py
import graphene

import shortener.schema


class Query(shortener.schema.Query, graphene.ObjectType):
    pass

schema = graphene.Schema(query=Query)

Enregistrez et fermez le fichier.

Ensuite, activez le point final GraphQL et l'interface GraphiQLqui est une interface graphique web utilisée pour interagir avec le système GraphQL. 

Ouvrez le fichiershorty/urls.py : 

  • vim shorty/urls.py

Pour les besoins de ce tutoriel, supprimez le contenu du fichier et enregistrez-le, afin de pouvoir recommencer à zéro.

Les premières lignes que vous ajouterez sont des déclarations d'importation en Python :

shorty/shorty/urls.py
from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

La fonction path est utilisée par Django pour créer une URL accessible pour l'interface GraphiQL. Après, vous importez le csrf_exempt qui permet aux clients d'envoyer des données au serveur. Une explication complète se trouve dans la documentation du Graphene. À la dernière ligne, vous avez importé le code réel responsable de l'interface via GraphQLView.

Ensuite, créez une variable nommée urlpatterns. 

shorty/shorty/urls.py
...
urlpatterns = [
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

Cela permettra d'assembler tout le code nécessaire pour rendre l'interface GraphiQL disponible dans le chemin graphql/ :

L'ensemble shortener/urls.py est affiché ici : 

shorty/shorty/urls.py
from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

urlpatterns = [
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
]

Enregistrez le dossier et fermez-le.

De retour dans le terminal, lancez la commande python manage.py runserver (si elle n'est pas déjà en cours d'exécution) :

  • python manage.py runserver

Ouvrez votre navigateur web à l'adresse suivante http://localhost:8000/graphql l'adresse. Cet écran s'affichera :

Interface GraphiQL 

GraphiQL est une interface dans laquelle vous pouvez exécuter des instructions GraphQL et voir les résultats. L'une des caractéristiques est la section « Docs » en haut à droite. Comme tout dans GraphQL est typé, vous obtenez une documentation gratuite sur tous vos types, requêtes, mutations, etc.

Après avoir exploré la page, insérez votre première requête dans la zone de texte principale :

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

Ce contenu montre comment une query GraphQL est structurée : tout d'abord, vous utilisez le mot-clé pour indiquer au serveur que vous ne voulez récupérer que certaines données. Ensuite, vous utilisez le champ urls défini dans le fichier shortener/schema.py à l'intérieur de la classe Query. A partir de là, vous demandez explicitement tous les champs définis dans le modèle d’URL en CamelCase qui est la valeur par défaut pour GraphQL. 

Maintenant, cliquez sur le bouton flèche de lecture en haut à gauche. 

Vous recevrez la réponse suivante, indiquant que vous n'avez toujours pas d'URLs :

Output
{ "data": { "urls": [] } }

Cela montre que GraphQL fonctionne. Dans votre terminal, appuyez sur CTRL+C pour arrêter votre serveur. 

Vous avez accompli beaucoup de choses dans cette étape, en créant le point final GraphQL, en effectuant une requête pour récupérer toutes les URL et en activant l'interface GraphiQL. Maintenant, vous allez créer des mutations pour modifier la base de données.

Étape 4 - Créer des mutations

La majorité des applications permettent de modifier l'état de la base de données en ajoutant, mettant à jour ou supprimant des données. Dans GraphQL, ces opérations sont appelées « Mutations ». Elles ressemblent à des requêtes mais utilisent des arguments pour envoyer des données au serveur.

Pour créer votre première Mutation, ouvrez shortener/schema.py: 

  • vim shortener/schema.py

A la fin du fichier, commencez par ajouter une nouvelle classe nommée CreateURL: 

shorty/shortener/schema.py
...
class CreateURL(graphene.Mutation):
    url = graphene.Field(URLType)

Cette classe hérite de l'aide graphène.Mutation pour avoir les capacités d'une mutation GraphQL. Elle a également un nom de propriété url qui définit le contenu renvoyé par le serveur une fois la mutation terminée. Dans ce cas, ce sera la structure de données URLType.

Ensuite, ajoutez une sous-classe nommée Arguments à la classe déjà définie : 

shorty/shortener/schema.py
...
    class Arguments:
        full_url = graphene.String()

Cela définit les données qui seront acceptées par le serveur. Ici, vous attendez un paramètre nommé full_url avec un contenu String :

Ajoutez maintenant les lignes suivantes pour créer la méthode mutate :

shorty/shortener/schema.py
...

    def mutate(self, info, full_url):
        url = URL(full_url=full_url)
        url.save()

Cette méthode mutate fait une grande partie du travail en recevant les données du client et en les enregistrant dans la base de données. À la fin, elle renvoie la classe elle-même contenant l'élément nouvellement créé.

Enfin, créez une Mutation qui contiendra toutes les Mutations de votre app, en ajoutant ces lignes : 

shorty/shortener/schema.py
...

class Mutation(graphene.ObjectType):
    create_url = CreateURL.Field()

Jusqu'à présent, vous n'aurez qu'une seule mutation nommée create_url. 

Le fichier complet shortener/schema.py est présenté ici :

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType

from .models import URL


class URLType(DjangoObjectType):
    class Meta:
        model = URL


class Query(graphene.ObjectType):
    urls = graphene.List(URLType)

    def resolve_urls(self, info, **kwargs):
        return URL.objects.all()


class CreateURL(graphene.Mutation):
    url = graphene.Field(URLType)

    class Arguments:
        full_url = graphene.String()

    def mutate(self, info, full_url):
        url = URL(full_url=full_url)
        url.save()

        return CreateURL(url=url)


class Mutation(graphene.ObjectType):
    create_url = CreateURL.Field()

Fermez et enregistrez le fichier.

Pour terminer l'ajout de la mutation, modifiez le fichiershorty/schema.py : 

  • vim shorty/schema.py

Modifiez le fichier pour y inclure le code surligné suivant :

shorty/shorty/schema.py

import graphene

import shortener.schema


class Query(shortener.schema.Query, graphene.ObjectType):
    pass


class Mutation(shortener.schema.Mutation, graphene.ObjectType):
    pass


schema = graphene.Schema(query=Query, mutation=Mutation)

Enregistrez et fermez le fichier. Si vous n'utilisez pas le serveur local, démarrez-le :

  • python manage.py runserver

Naviguez vers http://localhost:8000/graphql dans votre navigateur web. Exécutez votre première Mutation dans l'interface web GraphiQL en exécutant la déclaration suivante :

mutation {
  createUrl(fullUrl:"https://www.digitalocean.com/community") {
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

Vous avez composé la Mutation avec le nom createURL, l'argument fullUrl, et les données que vous souhaitez voir figurer dans la réponse définie à l'intérieur du champ de l’url. 

La sortie contiendra les informations URL que vous venez de créer dans les données GraphQL comme indiqué ici : 

Output
{ "data": { "createUrl": { "url": { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 0, "createdAt": "2020-01-30T19:15:10.820062+00:00" } } } }

Avec cela, une URL a été ajoutée à la base de données avec sa version hachée, comme vous pouvez le voir dans le champ urlHash. Essayez d'exécuter la requête que vous avez créée dans la dernière étape pour voir son résultat :

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

La sortie affichera l'URL enregistrée :

Output
{ "data": { "urls": [ { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 0, "createdAt": "2020-03-18T21:03:24.664934+00:00" } ] } }

Vous pouvez également essayer d'exécuter la même requête, mais en ne demandant que les champs que vous voulez.

Ensuite, essayez-le encore une fois avec une autre URL :

mutation {
  createUrl(fullUrl:"https://www.digitalocean.com/write-for-donations/") {
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

Le résultat sera :

Output
{ "data": { "createUrl": { "url": { "id": "2", "fullUrl": "https://www.digitalocean.com/write-for-donations/", "urlHash": "703562669b", "clicks": 0, "createdAt": "2020-01-30T19:31:10.820062+00:00" } } } }

Le système est maintenant capable de créer des URL courtes et de les répertorier. Dans l'étape suivante, vous permettrez aux utilisateurs d'accéder à une URL par sa version courte en les redirigeant vers la bonne page.

Étape 5 - Créer le point final d'accès

Dans cette étape, vous utiliserez Django Views-une méthode qui prend une demande et renvoie une réponse - pour rediriger toute personne accédant à la http://localhost:8000/url_hash à son URL complète. 

Ouvrez le shortener/views.py avec votre éditeur : 

  • vim shortener/views.py

Pour commencer, importez deux paquets en remplaçant le contenu par les lignes suivantes :

shorty/shortener/views.py
from django.shortcuts import get_object_or_404, redirect

from .models import URL

Celles-ci seront expliquées plus en détail ultérieurement.

Ensuite, vous allez créer une Django View nommée root (racine). Ajoutez ce bout de code responsable de la View à la fin de votre fichier :

shorty/shortener/views.py
...

def root(request, url_hash):
    url = get_object_or_404(URL, url_hash=url_hash)
    url.clicked()

    return redirect(url.full_url)

Il reçoit un argument appelé url_hash à partir de l'URL demandée par un utilisateur. Dans la fonction, la première ligne essaie de récupérer l'URL de la base de données en utilisant l'argument url_hash. Si elle n'est pas trouvée, elle renvoie l'erreur HTTP 404 au client, ce qui signifie que la ressource est manquante. Ensuite,elle augmente le la propriété cliquée de l'entrée de l'URL, en veillant à suivre le nombre de fois où l'URL est consultée. A la fin, elle redirige le client vers l'URL demandée.

L'ensemble shortener/views.py est affiché ici : 

shorty/shortener/views.py
from django.shortcuts import get_object_or_404, redirect

from .models import URL


def root(request, url_hash):
    url = get_object_or_404(URL, url_hash=url_hash)
    url.clicked()

    return redirect(url.full_url)

Enregistrez et fermez le fichier.

Ensuite, ouvrez shorty/urls.py :

  • vim shorty/urls.py

Ajoutez le code surligné suivant pour activer la View racine.

shorty/shorty/urls.py

from django.urls import path
from django.views.decorators.csrf import csrf_exempt

from graphene_django.views import GraphQLView

from shortener.views import root


urlpatterns = [
    path('graphql/', csrf_exempt(GraphQLView.as_view(graphiql=True))),
    path('<str:url_hash>/', root, name='root'),
]

La View racine sera accessible dans le /path de votre serveur, en acceptant un url_hash comme paramètre de chaîne.

Enregistrez et fermez le fichier. Si vous n'utilisez pas le serveur local, démarrez-le en exécutant la commande python manage.py runserver.

Pour tester votre nouvel ajout, ouvrez votre navigateur web et accédez à l'URL http://localhost:8000/077880af78.  Notez que la dernière partie de l'URL est le hachage créé par la mutation de l'étape 5. Vous serez redirigé vers la page URL du hachage, dans ce cas, le site web de la communauté DigitalOcean.

Maintenant que la redirection URL fonctionne, vous rendrez l'application plus sûre en mettant en œuvre un traitement des erreurs lors de l'exécution de la mutation.

Étape 6 - Mise en œuvre du traitement des erreurs

Le traitement des erreurs est une bonne habitude à avoir dans toutes les applications, car les développeurs ne contrôlent généralement pas ce qui sera envoyé au serveur. Dans ce cas, vous pouvez essayer de prévoir les défaillances et de minimiser leurs impacts. Dans un système complexe tel que GraphQL, beaucoup de choses peuvent mal tourner, du client qui demande des données erronées jusqu'au serveur qui perd l'accès à la base de données.

En tant que système typographique , GraphQL peut vérifier tout ce que le client demande et reçoit dans une opération appelée Schema Validation (Validation du schéma). Vous pouvez le voir en action en effectuant une Query ou (Requête) avec un champ non existant.

Naviguez vers http://localhost:8000/graphql dans votre navigateur, et exécutez la requête suivante dans l'interface GraphiQL avec le champ iDontExist : 

query {
  urls {
    id
    fullUrl
    urlHash
    clicks
    createdAt
    iDontExist
  }
}

Comme il n'y a pas de iDontExist défini dans votre requête, GraphQL renvoie un message d'erreur :

Output
{ "errors": [ { "message": "Cannot query field \"iDontExist\" on type \"URLType\".", "locations": [ { "line": 8, "column": 5 } ] } ] }

Ceci est important car, dans le système typographique GraphQL, l'objectif est d'envoyer et de recevoir uniquement les informations déjà définies dans le schéma.

La présente demande accepte tout type de chaîne arbitraire dans le champ full_url. Le problème est que si quelqu'un envoyait une URL mal construite, vous redirigeriez l'utilisateur vers nulle part en essayant les informations stockées. Dans ce cas, vous devez vérifier si le full_url est bien formaté avant de l'enregistrer dans la base de données, et, en cas d'erreur, lever l'exception GraphQLError avec un message personnalisé.

Mettons en place cette fonctionnalité en deux étapes. Tout d'abord, ouvrez le fichier raccourcis/modèles.py : 

  • vim shortener/models.py

Ajoutez les lignes surlignées dans la section d'importation :

shorty/shortener/models.py
from hashlib import md5

from django.db import models
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError

from graphql import GraphQLError
...

L’URLValidator est une aide de Django pour valider une chaîne d'URL et l'erreur GraphQLE est utilisée par Graphene pour lever des exceptions avec un message personnalisé.

Ensuite, assurez-vous de valider l'URL reçue par l'utilisateur avant de l'enregistrer dans la base de données. Activez cette opération en ajoutant le code mis en évidence dans le fichier raccourcis/modèles.py :

shorty/shortener/models.py
class URL(models.Model):
    full_url = models.URLField(unique=True)
    url_hash = models.URLField(unique=True)
    clicks = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    def clicked(self):
        self.clicks += 1
        self.save()

    def save(self, *args, **kwargs):
        if not self.id:
            self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]

        validate = URLValidator()
        try:
            validate(self.full_url)
        except ValidationError as e:
            raise GraphQLError('invalid url')

        return super().save(*args, **kwargs)

Premièrement, ce code instancie l’URLValidator dans la variable validate. A l'intérieur du bloc try/except, vous validez() l'URL reçue et faites apparaître une erreur GraphQLE avec le message personnalisé d'url invalide si quelque chose s'est mal passé. 

Le fichier complet shortener/models.py est affiché ici :

shorty/shortener/models.py
from hashlib import md5

from django.db import models
from django.core.validators import URLValidator
from django.core.exceptions import ValidationError

from graphql import GraphQLError


class URL(models.Model):
    full_url = models.URLField(unique=True)
    url_hash = models.URLField(unique=True)
    clicks = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=True)

    def clicked(self):
        self.clicks += 1
        self.save()

    def save(self, *args, **kwargs):
        if not self.id:
            self.url_hash = md5(self.full_url.encode()).hexdigest()[:10]

        validate = URLValidator()
        try:
            validate(self.full_url)
        except ValidationError as e:
            raise GraphQLError('invalid url')

        return super().save(*args, **kwargs)

Enregistrez et fermez le fichier. Si vous n'utilisez pas le serveur local, démarrez-le avec la commande python manage.py runserver. 

Ensuite, testez votre nouveau traitement des erreurs sur http://localhost:8000/graphql. Essayez de créer une nouvelle URL avec un full_url non valide dans l'interface GraphiQL :

mutation {
  createUrl(fullUrl:"not_valid_url"){
    url {
      id
      fullUrl
      urlHash
      clicks
      createdAt
    }
  }
}

Lorsque vous envoyez une URL non valide, votre exception sera signalée par le message personnalisé :

Output
{ "errors": [ { "message": "invalid url", "locations": [ { "line": 2, "column": 3 } ], "path": [ "createUrl" ] } ], "data": { "createUrl": null } }

Si vous regardez dans votre terminal où lacommande python manage.py runserver est en cours d'exécution, une erreur apparaîtra : 

Output
... graphql.error.located_error.GraphQLLocatedError: invalid url [30/Jan/2020 19:46:32] "POST /graphql/ HTTP/1.1" 200 121

Un terminal GraphQL échouera toujours avec un code d'état HTTP 200, ce qui signifie généralement un succès. Rappelez-vous que, même si GraphQL est construit au-dessus de HTTP, il n'utilise pas les concepts de codes d'état HTTP ou de méthodes HTTP comme le fait REST.

Grâce à la mise en place du traitement des erreurs, vous pouvez maintenant mettre en place un mécanisme pour filtrer vos requêtes en minimisant les informations renvoyées par le serveur.

Étape 7 - Mise en place des filtres

Imaginez que vous avez commencé à utiliser le raccourcisseur d'URL pour ajouter vos propres liens. Au bout d'un certain temps, il y aura tellement d'entrées qu'il sera difficile de trouver la bonne. Vous pouvez résoudre ce problème en utilisant des filtres.

Le filtrage est un concept courant dans les API REST, où généralement un Paramètre d'interrogation avec un champ et une valeur est ajouté à l'URL. Par exemple, pour filtrer tous les utilisateurs nommés jojo, vous pouvez utiliser GET /api/users?name=jojo. 

Dans GraphQL, vous utiliserez les Query Arguments (Arguments de requête) comme filtres. Ils créent une interface agréable et propre.

Vous pouvez résoudre le problème des « URL difficiles à trouver » en permettant au client de filtrer les URL par leur nom en utilisant le champ full_url. Pour ce faire, il faut ouvrir le fichiershortener/schema.py dans votre éditeur préféré. 

  • vim shortener/schema.py

Tout d'abord, importez la méthode Q dans la ligne surlignée :

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q

from .models import URL
...

Elle sera utilisée pour filtrer votre recherche dans la base de données.

Ensuite, réécrivez l'ensemble classe de recherche avec le contenu suivant : 

shorty/shortener/schema.py
...
class Query(graphene.ObjectType):
    urls = graphene.List(URLType, url=graphene.String())

    def resolve_urls(self, info, url=None, **kwargs):
        queryset = URL.objects.all()

        if url:
            _filter = Q(full_url__icontains=url)
            queryset = queryset.filter(_filter)

        return queryset
...

Les modifications que vous apportez sont :

  • Ajouter le paramètre de filtre d'url à l'intérieur de la variable urls et laméthode resolve_url. 
  • A l'intérieur des resolve_urls,si un paramètre nommé est donné, filtrer les résultats de la base de données aboutit à renvoyer les URL qui contiennent la valeur donnée en utilisant la méthode Q (full_url__icontains=url) méthode. 

Le fichier complet shortener/schema.py est présenté ici :

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q

from .models import URL


class URLType(DjangoObjectType):
    class Meta:
        model = URL


class Query(graphene.ObjectType):
    urls = graphene.List(URLType, url=graphene.String())

    def resolve_urls(self, info, url=None, **kwargs):
        queryset = URL.objects.all()

        if url:
            _filter = Q(full_url__icontains=url)
            queryset = queryset.filter(_filter)

        return queryset


class CreateURL(graphene.Mutation):
    url = graphene.Field(URLType)

    class Arguments:
        full_url = graphene.String()

    def mutate(self, info, full_url)
        url = URL(full_url=full_url)
        url.save()

        return CreateURL(url=url)


class Mutation(graphene.ObjectType):
    create_url = CreateURL.Field()

Enregistrez et fermez le fichier. Si vous n'utilisez pas le serveur local, commencez par python manage.py runserver. 

Testez vos derniers changements sur http://localhost:8000/graphql. Dans l'interface GraphiQL, écrivez la déclaration suivante. Il filtrera toutes les URL avec le mot communauté :

query {
  urls(url:"community") {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

Le résultat n'est qu'une seule entrée puisque vous venez d'ajouter une URL avec la chaîne communautaire dedans. Si vous avez ajouté d'autres URL auparavant, votre résultat peut varier.

Output
{ "data": { "urls": [ { "id": "1", "fullUrl": "https://www.digitalocean.com/community", "urlHash": "077880af78", "clicks": 1, "createdAt": "2020-01-30T19:27:36.243900+00:00" } ] } }

Vous avez maintenant la possibilité d'effectuer des recherches par le biais de vos URL. Cependant, avec trop de liens, vos clients pourraient se plaindre que la liste d'URL renvoie plus de données que leurs applications ne peuvent traiter. Pour résoudre ce problème, vous allez mettre en place la pagination.

Étape 8 - Mise en place de la pagination

Les clients qui utilisent votre backend peuvent se plaindre que le temps de réponse est trop long ou que sa taille est trop importante s'il y a trop d'entrées d'URL. Même votre base de données peut avoir du mal à rassembler un grand nombre d'informations. Pour résoudre ce problème, vous pouvez permettre au client de préciser le nombre d'éléments qu'il souhaite dans chaque demande en utilisant une technique appelée la pagination.

Il n'y a pas de moyen par défaut de mettre en œuvre cette fonctionnalité. Même dans les API REST, vous pouvez le voir dans les en-têtes HTTP ou les paramètres de requête, avec des noms et des comportements différents.

Dans cette application, vous implémenterez la pagination en activant deux arguments supplémentaires à la requête d'URL : first et skip. first sélectionnera le premier nombre variable d'éléments et skip spécifiera combien d'éléments doivent être sautés depuis le début. Par exemple, en utilisant d'abord == 10 et en sautant == 5, on obtient les 10 premières URL, mais on en saute 5, ce qui ne renvoie que les 5 restantes.

La mise en œuvre de cette solution est similaire à l'ajout d'un filtre.

Ouvrez le fichier shortener/schema.py :

  • vim shortener/schema.py

Dans le dossier, modifiez la classe de recherche en ajoutant les deux nouveaux paramètres dans la variable urls et la méthode resolve_urls, mise en évidence dans le code suivant :

shorty/shortener/schema.py
import graphene
from graphene_django import DjangoObjectType
from django.db.models import Q

from .models import URL


class Query(graphene.ObjectType):
    urls = graphene.List(URLType, url=graphene.String(), first=graphene.Int(), skip=graphene.Int())

    def resolve_urls(self, info, url=None, first=None, skip=None, **kwargs):
        queryset = URL.objects.all()

        if url:
            _filter = Q(full_url__icontains=url)
            queryset = queryset.filter(_filter)

        if first:
            queryset = queryset[:first]

        if skip:
            queryset = queryset[skip:]

        return queryset
...

Ce code utilise les premiers paramètres nouvellement créés et saute les paramètres à l'intérieur de la méthode resolve_urls pour filtrer la requête de la base de données.

Enregistrez et fermez le fichier. Si vous n'utilisez pas le serveur local, commencez par python manage.py runserver. 

Pour tester la pagination, lancez la requête suivante dans l'interface GraphiQL à http://localhost:8000/graphql: 

query {
  urls(first: 2, skip: 1) {
    id
    fullUrl
    urlHash
    clicks
    createdAt
  }
}

Votre raccourcisseur d'URL renvoie la deuxième URL créée dans votre base de données :

Output
{ "data": { "urls": [ { "id": "2", "fullUrl": "https://www.digitalocean.com/write-for-donations/", "urlHash": "703562669b", "clicks": 0, "createdAt": "2020-01-30T19:31:10.820062+00:00" } ] } }

Cela montre que la fonction de pagination fonctionne. N'hésitez pas à vous amuser en ajoutant d'autres URL et en testant différentes séries de first et skip.

Conclusion

L'ensemble de l'écosystème GraphQL se développe chaque jour avec une communauté active derrière lui. Des entreprises comme GitHub et Facebook ont prouvé qu'il était prêt au lancement, et vous pouvez maintenant appliquer cette technologie à vos propres projets.

Dans ce tutoriel, vous avez créé un service de raccourcissement d'URL à l'aide de GraphQL, Python et Django en utilisant des concepts comme les requêtes et les mutations. Mais plus que cela, vous comprenez maintenant comment s'appuyer sur ces technologies pour construire des applications web en utilisant le framework web Django.

Vous pouvez en savoir plus sur GraphQL et les outils utilisés ici dans le site web de GraphQL et les sites web de documentation consacrés à Graphene. De plus, DigitalOcean propose des tutoriels supplémentaires pour Python et Django que vous pouvez utiliser si vous souhaitez en savoir plus sur l'un ou l'autre. 

Creative Commons License