Relaciones recursivas de modelos en Django

    La necesidad de relaciones recursivas

    Surgen muchas veces en el desarrollo de aplicaciones web modernas donde los requisitos comerciales describen inherentemente relaciones que son recursivo. Un ejemplo bien conocido de esta regla de negocios es la descripción de los empleados y su relación con sus gerentes, que también son empleados. Note la naturaleza circular de esa declaración. Esto es exactamente lo que se entiende por relación recursiva. En este artículo, desarrollaremos una demostración básica en Django de una aplicación de listado de empleados de recursos humanos (RRHH) con esta relación recursiva entre empleados y gerentes.

    El código de este artículo se puede encontrar en este repositorio de GitHub.

    Configuración de la estructura del proyecto Django

    Para comenzar con un proyecto de Django, querrá crear un nuevo entorno virtual de Python (preferiblemente Python3). Si no está familiarizado con los entornos virtuales, consulte este artículo. Una vez dentro de su entorno virtual activado, pip instale Django.

    (venv) $ pip install django
    

    Con Django instalado, puede utilizar las utilidades de administración de Django para generar el texto estándar del proyecto, al que llamaremos “webapp”. Puede obtener más información sobre la configuración del proyecto Django en nuestro artículo, Flask vs Django.

    (venv) $ django-admin startproject webapp
    

    Ahora cd en el nuevo directorio de aplicaciones web para que podamos seguir utilizando otro conjunto de herramientas de Django a través del script manage.py. Usamos esto para crear la aplicación de nuestro proyecto, que llamaremos “hrmgmt”. Esto crea otro directorio llamado “hrmgmt” que es donde residirá el código para esta aplicación.

    (venv) $ cd webapp
    (venv) $ python manage.py startapp hrmgmt
    

    La última parte de la configuración del proyecto incluye informar al proyecto (aplicación web) sobre la aplicación “hrmgmt”. En “webapp / settings.py” busque la sección con un comentario de “Definición de aplicación” encima de la lista. INSTALLED_APPS y agregue una entrada de hrmgmt.apps.HrmgmtConfig, al igual que:

    # Application definition
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'hrmgmt.apps.HrmgmtConfig'
    ]
    

    Configurar las rutas

    En Django, el directorio que coincide con el nombre del proyecto, “webapp” en nuestro caso, es donde residen las configuraciones principales y el punto de entrada a las rutas para la aplicación de administración integrada y cualquier aplicación personalizada adicional. Entonces, en “webapp / urls.py” use el siguiente código para dirigir todas las rutas con el prefijo “/ hr” a la aplicación “hrmgmt”.

    # webapp/urls.py
    from django.conf.urls import url, include
    from django.contrib import admin
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^hr/', include('hrmgmt.urls'))
    ]
    

    En la aplicación personalizada “hrmgmt”, cree un nuevo archivo llamado “urls.py” y coloque el siguiente código. Esto especifica una vista que devolverá una lista de todos los empleados. El siguiente código usa una expresión regular para indicar que cuando se solicita una ruta de “/ hr /” desde nuestro servidor, entonces una función de vista llamada index debe manejar la solicitud y devolver una respuesta.

    # hrmgmt/urls.py
    from django.conf.urls import url
    
    import views
    
    urlpatterns = [
        # /hr/
        url(r'^$', views.index, name="index")
    ]
    

    A continuación, hablaremos sobre lo que hace la función de vista de índice.

    Stubbing de la función de vista de índice

    Ahora implementemos lo antes mencionado index Ver función para manejar las solicitudes a la ruta “/ hr /” y devolver una respuesta de texto para hacernos saber que hemos configurado las cosas correctamente. Más adelante volveremos y convertiremos esto en una función de vista más adecuada para enumerar a nuestros empleados.

    En hrmgmt / views.py incluya el siguiente código:

    # hrmgmt/views.py
    from django.http import HttpResponse
    
    def index(request):
        response = "My List of Employees Goes Here"
        return HttpResponse(response)
    

    Dentro del directorio de la aplicación web, inicie el servidor de desarrollo de Django y pruebe que hemos configurado correctamente nuestra función de ruta y vista:

    (venv) $ python manage.py runserver
    

    Ahora ve a tu navegador e ingresa http: // localhost: 8000 / hr / y debería ver una respuesta de texto de “Mi lista de empleados va aquí”

    Diseñar nuestras clases de modelos

    ¡Finalmente llegamos a la parte buena! En esta sección definimos nuestras clases de modelo que se traducirán en tablas de base de datos, todo hecho escribiendo código Python. O usando lo que la gente de .NET ha acuñado como un enfoque de “código primero” para el diseño de bases de datos.

    En hrmgmt / models.py coloque el siguiente código:

    # hrmgmt/models.py
    from django.db import models
    
    class Employee(models.Model):
        STANDARD = 'STD'
        MANAGER = 'MGR'
        SR_MANAGER = 'SRMGR'
        PRESIDENT = 'PRES'
    
        EMPLOYEE_TYPES = (
            (STANDARD, 'base employee'),
            (MANAGER, 'manager'),
            (SR_MANAGER, 'senior manager'),
            (PRESIDENT, 'president')
        )
    
        role = models.CharField(max_length=25, choices=EMPLOYEE_TYPES)
        first_name = models.CharField(max_length=100)
        last_name = models.CharField(max_length=100)
        manager = models.ForeignKey('self', null=True, related_name="employee")
    
        def __str__(self):
            return "<Employee: {} {}>".format(self.first_name, self.last_name)
    
        def __repr__(self):
            return self.__str__()
    

    Hay bastantes cosas en estas pocas líneas de código, así que analicémoslas. Lo primero a tener en cuenta es que una clase de Python llamada Employee está siendo declarado, que hereda del django.db.models.Model clase. Esta herencia da la Employee class la funcionalidad para acceder a la base de datos a través del ORM de Django.

    A continuación están las definiciones de cuatro campos de clase que son constantes (ESTÁNDAR, ADMINISTRADOR, SR_MANAGER, PRESIDENTE) y su uso para definir aún más una constante de campo de clase de tupla. Son una especie de enumeraciones que especifican los diferentes roles que puede asumir un empleado. De hecho, la tupla de tuplas constante se pasa a la definición del campo de la clase de roles para indicar qué valores debe aceptar la clase.

    Siguiente el first_name y last_name Los campos de clase se definen como campos de caracteres con una longitud máxima de 100 caracteres.

    El campo final que se define es quizás el más significativo, el manager campo. Es una clave externa que define una relación recursiva entre los empleados y sus gerentes. Esto significa que la columna de identificación de enteros de incremento automático implícito que Django hace en modelos que hereda de django.db.models.Model estará disponible como un valor de clave externa para la misma clase (o tabla).

    Esto satisfará nuestro caso de uso, que podría establecerse como “un empleado puede tener solo un gerente directo o ningún gerente en el caso del presidente, pero un empleado puede administrar muchos empleados diferentes”. Especificando self como primer parámetro del model.ForeignKey llamada, Django configurará esto como una relación recursiva. Luego, especificando null=True el modelo permitirá un empleado sin pesebre, que en nuestro ejemplo es el que representa al presidente.

    A continuación se muestra un diagrama ERD de la relación recursiva que hemos definido.

    Migración de nuestra definición de clase a la base de datos

    Para transformar el código que usamos para definir nuestra clase Employee en DDL SQL, usaremos nuevamente una utilidad de Django a la que se accede a través del script “manage.py” y que se conoce colectivamente como migraciones.

    En la línea de comandos, dentro de nuestro entorno virtual, por supuesto, ejecute lo siguiente para crear las tablas predeterminadas que utilizan todas las aplicaciones de Django. Por defecto, esta base de datos es una sqlite base de datos dentro de la carpeta raíz del proyecto.

    (venv) $ python manage.py migrate
    

    Una vez completada, podemos realizar una nueva migración que defina la tabla que respaldará nuestra Employee clase. Haga esto emitiendo los siguientes comandos y asegúrese de observar el resultado como se muestra a continuación:

    (venv) $ python manage.py makemigrations
    (venv) $ python manage.py migrate
    Operations to perform:
      Apply all migrations: admin, auth, contenttypes, hrmgmt, sessions
    Running migrations:
      Applying hrmgmt.0001_initial... OK
    

    Puede ver el SQL DDL real que crea la tabla ejecutando el siguiente comando:

    (venv) $ python manage.py sqlmigrate hrmgmt 0001
    
    BEGIN;
    --
    -- Create model Employee
    --
    CREATE TABLE "hrmgmt_employee" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "role" varchar(25) NOT NULL, "first_name" varchar(100) NOT NULL, "last_name" varchar(100) NOT NULL, "manager_id" integer NULL REFERENCES "hrmgmt_employee" ("id"));
    CREATE INDEX "hrmgmt_employee_manager_id_43028de6" ON "hrmgmt_employee" ("manager_id");
    COMMIT;
    

    Explorando modelos con Django Shell

    En la línea de comando, ingrese el siguiente comando para que el intérprete esté en funcionamiento con el contexto de nuestra aplicación Django precargado en el REPL:

    (venv) $ python manage.py shell
    

    Ahora que el intérprete de Python está en funcionamiento, ingrese los siguientes comandos:

    >>> from hrmgmt.models import Employee
    >>> janeD = Employee.objects.create(first_name="Jane", last_name="Doe", role=Employee.PRESIDENT)
    >>> johnD = Employee.objects.create(first_name="John", last_name="Doe", role=Employee.MANAGER, manager=janeD)
    >>> joeS = Employee.objects.create(first_name="Joe", last_name="Scho", role=Employee.STANDARD, manager=johnD)
    >>> johnB = Employee.objects.create(first_name="John", last_name="Brown", role=Employee.STANDARD, manager=johnD)
    

    El código anterior crea cuatro empleados ficticios. Jane Doe es la presidenta. Luego, John Doe tiene un rol de gerente y es dirigido por su madre Jane Doe (sí, claramente hay algo de nepotismo aquí). Bajo la supervisión de John Doe están Joe Schmo y John Brown, quienes tienen los roles de un empleado estándar o de base.

    Podemos probar nuestro campo de relaciones de employee inspeccionando la salida de la llamada employee en nuestro johnD variable:

    >>> johnD.employee.all()
    <QuerySet [<Employee: Joe Scho>, <Employee: John Brown>]>
    

    Así como con el janeD variable:

    >>> janeD.employee.all()
    <QuerySet [<Employee: John Doe>]>
    

    De manera similar, querremos probar nuestro campo de administrador para asegurarnos de que funciona como se desea:

    >>> johnD.manager
    <Employee: Jane Doe>
    

    ¡Excelente! Parece que las cosas están funcionando como se esperaba.

    Configurando nuestra vista

    En el mismo directorio que nuestro directorio “hrmgmt” crea otro directorio llamado “plantillas”. Luego, dentro del directorio “templates”, cree otro directorio llamado “hrmgmt”. Finalmente dentro del directorio “hrmgmt / templates / hrmgmt” crea un archivo HTML llamado “index.html”. Es dentro de este archivo donde escribiremos el código para construir nuestra lista de empleados.

    Copia y pega el siguiente código:

    <!-- hrmgmt/templates/hrmgmt/index.html -->
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <title>Employee Listing</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1">
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
            <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js" integrity="sha384-h0AbiXch4ZDo7tp9hKZ4TsHbi047NrKGLO3SEJAg45jXxnGIfYzk4Si90RDIqNm1" crossorigin="anonymous"></script>
        </head>
        <body>
            <div class="container">
                <div class="row">
                    <div class="col-md-12">
                        <h1>Employee Listing</h1>
                    </div>
                </div>
                <div class="row">
                    <dov class="col-md-12">
                        <table class="table table-striped">
                            <thead class="thead-inverse">
                                <tr>
                                    <th>Employee ID</th>
                                    <th>First Name</th>
                                    <th>Last Name</th>
                                    <th>Role</th>
                                    <th>Manager</th>
                                </tr>
                            </thead>
                            <tbody class="table-striped">
                                {% for employee in employees %}
                                <tr>
                                    <td>{{ employee.id }}</td>
                                    <td>{{ employee.first_name }}</td>
                                    <td>{{ employee.last_name }}</td>
                                    <td>{{ employee.get_role_display }}</td>
                                    <td>{% if employee.manager %}{{ employee.manager.first_name }} {{ employee.manager.last_name }}{% endif %}</td>
                                </tr>
                                {% endfor %}
                            </tbody>
                        </table>
                    </dov>
                </div>
            </div>
            <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
            <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
        </body>
    </html>
    

    Este archivo se conoce como modelo en el marco web de Django. Las plantillas representan un modelo para HTML reproducible que se genera dinámicamente en función de los datos que se le pasan. En nuestro caso, los datos que se pasan a nuestra plantilla “índice” representan nuestra lista de empleados.

    Para poder ofrecer nuestra plantilla, necesitaremos realizar un par de cambios en nuestra función de visualización. Es decir, necesitamos importar el render función auxiliar de los accesos directos de Django, luego en lugar de devolver HttpResponse devolveremos una llamada a render, pasando en el request object, la ruta a nuestra plantilla y un diccionario que contiene los datos para pasar a nuestra plantilla.

    # hrmgmt/views.py
    from django.shortcuts import render
    
    from .models import Employee
    
    def index(request):
        employees = Employee.objects.order_by('id').all()
        context = {'employees': employees}
        return render(request, 'hrmgmt/index.html', context)
    

    Nuevamente, encienda nuestro servidor de desarrollo Django y en un tipo de navegador http: // localhost: 8000 / hr / en el campo URL y luego presione “Enter”. Debería ver un resultado similar a la siguiente captura de pantalla:

    Puede ver en la columna “Administrador” resultante de la tabla que hemos vinculado correctamente un Employee a una Employee usando modelos de Django.

    Conclusión

    En este artículo, hemos repasado el caso de uso de por qué implementaríamos una relación recursiva dentro de un modelo de Django. Analizamos el código para definir tal relación recursiva, así como cómo interactuar con los modelos para conservarlos en la base de datos y luego cómo recuperarlos. Finalmente, terminamos las cosas viendo cómo mostrar la información en nuestros modelos respaldados por bases de datos en una plantilla de Django.

    Si ha llegado hasta aquí, me gustaría agradecerle por leer mi artículo. Espero que este artículo te inspire a seguir investigando el desarrollo web con el marco web Django. Como siempre, invito a todos y cada uno de los comentarios, sugerencias o críticas.

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *