Skip to content

pindutn/fabrica_pastas

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tutorial: Despliegue de una Aplicación Django con Docker

Práctico de Mapeo Objeto-Relacional para la materia, Bases de Datos de la carrera Ingeniería en Sistemas de la Universidad Tecnológica Nacional Facultad Regional Villa María.

Docker Django 5.1.11 Alpine Linux Python 3.13 PostgreSQL 17

Referencia Rápida

Mantenido Por: PINDU

Descargo de Responsabilidad:

El código proporcionado se ofrece "tal cual", sin garantía de ningún tipo, expresa o implícita. En ningún caso los autores o titulares de derechos de autor serán responsables de cualquier reclamo, daño u otra responsabilidad.

Donaciones:

Si encuentras útil este proyecto y deseas contribuir a su mantenimiento, considera hacer una donación. Tu apoyo es muy apreciado.

Donate

Introducción

Este tutorial te guiará paso a paso en la creación y despliegue de una aplicación Django utilizando Docker y Docker Compose. El objetivo es que puedas levantar un entorno de desarrollo profesional, portable y fácil de mantener, ideal tanto para pruebas como para producción.


Requisitos Previos

  • Docker y Docker Compose instalados en tu sistema. Puedes consultar la documentación oficial de Docker para la instalación.
  • Conocimientos básicos de Python y Django (no excluyente, el tutorial es autoexplicativo).

Recursos Útiles


1. Estructura del Proyecto

Crea una carpeta para tu proyecto. En este ejemplo, la llamaremos fabrica.

Puedes copiar todo este bloque y pegarlo directamente en tu terminal o archivo correspondiente.

mkdir fabrica
cd fabrica/

2. Definición de Dependencias

Crea un archivo requirements.txt para listar las dependencias de Python necesarias para tu aplicación.

Puedes copiar todo este bloque y pegarlo directamente en tu archivo requirements.txt.

# requirements.txt
Django
psycopg[binary]  # Driver para PostgreSQL

3. Creación del Dockerfile

El Dockerfile define la imagen de Docker que contendrá tu aplicación. Aquí se detallan las etapas de construcción, instalación de dependencias y configuración del entorno.

Puedes copiar todo este bloque y pegarlo directamente en tu archivo Dockerfile.

# Etapa de construcción
FROM python:3.13-alpine AS base
LABEL maintainer="Luciano Parruccia <parruccia@yahoo.com.ar>"
LABEL version="1.0"
LABEL description="cloudset"
RUN apk --no-cache add bash pango ttf-freefont py3-pip curl

# Etapa de construcción
FROM base AS builder
# Instalación de dependencias de construcción
RUN apk --no-cache add py3-pip py3-pillow py3-brotli py3-scipy py3-cffi \
  linux-headers autoconf automake libtool gcc cmake python3-dev \
  fortify-headers binutils libffi-dev wget openssl-dev libc-dev \
  g++ make musl-dev pkgconf libpng-dev openblas-dev build-base \
  font-noto terminus-font libffi

# Copia solo los archivos necesarios para instalar dependencias de Python
COPY ./requirements.txt .

# Instalación de dependencias de Python
RUN pip install --upgrade pip \
  && pip install --no-cache-dir -r requirements.txt \
  && rm requirements.txt

# Etapa de producción
FROM base
RUN mkdir /code
WORKDIR /code
# Copia solo los archivos necesarios desde la etapa de construcción
COPY ./requirements.txt .
RUN pip install -r requirements.txt \
  && rm requirements.txt
COPY --chown=user:group --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.12/site-packages 
#COPY --from=build-python /usr/local/bin/ /usr/local/bin/
ENV PATH /usr/local/lib/python3.13/site-packages:$PATH
# Configuración adicional
RUN ln -s /usr/share/zoneinfo/America/Cordoba /etc/localtime

# Comando predeterminado
CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "app.wsgi"]

4. Configuración de Variables de Entorno

Crea un archivo .env.db para almacenar las variables de entorno necesarias para la conexión a la base de datos.

Puedes copiar todo este bloque y pegarlo directamente en tu archivo .env.db.

# .env.db
# .env.db
DATABASE_ENGINE=django.db.backends.postgresql
POSTGRES_HOST=db
POSTGRES_PORT=5432
POSTGRES_DB=postgres
POSTGRES_USER=postgres
PGUSER=${POSTGRES_USER}
POSTGRES_PASSWORD=postgres
LANG=es_AR.utf8
POSTGRES_INITDB_ARGS="--locale-provider=icu --icu-locale=es-AR --auth-local=trust"

