One place for hosting & domains

      GraphQL

      Cómo crear un acortador de URL con Django y GraphQL


      El autor seleccionó Girls Who Code para recibir una donación como parte del programa Write for DOnations.

      Introducción

      GraphQL es un estándar de API de código abierto creado por Facebook como alternativa a las API REST. En vez de las API REST, GraphQL utiliza un sistema escrito para definir su estructura de datos, donde toda la información enviada y recibida debe cumplir con un Schema predefinido. También expone un endpoint individual para todas las comunicaciones en vez de múltiples URLs para diferentes recursos y resuelve el problema de overfetching devolviendo solo los datos pedidos por el cliente, generando así respuestas más pequeñas y concisas.

      En este tutorial creará un backend para un servicio acortador de URL que toma cualquier URL y genera una versión más corta y legible. Además profundizaremos en los conceptos de GraphQL, como “Query” y “Mutation”, y en las herramientas como la interfaz GraphiQL. Es posible que ya haya usado dichos servicios antes, como bit.ly.

      Ya que GraphQL es un lenguaje con tecnología agnóstica, se implemente sobre varios lenguajes y marcos. Aquí, usará el lenguaje de programación Python de uso general, el marco web Django, y la biblioteca Graphene-Django como la implementación de GraphQL Python con integraciones específicas para Django.

      Requisitos previos

      Paso 1: Configurar el proyecto Django

      En este paso, instalará todas las herramientas necesarias para la aplicación y para configurar su proyecto de Django.

      Una vez que haya creado el directorio de su proyecto e iniciado su entorno virtual, como se indica en los requisitos previos, instale los paquetes necesarios usando pip, el administrador de paquetes de Phython. Este tutorial instalará la versión 2.1.7 de Django y la versión 2.2.0 de Graphene-Django o superior.

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

      Ahora tendrá todas las herramientas necesarias para trabajar. A continuación, creará un proyecto Django usando el comando django-admin. Un proyecto es la plantilla predeterminada de Django, un conjunto de carpetas y archivos con todo lo necesario para iniciar el desarrollo de una aplicación web. En este caso, llamará a su proyecto shorty y lo creará dentro de su carpeta especificando el . al final:

      • django-admin startproject shorty .

      Tras crear su proyecto, ejecutará las migraciones de Django. Estos archivos contienen código de Python generado por Django y se encargan de cambiar la estructura de la aplicación según los modelos de Django. Los cambios pueden incluir la creación de una tabla, por ejemplo. Por defecto, Django cuenta con su propio conjuntos de migraciones responsables de los subsistemas como la Autenticación de Django, de forma que es necesario ejecutarlos con el siguiente comando:

      Este comando utiliza el intérprete de Python para invocar una secuencia de comandos llamada manage.py, responsable de gestionar los diferentes aspectos de su proyecto, como crear aplicaciones o ejecutar migraciones.

      Con esto, se mostrará un resultado similar al siguiente:

      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

      Una vez que la base de datos de Django esté lista, inicie su servidor de desarrollo local:

      • python manage.py runserver

      Esto proporcionará lo siguiente:

      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.

      Este comando eliminará la instrucción en su terminal e iniciará el servidor.

      Visite la página http://127.0.0.1:8000 en su navegador local. Verá esta página:

      Página frontal del servidor local de Django

      Para detener el servidor y volver a su terminal, pulse CTRL+C. Siempre que necesite acceder al navegador, asegúrese de que se esté ejecutando el comando anterior.

      A continuación, terminará este paso habilitando la biblioteca Django-Graphene en el proyecto. Django tiene el concepto de app, una aplicación web con una responsabilidad específica. Un proyecto se compone de una o múltiples aplicaciones. Por ahora, abra el archivo shorty/settings.py​​​ en el editor de texto que prefiera. En este tutorial usaremos vim:

      El archivo settings.py gestiona todos los ajustes de su proyecto. Dentro, busque la entrada INSTALLED_APPS y añada la línea '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',
      ]
      ...
      

      Esta adición indica a Django que usará una aplicación llamada graphene_django, que instaló en el paso 1.

      Al final del archivo, añada la siguiente variable:

      shorty/shorty/settings.py

      ...
      GRAPHENE = {
          'SCHEMA': 'shorty.schema.schema',
      }
      

      Esta última variable apunta a su esquema principal. Lo creará más tarde. En GraphQL, un Schema contiene todos los tipos de objeto, como “Resource”, “Query” y “Mutation”. Piense en ello como la documentación que representa todos los datos y funcionalidades disponibles en su sistema.

      Tras realizar las modificaciones, guarde y cierre el archivo.

      Con esto, habrá configurado el proyecto Django. En el siguiente paso, creará una aplicación Django y sus Modelos.

      Paso 2: Configurar una aplicación y modelos Django

      Una plataforma Django está normalmente compuesta de un proyecto y muchas aplicaciones o apps. Una app describe un conjunto de funciones dentro de un proyecto, y, si está bien diseñada, puede reutilizarse en varios proyectos Django.

      En este paso, creará una app llamada shortener, responsable de la función real de acortamiento de URL. Para crear su esqueleto básico, escriba el siguiente comando en su terminal:

      • python manage.py startapp shortener

      Aquí usó los parámetros startapp app_name​​​, que indican a manage.py que cree una app llamada shortener.

      Para terminar de crear la app, abra el archivo shorty/settings.py​​​.

      Añada el nombre de la app a la misma entrada INSTALLED_APPS que modificó antes:

      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',
      ]
      ...
      

      Guarde y cierre el archivo.

      Una vez que se añada su shortener a shorty/settings.py, podrá proceder a crear los modelos para su proyecto. Los modelos son una de las funciones clave de Django. Se usan para representar una base de datos “al estilo de Phyton”, lo cual le permite administrar, consultar y almacenar datos usando código Python.

      Antes de abrir el archivo models.py para realizar cambios, en este tutorial se le proporcionará una descripción general de los cambios que realizará.

      Su archivo modelo, shortener/models.py, contendrá el siguiente contenido una vez que haya sustituido el código existente:

      shorty/shortener/models.py

      from hashlib import md5
      
      from django.db import models
      

      Aquí importará los paquetes requeridos que su código necesita. Añadirá la línea from hashlib import md5 encima para importar la biblioteca estándar Python que se usará para crear un hash de la URl. La línea from django.db import models es un elemento auxiliar de Django para crear modelos.

      Advertencia: Este tutorial se refiere a hash como el resultado de una función que toma una entrada y siempre devuelve el mismo resultado. Este tutorial usará la función hash MD5 con fines demostrativos.

      Observe que MD5 tiene problemas de colisión y debería evitarse en producción.

      A continuación, añadirá un modelo llamado URL con los siguientes campos:

      • full_url: la URL a acortar.
      • url_hash: un hash corto que representa la URL completa.
      • clicks: cuántas veces se accedió a la URL corta.
      • created_at: la fecha y hora en la que se creó la URL.

      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)
      

      Generará la url_hash aplicando el algoritmo hash MD5 al campo full_url y usando solo los 10 primeros caracteres obtenidos durante el método save() del modelo, que se ejecuta cada vez que Django guarda una entrada en la base de datos. Adicionalmente, los acortadores de URL normalmente realizan un seguimiento de la cantidad de clics que se hacen sobre un enlace. Conseguirá esto invocando el método clicked() cuando un usuario visita una URL.

      Las operaciones mencionadas se añadirán dentro de su modelo URL con este código:

      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)
      

      Ahora que ha revisado el código, abra el archivo shortener/models.py:

      Sustituya el código con el siguiente contenido:

      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)
      

      Asegúrese de guardar y cerrar el archivo.

      Para aplicar estos cambios en la base de datos, deberá crear las migraciones ejecutando el siguiente comando:

      • python manage.py makemigrations

      Esto generará el siguiente resultado:

      Output

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

      Luego ejecute las migraciones:

      Verá el siguiente resultado en su terminal:

      Output

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

      Ahora que ha configurado los modelos, en el siguiente paso creará el endpoint GraphQL y una “Query”.

      Paso 3: Crear consultas

      La arquitectura REST expone diferentes recursos en diferentes extremos, cada uno con una estructura de datos bien definida. Por ejemplo, puede recuperar una lista de usuarios en /api/users, esperando siempre los mismos campos. GraphQL, por otro lado, tiene un endpoint único para todas las interacciones, y utiliza Query para acceder a los datos. La principal diferencia, y la más valiosa, es que puede usar una “Query” para recuperar todos los usuarios en una única solicitud.

      Comience creando una “Query” para recuperar todas las URL. Necesitará algunos elementos:

      • Un tipo de URL, vinculado a su modelo definido previamente.
      • Una instrucción “Query” llamada urls.
      • Un método para resolver su “Query”, lo que implica obtener todas las URL de la base de datos y devolverlas al cliente.

      Cree un nuevo archivo llamado shortener/shema.py:

      Comience añadiendo las instrucciones import de Python:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      
      from .models import URL
      

      La primera línea importa la biblioteca principal graphene, que contiene los tipos GraphQL básicos, como List. DjangoObjectType es un elemento auxiliar para crear una definición de esquema desde cualquier modelo de Django y la tercera línea importa su modelo URL creado previamente.

      Tras eso, cree un nuevo tipo de GraphQL para el modelo URL añadiendo las siguientes líneas:

      shorty/shortener/schema.py

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

      Finalmente, añada estas líneas para crear un tipo de “Query” para el modelo URL:

      shorty/shortener/schema.py

      ...
      class Query(graphene.ObjectType):
          urls = graphene.List(URLType)
      
          def resolve_urls(self, info, **kwargs):
              return URL.objects.all()
      

      Este código crea una clase Query con un campo llamado urls, que es una lista del URLType definido previamente. Cuando se resuelve la “Query” a través del método resolve_urls, muestra las URL almacenadas en la base de datos.

      El archivo completo shorterner/schema.py se muestra aquí:

      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()
      

      Guarde y cierre el archivo.

      Todas las “Query” deben añadirse ahora al esquema principal. Piense en él como un depósito para todos sus recursos.

      Cree un nuevo archivo en la ruta shorty/schema.py​​​ y ábralo con su editor:

      Importe los siguientes paquetes de Python añadiendo las líneas que se muestran a continuación. La primera, como se ha mencionado, contiene los tipos GraphQL básicos. La segunda línea importa el archivo de esquema creado previamente.

      shorty/shorty/schema.py

      import graphene
      
      import shortener.schema
      

      A continuación, añada la clase Query principal. Contendrá, mediante herencia, todas las “Query” para las operaciones futuras creadas:

      shorty/shorty/schema.py

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

      Por último, cree la variable schema:

      shorty/shorty/schema.py

      ...
      schema = graphene.Schema(query=Query)
      

      El ajuste SCHEMA que definió en el paso 2 apunta a la variable schema que acaba de crear.

      El archivo completo shorty/schema.py se muestra aquí:

      shorty/shorty/schema.py

      import graphene
      
      import shortener.schema
      
      
      class Query(shortener.schema.Query, graphene.ObjectType):
          pass
      
      schema = graphene.Schema(query=Query)
      

      Guarde y cierre el archivo.

      A continuación, habilite el extremo GraphQL y la interfaz GraphiQL, una interfaz web gráfica que se usa para interactuar con el sistema GraphQL.

      Abra el archivo shorty/urls.py:

      Con fines de aprendizaje, elimine el contenido del archivo y guárdelo, para que pueda comenzar desde cero.

      Las primeras líneas que añadirá son las instrucciones de importación de Python:

      shorty/shorty/urls.py

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

      Django utiliza la función path para crear una URL accesible para la interfaz GraphiQL. A continuación, importe csrf_exempt, que permite a los clientes enviar datos al servidor. Puede encontrar una explicación completa en la Documentación de Graphene. En la última línea, importó el código real responsable de la interfaz a través de GraphQLView.

      A continuación, cree una variable llamada urlpatterns.

      shorty/shorty/urls.py

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

      Esto unirá todo el código necesario para hacer que la interfaz GraphiQL esté disponible en la ruta graphql/:

      El archivo completo shorterner/urls.py se muestra aquí:

      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))),
      ]
      

      Guarde el archivo y ciérrelo.

      De vuelta en el terminal, ejecute el comando python manage.py runserver (si no se está ejecutando aún):

      • python manage.py runserver

      Abra su navegador web en la dirección http://localhost:8000/graphql. Se le presentará esta pantalla:

      Interfaz GraphiQL

      GraphiQL es una interfaz donde puede ejecutar instrucciones de GraphQL y ver los resultados. Una función es la sección Docs en la parte superior derecha. Debido a que en GraphQL se debe escribir todo, obtendrá documentación gratuita sobre sus “Type”(tipo), “Query” (consulta) y “Mutation” (mutación), entre otros aspectos.

      Tras explorar la página, inserte su primera “Query” en el área de texto principal:

      query {
        urls {
          id
          fullUrl
          urlHash
          clicks
          createdAt
        }
      }
      

      Este contenido muestra cómo se estructura una “Query” de GraphQL: primero utiliza la palabra clave query para indicar al servidor que solo desea recuperar algunos de los datos. A continuación, usará el campo urls definido en el archivo shortener/schema.py dentro de la clase Query. Desde ahí, solicita explícitamente todos los campos definidos en el modelo URL usando el estilo de capitalización camel, que es el predeterminado para GraphQL.

      Ahora, haga clic en el botón de flecha de reproducción en la parte superior izquierda.

      Recibirá la siguiente respuesta, indicando que aún no tiene URLs:

      Output

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

      Esto muestra que GraphQL está funcionando. En su terminal, pulse CTRL+C para detener su servidor.

      En este paso, logró un gran avance al crear el extremo de GraphQL, realizar una “Query” para obtener todas las URLs y habilitar la interfaz de GraphiQL. Ahora, creará “Mutation” para cambiar la base de datos.

      Paso 4: Crear mutaciones

      La mayoría de las aplicaciones tienen una forma de cambiar el estado de la base de datos añadiendo, actualizando o eliminando datos. En GraphQL, estas operaciones se denominan Mutation. Tienen el aspecto de las “Query” pero utilizan argumentos para enviar datos al servidor.

      Para crear su primera “Mutation”, abra shortener/schema.py:

      Al final del archivo, comience añadiendo una nueva clase llamada CreateURL:

      shorty/shortener/schema.py

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

      Esta clase hereda el elemento auxiliar graphene.Mutation para tener las capacidades de una mutación GraphQL. También tiene un nombre de propiedad url, que define el contenido mostrado por el servidor tras completarse la “Mutation”. En este caso, será una estructura de datos URLType.

      A continuación añada una subclase llamada Arguments a la clase ya definida:

      shorty/shortener/schema.py

      ...
          class Arguments:
              full_url = graphene.String()
      

      Esto define qué datos serán aceptados por el servidor. Aquí está esperando un parámetro llamado full_url con un contenido String:

      Ahora añada las siguientes líneas para crear el método mutate:

      shorty/shortener/schema.py

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

      Este método mutate hace gran parte del trabajo al recibir los datos del cliente y guardarlos en la base de datos. Al final, muestra la clase que contiene el elemento recién creado.

      Por último, cree una clase Mutation para albertar todas las “Mutation” para su app añadiendo estas líneas:

      shorty/shortener/schema.py

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

      Hasta ahora, solo tendrá una mutación llamada create_url.

      El archivo completo shorterner/schema.py se muestra aquí:

      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()
      

      Cierre y guarde el archivo.

      Para terminar de añadir la “Mutation”, cambie el archivo shorty/schema.py:

      Altere el archivo para incluya el siguiente código resaltado:

      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)
      

      Guarde y cierre el archivo. Si no está ejecutando el servidor local, inícielo:

      • python manage.py runserver

      Navegue a http://localhost:8000/graphql en su navegador web. Ejecute su primera “Mutation” en la interfaz web GrapiQL ejecutando la siguiente instrucción:

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

      Compuso la “Mutation” con el nombre createURL, el argumento fullUrl y los datos que desea en la respuesta definida dentro del campo url.

      El resultado contendrá la información de la URL que acaba de crear dentro del campo de GraphQL data, como se muestra aquí:

      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" } } } }

      Con eso, se añadió una URL a la base de datos con su versión en hash, como puede ver en el campo urlHash. Intente ejecutar la “Query” que creó en el último paso para ver su resultado:

      query {
        urls {
          id
          fullUrl
          urlHash
          clicks
          createdAt
        }
      }
      

      El resultado mostrará la URL almacenada:

      Output

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

      También puede intentar ejecutar la misma “Query”, pero solo pidiendo los campos que desea.

      A continuación, inténtelo una vez más con una URL diferente:

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

      El resultado será lo siguiente:

      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" } } } }

      El sistema ahora puede crear URLs cortas y listarlas. En el siguiente paso, permitirá a los usuarios acceder a una URL por medio de su versión corta y los redirigirá a la página correcta.

      Paso 5: Crear el endpoint de acceso

      En este paso, usará el método Django Views, un método que toma una solicitud y devuelve una respuesta, para redirigir a cualquiera que acceda al endpoint http://localhost:8000/url_hash a su URL completa.

      Abra el archivo shortener/views.py con su editor:

      Para comenzar, importe los dos paquetes sustituyendo el contenido con las siguientes líneas:

      shorty/shortener/views.py

      from django.shortcuts import get_object_or_404, redirect
      
      from .models import URL
      

      Estas se explicarán más detenidamente más adelante.

      A continuación, creará un Django View llamado root. Añada este snippet de código responsable de View al final del archivo:

      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)
      

      Esto recibe un argumento llamado url_hash desde la URL solicitada por un usuario. Dentro de la función, la primera línea intenta obtener la URL de la base de datos usando el argumento url_hash. Si no se encuentra, devuelve el error HTTP 404 al cliente, lo que significa que falta el recurso. Después, aumenta la propiedad clicked de la entrada de URL, asegurando que realiza un seguimiento de la cantidad de veces que se accede a la URL. Al final, redirige el cliente a la URL solicitada.

      El archivo completo shorterner/views.py se muestra aquí:

      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)
      

      Guarde y cierre el archivo.

      A continuación, abra shorty/urls.py:

      Añada el siguiente código resaltado para habilitar la vista root.

      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 vista root será accesible en la ruta / de su servidor, aceptando un url_hash como parámetro de la cadena.

      Guarde y cierre el archivo. Si no está ejecutando el servidor local, inícielo ejecutando el comando python manage.py runserver.

      Para probar su nueva adición, abra su navegador web y acceda a la URL http://localhost:8000/077880af78. Observe que la última parte de la URL es el hash creado por la “Mutation” del paso 5. Accederá a la página URL del hash; en este caso, el sitio web de la comunidad de DigitalOcean.

      Ahora que el redireccionamiento de la URL funciona, hará que la aplicación sea más segura implementando la gestión de errores cuando se ejecute la “Mutation”.

      Paso 6: Implementar la gestión de errores

      Gestionar errores es una práctica recomendada en todas las aplicaciones, ya que los desarrolladores normalmente no controlan lo que se enviará al servidor. En este caso, puede intentar prever los fallos y minimizar sus impactos. En un sistema complejo como GraphQL, pueden salir mal muchas cosas, desde que el cliente pida los datos erróneos erróneos hasta que el servidor pierda el acceso a la base de datos.

      Como sistema escrito, GraphQL puede verificar todo lo que el cliente solicite y reciba en una operación conocida como validación de esquema. Puede ver esto en acción realizando una “Query” con un campo no existente.

      Diríjase a http://localhost:8000/graphql​​​ en su navegador una vez más y ejecute la siguiente “Query” en la interfaz GraphiQL con el campo iDontExist:

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

      Ya que no hay un campo iDontExist definido en su “Query”, GraphQL muestra un mensaje de error:

      Output

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

      Esto es importante porque, en el sistema escrito de GraphQL, el objetivo es enviar y recibir solo la información ya definida en el esquema.

      La aplicación actual acepta cualquier cadena arbitraria en el campo full_url. El problema es que si alguien envía una URL mal construida, usted no redirigiría al usuario a ningún sitio al probar la información almacenada. En este caso, necesita verificar si la full_url está bien formateada antes de guardarla en la base de datos, y, si hay cualquier error, elevar la excepción GraphQLError con un mensaje personalizado.

      Vamos a implementar esta funcionalidad en dos pasos. Primero, abra el archivo shortener/models.py:

      Añada las líneas resaltadas en la sección import:

      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
      ...
      

      El URLValidator es un ayudante de Django para validar una cadena URL y Graphene utiliza GraphQLError para elevar excepciones con un mensaje personalizado.

      A continuación, asegúrese de validar la URL recibida por el usuario antes de guardarla en la base de datos. Habilite esta operación añadiendo el código resaltado en el archivo shortener/models.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)
      

      Primero, este código inicia el URLValidator en la variable validate. Dentro del bloque try/except, validate() la URL recibida y eleve un GraphQLError con el mensaje personalizado invalid url si se produjo algún error.

      El archivo completo shorterner/models.py se muestra aquí:

      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)
      

      Guarde y cierre el archivo. Si no está ejecutando el servidor local, inícielo con el comando python manage.py runserver.

      A continuación, pruebe su nueva gestión de errores en http://localhost:8000/graphql. Intente crear una nueva URL con una full_url no válida en la interfaz GraphiQL:

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

      Cuando envíe una URL no válida, su excepción se elevará con el mensaje personalizado:

      Output

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

      Si mira en su terminal donde está en ejecución el comando python manage.py runserver, aparecerá un error:

      Output

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

      Un extremo de GraphQL siempre generará un error con un código de estado HTTP 200, lo que normalmente significa que el resultado es correcto. Recuerde que, aunque GraphQL se construye sobre HTTP, no utiliza los conceptos de los códigos de estado HTTP o los métodos HTTP como REST hace.

      Con el manejo de errores implementado, ahora puede implementar un mecanismo para filtrar sus “Query”, minimizando la información devuelta por el servidor.

      Paso 7: Implementar filtros

      Imagine que ha comenzado a usar el acortador de URL para añadir sus propios enlaces. Después de un tiempo, habrá tantas entradas que será difícil encontrar la correcta. Puede resolver este problema usando filtros.

      El filtrado es un concepto común en las API de REST; normalmente, se anexa a la URL un parámetro Query con un campo y un valor. A modo de ejemplo, para filtrar todos los usuarios llamados jojo, podría usar GET /api/users?name=jojo.

      En GraphQL, usará argumentos “Query” como filtros. Crean una interfaz perfecta y limpia.

      Puede resolver el problema “hard to find a URL” permitiendo que el cliente filtre las URL por nombre con el campo full_url. Para implementar eso, abra el archivo shortener/schema.py en su editor favorito.

      Primero, importe el método Q en la línea resaltada:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      from django.db.models import Q
      
      from .models import URL
      ...
      

      Esto se usará para filtrar la consulta de su base de datos.

      A continuación, reescriba toda la clase Query con el siguiente contenido:

      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
      ...
      

      Las modificaciones que está realizando son:

      • Añadir el parámetro de filtro url dentro de la variable urls y el método resolve_url
      • Dentro de resolve_urls, si se da un parámetro llamado url, filtrar los resultados de la base de datos para devolver solo las URLs que contienen el valor dado, usando el método Q(full_url__icontains=url).

      El archivo completo shorterner/schema.py se muestra aquí:

      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()
      

      Guarde y cierre el archivo. Si no ejecuta el servidor local, inícielo con python manage.py runserver.

      Pruebe sus últimos cambios en http://localhost:8000/graphql. En la interfaz GraphiQL, escriba la siguiente instrucción. Filtrará todas las URL con la palabra community:

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

      El resultado es solo una entrada, ya que acaba de añadir una URL con la cadena community en ella. Si añadió más URL antes, el resultado puede variar.

      Output

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

      Ahora, tiene la capacidad de buscar en sus URLs. Sin embargo, con demasiados enlaces, sus clientes pueden quejarse de que la lista de URLs devuelve más datos de los que la aplicación puede gestionar. Para resolver esto, implementará la paginación.

      Paso 8 – Implementar la paginación

      Los clientes que usen su backend pueden quejarse de que el tiempo de respuesta es demasiado largo o que el tamaño es demasiado grande si hay demasiadas entradas de URL. Incluso puede ser difícil para su base de datos reunir un conjunto tan enorme de información. Para resolver este problema, puede permitir que el cliente especifique cuántos elementos quiere dentro de cada solicitud usando una técnica llamada paginación.

      No existe una forma predeterminada de implementar esta función. Incluso en las API de REST puede verlo en los encabezados HTTP o en los parámetros de consulta, con diferentes nombres y comportamientos.

      En esta aplicación, implementará la paginación habilitando dos argumentos más en la “Query” de URL: first y skip first seleccionará el primer número de elementos variables y skip especificará cuántos elementos deben omitirse desde el principio. Por ejemplo, si usa first == 10 y skip == 5 se obtendrán las primeras 10 URL, pero se omitirán 5, con lo cual se mostrarán solo las 5 restantes.

      Implementar esta solución es similar a añadir un filtro.

      Abra el archivo shortener/schema.py:

      En el archivo, cambie la clase Query añadiendo los dos nuevos parámetros en la variable urls y el método resolve_urls, resaltados en el siguiente código:

      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
      ...
      

      Este código utiliza los parámetros first y skip recién creados dentro del método resolve_urls para filtrar la consulta de la base de datos.

      Guarde y cierre el archivo. Si no está ejecutando el servidor local, inícielo con python manage.py runserver.

      Para probar la paginación, emita la siguiente “Query” en la interfaz GraphiQL en http://localhost:8000/graphql:

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

      Su acortador de URL mostrará la segunda URL creada en su base de datos:

      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" } ] } }

      Esto muestra que la función de paginación funciona. Puede experimentar añadiendo más URLs y probando diferentes conjuntos de first y skip.

      Conclusión

      Todo el ecosistema de GraphQL crece día a día y una comunidad activa lo respalda. Empresas como GitHub y Facebook han demostrado que está listo para la producción, y ahora puede aplicar esta tecnología a sus propios proyectos.

      En este tutorial, creó un acortador de URL usando GraphQL, Python y Django y conceptos como “Query” y “Mutation”. Pero sobre todo, ahora comprende cómo depender de estas tecnologías para crear aplicaciones web usando el marco web Django.

      Puede explorar más sobre GraphQL y las herramientas usadas aquí en el sitio web de GraphQL y en los sitios web de documentación de Graphene. Además, DigitalOcean tiene tutoriales adicionales para Python y Django que puede usar si desea aprender más sobre esto.



      Source link

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


      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 : 

      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 : 

      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

      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 :

      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 :

      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: 

      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 : 

      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 : 

      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: 

      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 : 

      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 : 

      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 :

      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 : 

      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é. 

      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.

      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 :

      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. 



      Source link

      Создание коротких ссылок с помощью Django и GraphQL


      Автор выбрал Girls Who Code для получения пожертвования в рамках программы Write for DOnations.

      Введение

      GraphQL — это стандарт API с открытым исходным кодом, созданный Facebook в качестве альтернативы REST API. В отличие от REST API, GraphQL использует типизированную систему для определения структуры данных, где вся отправляемая и получаемая информация должна соответствовать предварительно заданной схеме. Также он подразумевает использование одной конечной точки для любых коммуникаций вместо использования нескольких URL-адресов для разных ресурсов, а также позволяет решить проблему оверфетчинга, возвращая только запрошенные клиентом данные, генерируя более компактные и понятные ответы.

      В этом обучающем руководстве вы сможете создать серверную часть для службы коротких ссылок, которая принимает любой URL-адрес и генерирует более короткую и понятную версию, а также познакомиться с такими понятиями GraphQL, как запросы и мутации, и инструментами, например, интерфейсом GraphiQL. Возможно, вы уже использовали ранее такие сервисы, как bit.ly.

      Поскольку GraphQL является независимой от языка технологией, она реализуется поверх самых разных языков и фреймворков. Здесь вы будете использовать универсальный язык программирования Python, веб-фреймворк Django и библиотеку Graphene-Django в качестве имплементации Python для GraphQL с конкретными интеграциями для Django.

      Предварительные требования

      • Для продолжения выполнения этого руководства вам потребуется версия Python 3.5 или выше, установленная на вашем компьютере для разработки. Для установки Python воспользуйтесь нашим руководством Установка и настройка локальной среды программирования для Python 3 для вашей ОС. Обязательно создайте и запустите виртуальную среду; чтобы выполнять указания этого обучающего руководства, вы можете использовать директорию проекта shorty.

      • Желательно наличие базового знания Django, но это необязательно. Если вам интересно, вы можете воспользоваться этой серией материалов о разработке в Django, созданной сообществом DigitalOcean.

      Шаг 1 — Настройка проекта Django

      На этом шаге вы выполните установку всех необходимых инструментов для приложения и настроите ваш проект Django.

      После создания директории проекта и запуска вашей виртуальной среды, как указано в предварительных условиях, установите необходимые пакеты с помощью pip, диспетчера пакетов Python. В этом обучающем руководстве используется версия Django 2.1.7 и версия Graphene-Django 2.2.0 или выше:

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

      Теперь у вас есть все необходимые инструменты. Далее вам необходимо создать проект Django, используя команду django-admin. Проект представляет собой стандартный шаблон Django по умолчанию, т. е. набор папок и файлов со всем необходимым для начала разработки веб-приложения. В этом случае вы можете вызвать ваш проект shorty и создать его в текущей папке, указав в конце .:

      • django-admin startproject shorty .

      После создания вашего проекта выполните запуск миграции Django. Эти файлы содержат код Python, сгенерированный Django, и несут ответственность за изменение структуры приложения в соответствии с моделями Django. Изменения могут, например, включать создание таблицы. По умолчанию Django имеет собственный набор миграций, отвечающий за такие подсистемы, как аутентификация Django, поэтому необходимо выполнить их с помощью следующей команды:

      Эта команда использует интерпретатор Python для вызова скрипта Django с именем manage.py, который отвечает за управление различными аспектами вашего проекта, например, созданием приложений или запуском миграции.

      Результат будет выглядеть примерно следующим образом:

      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

      Когда база данных Django будет готова, запустите локальный сервер разработки:

      • python manage.py runserver

      Это даст нам следующее:

      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.

      Эта команда будет отменять запрос в вашем терминале и запускать сервер.

      Перейдите на страницу http://127.0.0.1:8000 в вашем браузере. Вы увидите следующую страницу:

      Стартовая страница локального сервера Django

      Чтобы остановить сервер и вернуться к терминалу, нажмите CTRL+C. Когда вам потребуется получить доступ к браузеру, убедитесь, что предыдущая команда запущена.

      Далее мы закончим выполнение этого шага, активировав библиотеку Django-Graphene в проекте. Django использует концепцию app, т. е. веб-приложения с конкретной ответственностью. Проект включает одно или несколько приложений. Откройте файл shorty/settings.py в текстовом редакторе по вашему выбору. В этом обучающем руководстве мы будем использовать vim:

      Файл settings.py управляет всеми параметрами вашего проекта. Внутри файла найдите запись INSTALLED_APPS и добавьте строку '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',
      ]
      ...
      

      Это добавление указывает Django, что вы будете использовать приложение с именем graphene_django, которое вы установили на шаге 1.

      Добавьте внизу файла следующую переменную:

      shorty/shorty/settings.py

      ...
      GRAPHENE = {
          'SCHEMA': 'shorty.schema.schema',
      }
      

      Последняя переменная указывает на вашу основную схему, которую вы создадите позже. В GraphQL схема содержит все типы объектов, такие как ресурсы, запросы и мутации. Вы можете рассматривать ее как документацию, которая представляет все данные и функционал, доступный в вашей системе.

      После внесения изменений сохраните и закройте файл.

      Вы закончили настройку проекта Django. На следующем шаге мы создадим приложение Django и модели.

      Шаг 2 — Настройка приложения Django и моделей

      Платформа Django обычно включает один проект и несколько приложений или app. App описывает набор функций внутри проекта, и при правильной разработке может повторно использоваться в других проектах Django.

      На этом шаге мы создадим приложение shortener​​​, которое отвечает за фактическое укорачивание URL-адреса. Для создания базового каркаса введите следующую команду в терминале:

      • python manage.py startapp shortener

      Здесь вы использовали параметры startapp app_name​​, которые указывают manage.py​​​ создать приложение с именем shortener.

      Чтобы завершить процесс создания приложения, откройте файл shorty/settings.py.

      Добавьте имя приложения в ту же запись INSTALLED_APPS, которую вы изменили ранее:

      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',
      ]
      ...
      

      Сохраните и закройте файл.

      После добавления shortener в shorty/settings.py вы можете перейти к созданию моделей вашего проекта. Модели — одна из ключевых функций в Django. Они используются для представления базы данных в используемом в Python образе, что позволяет управлять, запрашивать и сохранять данные с помощью кода Python.

      Прежде чем открыть файл models.py для внесения изменений, в этом обучающем руководстве мы разместим обзор изменений, которые вы вносите.

      Ваш файл модели —shortener/models.py — будет содержать следующее содержание, после того как вы заменили существующий код:

      shorty/shortener/models.py

      from hashlib import md5
      
      from django.db import models
      

      Здесь вы импортируете требуемые пакеты, необходимые вашему коду. Вам нужно добавить строку from hashlib import md5 в верхней части для импорта стандартной библиотеки, которая будет использоваться для создания хэша URL-адреса. Строка from django.db import models — это элемент Django для создания моделей.

      Предупреждение. В этом обучающем руководстве хэш является результатом выполнения функции, которая получает данные и всегда возвращает один вывод. В этом обучающем руководстве мы будем использовать хэш-функцию MD5 в демонстрационных целях.

      Обратите внимание, что MD5 имеет проблемы с коллизиями, поэтому рекомендуется избегать ее использования в производственной среде.

      Далее вам необходимо добавить модель с именем URL и следующими полями:

      • full_url: URL для сокращения.
      • url_hash: краткий хэш, представляющий полный URL.
      • clicks: сколько раз был использован короткий URL.
      • created_at: дата и время создания URL.

      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)
      

      Вы сгенерируете url_hash, применив алгоритм хеширования MD5 для поля full_url​​​ и используя только первые 10 символов, которые возвращает метод save() модели, выполняемый каждый раз, когда Django сохраняет запись в базе данных. Кроме того, инструменты для сокращения URL-адреса обычно отслеживают, сколько раз была использована ссылка. Вы можете вызвать для этого метод clicked()​​, когда URL используется пользователем.

      Упомянутые операции будут добавлены в вашу модель URL с помощью этого кода:

      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)
      

      Теперь, когда вы просмотрели код, откройте файл shortener/models.py:

      Замените код на следующий:

      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)
      

      Обязательно сохраните и закройте файл.

      Для применения этих изменений в базе данных вам потребуется создать миграции, запустив следующую команду:

      • python manage.py makemigrations

      Результат будет выглядеть следующим образом:

      Output

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

      Затем выполните миграции:

      В своем терминале вы увидите следующее:

      Output

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

      Теперь, когда вы настроили модели, на следующем шаге мы создадим конечную точку GraphQL и запрос.

      Шаг 3 — Создание запросов

      Архитектура REST предоставляет разные ресурсы в разных конечных точках, каждый из которых содержит четко определенную структуру данных. Например, вы можете получить список пользователей в /api/users, который всегда ожидает одни и те же поля. GraphQL, с другой стороны, имеет одну конечную точку для всех взаимодействий и использует запросы для доступа к данным. Главное, и самое ценное, различие заключается в том, что вы можете использовать запрос для получения всех ваших пользователей с помощью одного запроса.

      Начните с создания запроса для получения всех URL. Вам потребуется несколько вещей:

      • Тип URL, который привязан к ранее определенной модели.
      • Оператор запроса с именем urls.
      • Метод для обработки вашего запроса, т. е. получения всех URL из базы данных и их возвращения клиенту.

      Создайте новый файл shortener/schema.py:

      Начнем с добавления оператора import Python:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      
      from .models import URL
      

      Первая строка импортирует основную библиотеку graphene, которая содержит базовые типы GraphQL, например List. DjangoObjectType — это вспомогательный метод для создания определения схемы из любой модели Django, а третья строка импортирует ранее созданную модель URL.

      Создайте новый тип GraphQL для модели URL, добавив следующие строки:

      shorty/shortener/schema.py

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

      В заключение добавьте эти строки для создания типа запроса для модели URL:

      shorty/shortener/schema.py

      ...
      class Query(graphene.ObjectType):
          urls = graphene.List(URLType)
      
          def resolve_urls(self, info, **kwargs):
              return URL.objects.all()
      

      Этот код создает класс Query с одним полем urls, который представляет собой список с ранее определенным типом URLType. При обработке запроса с помощью метода resolve_urls вы возвращаете все URL, сохраненные в базе данных.

      Полное содержание файла shortener/schema.py показано здесь:

      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()
      

      Сохраните и закройте файл.

      Теперь все запросы необходимо добавить в основную схему. Рассматривайте ее как место хранения всех ваших ресурсов.

      Создайте новый файл в shorty/schema.py и откройте его в редакторе:

      Импортируйте пакеты Python, добавив следующие строки. Первая, как уже упоминалось, содержит базовые типы GraphQL. Вторая строка импортирует ранее созданный файл схемы.

      shorty/shorty/schema.py

      import graphene
      
      import shortener.schema
      

      Затем добавьте главный класс Query. Он будет хранить, через наследование, все запросы и будущие операции:

      shorty/shorty/schema.py

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

      В заключение создайте переменную schema:

      shorty/shorty/schema.py

      ...
      schema = graphene.Schema(query=Query)
      

      Настройка SCHEMA, которую вы задали на шаге 2, указывает на переменную schema, которую вы только что создали.

      Полное содержание файла shorty/schema.py​​​ показано здесь:

      shorty/shorty/schema.py

      import graphene
      
      import shortener.schema
      
      
      class Query(shortener.schema.Query, graphene.ObjectType):
          pass
      
      schema = graphene.Schema(query=Query)
      

      Сохраните и закройте файл.

      Затем активируйте конечную точку GraphQL и интерфейс GraphiQL, который представляет собой графический веб-интерфейс, который используется для взаимодействия с системой GraphQL.

      Откройте файл shorty/urls.py​​​:

      В целях обучения удалите содержимое файла и сохраните его, чтобы начать с нуля.

      Первые строки, которые нужно добавить, объявляют импорт Python:

      shorty/shorty/urls.py

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

      Функция path используется Django для создания доступного URL-адреса для интерфейса GraphiQL. Затем импортируйте csrf_exempt, который позволяет клиентам отправлять данные на сервер. Полное объяснение можно найти в документации по Graphene. В последней строке вы импортировали реальный код, который отвечает за интерфейс, через GraphQLView.

      Затем создайте переменную urlpatterns.

      shorty/shorty/urls.py

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

      Она будет связывать весь код, который необходим для создания интерфейса GraphiQL, который будет доступен в пути graphql/:

      Полное содержание файла shortener/urls.py показано здесь:

      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))),
      ]
      

      Сохраните и закройте файл.

      Вернитесь в терминал, запустите команду python manage.py runserver (если она еще не запущена):

      • python manage.py runserver

      Откройте в браузере адрес http://localhost:8000/graphql​​​. Вы увидите следующее:

      Интерфейс GraphiQL

      GraphiQL — это интерфейс, где вы можете запускать операторы GraphQL и просматривать результаты. Одна из функций — раздел Docs в правом верхнем углу. Поскольку все в GraphQL типизировано, вы получите свободную документацию обо всех ваших типах, запросах, мутациях и т. д.

      После изучения страницы вставьте ваш первый запрос в основную текстовую область:

      query {
        urls {
          id
          fullUrl
          urlHash
          clicks
          createdAt
        }
      }
      

      Это содержание показывает, какую структуру имеет запрос GraphQL: сначала вы используете ключевое слово query, чтобы указать серверу, что вы хотите получить только определенные данные. Далее мы используем поле urls, определенное в файле shortener/schema.py, внутри класса Query. С помощью этого действия вы явно запрашиваете все поля, определенные в модели URL, используя «верблюжий» стиль, используемый по умолчанию в GraphQL.

      Теперь нажмите кнопку запуска со стрелкой в левом верхнем углу.

      Вы получите следующий ответ, указывающий, что у вас все еще нет URL-адресов:

      Output

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

      Это показывает, что GraphQL работает. В терминале нажмите CTRL+C, чтобы остановить ваш сервер.

      Вы выполнили большой объем работы на этом шаге, создав конечную точку GraphQL, создав запрос для получения всех URL-адресов и активировав интерфейс GraphiQL. Теперь мы создадим мутации для изменения базы данных.

      Шаг 4 — Создание мутаций

      Большинство приложений имеют возможность изменять состояние базы данных, добавляя, обновляя или удаляя данные. В GraphQL эти операции называются мутациями. Они похожи на запросы, но используют аргументы для отправки данных на сервер.

      Для создания первой мутации откройте shortener/schema.py​​​:

      В конце файла добавьте новый класс CreateURL:

      shorty/shortener/schema.py

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

      Этот класс наследует вспомогательный класс graphene.Mutation для использования возможностей мутаций GraphQL. Также у него есть имя свойства url, определяющее содержание, возвращаемое сервером после завершения мутации. В этом случае это структура данных с типом URLType.

      Затем добавьте подкласс с именем Arguments в уже определенный класс:

      shorty/shortener/schema.py

      ...
          class Arguments:
              full_url = graphene.String()
      

      Он определяет, какие данные будут приниматься сервером. Здесь вы ожидаете параметр full_url со строковым содержанием:

      Теперь добавьте следующие строки для создания метода mutate:

      shorty/shortener/schema.py

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

      Этот метод mutate выполняет большой объем работы, получая данные от клиента и сохраняя их в базу данных. В результате он возвращает сам класс, содержащий вновь созданный элемент.

      В заключение создайте класс Mutation для хранения всех мутаций вашего приложения, добавив следующие строки:

      shorty/shortener/schema.py

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

      Пока у вас будет только одна мутация с именем create_url.

      Полное содержание файла shortener/schema.py показано здесь:

      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()
      

      Закройте и сохраните файл.

      Чтобы завершить добавление мутации, измените файл shorty/schema.py​​​:

      Измените файл, чтобы добавить в него следующий выделенный код:

      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)
      

      Сохраните и закройте файл. Если ваш локальный сервер не запущен, запустите его:

      • python manage.py runserver

      Перейдите на страницу http://localhost:8000/graphql​​​ в браузере. Выполните вашу первую мутацию в веб-интерфейсе GraphiQL, запустив следующее выражение:

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

      Вы создали мутацию с именем createURL, аргументом fullUrl и данными, которые хотите получить в ответ, определенные в поле url.

      Вывод будет содержать информацию о URL, которую вы только что создали в поле data GraphQL, как показано здесь:

      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" } } } }

      В результате URL был добавлен в базу данных с хэшированной версией, как вы можете видеть в поле urlHash. Попробуйте запустить запрос, который вы создали в последнем шаге, чтобы увидеть результат:

      query {
        urls {
          id
          fullUrl
          urlHash
          clicks
          createdAt
        }
      }
      

      Вывод будет показывать сохраненный URL:

      Output

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

      Также вы можете попробовать выполнить тот же запрос, но на этот раз запросить только поля, которые хотите получить.

      Попробуйте выполнить его еще раз с другим URL-адресом:

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

      Результат будет выглядеть следующим образом:

      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" } } } }

      Теперь система может создавать короткие URL-адреса и выводить их список. На следующем шаге мы будем предоставлять пользователям доступ к URL по его сокращенной версии, перенаправляя их на верную страницу.

      Шаг 5 — Создание конечной точки доступа

      На этом шаге мы будем использовать метод Django Views, который выполняет запрос и возвращает ответ для перенаправления любого, кто пытается получить доступ через конечную точку http://localhost:8000/url_hash​​, на полный URL-адрес.

      Откройте файл shortener/views.py в редакторе:

      Вначале импортируйте два пакета, заменив содержимое на следующие строки:

      shorty/shortener/views.py

      from django.shortcuts import get_object_or_404, redirect
      
      from .models import URL
      

      Это будет более подробно разъяснено позднее.

      Далее мы создадим представление Django с именем root. Добавьте этот отрывок кода, который будет отвечать за представление, в конец вашего файла:

      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)
      

      Он получает аргумент с именем url_hash из URL, запрошенного пользователем. Внутри функции первая строка пытается получить URL из базы данных, используя аргумент url_hash. Если он не будет найден, клиент получает ошибку 404, что означает, что ресурс отсутствует. Затем он увеличивает значение свойства clicked для URL, что позволяет отслеживать, сколько раз был использован URL. В конце он перенаправляет клиента на запрошенный URL-адрес.

      Полное содержание файла shortener/views.py показано здесь:

      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)
      

      Сохраните и закройте файл.

      Откройте shorty/urls.py​​:

      Добавьте следующий выделенный код, чтобы активировать представление root.

      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'),
      ]
      

      Представление root будет доступно в пути / вашего сервера, принимая url_hash в качестве строкового параметра.

      Сохраните и закройте файл. Если ваш локальный сервер не запущен, запустите его с помощью команды python manage.py runserver.

      Чтобы протестировать ваши добавленные данные, откройте ваш браузер и перейдите на URL-адрес http://localhost:8000/077880af78. Обратите внимание, что последняя часть URL-адреса — это хэш, созданный мутацией из шага 5. Вы будете перенаправлены на страницу хэша URL, в этом случае на веб-сайт сообщества DigitalOcean.

      Теперь, когда у вас есть работающее перенаправление URL, мы обеспечим дополнительную безопасность приложения, реализовав обработку ошибок при выполнении мутации.

      Шаг 6 — Реализация обработки ошибок

      Обработка ошибок — это лучшая практика для всех приложений, поскольку разработчики обычно не контролируют, что будет отправляться на сервер. В этом случае вы можете попытаться предусмотреть возможные ошибки и свести к минимуму их воздействие. В такой сложной системе, как GraphQL, очень многое может пойти не так, начиная с запроса неверных данных клиентом и заканчивая потерей подключения сервера к базе данных.

      Будучи типизированной системой, GraphQL может проверять все, что запрашивает и получает клиент, с помощью операции под названием валидация схемы. Вы можете посмотреть это в действии, используя запрос несуществующего поля.

      Перейдите на страницу http://localhost:8000/graphql в браузере еще раз и выполните следующий запрос внутри интерфейса GraphiQL, где мы будем использовать поле iDontExist:

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

      Поскольку в классе Query отсутствует поле iDontExist, GraphQL возвращает сообщение об ошибке:

      Output

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

      Это важно, поскольку в типизированной системе GraphQL цель заключается в отправке и получении только той информации, которая уже определена в схеме.

      Текущее приложение принимает любую произвольную строку в поле full_url. Проблема в том, что если кто-то отправляет плохо построенный URL-адрес, вы можете перенаправлять пользователя в никуда при попытке получения сохраненной информации. В этом случае вам необходимо проверить, имеет ли full_url​​​ корректный формат, прежде чем сохранить его в базе данных, и при наличии ошибки выбрасывать исключение GraphQLError с заданным сообщением.

      Давайте реализуем этот функционал в два этапа. Вначале откройте файл shortener/models.py​​​:

      Добавьте выделенные строки в раздел импорта:

      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
      ...
      

      URLValidator — это вспомогательный класс Django для валидации строки URL, а GraphQLError используется Graphene для генерации исключений с заданным сообщением.

      Затем необходимо выполнить валидацию URL-адреса, который получает пользователь, прежде чем сохранить его в базе данных. Активируйте эту операцию, добавив выделенный код в файл shortener/models.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)
      

      Сначала этот код инициализирует URLValidator в переменной validate. Внутри блока try/except​​​ вы с помощью метода validate()​​​ выполняете валидацию полученного URL-адреса и генерируете исключение GraphQLError с заданным сообщением invalid url​​​, если что-то пойдет не так.

      Полное содержание файла shortener/models.py показано здесь:

      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)
      

      Сохраните и закройте файл. Если ваш локальный сервер не запущен, запустите его с помощью команды python manage.py runserver.

      Затем протестируйте вашу обработку ошибок на странице http://localhost:8000/graphql. Попробуйте создать новый URL с недействительным значением full_url​​​ в интерфейсе GraphiQL:

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

      При отправке недействительного URL-адреса будет сгенерировано исключение с заданным сообщением:

      Output

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

      Если вы посмотрите в терминале, где запущена команда python manage.py runserver, ошибка будет выглядеть следующим образом:

      Output

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

      Конечная точка GraphQL будет выдавать сбой с кодом состояния HTTP 200, который обычно означает успешное выполнение операции. Не забывайте, что, хотя GraphQL использует HTTP, он не обязательно использует концепции кодов состояния HTTP или методы HTTP, как это делает REST.

      После реализации обработки ошибок вы можете создать механизм для фильтрации запросов, минимизируя объем информации, получаемый пользователем.

      Шаг 7 — Внедрение фильтров

      Представьте, что вы начали использовать инструмент для получения коротких URL-адресов для добавления собственных ссылок. После этого появится очень большое количество записей, так что найти нужную ссылку будет сложно. Вы можете решить эту проблему, используя фильтры.

      Фильтрация — это стандартная концепция в REST API, когда параметр запроса с полем и значением присоединяется к URL-адресу. Например, чтобы воспользоваться фильтром для всех пользователей с именем jojo, вы можете использовать GET /api/users?name=jojo.

      В GraphQL вы будете использовать аргументы запроса в качестве фильтров. Они позволяют получить хороший и чистый интерфейс.

      Вы можете решить проблему трудноуловимых URL-адресов, разрешив клиенту фильтровать URL-адреса по имени с помощью поля full_url. Для этого откройте файл shortener/schema.py в предпочитаемом вами редакторе.

      Вначале импортируйте метод Q в выделенной строке:

      shorty/shortener/schema.py

      import graphene
      from graphene_django import DjangoObjectType
      from django.db.models import Q
      
      from .models import URL
      ...
      

      Это будет использоваться для фильтрации запроса базы данных.

      Затем перепишите весь класс Query, добавив следующее содержание:

      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
      ...
      

      Ниже представлены изменения, которые вы вносите:

      • Добавление параметра фильтрации url внутри переменной urls и метод resolve_url.
      • Внутри resolve_urls​​​, если указан параметр url, выполните фильтрацию результатов базы данных для получения только URL-адресов, которые содержат указанное значение, используя метод Q(full_url__icontains=url).

      Полное содержание файла shortener/schema.py показано здесь:

      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()
      

      Сохраните и закройте файл. Если ваш локальный сервер не запущен, запустите его с помощью команды python manage.py runserver.

      Проверьте внесенные изменения, перейдя на страницу http://localhost:8000/graphql. В интерфейсе GraphiQL добавьте следующее выражение. Оно будет фильтровать все URL-адреса со словом community:

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

      Вывод представляет собой одну запись, поскольку вы добавили один URL со строкой community. Если вы добавили несколько URL-адресов ранее, вывод может отличаться.

      Output

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

      Теперь у вас есть возможность выполнять поиск по вашим URL-адресам. Однако при большом количестве ссылок ваши клиенты могут жаловаться, что список URL-адресов возвращает больше данных, чем могут обработать их приложения. Чтобы устранить эту проблему, необходимо добавить пагинацию.

      Шаг 8 — Реализация пагинации

      Клиенты, использующие ваш сервер, могут жаловаться, что на получение ответа уходит очень много времени, либо на слишком большой размер, если он содержит слишком много URL. Даже ваша база данных может не справляться со сборкой большого набора информации. Чтобы решить эту проблему, вы можете разрешить клиенту указывать, сколько элементов он хочет получать в каждом запросе, используя технику под названием pagination.

      Используемого по умолчанию способа для реализации этой функции не существует. Даже в REST API вы можете видеть ее в заголовках HTTP или параметрах запроса с разными именами и поведением.

      В этом приложении мы будем применять пагинацию, добавив два аргумента в запрос URL: first и skip. first выполняет выбор первого переменного числа элементов, а skip будет указывать, сколько элементов следует пропустить с начала. Например, используя first == 10 и skip == 5, вы получите первые 10 URL-адресов, но пропустите 5 из них, возвращая только 5 оставшихся.

      Реализация этого решения аналогична добавлению фильтра.

      Откройте файл shortener/schema.py:

      В файле измените класс Query, добавив два новых параметра в переменную urls и метод resolve_urls, как показано в следующем коде:

      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
      ...
      

      Этот код использует созданные параметры first и skip внутри метода resolve_urls для фильтрации запроса базы данных.

      Сохраните и закройте файл. Если ваш локальный сервер не запущен, запустите его с помощью команды python manage.py runserver.

      Чтобы протестировать пагинацию, воспользуйтесь следующим запросом в интерфейсе GraphiQL на странице http://localhost:8000/graphql:

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

      Ваш инструмент для сокращения URL-адресов будет возвращать второй URL, созданный в базе данных:

      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" } ] } }

      Это показывает, что функция пагинации работает. Вы можете попробовать добавить несколько URL-адресов и протестировать разные значения first и skip.

      Заключение

      Экосистема GraphQL развивается каждый день и поддерживается активным сообществом. Она доказала свою способность для производства и используется такими компаниями, как GitHub и Facebook. И теперь вы можете использовать эту технологию для ваших проектов.

      В этом обучающем руководстве вы создали сервис для сокращения URL-адресов с помощью GraphQL, Python и Django, используя такие понятия, как запросы и мутации. Но помимо этого, теперь у вас есть понимание того, как использовать эти технологии для создания веб-приложений, используя веб-фреймворк Django.

      Вы можете узнать больше о GraphQL и инструментах, используемых здесь, на сайте GraphQL и сайтах с документацией для Graphene. DigitalOcean предоставляет дополнительные руководства для Python и Django, если вы хотите узнать больше.



      Source link