diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..76bb1ef3
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,9 @@
+README.MD
+Dockerfile
+.git
+.ruff
+.gitignore
+.editorconfig
+__pycache__
+functional_tests
+.env
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 00000000..92606b12
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,37 @@
+name: cd
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ deploy:
+ name: Deploy
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: check out code
+ uses: actions/checkout@v4
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Get the date
+ run: echo "date=$(date '+%Y%m%d%H%M%S')" >> $GITHUB_ENV
+
+ - name: Build and push
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: ${{ secrets.DOCKERHUB_USERNAME }}/vetsoft:latest,${{ secrets.DOCKERHUB_USERNAME }}/vetsoft:${{ env.date }}
+
+ - name: Deploy
+ run: curl ${{ secrets.RENDER_HOOK }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 00000000..35b673a7
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,36 @@
+name: ci
+
+on:
+ pull_request:
+ branches: [main]
+
+jobs:
+ tests:
+ name: tests
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4
+
+ - name: Set up python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.10'
+ cache: 'pip'
+ - run: pip install -r requirements.txt
+
+ - name: Install playwright
+ run: python -m playwright install --with-deps firefox
+
+ - name: Run static test
+ run: ruff check
+
+ - name: Run unit and integration tests
+ run: coverage run --source="./app" --omit="./app/migrations/**" manage.py test app
+
+ - name: Check coverage
+ run: coverage report --fail-under=92
+
+ - name: Run e2e tests
+ run: python manage.py test functional_tests
diff --git a/.ruff.toml b/.ruff.toml
index a981d446..f532304d 100644
--- a/.ruff.toml
+++ b/.ruff.toml
@@ -26,7 +26,8 @@ exclude = [
"node_modules",
"site-packages",
"venv",
- "migrations"
+ "migrations",
+ "test*"
]
# Same as Black.
@@ -38,7 +39,7 @@ target-version = "py38"
[lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
-select = ["E4", "E7", "E9", "F"]
+select = ["E4", "E7", "E9", "F","COM812"]
ignore = []
# Allow fix for all enabled rules (when `--fix`) is provided.
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..739db981
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,30 @@
+#Usamos como base la imagen oficial de python slim
+FROM python:3.12-slim
+
+#Establecemos el directorio de trabajo dentro del contenedor
+WORKDIR /app
+
+#Copiamos los requerimientos al directorio de trabajo
+COPY requirements.txt .
+
+#Instalamos los paquetes requeridos dentro del contenedor
+RUN pip install --no-cache-dir -r requirements.txt
+
+#Copiamos el resto de la aplicacion
+COPY . .
+
+#Exponemos el puerto para poder acceder desde afuera del contenedor
+EXPOSE 8000
+
+#Corremos el comando para iniciar la aplicacion
+#CMD [ "python", "manage.py","runserver","0.0.0.0:8000" ]
+
+# Copiamos el script de entrypoint y le damos permisos de ejecución
+COPY entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/entrypoint.sh
+
+# Ejecutamos el script de entrypoint cuando se enciende el contenedor
+ENTRYPOINT ["entrypoint.sh"]
+
+
+
diff --git a/README.MD b/README.MD
new file mode 100644
index 00000000..78e3e53e
--- /dev/null
+++ b/README.MD
@@ -0,0 +1,42 @@
+# Vetsoft
+
+Aplicación web para veterinarias utilizada en la cursada 2024 de Ingeniería y Calidad de Software. UTN-FRLP
+
+## Integrantes
+
+- Chesini Pablo
+- Da Silva Franco
+- Lucich Francisco
+- Scianca Manuel
+
+## Dependencias
+
+- python 3
+- Django
+- sqlite
+- playwright
+- ruff
+
+## Instalar dependencias
+
+`pip install -r requirements.txt`
+
+## Iniciar la Base de Datos
+
+`python manage.py migrate`
+
+## Iniciar app
+
+`python manage.py runserver`
+
+## Construir imagen docker
+
+`docker build -t vetsoft-app:1.0 .`
+
+## Desplegar contenedor
+
+Antes de desplegar debemos crea el archivo .env en la raiz del repositorio y completarlo, para hacerlo se puede seguir el .env-example
+
+`docker run -d -p 8000:8000 --env-file .env --name "Vetsoft" vetsoft-app:1.0`
+
+Luego se puede acceder a la aplicacion desde localhost:8000
diff --git a/README.md b/README.md
deleted file mode 100644
index 548b2c00..00000000
--- a/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Vetsoft
-
-Aplicación web para veterinarias utilizada en la cursada 2024 de Ingeniería y Calidad de Software. UTN-FRLP
-
-## Dependencias
-
-- python 3
-- Django
-- sqlite
-- playwright
-- ruff
-
-## Instalar dependencias
-
-`pip install -r requirements.txt`
-
-## Iniciar la Base de Datos
-
-`python manage.py migrate`
-
-## Iniciar app
-
-`python manage.py runserver`
-
diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py
index 6f470456..9347b629 100644
--- a/app/migrations/0001_initial.py
+++ b/app/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.0.4 on 2024-04-20 18:47
+# Generated by Django 5.0.4 on 2024-04-30 19:00
from django.db import migrations, models
@@ -12,12 +12,48 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
- name='Cliente',
+ name='Client',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('nombre', models.CharField(max_length=100)),
- ('telefono', models.CharField(max_length=15)),
+ ('name', models.CharField(max_length=100)),
+ ('phone', models.CharField(max_length=15)),
('email', models.EmailField(max_length=254)),
+ ('address', models.CharField(blank=True, max_length=100)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Medi',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('description', models.TextField()),
+ ('dose', models.IntegerField()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Product',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('type', models.CharField(max_length=100)),
+ ('price', models.FloatField()),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Provider',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('email', models.EmailField(max_length=254)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Vet',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ('email', models.EmailField(max_length=254)),
+ ('phone', models.CharField(max_length=15)),
],
),
]
diff --git a/app/migrations/0002_client_delete_cliente.py b/app/migrations/0002_client_delete_cliente.py
deleted file mode 100644
index 649bc978..00000000
--- a/app/migrations/0002_client_delete_cliente.py
+++ /dev/null
@@ -1,26 +0,0 @@
-# Generated by Django 5.0.4 on 2024-04-21 21:28
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('app', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='Client',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=100)),
- ('phone', models.CharField(max_length=15)),
- ('email', models.EmailField(max_length=254)),
- ('address', models.CharField(blank=True, max_length=100)),
- ],
- ),
- migrations.DeleteModel(
- name='Cliente',
- ),
- ]
diff --git a/app/migrations/0002_provider_address.py b/app/migrations/0002_provider_address.py
new file mode 100644
index 00000000..bbb08893
--- /dev/null
+++ b/app/migrations/0002_provider_address.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.0.4 on 2024-05-26 19:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('app', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='provider',
+ name='address',
+ field=models.CharField(blank=True, max_length=100),
+ ),
+ ]
diff --git a/app/migrations/0002_vet_specialty.py b/app/migrations/0002_vet_specialty.py
new file mode 100644
index 00000000..8f145988
--- /dev/null
+++ b/app/migrations/0002_vet_specialty.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.0.4 on 2024-05-26 04:35
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('app', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='vet',
+ name='specialty',
+ field=models.CharField(choices=[('Sin especialidad', 'Sin Especialidad'), ('Cardiología', 'Cardiologia'), ('Medicina interna de pequeños animales', 'Medicina Interna Pequenos Animales'), ('Medicina interna de grandes animales', 'Medicina Interna Grandes Animales'), ('Neurología', 'Neurologia'), ('Oncología', 'Oncologia'), ('Nutrición', 'Nutricion')], default='Sin especialidad', max_length=100),
+ ),
+ ]
diff --git a/app/migrations/0003_merge_0002_provider_address_0002_vet_specialty.py b/app/migrations/0003_merge_0002_provider_address_0002_vet_specialty.py
new file mode 100644
index 00000000..c78f78c8
--- /dev/null
+++ b/app/migrations/0003_merge_0002_provider_address_0002_vet_specialty.py
@@ -0,0 +1,14 @@
+# Generated by Django 5.0.4 on 2024-05-27 00:27
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('app', '0002_provider_address'),
+ ('app', '0002_vet_specialty'),
+ ]
+
+ operations = [
+ ]
diff --git a/app/models.py b/app/models.py
index 8175ecc3..cd8dbb53 100644
--- a/app/models.py
+++ b/app/models.py
@@ -1,5 +1,5 @@
from django.db import models
-
+from django.utils.translation import gettext_lazy as _
def validate_client(data):
errors = {}
@@ -22,6 +22,85 @@ def validate_client(data):
return errors
+def validate_medicine(data):
+ errors = {}
+
+ name = data.get("name", "")
+ description = data.get("description", "")
+ dose = data.get("dose", "")
+
+ if name == "":
+ errors["name"] = "Por favor ingrese un nombre"
+
+ if description == "":
+ errors["description"] = "Por favor ingrese una descripcion"
+
+ if dose == "":
+ errors["dose"] = "Por favor ingrese una dosis"
+ else:
+ try:
+ dose_value = int(dose)
+ if dose_value < 1 or dose_value > 10:
+ errors["dose"] = "Por favor ingrese una dosis entre 1 y 10"
+ except ValueError:
+ errors["dose"] = "La dosis debe ser un número entero"
+
+ return errors
+
+def validate_product(data):
+ errors = {}
+
+ name = data.get("name", "")
+ type = data.get("type", "")
+ price = data.get("price", "")
+
+ if name == "":
+ errors["name"] = "Por favor ingrese un nombre"
+
+ if type == "":
+ errors["type"] = "Por favor ingrese un tipo"
+
+ '''if price == "":
+ errors["price"] = "Por favor ingrese un precio"
+ elif float(price) <= 0:
+ errors["price"] = "Por favor ingrese un precio mayor a cero"
+ '''
+ if price == "":
+ errors["price"] = "Por favor ingrese un precio"
+ else:
+ try:
+ price_float = float(price)
+ if price_float <= 0:
+ errors["price"] = "Por favor ingrese un precio mayor a cero"
+ except ValueError:
+ errors["price"] = "Por favor ingrese un precio válido"
+
+ return errors
+
+
+def validate_provider(data):
+ errors = {}
+
+ name = data.get("name", "")
+ email = data.get("email", "")
+ address = data.get("address", "")
+
+
+ if name == "":
+ errors["name"] = "Por favor ingrese un nombre"
+
+ if email == "":
+ errors["email"] = "Por favor ingrese un email"
+ elif email.count("@") == 0:
+ errors["email"] = "Por favor ingrese un email valido"
+
+ if address == "":
+ errors["address"] = "Por favor ingrese una dirección"
+
+
+ return errors
+
+
class Client(models.Model):
name = models.CharField(max_length=100)
phone = models.CharField(max_length=15)
@@ -54,3 +133,156 @@ def update_client(self, client_data):
self.address = client_data.get("address", "") or self.address
self.save()
+
+
+class Product(models.Model):
+ name = models.CharField(max_length=100)
+ type = models.CharField(max_length=100)
+ price = models.FloatField()
+
+ def __str__(self):
+ return self.name
+
+ @classmethod
+ def save_product(cls, product_data):
+ errors = validate_product(product_data)
+
+ if len(errors.keys()) > 0:
+ return False, errors
+
+ Product.objects.create(
+ name=product_data.get("name"),
+ type=product_data.get("type"),
+ price=product_data.get("price"),
+ )
+
+ return True, None
+
+ def update_product(self, product_data):
+ self.name = product_data.get("name", "") or self.name
+ self.type = product_data.get("type", "") or self.type
+ try:
+ price = float(product_data.get("price", ""))
+ except ValueError:
+ # Si el precio no es un valor numérico válido, retorna un mensaje de error
+ return False, {"price": "Por favor ingrese un precio válido"}
+
+ if price <= 0:
+ # Si el precio es menor o igual a cero, retorna un mensaje de error
+ return False, {"price": "Por favor ingrese un precio mayor a cero"}
+
+ # Si no hay errores, actualiza el precio y guarda el objeto en la base de datos
+ self.price = price
+ self.save()
+ return True, None
+
+class Vet(models.Model):
+ class VetSpecialties(models.TextChoices):
+ SIN_ESPECIALIDAD="Sin especialidad", _("Sin especialidad")
+ CARDIOLOGIA="Cardiología", _("Cardiología")
+ MEDICINA_INTERNA_PEQUENOS_ANIMALES="Medicina interna de pequeños animales", _("Medicina interna de pequeños animales")
+ MEDICINA_INTERNA_GRANDES_ANIMALES="Medicina interna de grandes animales", _("Medicina interna de grandes animales")
+ NEUROLOGIA="Neurología", _("Neurología")
+ ONCOLOGIA="Oncología", _("Oncología")
+ NUTRICION="Nutrición", _("Nutrición")
+
+
+ name = models.CharField(max_length=100)
+ email = models.EmailField()
+ phone = models.CharField(max_length=15)
+ specialty = models.CharField(
+ max_length=100,
+ choices=VetSpecialties,
+ default=VetSpecialties.SIN_ESPECIALIDAD, # se agrego la coma faltante detectada con ruff
+ )
+
+ def __str__(self):
+ return self.name
+
+ @classmethod
+ def save_vet(cls, vet_data):
+ errors = validate_client(vet_data)
+
+ if len(errors.keys()) > 0:
+ return False, errors
+
+ Vet.objects.create(
+ name=vet_data.get("name"),
+ phone=vet_data.get("phone"),
+ email=vet_data.get("email"),
+ specialty=vet_data.get("specialty"),
+ )
+
+ return True, None
+
+ def update_vet(self, vet_data):
+ self.name = vet_data.get("name", "") or self.name
+ self.email = vet_data.get("email", "") or self.email
+ self.phone = vet_data.get("phone", "") or self.phone
+ self.specialty = vet_data.get("specialty", "") or self.specialty
+ self.save()
+
+
+class Medi(models.Model):
+ name = models.CharField(max_length=100)
+ description = models.TextField()
+ dose = models.IntegerField()
+
+ def __str__(self):
+ return self.name
+
+ @classmethod
+ def save_medi(cls, medi_data):
+ errors = validate_medicine(medi_data)
+
+ if len(errors.keys()) > 0:
+ return False, errors
+
+ Medi.objects.create(
+ name=medi_data.get("name"),
+ description=medi_data.get("description"),
+ dose=medi_data.get("dose"),
+ )
+
+ return True, None
+
+ def update_medi(self, medi_data):
+ self.name = medi_data.get("name", "") or self.name
+ self.description = medi_data.get("description", "") or self.description
+ self.dose = medi_data.get("dose", "") or self.dose
+ self.save()
+
+
+
+
+class Provider(models.Model):
+ name = models.CharField(max_length=100)
+ email = models.EmailField()
+ address = models.CharField(max_length=100, blank=True)
+
+
+ def __str__(self):
+ return self.name
+
+ @classmethod
+ def save_provider(cls, provider_data):
+ errors = validate_provider(provider_data)
+
+ if len(errors.keys()) > 0:
+ return False, errors
+
+ Provider.objects.create(
+ name=provider_data.get("name"),
+ email=provider_data.get("email"),
+ address=provider_data.get("address"),
+
+ )
+
+ return True, None
+
+ def update_provider(self, provider_data):
+ self.name = provider_data.get("name","") or self.name
+ self.email = provider_data.get("email","") or self.email
+ self.address = provider_data.get("address","") or self.address
+
+ self.save()
diff --git a/app/templates/home.html b/app/templates/home.html
index 306de896..38cde420 100644
--- a/app/templates/home.html
+++ b/app/templates/home.html
@@ -18,6 +18,67 @@
+
+
+
+
-{% endblock %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/templates/medicine/form.html b/app/templates/medicine/form.html
new file mode 100644
index 00000000..fb4c7b15
--- /dev/null
+++ b/app/templates/medicine/form.html
@@ -0,0 +1,73 @@
+{% extends 'base.html' %}
+
+{% block main %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/app/templates/medicine/repository.html b/app/templates/medicine/repository.html
new file mode 100644
index 00000000..dcc2a5a1
--- /dev/null
+++ b/app/templates/medicine/repository.html
@@ -0,0 +1,54 @@
+{% extends 'base.html' %}
+
+{% block main %}
+
+
Medicina
+
+
+
+
+
+
+ Nombre |
+ Descripcion |
+ Dósis |
+ |
+
+
+
+
+ {% for medi in medis %}
+
+ {{medi.name}} |
+ {{medi.description}} |
+ {{medi.dose}} |
+
+ Editar
+
+ |
+
+ {% empty %}
+
+
+ No Se encuentra la Medicina solicitada
+ |
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/app/templates/products/form.html b/app/templates/products/form.html
new file mode 100644
index 00000000..20f4e006
--- /dev/null
+++ b/app/templates/products/form.html
@@ -0,0 +1,73 @@
+{% extends 'base.html' %}
+
+{% block main %}
+
+{% endblock %}
diff --git a/app/templates/products/repository.html b/app/templates/products/repository.html
new file mode 100644
index 00000000..076c3f7e
--- /dev/null
+++ b/app/templates/products/repository.html
@@ -0,0 +1,54 @@
+{% extends 'base.html' %}
+
+{% block main %}
+
+
Productos
+
+
+
+
+
+
+ Nombre |
+ Tipo |
+ Precio |
+ |
+
+
+
+
+ {% for product in products %}
+
+ {{product.name}} |
+ {{product.type}} |
+ {{product.price}} |
+
+ Editar
+
+ |
+
+ {% empty %}
+
+
+ No existen productos
+ |
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/app/templates/provider/form.html b/app/templates/provider/form.html
new file mode 100644
index 00000000..6c1f2808
--- /dev/null
+++ b/app/templates/provider/form.html
@@ -0,0 +1,74 @@
+{% extends 'base.html' %}
+
+{% block main %}
+
+{% endblock %}
diff --git a/app/templates/provider/repository.html b/app/templates/provider/repository.html
new file mode 100644
index 00000000..89cce8f3
--- /dev/null
+++ b/app/templates/provider/repository.html
@@ -0,0 +1,54 @@
+{% extends 'base.html' %}
+
+{% block main %}
+
+
Proveedores
+
+
+
+
+
+
+ Nombre |
+ Email |
+ Dirección |
+ |
+
+
+
+
+ {% for prov in provider %}
+
+ {{prov.name}} |
+ {{prov.email}} |
+ {{prov.address}} |
+
+ Editar
+
+ |
+
+ {% empty %}
+
+
+ No existen proveedores
+ |
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/app/templates/vets/form.html b/app/templates/vets/form.html
new file mode 100644
index 00000000..9717e94f
--- /dev/null
+++ b/app/templates/vets/form.html
@@ -0,0 +1,90 @@
+{% extends 'base.html' %}
+
+{% block main %}
+
+
+
+
Nuevo Veterinario
+
+
+
+
+
+{% endblock %}
diff --git a/app/templates/vets/repository.html b/app/templates/vets/repository.html
new file mode 100644
index 00000000..3b380d77
--- /dev/null
+++ b/app/templates/vets/repository.html
@@ -0,0 +1,56 @@
+{% extends 'base.html' %}
+
+{% block main %}
+
+
Veterinarios
+
+
+
+
+
+
+ Nombre |
+ Teléfono |
+ Email |
+ Especialidad |
+ |
+
+
+
+
+ {% for vet in vets %}
+
+ {{vet.name}} |
+ {{vet.phone}} |
+ {{vet.email}} |
+ {{vet.specialty}} |
+
+ Editar
+
+ |
+
+ {% empty %}
+
+
+ No existen veterinarios
+ |
+
+ {% endfor %}
+
+
+
+{% endblock %}
diff --git a/app/tests_integration.py b/app/tests_integration.py
index 10392dad..35719d87 100644
--- a/app/tests_integration.py
+++ b/app/tests_integration.py
@@ -1,6 +1,6 @@
from django.test import TestCase
from django.shortcuts import reverse
-from app.models import Client
+from app.models import Product, Client, Vet, Provider, Medi
class HomePageTest(TestCase):
@@ -93,3 +93,246 @@ def test_edit_user_with_valid_data(self):
self.assertEqual(editedClient.phone, client.phone)
self.assertEqual(editedClient.address, client.address)
self.assertEqual(editedClient.email, client.email)
+
+###TEST MEDICINE###
+class MedicineTest(TestCase):
+
+ def test_validation_invalid_dose_below_range(self):
+ # Enviamos una solicitud POST al formulario de creación de medicamentos con una dosis inválida (fuera del rango permitido, menor que 1)
+ response = self.client.post(
+ reverse("medi_form"),
+ data={
+ "name": "Paracetamol",
+ "description": "Analgesico",
+ "dose": "0", # Dosis fuera del rango permitido (menor que 1)
+ },
+ )
+ self.assertContains(response, "Por favor ingrese una dosis entre 1 y 10")
+
+
+ def test_validation_invalid_dose_above_range(self):
+ # Enviamos una solicitud POST al formulario de creación de medicamentos con una dosis inválida (fuera del rango permitido, mayor que 10)
+ response = self.client.post(
+ reverse("medi_form"),
+ data={
+ "name": "Paracetamol",
+ "description": "Analgesico",
+ "dose": 15, # Dosis fuera del rango permitido (mayor que 10)
+ },
+ )
+
+ self.assertContains(response, "Por favor ingrese una dosis entre 1 y 10")
+
+ def test_edit_medicine_with_valid_data(self):
+ # Creamos un medicamento en la base de datos
+ medicine = Medi.objects.create(
+ name="Paracetamol",
+ description="Analgesico",
+ dose=5,
+ )
+
+ # Enviamos una solicitud POST para editar el medicamento
+ response = self.client.post(
+ reverse("medi_form"),
+ data={
+ "id": medicine.id,
+ "dose": "9", # Nueva dosis
+ },
+ )
+
+ # Verificamos que la solicitud redirija correctamente
+ self.assertEqual(response.status_code, 302)
+
+ # Obtenemos el objeto de medicamento actualizado de la base de datos
+ edited_medicine = Medi.objects.get(pk=medicine.id)
+
+ # Verificamos que la dosis se haya actualizado correctamente
+ self.assertEqual(edited_medicine.name, medicine.name)
+ self.assertEqual(edited_medicine.description, medicine.description)
+ self.assertEqual(edited_medicine.dose, 9) # Verificamos la nueva dosis
+
+
+class VetsTest(TestCase):
+ def test_vet_table_shows_specialty(self):
+
+ self.client.post(
+ reverse("vets_form"),
+ data={
+ "name": "Mariano Navone",
+ "phone": "2219870789",
+ "email": "lanavoneta@gmail.com",
+ "specialty": Vet.VetSpecialties.ONCOLOGIA,
+ },
+ )
+
+ vet = Vet.objects.all()[0] #Creo un vet y lo recupero
+
+
+ response = self.client.get(reverse("vets_repo"))
+ self.assertTemplateUsed(response, "vets/repository.html")
+ self.assertContains(response, '
Especialidad') #Verifico que la tabla tenga especialidad
+ self.assertContains(response, vet.specialty) #Verifico que la especialidad se muestre
+
+class ProductsTest(TestCase):
+ def test_repo_use_repo_template(self):
+ response = self.client.get(reverse("products_repo"))
+ self.assertTemplateUsed(response, "products/repository.html")
+
+ def test_repo_display_all_products(self):
+ response = self.client.get(reverse("products_repo"))
+ self.assertTemplateUsed(response, "products/repository.html")
+
+ def test_form_use_form_template(self):
+ response = self.client.get(reverse("products_form"))
+ self.assertTemplateUsed(response, "products/form.html")
+
+ def test_can_create_product(self):
+ response = self.client.post(
+ reverse("products_form"),
+ data={
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": 100.0,
+ },
+ )
+ products = Product.objects.all()
+ self.assertEqual(len(products), 1)
+
+ self.assertEqual(products[0].name, "Producto 1")
+ self.assertEqual(products[0].type, "Alimento")
+ self.assertEqual(products[0].price, 100)
+
+ self.assertRedirects(response, reverse("products_repo"))
+
+ def test_validation_errors_create_product(self):
+ response = self.client.post(
+ reverse("products_form"),
+ data={},
+ )
+
+ self.assertContains(response, "Por favor ingrese un nombre")
+ self.assertContains(response, "Por favor ingrese un tipo")
+ self.assertContains(response, "Por favor ingrese un precio")
+
+ def test_should_response_with_404_status_if_product_doesnt_exists(self):
+ response = self.client.get(reverse("products_edit", kwargs={"id": 100}))
+ self.assertEqual(response.status_code, 404)
+
+ def test_validation_invalid_price_zero(self):
+ response = self.client.post(
+ reverse("products_form"),
+ data={
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": 0.0,
+ },
+ )
+
+ self.assertContains(response, "Por favor ingrese un precio mayor a cero")
+
+ def test_validation_invalid_negative_price(self):
+ response = self.client.post(
+ reverse("products_form"),
+ data={
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": -100.0,
+ },
+ )
+
+ self.assertContains(response, "Por favor ingrese un precio mayor a cero")
+
+ def test_validation_invalid_price_input(self):
+ response = self.client.post(
+ reverse("products_form"),
+ data={
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": "abcd",
+ },
+ )
+
+ self.assertContains(response, "Por favor ingrese un precio válido")
+
+
+ def test_edit_product_with_valid_data(self):
+ product = Product.objects.create(
+ name="Producto 1",
+ type= "Alimento",
+ price= 100,
+ )
+
+ response = self.client.post(
+ reverse("products_form"),
+ data={
+ "id": product.id,
+ "name": "Producto 2",
+ "type": product.type,
+ "price": product.price,
+ },
+ )
+
+ self.assertEqual(response.status_code, 302)
+
+ editedProduct = Product.objects.get(pk=product.id)
+ self.assertEqual(editedProduct.name, "Producto 2")
+ self.assertEqual(editedProduct.type, product.type)
+ self.assertEqual(editedProduct.price, product.price)
+
+
+
+class ProviderIntegrationTest(TestCase):
+ def test_can_create_provider(self):
+ response = self.client.post(
+ reverse("provider_form"),
+ data={
+ "name": "Proveedor Ejemplo",
+ "email": "proveedor@ejemplo.com",
+ "address": "Calle Falsa 123",
+ },
+ )
+ providers = Provider.objects.all()
+ self.assertEqual(len(providers), 1)
+
+ self.assertEqual(providers[0].name, "Proveedor Ejemplo")
+ self.assertEqual(providers[0].email, "proveedor@ejemplo.com")
+ self.assertEqual(providers[0].address, "Calle Falsa 123")
+
+ self.assertRedirects(response, reverse("provider_repo"))
+
+ def test_validation_errors_create_provider(self):
+ response = self.client.post(
+ reverse("provider_form"),
+ data={},
+ )
+
+ self.assertContains(response, "Por favor ingrese un nombre")
+ self.assertContains(response, "Por favor ingrese un email")
+ self.assertContains(response, "Por favor ingrese una dirección")
+
+ def test_should_response_with_404_status_if_provider_doesnt_exists(self):
+ response = self.client.get(reverse("provider_edit", kwargs={"id": 100}))
+ self.assertEqual(response.status_code, 404)
+
+ def test_edit_provider_with_valid_data(self):
+ provider = Provider.objects.create(
+ name="Proveedor Ejemplo",
+ email="proveedor@ejemplo.com",
+ address="Calle Falsa 123",
+ )
+
+ response = self.client.post(
+ reverse("provider_form"),
+ data={
+ "id": provider.id,
+ "name": "Nuevo Proveedor",
+ },
+ )
+
+ self.assertEqual(response.status_code, 302)
+
+ editedProvider = Provider.objects.get(pk=provider.id)
+ self.assertEqual(editedProvider.name, "Nuevo Proveedor")
+ self.assertEqual(editedProvider.email, provider.email)
+ self.assertEqual(editedProvider.address, provider.address)
\ No newline at end of file
diff --git a/app/tests_unit.py b/app/tests_unit.py
index ef385cfb..c5b9d0e5 100644
--- a/app/tests_unit.py
+++ b/app/tests_unit.py
@@ -1,5 +1,5 @@
from django.test import TestCase
-from app.models import Client
+from app.models import Product, Client, Vet, Provider, Medi
class ClientModelTest(TestCase):
@@ -10,7 +10,7 @@ def test_can_create_and_get_client(self):
"phone": "221555232",
"address": "13 y 44",
"email": "brujita75@hotmail.com",
- }
+ },
)
clients = Client.objects.all()
self.assertEqual(len(clients), 1)
@@ -27,7 +27,7 @@ def test_can_update_client(self):
"phone": "221555232",
"address": "13 y 44",
"email": "brujita75@hotmail.com",
- }
+ },
)
client = Client.objects.get(pk=1)
@@ -46,7 +46,7 @@ def test_update_client_with_error(self):
"phone": "221555232",
"address": "13 y 44",
"email": "brujita75@hotmail.com",
- }
+ },
)
client = Client.objects.get(pk=1)
@@ -57,3 +57,214 @@ def test_update_client_with_error(self):
client_updated = Client.objects.get(pk=1)
self.assertEqual(client_updated.phone, "221555232")
+
+class MedicineModelTest(TestCase):
+ #verifica si se puede crear un nuevo medicamento y si se guarda en la bd
+ def test_can_create_and_get_medicine(self):
+ Medi.save_medi(
+ {
+ "name": "Paracetamol",
+ "description": "Analgesico",
+ "dose": "5",
+ },
+ )
+ medicines = Medi.objects.all()
+ self.assertEqual(len(medicines), 1)
+
+ self.assertEqual(medicines[0].name, "Paracetamol")
+ self.assertEqual(medicines[0].description, "Analgesico")
+ self.assertEqual(medicines[0].dose, 5)
+
+ #Esta prueba comprueba si se puede actualizar la dosis de un medicamento
+ def test_can_update_medicine(self):
+ Medi.save_medi(
+ {
+ "name": "Paracetamol",
+ "description": "Analgesico",
+ "dose": "5",
+ },
+ )
+ medicine = Medi.objects.get(pk=1)
+
+ self.assertEqual(medicine.dose, 5)
+
+ medicine.update_medi({"dose": "9"}) # Nueva dosis
+
+ medicine_updated = Medi.objects.get(pk=1)
+
+ self.assertEqual(medicine_updated.dose, 9)
+
+ def test_update_medicine_with_error(self):
+ Medi.save_medi(
+ {
+ "name": "Paracetamol",
+ "description": "Analgesico",
+ "dose": "5",
+ },
+ )
+ medicine = Medi.objects.get(pk=1)
+
+ self.assertEqual(medicine.dose, 5)
+
+ # Intentamos actualizar la dosis con un valor inválido en este caso vacio
+ medicine.update_medi({"dose": ""})
+
+ # El valor de la dosis no debe haber cambiado
+ medicine_updated = Medi.objects.get(pk=1)
+ self.assertEqual(medicine_updated.dose, 5, "La dosis no debe cambiar si se proporciona un valor de dosis inválido")
+
+class VetModelTest(TestCase):
+
+ def test_can_create_and_get_vet(self):
+ Vet.save_vet(
+ {
+ "name": "Mariano Navone",
+ "phone": "2219870789",
+ "email": "lanavoneta@gmail.com",
+ "specialty": Vet.VetSpecialties.SIN_ESPECIALIDAD,
+ },
+ )
+ vets = Vet.objects.all()
+ self.assertEqual(len(vets), 1)
+
+ self.assertEqual(vets[0].name, "Mariano Navone")
+ self.assertEqual(vets[0].phone, "2219870789")
+ self.assertEqual(vets[0].email, "lanavoneta@gmail.com")
+ self.assertEqual(vets[0].specialty, Vet.VetSpecialties.SIN_ESPECIALIDAD)
+
+ def test_can_update_vet_specialty(self):
+ Vet.save_vet(
+ {
+ "name": "Mariano Navone",
+ "phone": "2219870789",
+ "email": "lanavoneta@gmail.com",
+ "specialty": Vet.VetSpecialties.SIN_ESPECIALIDAD,
+ },
+ )
+ vet = Vet.objects.get(pk=1)
+
+ self.assertEqual(vet.specialty, Vet.VetSpecialties.SIN_ESPECIALIDAD)
+
+ vet.update_vet({"specialty": Vet.VetSpecialties.CARDIOLOGIA})
+
+ vet_updated = Vet.objects.get(pk=1)
+
+ self.assertEqual(vet_updated.specialty, Vet.VetSpecialties.CARDIOLOGIA)
+
+ def test_specialty_choices(self):
+ expected_choices = [
+ ("Sin especialidad", "Sin especialidad"),
+ ("Cardiología", "Cardiología"),
+ ("Medicina interna de pequeños animales", "Medicina interna de pequeños animales"),
+ ("Medicina interna de grandes animales", "Medicina interna de grandes animales"),
+ ("Neurología", "Neurología"),
+ ("Oncología", "Oncología"),
+ ("Nutrición", "Nutrición"),
+ ]
+
+ self.assertEqual(Vet.VetSpecialties.choices, expected_choices)
+
+class ProductModelTest(TestCase):
+ def test_can_create_and_get_product(self):
+ Product.save_product(
+ {
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": 100.0,
+ },
+ )
+ products = Product.objects.all()
+ self.assertEqual(len(products), 1)
+
+ self.assertEqual(products[0].name, "Producto 1")
+ self.assertEqual(products[0].type, "Alimento")
+ self.assertEqual(products[0].price, 100.0)
+
+ def test_can_update_product(self):
+ Product.save_product(
+ {
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": 100.0,
+ },
+ )
+
+ product = Product.objects.get(pk=1)
+
+ self.assertEqual(product.price, 100.0)
+
+ product.update_product({"price": 200.0})
+
+ product_updated = Product.objects.get(pk=1)
+
+ self.assertEqual(product_updated.price, 200.0)
+
+ def test_update_product_with_empty_price(self):
+ Product.save_product(
+ {
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": 100.0,
+ },
+ )
+ product = Product.objects.get(pk=1)
+
+ self.assertEqual(product.price, 100.0)
+
+ product.update_product({"price": ""})
+
+ product_updated = Product.objects.get(pk=1)
+
+ self.assertEqual(product_updated.price, 100.0)
+
+ def test_update_product_with_negative_price(self):
+ Product.save_product(
+ {
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": 100.0,
+ },
+ )
+ product = Product.objects.get(pk=1)
+
+ self.assertEqual(product.price, 100.0)
+
+ product.update_product({"price": -100.0})
+
+ product_updated = Product.objects.get(pk=1)
+
+ self.assertEqual(product_updated.price, 100.0)
+
+ def test_update_product_with_price_zero(self):
+ Product.save_product(
+ {
+ "name": "Producto 1",
+ "type": "Alimento",
+ "price": 100.0,
+ },
+ )
+ product = Product.objects.get(pk=1)
+
+ self.assertEqual(product.price, 100.0)
+
+ product.update_product({"price": 0.0})
+
+ product_updated = Product.objects.get(pk=1)
+
+ self.assertEqual(product_updated.price, 100.0)
+
+
+
+class ProviderModelTest(TestCase):
+ def test_can_create_and_get_provider(self):
+ Provider.objects.create(
+ name="Proveedor Ejemplo",
+ email="proveedor@ejemplo.com",
+ address="13 y 32",
+ )
+ providers = Provider.objects.all()
+ self.assertEqual(len(providers), 1)
+
+ self.assertEqual(providers[0].name, "Proveedor Ejemplo")
+ self.assertEqual(providers[0].email, "proveedor@ejemplo.com")
+ self.assertEqual(providers[0].address, "13 y 32")
\ No newline at end of file
diff --git a/app/urls.py b/app/urls.py
index 8a1b1661..823c2fe4 100644
--- a/app/urls.py
+++ b/app/urls.py
@@ -3,8 +3,30 @@
urlpatterns = [
path("", view=views.home, name="home"),
+
path("clientes/", view=views.clients_repository, name="clients_repo"),
path("clientes/nuevo/", view=views.clients_form, name="clients_form"),
path("clientes/editar//", view=views.clients_form, name="clients_edit"),
path("clientes/eliminar/", view=views.clients_delete, name="clients_delete"),
+
+ path("veterinarios/", view=views.vets_repository, name="vets_repo"),
+ path("veterinarios/nuevo/", view=views.vets_form, name="vets_form"),
+ path("veterinarios/editar//", view=views.vets_form, name="vets_edit"),
+ path("veterinarios/eliminar/", view=views.vets_delete, name="vets_delete"),
+
+ path("medicina/", view=views.medis_repository, name="medi_repo"),
+ path("medicina/nuevo/", view=views.medis_form, name="medi_form"),
+ path("medicina/editar//", view=views.medis_form, name="medi_edit"),
+ path("medicina/eliminar/", view=views.medis_delete, name="medi_delete"),
+
+ path("productos/", view=views.products_repository, name="products_repo"),
+ path("productos/nuevo/", view=views.products_form, name="products_form"),
+ path("productos/editar//", view=views.products_form, name="products_edit"),
+ path("productos/eliminar/", view=views.products_delete, name="products_delete"),
+
+ path("proveedor/", view=views.provider_repository, name="provider_repo"),
+ path("proveedor/nuevo/", view=views.provider_form, name="provider_form"),
+ path("proveedor/editar//", view=views.provider_form, name="provider_edit"),
+ path("proveedor/eliminar/", view=views.provider_delete, name="provider_delete"),
+
]
diff --git a/app/views.py b/app/views.py
index bfc81151..26c2ef9f 100644
--- a/app/views.py
+++ b/app/views.py
@@ -1,5 +1,5 @@
from django.shortcuts import render, redirect, reverse, get_object_or_404
-from .models import Client
+from .models import Client, Vet, Provider,Product, Medi
def home(request):
@@ -27,7 +27,7 @@ def clients_form(request, id=None):
return redirect(reverse("clients_repo"))
return render(
- request, "clients/form.html", {"errors": errors, "client": request.POST}
+ request, "clients/form.html", {"errors": errors, "client": request.POST},
)
client = None
@@ -43,3 +43,152 @@ def clients_delete(request):
client.delete()
return redirect(reverse("clients_repo"))
+
+def products_repository(request):
+ products = Product.objects.all()
+ return render(request, "products/repository.html", {"products": products})
+
+def products_form(request, id=None):
+ if request.method == "POST":
+ product_id = request.POST.get("id", "")
+ errors = {}
+ saved = True
+
+ if product_id == "":
+ saved, errors = Product.save_product(request.POST)
+ else:
+ product = get_object_or_404(Product, pk=product_id)
+ saved, errors = product.update_product(request.POST)
+
+ if saved:
+ return redirect(reverse("products_repo"))
+
+ return render(
+ request, "products/form.html", {"errors": errors, "product": request.POST},
+ )
+
+ product = None
+ if id is not None:
+ product = get_object_or_404(Product, pk=id)
+
+ return render(request, "products/form.html", {"product": product})
+
+def products_delete(request):
+ product_id = request.POST.get("product_id")
+ product = get_object_or_404(Product, pk=int(product_id))
+ product.delete()
+
+ return redirect(reverse("products_repo"))
+
+def vets_repository(request):
+ vets = Vet.objects.all()
+ return render(request, "vets/repository.html", {"vets": vets})
+
+
+def vets_form(request, id=None):
+
+ specialties = Vet.VetSpecialties.choices
+ if request.method == "POST":
+ vet_id = request.POST.get("id", "")
+ errors = {}
+ saved = True
+
+ if vet_id == "":
+ saved, errors = Vet.save_vet(request.POST)
+ else:
+ vet = get_object_or_404(Vet, pk=vet_id)
+ vet.update_vet(request.POST)
+
+ if saved:
+ return redirect(reverse("vets_repo"))
+
+ return render(
+ request, "vets/form.html", {"errors": errors, "vet": request.POST, "specialties" : specialties},
+ )
+
+ vet = None
+ if id is not None:
+ vet = get_object_or_404(Vet, pk=id)
+
+ return render(request, "vets/form.html", {"vet": vet, "specialties" : specialties})
+
+def vets_delete(request):
+ vet_id = request.POST.get("vet_id")
+ vet = get_object_or_404(Vet, pk=int(vet_id))
+ vet.delete()
+
+ return redirect(reverse("vets_repo"))
+
+
+#medicine
+def medis_repository(request):
+ medis = Medi.objects.all()
+ return render(request, "medicine/repository.html", {"medis": medis})
+
+
+def medis_form(request, id=None):
+ if request.method == "POST":
+ medi_id = request.POST.get("id", "")
+ errors = {}
+ saved = True
+
+ if medi_id == "":
+ saved, errors = Medi.save_medi(request.POST)
+ else:
+ medi = get_object_or_404(Medi, pk=medi_id)
+ medi.update_medi(request.POST)
+
+ if saved:
+ return redirect(reverse("medi_repo"))
+
+ return render(
+ request, "medicine/form.html", {"errors": errors, "medi": request.POST},
+ )
+
+ medi = None
+ if id is not None:
+ medi = get_object_or_404(Medi, pk=id)
+
+ return render(request, "medicine/form.html", {"medi": medi})
+
+def medis_delete(request):
+ medi_id = request.POST.get("medi_id")
+ medi = get_object_or_404(Medi, pk=int(medi_id))
+ medi.delete()
+
+ return redirect(reverse("medi_repo"))
+def provider_repository(request):
+ provider = Provider.objects.all()
+ return render(request, "provider/repository.html", {"provider": provider})
+
+def provider_form(request, id=None):
+ if request.method == "POST":
+ provider_id = request.POST.get("id", "")
+ errors = {}
+ saved = True
+
+ if provider_id == "":
+ saved, errors = Provider.save_provider(request.POST)
+ else:
+ provider = get_object_or_404(Provider, pk=provider_id)
+ provider.update_provider(request.POST)
+
+ if saved:
+ return redirect(reverse("provider_repo"))
+
+ return render(
+ request, "provider/form.html", {"errors": errors, "provider": request.POST},
+ )
+
+ provider = None
+ if id is not None:
+ provider = get_object_or_404(Provider, pk=id)
+
+ return render(request, "provider/form.html", {"provider": provider})
+
+def provider_delete(request):
+ provider_id = request.POST.get("prov_id")
+ provider = get_object_or_404(Provider, pk=int(provider_id))
+ provider.delete()
+
+ return redirect(reverse("provider_repo"))
diff --git a/db.sqlite b/db.sqlite
new file mode 100644
index 00000000..c47f5f60
Binary files /dev/null and b/db.sqlite differ
diff --git a/entrypoint.sh b/entrypoint.sh
new file mode 100644
index 00000000..0c706ec2
--- /dev/null
+++ b/entrypoint.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Esperamos a que las migraciones se completen.
+until python manage.py migrate 2>&1; do
+ echo "La base de datos no está disponible todavía, esperando..."
+ sleep 2
+done
+
+# Iniciamos la aplicación
+exec python manage.py runserver 0.0.0.0:8000
\ No newline at end of file
diff --git a/env-example b/env-example
new file mode 100644
index 00000000..f5db7fa7
--- /dev/null
+++ b/env-example
@@ -0,0 +1,18 @@
+#Una clave secreta para una instalación particular de Django.
+#Esto se utiliza para proporcionar firmas criptográficas y debe configurarse con un valor único e impredecible.
+SECRET_KEY= ""
+
+#Un booleano que activa/desactiva el modo de depuración.
+DEBUG=False
+
+#Configuracion de la base de datos
+DBENGINE=django.db.backends.sqlite3
+
+#Nombre de la base de datos
+DBNAME=db.sqlite3
+
+#Variables necesarias para el deploy en un ambiente no local
+
+ALLOWED_HOSTS = '*'
+
+CSRF_TRUSTED_ORIGINS = 'https://*'
diff --git a/functional_tests/tests.py b/functional_tests/tests.py
index b0c473f3..20a4de1a 100644
--- a/functional_tests/tests.py
+++ b/functional_tests/tests.py
@@ -5,14 +5,14 @@
from django.urls import reverse
-from app.models import Client
+from app.models import Client, Product, Vet, Provider, Medi
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
playwright = sync_playwright().start()
headless = os.environ.get("HEADLESS", 1) == 1
+#headless = os.environ.get("HEADLESS", "0") == 1
slow_mo = os.environ.get("SLOW_MO", 0)
-
class PlaywrightTestCase(StaticLiveServerTestCase):
@classmethod
def setUpClass(cls):
@@ -60,6 +60,13 @@ def test_should_have_home_cards_with_links(self):
expect(home_clients_link).to_have_text("Clientes")
expect(home_clients_link).to_have_attribute("href", reverse("clients_repo"))
+ self.page.goto(self.live_server_url)
+
+ home_products_link = self.page.get_by_test_id("home-Productos")
+
+ expect(home_products_link).to_be_visible()
+ expect(home_products_link).to_have_text("Productos")
+ expect(home_products_link).to_have_attribute("href", reverse("products_repo"))
class ClientsRepoTestCase(PlaywrightTestCase):
def test_should_show_message_if_table_is_empty(self):
@@ -242,3 +249,605 @@ def test_should_be_able_to_edit_a_client(self):
expect(edit_action).to_have_attribute(
"href", reverse("clients_edit", kwargs={"id": client.id})
)
+
+
+class MedicineCreateEditTestCase(PlaywrightTestCase):
+ def test_should_be_able_to_create_a_new_medicine(self):
+ self.page.goto(f"{self.live_server_url}{reverse('medi_form')}")
+
+ expect(self.page.get_by_role("form")).to_be_visible()
+
+ self.page.get_by_label("Nombre").fill("ibuprofeno")
+ self.page.get_by_label("Descripcion").fill("para el dolor")
+ self.page.get_by_label("Dosis").fill("5")
+
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("ibuprofeno")).to_be_visible()
+ expect(self.page.get_by_text("para el dolor")).to_be_visible()
+ expect(self.page.get_by_text("5")).to_be_visible()
+
+
+ def test_should_view_errors_if_form_is_invalid(self):
+ self.page.goto(f"{self.live_server_url}{reverse('medi_form')}")
+
+ expect(self.page.get_by_role("form")).to_be_visible()
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Por favor ingrese un nombre")).to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese una descripcion")).to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese una dosis")).to_be_visible()
+
+ self.page.get_by_label("Nombre").fill("ibuprofeno")
+ self.page.get_by_label("Descripcion").fill("para el dolor")
+ self.page.get_by_label("Dosis").fill("0")
+
+
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Por favor ingrese un nombre")).not_to_be_visible()
+ expect(
+ self.page.get_by_text("Por favor ingrese una descripcion")
+ ).not_to_be_visible()
+
+ """ expect(
+ self.page.get_by_text("Por favor ingrese una dosis entre 1 y 10")
+ ).to_be_visible()
+ self.page.get_by_label("Dosis").fill("")
+ expect(
+ self.page.get_by_text("Por favor ingrese una dosis")
+ ).to_be_visible() """
+
+ def test_should_be_able_to_edit_a_medicine(self):
+ medi = Medi.objects.create(
+ name="ibuprofeno",
+ description="para el dolor",
+ dose="5",
+
+ )
+
+ path = reverse("medi_edit", kwargs={"id": medi.id})
+ self.page.goto(f"{self.live_server_url}{path}")
+
+ self.page.get_by_label("Nombre").fill("paracetamol")
+ self.page.get_by_label("Descripcion").fill("para la fiebre")
+ self.page.get_by_label("Dosis").fill("8")
+
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("ibuprofeno")).not_to_be_visible()
+ expect(self.page.get_by_text("para el dolor")).not_to_be_visible()
+ expect(self.page.get_by_text("5")).not_to_be_visible()
+
+
+ expect(self.page.get_by_text("paracetamol")).to_be_visible()
+ expect(self.page.get_by_text("para la fiebre")).to_be_visible()
+ expect(self.page.get_by_text("8")).to_be_visible()
+
+
+ edit_action = self.page.get_by_role("link", name="Editar")
+ expect(edit_action).to_have_attribute(
+ "href", reverse("medi_edit", kwargs={"id": medi.id})
+ )
+
+class ProductsRepoTestCase(PlaywrightTestCase):
+ def test_should_show_message_if_table_is_empty(self):
+ self.page.goto(f"{self.live_server_url}{reverse('products_repo')}")
+
+ expect(self.page.get_by_text("No existen productos")).to_be_visible()
+
+ def test_should_show_products_data(self):
+ Product.objects.create(
+ name="Producto A",
+ type="Tipo A",
+ price=100.0,
+ )
+
+ Product.objects.create(
+ name="Producto B",
+ type="Tipo B",
+ price=200.0,
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('products_repo')}")
+
+ expect(self.page.get_by_text("No existen productos")).not_to_be_visible()
+
+ expect(self.page.get_by_text("Producto A")).to_be_visible()
+ expect(self.page.get_by_text("Tipo A")).to_be_visible()
+ expect(self.page.get_by_text("100.0")).to_be_visible()
+
+ expect(self.page.get_by_text("Producto B")).to_be_visible()
+ expect(self.page.get_by_text("Tipo B")).to_be_visible()
+ expect(self.page.get_by_text("200.0")).to_be_visible()
+
+ def test_should_show_add_product_action(self):
+ self.page.goto(f"{self.live_server_url}{reverse('products_repo')}")
+
+ add_product_action = self.page.get_by_role(
+ "link", name="Nuevo producto", exact=False
+ )
+ expect(add_product_action).to_have_attribute("href", reverse("products_form"))
+
+ def test_should_show_product_edit_action(self):
+ product = Product.objects.create(
+ name="Producto A",
+ type="Tipo A",
+ price=100.0,
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('products_repo')}")
+
+ edit_action = self.page.get_by_role("link", name="Editar")
+ expect(edit_action).to_have_attribute(
+ "href", reverse("products_edit", kwargs={"id": product.id})
+ )
+
+ def test_should_show_product_delete_action(self):
+ product = Product.objects.create(
+ name="Producto A",
+ type="Tipo A",
+ price=100.0,
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('products_repo')}")
+
+ edit_form = self.page.get_by_role(
+ "form", name="Formulario de eliminación de producto"
+ )
+ product_id_input = edit_form.locator("input[name=product_id]")
+
+ expect(edit_form).to_be_visible()
+ expect(edit_form).to_have_attribute("action", reverse("products_delete"))
+ expect(product_id_input).not_to_be_visible()
+ expect(product_id_input).to_have_value(str(product.id))
+ expect(edit_form.get_by_role("button", name="Eliminar")).to_be_visible()
+
+ def test_should_can_be_able_to_delete_a_product(self):
+ Product.objects.create(
+ name="Producto A",
+ type="Tipo A",
+ price=100.0,
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('products_repo')}")
+
+ expect(self.page.get_by_text("Producto A")).to_be_visible()
+
+ def is_delete_response(response):
+ return response.url.find(reverse("products_delete"))
+
+ # verificamos que el envio del formulario fue exitoso
+ with self.page.expect_response(is_delete_response) as response_info:
+ self.page.get_by_role("button", name="Eliminar").click()
+
+ response = response_info.value
+ self.assertTrue(response.status < 400)
+
+ expect(self.page.get_by_text("Producto A")).not_to_be_visible()
+
+
+class ProductCreateEditTestCase(PlaywrightTestCase):
+ def test_should_be_able_to_create_a_new_product(self):
+ self.page.goto(f"{self.live_server_url}{reverse('products_form')}")
+
+ expect(self.page.get_by_role("form")).to_be_visible()
+
+ self.page.get_by_label("Nombre").fill("Producto A")
+ self.page.get_by_label("Tipo").fill("Tipo A")
+ self.page.get_by_label("Precio").fill("100.0")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Producto A")).to_be_visible()
+ expect(self.page.get_by_text("Tipo A")).to_be_visible()
+ expect(self.page.get_by_text("100.0")).to_be_visible()
+
+ def test_should_view_errors_if_form_is_invalid(self):
+ self.page.goto(f"{self.live_server_url}{reverse('products_form')}")
+
+ expect(self.page.get_by_role("form")).to_be_visible()
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Por favor ingrese un nombre")).to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un tipo")).to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un precio")).to_be_visible()
+
+ self.page.get_by_label("Nombre").fill("Producto A")
+ self.page.get_by_label("Tipo").fill("Tipo A")
+ self.page.get_by_label("Precio").fill("-100.0")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Por favor ingrese un nombre")).not_to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un tipo")).not_to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un precio mayor a cero")).to_be_visible()
+
+ self.page.get_by_label("Precio").fill("0.0")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Por favor ingrese un nombre")).not_to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un tipo")).not_to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un precio mayor a cero")).to_be_visible()
+
+ def test_should_be_able_to_edit_a_product(self):
+ product = Product.objects.create(
+ name="Producto A",
+ type="Tipo A",
+ price=100.0,
+ )
+
+ path = reverse("products_edit", kwargs={"id": product.id})
+ self.page.goto(f"{self.live_server_url}{path}")
+
+ self.page.get_by_label("Nombre").fill("Producto B")
+ self.page.get_by_label("Tipo").fill("Tipo B")
+ self.page.get_by_label("Precio").fill("200.0")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Producto A")).not_to_be_visible()
+ expect(self.page.get_by_text("Tipo A")).not_to_be_visible()
+ expect(self.page.get_by_text("100.0")).not_to_be_visible()
+
+ expect(self.page.get_by_text("Producto B")).to_be_visible()
+ expect(self.page.get_by_text("Tipo B")).to_be_visible()
+ expect(self.page.get_by_text("200.0")).to_be_visible()
+
+ edit_action = self.page.get_by_role("link", name="Editar")
+ expect(edit_action).to_have_attribute(
+ "href", reverse("products_edit", kwargs={"id": product.id})
+ )
+
+
+class VetRepoTestCase(PlaywrightTestCase):
+ def test_should_show_message_if_table_is_empty(self):
+ self.page.goto(f"{self.live_server_url}{reverse('vets_repo')}")
+
+ expect(self.page.get_by_text("No existen veterinarios")).to_be_visible()
+
+ def test_should_show_vets_data(self):
+ Vet.objects.create(
+ name = "Mariano Navone",
+ phone = "2219870789",
+ email = "lanavoneta@gmail.com",
+ specialty = Vet.VetSpecialties.SIN_ESPECIALIDAD
+ )
+
+ Vet.objects.create(
+ name="Tomás Martín Etcheverry",
+ phone="2217462854",
+ email="tetcheverry@gmail.com",
+ specialty = Vet.VetSpecialties.CARDIOLOGIA
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('vets_repo')}")
+
+ expect(self.page.get_by_text("No existen veterinarios")).not_to_be_visible()
+
+ expect(self.page.get_by_text("Mariano Navone")).to_be_visible()
+ expect(self.page.get_by_text("2219870789")).to_be_visible()
+ expect(self.page.get_by_text("lanavoneta@gmail.com")).to_be_visible()
+ expect(self.page.get_by_text(Vet.VetSpecialties.SIN_ESPECIALIDAD)).to_be_visible()
+
+ expect(self.page.get_by_text("Tomás Martín Etcheverry")).to_be_visible()
+ expect(self.page.get_by_text("2217462854")).to_be_visible()
+ expect(self.page.get_by_text("tetcheverry@gmail.com")).to_be_visible()
+ expect(self.page.get_by_text(Vet.VetSpecialties.CARDIOLOGIA)).to_be_visible()
+
+ def test_should_show_add_vet_action(self):
+ self.page.goto(f"{self.live_server_url}{reverse('vets_repo')}")
+
+ add_vet_action = self.page.get_by_role(
+ "link", name="Nuevo Veterinario", exact=False
+ )
+ expect(add_vet_action).to_have_attribute("href", reverse("vets_form"))
+
+ def test_should_show_vet_edit_action(self):
+ vet = Vet.objects.create(
+ name = "Mariano Navone",
+ phone = "2219870789",
+ email = "lanavoneta@gmail.com",
+ specialty = Vet.VetSpecialties.SIN_ESPECIALIDAD
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('vets_repo')}")
+
+ edit_action = self.page.get_by_role("link", name="Editar")
+ expect(edit_action).to_have_attribute(
+ "href", reverse("vets_edit", kwargs={"id": vet.id})
+ )
+
+ def test_should_show_vet_delete_action(self):
+ vet = Vet.objects.create(
+ name = "Mariano Navone",
+ phone = "2219870789",
+ email = "lanavoneta@gmail.com",
+ specialty = Vet.VetSpecialties.SIN_ESPECIALIDAD
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('vets_repo')}")
+
+ edit_form = self.page.get_by_role(
+ "form", name="Formulario de eliminación de veterinario"
+ )
+ vet_id_input = edit_form.locator("input[name=vet_id]")
+
+ expect(edit_form).to_be_visible()
+ expect(edit_form).to_have_attribute("action", reverse("vets_delete"))
+ expect(vet_id_input).not_to_be_visible()
+ expect(vet_id_input).to_have_value(str(vet.id))
+ expect(edit_form.get_by_role("button", name="Eliminar")).to_be_visible()
+
+ def test_should_can_be_able_to_delete_a_vet(self):
+ Vet.objects.create(
+ name = "Mariano Navone",
+ phone = "2219870789",
+ email = "lanavoneta@gmail.com",
+ specialty = Vet.VetSpecialties.SIN_ESPECIALIDAD
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('vets_repo')}")
+
+ expect(self.page.get_by_text("Mariano Navone")).to_be_visible()
+
+ def is_delete_response(response):
+ return response.url.find(reverse("vets_delete"))
+
+ # verificamos que el envio del formulario fue exitoso
+ with self.page.expect_response(is_delete_response) as response_info:
+ self.page.get_by_role("button", name="Eliminar").click()
+
+ response = response_info.value
+ self.assertTrue(response.status < 400)
+
+ expect(self.page.get_by_text("Mariano Navone")).not_to_be_visible()
+
+class VetCreateEditTestCase(PlaywrightTestCase):
+ def test_should_be_able_to_create_a_new_vet(self):
+ self.page.goto(f"{self.live_server_url}{reverse('vets_form')}")
+
+ expect(self.page.get_by_role("form")).to_be_visible()
+
+ self.page.get_by_label("Nombre").fill("Mariano Navone")
+ self.page.get_by_label("Teléfono").fill("2219870789")
+ self.page.get_by_label("Email").fill("lanavoneta@gmail.com")
+ self.page.get_by_label("Especialidad").select_option("Cardiología")
+
+
+
+
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Mariano Navone")).to_be_visible()
+ expect(self.page.get_by_text("2219870789")).to_be_visible()
+ expect(self.page.get_by_text("lanavoneta@gmail.com")).to_be_visible()
+ expect(self.page.get_by_text("Cardiología")).to_be_visible()
+
+ def test_should_view_errors_if_form_is_invalid(self):
+ self.page.goto(f"{self.live_server_url}{reverse('vets_form')}")
+
+ expect(self.page.get_by_role("form")).to_be_visible()
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Por favor ingrese un nombre")).to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un teléfono")).to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un email")).to_be_visible()
+
+ self.page.get_by_label("Nombre").fill("Mariano Navone")
+ self.page.get_by_label("Teléfono").fill("2219870789")
+ self.page.get_by_label("Email").fill("lanavonetagmail.com")
+ self.page.get_by_label("Especialidad").select_option("Cardiología")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(
+ self.page.get_by_text("Por favor ingrese un nombre")
+ ).not_to_be_visible()
+
+ expect(
+ self.page.get_by_text("Por favor ingrese un teléfono")
+ ).not_to_be_visible()
+
+ expect(
+ self.page.get_by_text("Por favor ingrese un email valido")
+ ).to_be_visible()
+
+ def test_should_be_able_to_edit_a_vet(self):
+ vet = Vet.objects.create(
+ name = "Mariano Navone",
+ phone = "2219870789",
+ email = "lanavoneta@gmail.com",
+ specialty = Vet.VetSpecialties.SIN_ESPECIALIDAD
+ )
+
+ path = reverse("vets_edit", kwargs={"id": vet.id})
+ self.page.goto(f"{self.live_server_url}{path}")
+
+
+ self.page.get_by_label("Nombre").fill("Tomás Martín Etcheverry")
+ self.page.get_by_label("Teléfono").fill("2217462854")
+ self.page.get_by_label("Email").fill("tetcheverry@gmail.com")
+ self.page.get_by_label("Especialidad").select_option("Cardiología")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Mariano Navone")).not_to_be_visible()
+ expect(self.page.get_by_text("2219870789")).not_to_be_visible()
+ expect(self.page.get_by_text("lanavoneta@gmail.com")).not_to_be_visible()
+ expect(self.page.get_by_text("Sin especialidad")).not_to_be_visible()
+
+ expect(self.page.get_by_text("Tomás Martín Etcheverry")).to_be_visible()
+ expect(self.page.get_by_text("2217462854")).to_be_visible()
+ expect(self.page.get_by_text("tetcheverry@gmail.com")).to_be_visible()
+ expect(self.page.get_by_text("Cardiología")).to_be_visible()
+
+ edit_action = self.page.get_by_role("link", name="Editar")
+ expect(edit_action).to_have_attribute(
+ "href", reverse("vets_edit", kwargs={"id": vet.id})
+ )
+
+ def test_can_select_every_specialty_on_create(self):
+
+ self.page.goto(f"{self.live_server_url}{reverse('vets_form')}")
+
+ for option in Vet.VetSpecialties:
+ self.page.get_by_label("Especialidad").select_option(option)
+ expect(self.page.get_by_label("Especialidad")).to_have_value(option)
+
+ def test_can_select_every_specialty_on_edit(self):
+
+ vet = Vet.objects.create(
+ name = "Mariano Navone",
+ phone = "2219870789",
+ email = "lanavoneta@gmail.com",
+ specialty = Vet.VetSpecialties.SIN_ESPECIALIDAD
+ )
+
+ path = reverse("vets_edit", kwargs={"id": vet.id})
+ self.page.goto(f"{self.live_server_url}{path}")
+
+ for option in Vet.VetSpecialties:
+ self.page.get_by_label("Especialidad").select_option(option)
+ expect(self.page.get_by_label("Especialidad")).to_have_value(option)
+
+class ProviderRepoTestCase(PlaywrightTestCase):
+ def test_should_show_message_if_table_is_empty(self):
+ self.page.goto(f"{self.live_server_url}{reverse('provider_repo')}")
+
+ expect(self.page.get_by_text("No existen proveedores")).to_be_visible()
+
+ def test_should_show_providers_data(self):
+ Provider.objects.create(
+ name="Proveedor Ejemplo",
+ email="proveedor@ejemplo.com",
+ address="13 y 32",
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('provider_repo')}")
+
+ expect(self.page.get_by_text("No existen proveedores")).not_to_be_visible()
+
+ expect(self.page.get_by_text("Proveedor Ejemplo")).to_be_visible()
+ expect(self.page.get_by_text("proveedor@ejemplo.com")).to_be_visible()
+ expect(self.page.get_by_text("13 y 32")).to_be_visible()
+
+ def test_should_show_add_provider_action(self):
+ self.page.goto(f"{self.live_server_url}{reverse('provider_repo')}")
+
+ add_provider_action = self.page.get_by_role(
+ "link", name="Nuevo proveedor", exact=False
+ )
+ expect(add_provider_action).to_have_attribute("href", reverse("provider_form"))
+
+ def test_should_show_provider_edit_action(self):
+ provider = Provider.objects.create(
+ name="Proveedor Ejemplo",
+ email="proveedor@ejemplo.com",
+ address="13 y 32",
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('provider_repo')}")
+
+ edit_action = self.page.get_by_role("link", name="Editar")
+ expect(edit_action).to_have_attribute(
+ "href", reverse("provider_edit", kwargs={"id": provider.id})
+ )
+
+ def test_should_can_be_able_to_delete_a_provider(self):
+ Provider.objects.create(
+ name="Proveedor Ejemplo",
+ email="proveedor@ejemplo.com",
+ address="13 y 32",
+ )
+
+ self.page.goto(f"{self.live_server_url}{reverse('provider_repo')}")
+
+ expect(self.page.get_by_text("Proveedor Ejemplo")).to_be_visible()
+
+ def is_delete_response(response):
+ return response.url.find(reverse("provider_delete"))
+
+ # verificamos que el envio del formulario fue exitoso
+ with self.page.expect_response(is_delete_response) as response_info:
+ self.page.get_by_role("button", name="Eliminar").click()
+
+ response = response_info.value
+ self.assertTrue(response.status < 400)
+
+ expect(self.page.get_by_text("Proveedor Ejemplo")).not_to_be_visible()
+
+
+class ProviderCreateEditTestCase(PlaywrightTestCase):
+ def test_should_be_able_to_create_a_new_provider(self):
+ self.page.goto(f"{self.live_server_url}{reverse('provider_form')}")
+
+ expect(self.page.get_by_role("form")).to_be_visible()
+
+ self.page.get_by_label("Nombre").fill("Proveedor Ejemplo")
+ self.page.get_by_label("Email").fill("proveedor@ejemplo.com")
+ self.page.get_by_label("Dirección").fill("13 y 32")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Proveedor Ejemplo")).to_be_visible()
+ expect(self.page.get_by_text("proveedor@ejemplo.com")).to_be_visible()
+ expect(self.page.get_by_text("13 y 32")).to_be_visible()
+
+ def test_should_view_errors_if_form_is_invalid(self):
+ self.page.goto(f"{self.live_server_url}{reverse('provider_form')}")
+
+ expect(self.page.get_by_role("form")).to_be_visible()
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Por favor ingrese un nombre")).to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un email")).to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese una dirección")).to_be_visible()
+
+ self.page.get_by_label("Nombre").fill("Proveedor Ejemplo")
+ self.page.get_by_label("Email").fill("proveedor@ejemplo.com")
+ self.page.get_by_label("Dirección").fill("13 y 32")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_text("Por favor ingrese un nombre")).not_to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese un email")).not_to_be_visible()
+ expect(self.page.get_by_text("Por favor ingrese una dirección")).not_to_be_visible()
+
+ def test_should_be_able_to_edit_a_provider(self):
+ provider = Provider.objects.create(
+ name="Proveedor Ejemplo",
+ email="proveedor@ejemplo.com",
+ address="13 y 32",
+ )
+
+ path = reverse("provider_edit", kwargs={"id": provider.id})
+ self.page.goto(f"{self.live_server_url}{path}")
+
+ self.page.get_by_label("Nombre").fill("Nuevo Proveedor")
+ self.page.get_by_label("Email").fill("nuevo@proveedor.com")
+ self.page.get_by_label("Dirección").fill("Nueva Calle 123")
+
+ self.page.get_by_role("button", name="Guardar").click()
+
+ expect(self.page.get_by_role("link", name="Nuevo Proveedor")).to_be_visible()
+ expect(self.page.get_by_text("nuevo@proveedor.com")).to_be_visible()
+ expect(self.page.get_by_text("Nueva Calle 123")).to_be_visible()
+
+ expect(self.page.get_by_text("Proveedor Ejemplo")).not_to_be_visible()
+ expect(self.page.get_by_text("proveedor@ejemplo.com")).not_to_be_visible()
+ expect(self.page.get_by_text("13 y 32")).not_to_be_visible()
+
+ edit_action = self.page.get_by_role("link", name="Editar")
+ expect(edit_action).to_have_attribute(
+ "href", reverse("provider_edit", kwargs={"id": provider.id})
+ )
diff --git a/manage.py b/manage.py
index ff2785ce..aec8a286 100644
--- a/manage.py
+++ b/manage.py
@@ -14,7 +14,7 @@ def main():
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
- "forget to activate a virtual environment?"
+ "forget to activate a virtual environment?",
) from exc
execute_from_command_line(sys.argv)
diff --git a/requirements.txt b/requirements.txt
index 7f823098..88fb72df 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,3 +6,5 @@ pyee==11.1.0
ruff==0.4.1
sqlparse==0.5.0
typing_extensions==4.11.0
+python-dotenv==1.0.1
+coverage==7.5.3
diff --git a/vetsoft/settings.py b/vetsoft/settings.py
index 973267a1..c9e8e9c6 100644
--- a/vetsoft/settings.py
+++ b/vetsoft/settings.py
@@ -10,8 +10,13 @@
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
+import os
from pathlib import Path
+from dotenv import load_dotenv
+
+load_dotenv()
+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -20,12 +25,14 @@
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = "django-insecure-p)^5i@33!)v)l7*c#q)%j(g5d+**-yo%)6l*vg!gs_w-e=^_ig"
+SECRET_KEY = os.getenv("SECRET_KEY")
# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
+DEBUG = os.getenv("DEBUG")
-ALLOWED_HOSTS = []
+ALLOWED_HOSTS = [os.getenv("ALLOWED_HOSTS")]
+
+CSRF_TRUSTED_ORIGINS = [os.getenv("CSRF_TRUSTED_ORIGINS")]
# Application definition
@@ -77,9 +84,9 @@
DATABASES = {
"default": {
- "ENGINE": "django.db.backends.sqlite3",
- "NAME": BASE_DIR / "db.sqlite3",
- }
+ "ENGINE": os.getenv("DBENGINE"),
+ "NAME": BASE_DIR / os.getenv("DBNAME"),
+ },
}