5. Definición de Servicios con Docker Compose

El archivo docker-compose.yml orquesta los servicios necesarios: base de datos, backend de Django y utilidades para generación y administración del proyecto.

Puedes copiar todo este bloque y pegarlo directamente en tu archivo docker-compose.yml.

services:
  db:
    image: postgres:alpine
    env_file:
      - .env.db
    environment:
      - POSTGRES_INITDB_ARGS=--auth-host=md5 --auth-local=trust
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready" ]
      interval: 10s
      timeout: 2s
      retries: 5
    volumes:
      - postgres-db:/var/lib/postgresql/data
    networks:
      - net

  backend:
    build: .
    command: runserver 0.0.0.0:8000
    entrypoint: python3 manage.py
    env_file:
      - .env.db
    expose:
      - "8000"
    ports:
      - "8000:8000"
    volumes:
      - ./src:/code
    depends_on:
      db:
        condition: service_healthy
    networks:
      - net

  generate:
    build: .
    user: root
    command: /bin/sh -c 'mkdir src && django-admin startproject app src'
    env_file:
      - .env.db
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - .:/code
    networks:
      - net

  manage:
    build: .
    entrypoint: python3 manage.py
    env_file:
      - .env.db
    volumes:
      - ./src:/code
    depends_on:
      db:
        condition: service_healthy
    networks:
      - net

networks:
  net:

volumes:
  postgres-db:

6. Generación y Configuración de la Aplicación

Generar la estructura base del proyecto y la app

Hay que tener el archivo LICENSE para que la generación de a imagen no produzca un error.

Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

docker compose run --rm generate
docker compose run --rm manage startapp pastas
sudo chown $USER:$USER -R .

Configuración de settings.py

Edita el archivo settings.py para agregar tu app y configurar la base de datos usando las variables de entorno.

Puedes copiar todo este bloque y pegarlo al final directamente en tu archivo ./src/app/settings.py.

import os
ALLOWED_HOSTS = [os.environ.get("ALLOWED_HOSTS", "*")]
INSTALLED_APPS += [
    'pastas',  # Agrega tu app aquí
]
# Configuración de la base de datos
DATABASE_ENGINE = os.environ.get("DATABASE_ENGINE", "")
POSTGRES_USER = os.getenv("POSTGRES_USER", "")
POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD", "")
POSTGRES_DB = os.environ.get("POSTGRES_DB", "") or os.getenv("DB_NAME")
POSTGRES_HOST = os.environ.get("POSTGRES_HOST", "") or os.getenv("DB_HOST")
POSTGRES_PORT = os.environ.get("POSTGRES_PORT", "") or os.getenv("DB_PORT")
DATABASES = {
    "default": {
        "ENGINE": DATABASE_ENGINE,
        "NAME": POSTGRES_DB,
        "USER": POSTGRES_USER,
        "PASSWORD": POSTGRES_PASSWORD,
        "HOST": POSTGRES_HOST,
        "PORT": POSTGRES_PORT,
    }
}

7. Primeros Pasos con Django

Migrar la base de datos

Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

docker compose run --rm manage migrate

Crear un superusuario

Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

docker compose run --rm manage createsuperuser

Iniciar la aplicación

Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

docker compose up -d backend

Accede a la administración de Django en http://localhost:8000/admin/

Ver logs de los contenedores

Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

docker compose logs -f

8. Comandos Útiles

  • Aplicar migraciones:

    Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

    docker compose run manage makemigrations
    docker compose run manage migrate
  • Detener y eliminar contenedores:

    Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

    docker compose down
  • Detener y eliminar contenedores con imagenes y contenedores sin uso:

    Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

    docker compose down -v --remove-orphans --rmi all
  • Limpiar recursos de Docker:

    Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

    docker system prune -a
  • Cambiar permisos de archivos:

    Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

    sudo chown $USER:$USER -R .

9. Modelado de la Aplicación

Ejemplo de models.py

Incluye modelos bien documentados y estructurados para una gestión profesional de tus datos.

Puedes copiar todo este bloque y pegarlo directamente en tu archivo ./src/pastas/models.py.

from django.db import models

# Create your models here.
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User


