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 *