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.
Referencia Rápida
Mantenido Por: PINDU
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.
Si encuentras útil este proyecto y deseas contribuir a su mantenimiento, considera hacer una donación. Tu apoyo es muy apreciado.
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.
- 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).
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/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 PostgreSQLEl 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"]
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"
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: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 .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,
}
}Puedes copiar todo este bloque y pegarlo directamente en tu terminal.
docker compose run --rm manage migratePuedes copiar todo este bloque y pegarlo directamente en tu terminal.
docker compose run --rm manage createsuperuserPuedes copiar todo este bloque y pegarlo directamente en tu terminal.
docker compose up -d backendAccede a la administración de Django en http://localhost:8000/admin/
Puedes copiar todo este bloque y pegarlo directamente en tu terminal.
docker compose logs -f- 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 .
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')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]Puedes copiar todo este bloque y pegarlo directamente en tu terminal.
docker compose run --rm manage makemigrations
docker compose run --rm manage migrateAccede 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.
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_dataCon 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!