class NombreAbstract(models.Model):
    nombre = models.CharField(
        _('Nombre'),
        help_text=_('Nombre descriptivo'),
        max_length=200,
        # unique=True,
    )

    def save(self, *args, **kwargs):
        self.nombre = self.nombre.upper()
        return super().save(*args, **kwargs)

    def __str__(self) -> str:
        return f'{self.nombre}'

    class Meta:
        abstract = True
        ordering = ['nombre']


class Localidad(NombreAbstract):
    class Meta:
        verbose_name = 'localidad'
        verbose_name_plural = 'localidades'


class Barrio(NombreAbstract):
    class Meta:
        verbose_name = 'barrio'
        verbose_name_plural = 'barrios'


class Provincia(NombreAbstract):
    class Meta:
        verbose_name = 'provincia'
        verbose_name_plural = 'provincias'


class Producto(NombreAbstract):
    ganancia = models.DecimalField(
        _('Ganancia'),
        max_digits=15,
        decimal_places=2,
        help_text=_('Ganancia del producto, expresado en coeficiente.'),
        default=0
    )
    es_relleno = models.BooleanField(
        _('Es Relleno'),
        help_text=_('Especifica si el producto contiene relleno.'),
        default=False
    )

    @property
    def precio(self):
        total = 0
        for ingrediente in self.ingredientes.all():
            total += ingrediente.cantidad * ingrediente.ingrediente.costo
        return round(total * self.ganancia, 2)

    class Meta:
        verbose_name = 'producto'
        verbose_name_plural = 'productos'


class Cliente(NombreAbstract):
    numero_documento = models.BigIntegerField(
        _('numero documento'),
        help_text=_('numero de documento / CUIT'),
        null=True
    )
    direccion = models.CharField(
        _('dirección'),
        help_text=_('dirección del cliente'),
        max_length=200,
        blank=True,
        null=True
    )
    celular = models.BigIntegerField(
        _('Celular'),
        help_text=_(
            'Número de celular con característica del/la administrador/a'),
        blank=True,
        null=True
    )
    telefono = models.BigIntegerField(
        _('teléfono'),
        help_text=_('teléfono fijo'),
        blank=True,
        null=True
    )
    email = models.EmailField(
        _('email'),
        help_text=_('email del cliente'),
        null=True,
        blank=True,
    )
    barrio = models.ForeignKey(
        Barrio,
        verbose_name=_('barrio'),
        help_text=_('barrio donde reside '),
        related_name='%(app_label)s_%(class)s_related',
        on_delete=models.SET_NULL,
        blank=True,
        null=True
    )
    localidad = models.ForeignKey(
        Localidad,
        verbose_name=_('localidad'),
        help_text=_('localidad donde reside el cliente'),
        related_name='%(app_label)s_%(class)s_related',
        on_delete=models.CASCADE,
        blank=True,
        null=True
    )
    provincia = models.ForeignKey(
        Provincia,
        verbose_name=_('provincia'),
        help_text=_('provincia donde reside'),
        related_name='%(app_label)s_%(class)s_related',
        on_delete=models.PROTECT,
        blank=True,
        null=True
    )
    user = models.ForeignKey(
        User,
        help_text=_('Usuario con el que se loguea al sistema'),
        verbose_name='usuario',
        related_name='%(app_label)s_%(class)s',
        related_query_name='%(app_label)s_%(class)s',
        on_delete=models.PROTECT,
        null=True,
        blank=True
    )

    def __str__(self):
        return '{} {}'.format(self.nombre, self.numero_documento)

    class Meta:
        indexes = [
            models.Index(
                fields=[
                    'numero_documento',
                    'user',
                ],
                name='%(app_label)s_%(class)s_unico'
            ),
        ]


class Venta(models.Model):
    fecha = models.DateField(
        _('fecha'),
        help_text=_('fecha de la venta')
    )
    cliente = models.ForeignKey(
        Cliente,
        verbose_name=_('cliente'),
        help_text=_('cliente que realiza la compra'),
        related_name='compras',
        on_delete=models.PROTECT,
        blank=False,
        null=False
    )

    def __str__(self):
        return '{} {}'.format(self.fecha, self.cliente.nombre)

    class Meta:
        ordering = ['fecha']
        verbose_name = 'venta'
        verbose_name_plural = 'ventas'


class DetalleVenta(models.Model):
    venta = models.ForeignKey(
        Venta,
        verbose_name=_('venta'),
        help_text=_('detalle de la compra'),
        related_name='detalle',
        on_delete=models.PROTECT,
        blank=False,
        null=False
    )
    cantidad = models.DecimalField(
        _('cantidad'),
        max_digits=15,
        decimal_places=2,
        help_text=_('cantidad'),
        blank=True,
        null=True,
        default=None
    )
    producto = models.ForeignKey(
        Producto,
        verbose_name=_('producto'),
        help_text=_('producto'),
        related_name='detalle',
        on_delete=models.PROTECT,
        blank=False,
        null=False
    )


class UnidadMedida(NombreAbstract):
    pass


class Ingrediente(NombreAbstract):
    costo = models.DecimalField(
        _('Costo'),
        max_digits=15,
        decimal_places=2,
        help_text=_('Costo del ingrediente expresado en pesos'),
        default=0
    )
    unidad_medida = models.ForeignKey(
        UnidadMedida,
        related_name='ingredientes',
        on_delete=models.PROTECT,
        help_text=_('Unidad de medida del ingrediente'),
        null=False,
        blank=False,
        default=1
    )

    class Meta:
        verbose_name = _('Ingrediente')
        verbose_name_plural = _('Ingredientes')


class Receta(models.Model):
    cantidad = models.DecimalField(
        _('Cantidad'),
        max_digits=15,
        decimal_places=3,
        help_text=_(
            'Cantidad del ingrediente, expresado en su unidad de medida.'),
        default=0
    )
    ingrediente = models.ForeignKey(
        Ingrediente,
        related_name='productos',
        on_delete=models.PROTECT,
        help_text=_('Ingrediente de la receta'),
    )
    producto = models.ForeignKey(
        Producto,
        related_name='ingredientes',
        on_delete=models.PROTECT,
        help_text=_('Producto de la receta'),
    )

    class Meta:
        ordering = ['ingrediente']
        verbose_name = _('Producto')
        verbose_name_plural = _('Productos')

10. Administración de la Aplicación

Ejemplo de admin.py

Registra tus modelos para gestionarlos desde el panel de administración de Django.

Puedes copiar todo este bloque y pegarlo directamente en tu archivo ./src/pastas/admin.py.

from django.contrib import admin
from pastas.models import *
# Register your models here.
admin.site.register(UnidadMedida)
admin.site.register(Ingrediente)
admin.site.register(Barrio)
admin.site.register(Localidad)
admin.site.register(Provincia)
admin.site.register(Cliente)


class RecetaInline(admin.TabularInline):
    model = Receta
    extra = 0


@admin.register(Producto)
class ProductoAdmin(admin.ModelAdmin):
    inlines = [
        RecetaInline,
    ]
    list_display = (
        'nombre',
        'precio',
    )
    ordering = ['nombre']  # -nombre descendente, nombre ascendente
    search_fields = ['nombre']
    list_filter = (
        'nombre',
    )


class DetalleVentaInline(admin.TabularInline):
    model = DetalleVenta
    extra = 0


@admin.register(Venta)
class ComprobanteAdmin(admin.ModelAdmin):
    save_on_top = True
    save_as = True
    list_per_page = 20
    date_hierarchy = 'fecha'
    list_display = (
        'fecha',
        'cliente',
    )

    list_filter = (
        'cliente__nombre',
    )

    inlines = [
        DetalleVentaInline]

11. Migraciones y Carga de Datos Iniciales

Realizar migraciones de la app nueva.

Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

docker compose run --rm manage makemigrations
docker compose run --rm manage migrate

Accede a la administración de Django en http://localhost:8000/admin/ donde ya se van a ver los cambios realizados en la app, pero todavía sin datos pre cargados.

Crear y cargar fixtures (datos iniciales)

Crea la carpeta ./src/pastas/fixtures dentro de tu app y agrega el archivo initial_data.json con los datos de ejemplo. Luego, carga los datos:

Puedes copiar todo este bloque y pegarlo directamente en tu archivo initial_data.json.

[
    {
        "model": "pastas.unidadmedida",
        "pk": 1,
        "fields": {
            "nombre": "KILO"
        }
    },
    {
        "model": "pastas.unidadmedida",
        "pk": 2,
        "fields": {
            "nombre": "UNIDAD"
        }
    },
    {
        "model": "pastas.ingrediente",
        "pk": 1,
        "fields": {
            "nombre": "HARINA",
            "costo": "50.00",
            "unidad_medida": 1
        }
    },
    {
        "model": "pastas.ingrediente",
        "pk": 2,
        "fields": {
            "nombre": "SAL",
            "costo": "50.00",
            "unidad_medida": 1
        }
    },
    {
        "model": "pastas.ingrediente",
        "pk": 3,
        "fields": {
            "nombre": "HUEVO",
            "costo": "10.00",
            "unidad_medida": 2
        }
    },
    {
        "model": "pastas.ingrediente",
        "pk": 4,
        "fields": {
            "nombre": "JAM\u00d3N",
            "costo": "600.00",
            "unidad_medida": 1
        }
    },
    {
        "model": "pastas.ingrediente",
        "pk": 5,
        "fields": {
            "nombre": "QUESO",
            "costo": "250.00",
            "unidad_medida": 1
        }
    },
    {
        "model": "pastas.ingrediente",
        "pk": 6,
        "fields": {
            "nombre": "ESPINACA",
            "costo": "100.00",
            "unidad_medida": 1
        }
    },
    {
        "model": "pastas.producto",
        "pk": 1,
        "fields": {
            "nombre": "TALLARIN",
            "ganancia": "2.00",
            "es_relleno": false
        }
    },
    {
        "model": "pastas.producto",
        "pk": 2,
        "fields": {
            "nombre": "RAVIOLI ESPINACA",
            "ganancia": "3.00",
            "es_relleno": true
        }
    },
    {
        "model": "pastas.producto",
        "pk": 3,
        "fields": {
            "nombre": "SORRENTINO JAM\u00d3N Y QUESO",
            "ganancia": "3.80",
            "es_relleno": true
        }
    },
    {
        "model": "pastas.receta",
        "pk": 1,
        "fields": {
            "cantidad": "0.90",
            "ingrediente": 1,
            "producto": 1
        }
    },
    {
        "model": "pastas.receta",
        "pk": 2,
        "fields": {
            "cantidad": "0.05",
            "ingrediente": 2,
            "producto": 1
        }
    },
    {
        "model": "pastas.receta",
        "pk": 3,
        "fields": {
            "cantidad": "4.00",
            "ingrediente": 3,
            "producto": 1
        }
    },
    {
        "model": "pastas.receta",
        "pk": 4,
        "fields": {
            "cantidad": "0.60",
            "ingrediente": 1,
            "producto": 2
        }
    },
    {
        "model": "pastas.receta",
        "pk": 5,
        "fields": {
            "cantidad": "4.00",
            "ingrediente": 3,
            "producto": 2
        }
    },
    {
        "model": "pastas.receta",
        "pk": 6,
        "fields": {
            "cantidad": "0.30",
            "ingrediente": 6,
            "producto": 2
        }
    },
    {
        "model": "pastas.receta",
        "pk": 7,
        "fields": {
            "cantidad": "0.60",
            "ingrediente": 1,
            "producto": 3
        }
    },
    {
        "model": "pastas.receta",
        "pk": 8,
        "fields": {
            "cantidad": "4.00",
            "ingrediente": 3,
            "producto": 3
        }
    },
    {
        "model": "pastas.receta",
        "pk": 9,
        "fields": {
            "cantidad": "0.15",
            "ingrediente": 4,
            "producto": 3
        }
    },
    {
        "model": "pastas.receta",
        "pk": 10,
        "fields": {
            "cantidad": "0.15",
            "ingrediente": 5,
            "producto": 3
        }
    },
    {
        "model": "pastas.barrio",
        "pk": 1,
        "fields": {
            "nombre": "centro"
        }
    },
    {
        "model": "pastas.barrio",
        "pk": 2,
        "fields": {
            "nombre": "lamadrid"
        }
    },
    {
        "model": "pastas.barrio",
        "pk": 1,
        "fields": {
            "nombre": "ameghino"
        }
    }
]

Puedes copiar todo este bloque y pegarlo directamente en tu terminal.

docker compose run --rm manage loaddata initial_data

Conclusión

Con estos pasos, tendrás un entorno Django profesional, portable y listo para desarrollo o producción. Recuerda consultar la documentación oficial de Django y Docker para profundizar en cada tema. ¡Éxitos en tu proyecto!


About

Práctico de la materia de bases de datos de 3er año de UTN FRVM

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors