diff --git a/.gitignore b/.gitignore
index a97ed864..60d243f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
venv
__pycache__
-db.sqlite3
\ No newline at end of file
+db.sqlite3
+.vs/
\ No newline at end of file
diff --git a/.scannerwork/.sonar_lock b/.scannerwork/.sonar_lock
new file mode 100644
index 00000000..e69de29b
diff --git a/.scannerwork/report-task.txt b/.scannerwork/report-task.txt
new file mode 100644
index 00000000..b449a338
--- /dev/null
+++ b/.scannerwork/report-task.txt
@@ -0,0 +1,6 @@
+projectKey=django-notes
+serverUrl=http://localhost:9000
+serverVersion=10.1.0.73491
+dashboardUrl=http://localhost:9000/dashboard?id=django-notes
+ceTaskId=AYyAJQnZutHEp8dBXesN
+ceTaskUrl=http://localhost:9000/api/ce/task?id=AYyAJQnZutHEp8dBXesN
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..64ecd4ab
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "python.testing.pytestArgs": [
+ "djangocrud"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true
+}
\ No newline at end of file
diff --git a/JenkinsFile b/JenkinsFile
new file mode 100644
index 00000000..e6b347cb
--- /dev/null
+++ b/JenkinsFile
@@ -0,0 +1,28 @@
+pipeline {
+ agent any
+ stages {
+ stage('Construcción Automática') {
+ steps {
+ bat 'python -m venv venv'
+ bat 'venv\\Scripts\\activate'
+ bat 'pip install -r requirements.txt'
+ bat 'python manage.py makemigrations'
+ bat 'python manage.py migrate'
+
+ script {
+ // Ejecuta el servidor Django en segundo plano
+ bat 'start /B cmd /c "python manage.py runserver"'
+
+ // Espera un tiempo para asegurar que el servidor se haya iniciado completamente
+ sleep time: 15, unit: 'SECONDS'
+ bat 'python manage.py test'
+
+ // Realiza otras tareas mientras el servidor está en ejecución
+
+ // Cierra el servidor usando Ctrl+C
+ bat 'taskkill /F /IM "python.exe" /FI "WINDOWTITLE eq Django"'
+ }
+ }
+ }
+ }
+}
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..64f24878
--- /dev/null
+++ b/README.md
@@ -0,0 +1,602 @@
+# INTEGRANTES:
+- Diego Francisco Apaza Andaluz (Rama_Diego)
+- Christian Pardavé Espinoza (rama-christian)
+- Eduardo German Ruiz Mamani (rama_Eduardo_real)
+- Sergio Sebastian Santos Mena Quispe (rama-Sergio)
+- Saul Arturo Condori Machaca (rama-Saul)
+- Jesús Bruno Chancayauri Soncco (rama_Bruno)
+- Paolo Daniel Benavente Aguirre (Paolo Benavente_rama)
+# Gestor de Tareas
+
+Este proyecto es una aplicación web para la gestión de tareas personales. Permite a los usuarios crear, organizar y priorizar sus tareas diarias de manera efectiva.
+## Tecnologías Utilizadas
+- Django (Framework de Python para desarrollo web)
+- SQLite (Base de datos)
+- HTML/CSS (Frontend)
+- Bootstrap (Framework de CSS)
+
+# Trello:
+
+https://trello.com/b/fbrJG4IH/proyecto-final
+
+## Características
+
+- Registro e inicio de sesión de usuarios.
+- Creación y gestión de tareas personales.
+- Marcar tareas como importantes.
+- Visualizar y editar tareas pendientes.
+- Registrar tareas completadas con fecha de finalización.
+- Navegación intuitiva a través de un menú de usuario.
+
+## Instalación y Ejecución
+
+Primero tener descargado sqlite e iniciar el ejecutable de sqlite3.
+[Link de descarga del .zip](https://www.sqlite.org/2023/sqlite-tools-win-x64-3440200.zip)
+
+
+1. `pip install -r requirements.txt`
+2. `python manage.py makemigrations`
+3. `python manage.py migrate`
+4. `python manage.py createsuperuser`
+5. `python manage.py runserver`
+6. Ingresar a tu navegador en el puerto 8000 [http://127.0.0.1:8000/](http://127.0.0.1:8000/)
+
+<<<<<<< HEAD
+## Contribuciones Específicas
+
+### Adicion de una fecha limite
+
+Se agrego una opcion para agregar una fecha limite en caso de necesitarla.
+
+### Tareas Públicas (Public Tasks)
+
+Desarrollo de la funcionalidad "Tareas Públicas" (Public Tasks). Esta característica permite a los usuarios marcar sus tareas como públicas, haciéndolas accesibles a todos los usuarios de la plataforma. Fomenta la colaboración y transparencia, y añade una dimensión comunitaria a la gestión de tareas.
+
+
+### FEATURE PROGRESS BAR
+
+Se agrego el feature de una barra de progreso de tareas completadas y por completar.
+
+```python
+
+@login_required
+def tasks(request):
+ tasks = Task.objects.filter(user=request.user, datecompleted__isnull=True)
+ tasks_completed = Task.objects.filter(user=request.user, datecompleted__isnull=False)
+
+ count_completed = tasks_completed.count()
+ count_not_completed = tasks.count()
+
+ return render(request, 'tasks.html', {"tasks": tasks, "count_total": count_completed + count_not_completed, "count_completed": count_completed})
+
+
+@login_required
+def tasks_completed(request):
+ tasks = Task.objects.filter(user=request.user, datecompleted__isnull=False).order_by('-datecompleted')
+
+ tasks_not_completed = Task.objects.filter(user=request.user, datecompleted__isnull=True)
+ count_not_completed = tasks_not_completed.count()
+ count_completed = tasks.count()
+
+ return render(request, 'tasks.html', {"tasks": tasks, "count_total": count_completed + count_not_completed, "count_completed": count_completed})
+
+```
+
+- Test vista tasks.
+- Implementación de comentarios en tareas públicas.
+
+
+Para la adición de comentarios en las tareas se creó la aplicación _comments_ dentro de django, para separar sus metodos de la aplicación _tasks_. Para esta implementación se identificó los modulos del proyecto.
+
+
+
+## MÓDULO AUTH
+
+Se agrego el módulo de "auth", en el que se incluyeron funciones como el "sigin", "signout", y "signup".
+
+```python
+
+def signup(request):
+ if request.method == 'GET':
+ return render(request, 'signup.html', {"form": UserCreationForm})
+ else:
+
+ if request.POST["password1"] == request.POST["password2"]:
+ try:
+ user = User.objects.create_user(
+ request.POST["username"], password=request.POST["password1"])
+ user.save()
+ login(request, user) # Replace LoginFailure with login
+ return redirect('tasks')
+ except IntegrityError:
+ return render(request, 'signup.html', {"form": UserCreationForm, "error": "Username already exists."})
+
+ return render(request, 'signup.html', {"form": UserCreationForm, "error": "Passwords did not match."})
+
+
+def signin(request):
+ if request.method == 'GET':
+ return render(request, 'signin.html', {"form": AuthenticationForm})
+ else:
+ user = authenticate(
+ request, username=request.POST['username'], password=request.POST['password'])
+ if user is None:
+ return render(request, 'signin.html', {"form": AuthenticationForm, "error": "Username or password is incorrect."})
+
+ login(request, user)
+ return redirect('tasks')
+
+@login_required
+def signout(request):
+ logout(request)
+ return redirect('home')
+
+
+```
+# Frameworks Utilizados: pytest
+
+Las pruebas unitarias son ejecutadas de manera automatizada utilizando pytest, un framework de pruebas para Python. Esto asegura la correcta funcionalidad de cada componente del software.
+
+
+# Pruebas Unitarias
+# Creacion de pruebas unitarias para el uso de fechas en la creacion de una nueva tarea:
+
+
+
+
+
+
+
+
+
+
+Verifica que la fecha de creación de una tarea sea una fecha valida
+
+
+
+
+Además, se implemento varios casos de prueba para asegurar la funcionalidad y robustez de la aplicación. Estos tests incluyen:
+
+- Pruebas para la creación de tareas, tanto con datos válidos como inválidos.
+- Verificación del funcionamiento correcto del formulario de creación de tareas.
+- Tests para la lógica de visualización y manejo de tareas públicas.
+
+Estos casos de prueba fueron fundamentales para mantener la calidad y estabilidad del software durante el desarrollo.
+
+
+Se hizo una prueba de test para comprobar que la barra de progreso exista.
+=======
+
+# PROGRESO DE DISEÑO
+
+Se diseño una nueva intefas para el login.
+
+```python
+
+
+```
+
+
+
+
+
+
+
+### CASOS DE PRUEBA
+
+Se hizo una prueba de test para comprobar la existencia de usuarios y la de no usuarios.
+>>>>>>> rama-Paolo
+
+
+```python
+class TasksTestCase(LiveServerTestCase):
+ def setUp(self):
+ # Configuración del navegador Selenium (puedes ajustar según tus necesidades)
+ chrome_options = Options()
+ # chrome_options.add_argument("--headless") # Ejecución en modo sin cabeza para pruebas en segundo plano
+ self.selenium = webdriver.Chrome(options=chrome_options)
+ self.selenium.implicitly_wait(10) # Espera implícita de 10 segundos
+ super(TasksTestCase, self).setUp()
+
+ def tearDown(self):
+ self.selenium.quit()
+ super(TasksTestCase, self).tearDown()
+
+ def test_tasks_page(self):
+ # Crear un usuario y tareas para ese usuario
+ user = User.objects.create_user(username='testuser', password='testpass')
+ Task.objects.create(user=user, description='Task 1')
+
+<<<<<<< HEAD
+ # Acceder a la página de tareas con Selenium
+ self.selenium.get(f'{self.live_server_url}/signup/')
+ self.selenium.find_element(By.NAME, 'username').send_keys('mena3')
+ self.selenium.find_element(By.NAME, 'password1').send_keys('123qweop')
+ self.selenium.find_element(By.NAME, 'password2').send_keys('123qweop')
+ self.selenium.find_element(By.NAME, 'password2').send_keys(Keys.ENTER)
+
+ # Verificar que la página de tareas se carga correctamente
+ self.assertIn('tasks', self.selenium.find_element(By.CSS_SELECTOR, "h1").text.lower())
+
+=======
+ # Acceder a la página de login para verificar un usuario existente.
+ self.selenium.get(f'{self.live_server_url}/signin/')
+ self.selenium.find_element(By.NAME, 'username').send_keys('marisol')
+ self.selenium.find_element(By.NAME, 'password').send_keys('123456')
+ self.selenium.find_element(By.NAME, 'password').send_keys(Keys.ENTER)
+
+ # Verificar que la página de tareas se carga correctamente
+ self.assertIn('tasks', self.selenium.find_element(By.CSS_SELECTOR, "h1").text.lower())
+
+>>>>>>> rama-Paolo
+ WebDriverWait(self.selenium, 10).until(
+ EC.presence_of_element_located((By.TAG_NAME, 'progress'))
+ )
+
+<<<<<<< HEAD
+ # Verificar que la tarea se muestra en la página
+ progress_bar = self.selenium.find_element(By.TAG_NAME, 'progress')
+ self.assertIsNotNone(progress_bar, 'Progress bar not found')
+
+
+ WebDriverWait(self.selenium, 10).until(
+ EC.presence_of_element_located((By.ID, 'progress-message'))
+ )
+
+ progress_message = self.selenium.find_element(By.ID, 'progress-message')
+ self.assertIn('Tasks completed 0/0', progress_message.text)
+```
+
+
+
+
+
+## Lenguaje ubicuo:
+
+
+
+**Modelo (Task):** Se utiliza el término "Task" para referirse al modelo.el término "Task" representa la entidad que está siendo modelada en la base de datos.
+
+**ModelForm:** El nombre TaskForm comunica que este formulario está específicamente diseñado para el modelo Task. Utilizar "Form" al final del nombre es una convención común para los formularios basados en modelos en Django.
+
+**Meta:** En Django, la clase Meta se utiliza para proporcionar metainformación sobre el formulario, como el modelo al que está vinculado y los campos que deben incluirse.
+
+**fields = ['title', 'description', 'important', 'fecha_limite']:** Al especificar los campos que deben incluirse en el formulario, se sigue utilizando el lenguaje del modelo. En este caso, los campos son 'title', 'description', 'important', y 'fecha_limite', que son los mismos campos definidos en el modelo Task.
+
+
+
+
+**title:** Un campo de caracteres que almacena el título de la tarea con una longitud máxima de 200 caracteres.
+
+**description:** Un campo de texto más largo que almacena la descripción de la tarea con una longitud máxima de 1000 caracteres.
+
+**created:** Un campo de fecha y hora que se establece automáticamente en la fecha y hora actual cuando se crea la tarea.
+
+**datecompleted:** Un campo de fecha y hora que puede ser nulo y en blanco. Almacena la fecha y hora en que se completó la tarea.
+
+**important:** Un campo booleano que indica si la tarea es importante o no. El valor predeterminado es False.
+
+**user:** Una clave externa que se relaciona con el modelo de usuario (User). Utiliza on_delete=models.CASCADE, lo que significa que si un usuario se elimina, también se eliminarán todas sus tareas asociadas.
+
+**fecha_limite:** Un campo de fecha que puede ser nulo y en blanco. Almacena la fecha límite para la tarea.
+
+**def __str__(self)::** Un método que devuelve una representación de cadena del objeto. En este caso, devuelve una cadena que combina el título de la tarea y el nombre de usuario del propietario.
+
+
+
+
+
+
+
+# MODELO DE DOMINIO
+
+El modelo de dominio se centra en el dominio de "Task", del cual se extienden otros modelos como "TaskAdmin", "TaskForm", y "TaskConfig", y también otro modelo "User" que administras las tasks.
+
+
+
+
+=======
+```python
+ # Acceder a la página de login para verificar un usuario no existente.
+ self.selenium.get(f'{self.live_server_url}/signin/')
+ self.selenium.find_element(By.NAME, 'username').send_keys('marisol')
+ self.selenium.find_element(By.NAME, 'password').send_keys('123')
+ self.selenium.find_element(By.NAME, 'password').send_keys(Keys.ENTER)
+
+ # Verificar que la página de tareas se carga correctamente
+ self.assertIn('tasks', self.selenium.find_element(By.CSS_SELECTOR, "h1").text.lower())
+
+ WebDriverWait(self.selenium, 10).until(
+ EC.presence_of_element_located((By.TAG_NAME, 'progress'))
+ )
+
+ progress_message = self.selenium.find_element(By.ID, 'progress-message')
+ self.assertIn('Completed 0/0', progress_message.text)
+```
+### MODELO DE DOMINIO
+
+Modelo de Dominio:
+*Entidades:
+Usuario
+Tarea
+Progreso
+
+*Atributos:
+Usuario: ID, Nombre, Correo Electrónico, Contraseña
+Tarea: ID, Descripción, Estado (pendiente, completada, en progreso), Fecha de Creación, Fecha de Vencimiento
+Progreso: ID, ID del Usuario, Tareas Completadas
+
+*Relaciones:
+Un Usuario puede tener varias Tareas.
+Un Usuario puede tener un Progreso asociado.
+
+
+
+
+
+### MICROSERVICIOS IDENTIFICADOS
+
+1. Microservicio de Autenticación y Registro:
+
+ Funcionalidades:
+ - Iniciar sesión con correo electrónico y contraseña.
+ - Registrarse como nuevo usuario.
+
+ Contexto delimitado: Autenticación
+
+ Responsabilidades:
+ - Validar credenciales de usuario.
+ - Generar y gestionar tokens de sesión.
+ - Registro y gestión de usuarios.
+
+2. Microservicio de Gestión de Tareas:
+
+ Funcionalidades:
+ - Crear nuevas tareas con descripción, fecha de creación y fecha de vencimiento.
+ - Marcar tareas como pendientes, en progreso o completadas.
+ - Visualizar la lista de tareas propias.
+ - Visualizar las tareas de otros usuarios.
+
+ Contexto delimitado: Gestión de Tareas
+
+ Responsabilidades:
+ - Crear y gestionar tareas.
+ - Asociar tareas a usuarios.
+ - Cambiar estados de tareas.
+
+3. Microservicio de Progreso del Usuario:
+
+ Funcionalidades:
+ - Seguimiento del progreso general del usuario.
+ - Visualización de la cantidad de tareas completadas.
+
+ Contexto delimitado: Progreso del Usuario
+
+ Responsabilidades:
+ - Seguimiento del progreso general del usuario.
+ - Registrar y actualizar tareas completadas.
+
+4. Microservicio de Gestión de Usuarios:
+
+ Funcionalidades:
+ - Actualizar información del perfil.
+ - Cambiar la contraseña.
+
+ Contexto delimitado: Perfil del Usuario
+
+ Responsabilidades:
+ - Actualizar información del perfil del usuario.
+ - Cambiar la contraseña del usuario.
+
+5. Microservicio de Sesión de Usuario:
+
+ Funcionalidades:
+ - Cerrar sesión.
+
+ Contexto delimitado: Sesión de Usuario
+
+ Responsabilidades:
+ - Iniciar y cerrar sesiones de usuario.
+ - Gestionar tokens de sesión.
+
+
+
+
+# Frameworks Utilizados: Selenium y pytest
+
+```python
+pipeline {
+ agent any
+ stages {
+ stage('Construcción Automática') {
+ steps {
+ bat 'python -m venv venv'
+ bat 'venv\\Scripts\\activate'
+ bat 'pip install -r requirements.txt'
+ bat 'python manage.py makemigrations'
+ bat 'python manage.py migrate'
+
+ script {
+ // Ejecuta el servidor Django en segundo plano
+ bat 'start /B cmd /c "python manage.py runserver"'
+
+ // Espera un tiempo para asegurar que el servidor se haya iniciado completamente
+ sleep time: 15, unit: 'SECONDS'
+ bat 'python manage.py test'
+
+ // Realiza otras tareas mientras el servidor está en ejecución
+
+ // Cierra el servidor usando Ctrl+C
+ bat 'taskkill /F /IM "python.exe" /FI "WINDOWTITLE eq Django"'
+ }
+ }
+ }
+ }
+}
+```
+
+Las pruebas funcionales automatizadas son realizadas con Selenium, un potente framework para pruebas web
+
+
+### SonarQube
+
+Análisis del proyecto después de agregar las nuevas funcionalidades:
+
+
+
+
+
+
+# OWASP ZAP
+
+En el marco de nuestro compromiso con la seguridad del Proyecto de Gestor de Tareas, hemos implementado OWASP Zed Attack Proxy (ZAP) como una herramienta esencial para evaluar y mitigar posibles vulnerabilidades de seguridad. OWASP ZAP es una herramienta de prueba de seguridad de código abierto que nos permite identificar y abordar posibles riesgos de seguridad en nuestra aplicación.
+
+
+
+
+
+# JMETER
+
+Como parte integral de nuestro proceso de aseguramiento de la calidad, hemos incorporado Apache JMeter para realizar pruebas de rendimiento en el Proyecto de Gestor de Tareas. Apache JMeter es una herramienta potente y versátil que nos permite evaluar el rendimiento, la estabilidad y la escalabilidad de nuestra aplicación, especialmente en situaciones de carga simulada.
+
+
+
+
+
+
diff --git a/auth/views.py b/auth/views.py
new file mode 100644
index 00000000..d2fbbd7a
--- /dev/null
+++ b/auth/views.py
@@ -0,0 +1,42 @@
+from django.shortcuts import render, redirect, get_object_or_404
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
+from django.contrib.auth import login, logout, authenticate
+from django.contrib.auth.models import User
+from django.db import IntegrityError
+from django.contrib.auth.decorators import login_required
+
+
+def signup(request):
+ if request.method == 'GET':
+ return render(request, 'signup.html', {"form": UserCreationForm})
+ else:
+
+ if request.POST["password1"] == request.POST["password2"]:
+ try:
+ user = User.objects.create_user(
+ request.POST["username"], password=request.POST["password1"])
+ user.save()
+ login(request, user) # Replace LoginFailure with login
+ return redirect('tasks')
+ except IntegrityError:
+ return render(request, 'signup.html', {"form": UserCreationForm, "error": "Username already exists."})
+
+ return render(request, 'signup.html', {"form": UserCreationForm, "error": "Passwords did not match."})
+
+
+def signin(request):
+ if request.method == 'GET':
+ return render(request, 'signin.html', {"form": AuthenticationForm})
+ else:
+ user = authenticate(
+ request, username=request.POST['username'], password=request.POST['password'])
+ if user is None:
+ return render(request, 'signin.html', {"form": AuthenticationForm, "error": "Username or password is incorrect."})
+
+ login(request, user)
+ return redirect('tasks')
+
+@login_required
+def signout(request):
+ logout(request)
+ return redirect('home')
diff --git a/comments/__init__.py b/comments/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/comments/admin.py b/comments/admin.py
new file mode 100644
index 00000000..89b8f345
--- /dev/null
+++ b/comments/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from .models import Comment
+
+admin.site.register(Comment)
diff --git a/comments/apps.py b/comments/apps.py
new file mode 100644
index 00000000..a90cc976
--- /dev/null
+++ b/comments/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class CommentsConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'comments'
diff --git a/comments/forms.py b/comments/forms.py
new file mode 100644
index 00000000..11703401
--- /dev/null
+++ b/comments/forms.py
@@ -0,0 +1,8 @@
+from django.forms import ModelForm
+from .models import Comment
+
+
+class CommentForm(ModelForm):
+ class Meta:
+ model = Comment
+ fields = ['text']
diff --git a/comments/migrations/0001_initial.py b/comments/migrations/0001_initial.py
new file mode 100644
index 00000000..5ddc5868
--- /dev/null
+++ b/comments/migrations/0001_initial.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.7 on 2023-12-26 03:28
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('tasks', '0006_delete_comment'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Comment',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('text', models.TextField(max_length=500)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='tasks.task')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/comments/migrations/__init__.py b/comments/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/comments/models.py b/comments/models.py
new file mode 100644
index 00000000..93f25d44
--- /dev/null
+++ b/comments/models.py
@@ -0,0 +1,16 @@
+from django.db import models
+from django.contrib.auth.models import User
+from tasks.models import Task
+
+# Creacion del modelo para los comentarios
+
+
+class Comment(models.Model):
+ task = models.ForeignKey(
+ Task, on_delete=models.CASCADE, related_name='comments')
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ text = models.TextField(max_length=500)
+ created = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return f"{self.user.username} - {self.text}"
diff --git a/comments/tests.py b/comments/tests.py
new file mode 100644
index 00000000..b681c81e
--- /dev/null
+++ b/comments/tests.py
@@ -0,0 +1,48 @@
+# tests.py
+from django.test import TestCase
+from django.contrib.auth.models import User
+from .models import Task
+from tasks.forms import TaskForm
+
+class TaskFormTest(TestCase):
+ def setUp(self):
+ self.user = User.objects.create_user(username='testuser', password='testpassword')
+
+ def test_valid_form(self):
+ data = {
+ 'title': 'Test Task',
+ 'description': 'This is a test task',
+ 'fecha_limite': '2023-12-31',
+ 'important': True,
+ }
+
+ form = TaskForm(data=data)
+ self.assertTrue(form.is_valid())
+
+ def test_invalid_form(self):
+
+ data = {
+ 'title': 'Test Task',
+ 'description': 'This is a test task',
+ 'fecha_limite': '54164651',
+ 'important': True,
+ }
+
+ form = TaskForm(data=data)
+ self.assertFalse(form.is_valid())
+ self.assertIn('fecha_limite', form.errors) # Verifica que haya errores en el campo de fecha
+
+ def test_blank_fields(self):
+ # Prueba que el formulario sea inv�lido si algunos campos requeridos est�n en blanco
+ data = {
+ 'title': '',
+ 'description': '',
+ 'fecha_limite': '',
+ 'important': False,
+ }
+
+ form = TaskForm(data=data)
+ self.assertFalse(form.is_valid())
+ self.assertIn('title', form.errors)
+ self.assertIn('description', form.errors)
+
diff --git a/comments/views.py b/comments/views.py
new file mode 100644
index 00000000..91ea44a2
--- /dev/null
+++ b/comments/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/djangocrud/settings.py b/djangocrud/settings.py
index 3bffbba4..6c16d6ef 100644
--- a/djangocrud/settings.py
+++ b/djangocrud/settings.py
@@ -37,7 +37,8 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
- 'tasks'
+ 'tasks',
+ 'comments'
]
MIDDLEWARE = [
@@ -116,7 +117,7 @@
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
-STATIC_URL = 'static/'
+STATIC_URL = '/static/'
LOGIN_URL = '/signin'
diff --git a/djangocrud/urls.py b/djangocrud/urls.py
index fe947ad6..fde225dd 100644
--- a/djangocrud/urls.py
+++ b/djangocrud/urls.py
@@ -23,10 +23,14 @@
path('signup/', views.signup, name='signup'),
path('tasks/', views.tasks, name='tasks'),
path('tasks_completed/', views.tasks_completed, name='tasks_completed'),
+ path('shared_tasks/', views.shared_tasks, name='shared_tasks'),
path('logout/', views.signout, name='logout'),
path('signin/', views.signin, name='signin'),
path('create_task/', views.create_task, name='create_task'),
path('tasks/', views.task_detail, name='task_detail'),
- path('taks//complete', views.complete_task, name='complete_task'),
+ path('tasks//complete', views.complete_task, name='complete_task'),
path('tasks//delete', views.delete_task, name='delete_task'),
+ path('task_public/', views.task_public, name='task_public'),
+ path('tasks//comment',
+ views.add_comment, name='add_comment'),
]
diff --git a/image.png b/image.png
new file mode 100644
index 00000000..bcc751d7
Binary files /dev/null and b/image.png differ
diff --git a/images/Captura de pantalla 2023-12-25 225503.png b/images/Captura de pantalla 2023-12-25 225503.png
new file mode 100644
index 00000000..546843dd
Binary files /dev/null and b/images/Captura de pantalla 2023-12-25 225503.png differ
diff --git a/images/Imagen de WhatsApp 2023-12-26 a las 22.47.02_cad53d01.jpg b/images/Imagen de WhatsApp 2023-12-26 a las 22.47.02_cad53d01.jpg
new file mode 100644
index 00000000..7a954d3a
Binary files /dev/null and b/images/Imagen de WhatsApp 2023-12-26 a las 22.47.02_cad53d01.jpg differ
diff --git a/images/info.txt b/images/info.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/images/info.txt
@@ -0,0 +1 @@
+
diff --git a/tasks/admin.py b/tasks/admin.py
index 7db052b7..ba56ee97 100644
--- a/tasks/admin.py
+++ b/tasks/admin.py
@@ -2,7 +2,10 @@
from .models import Task
# Register your models here.
+
+
class TaskAdmin(admin.ModelAdmin):
- readonly_fields = ('created', )
+ readonly_fields = ('created', )
+
-admin.site.register(Task, TaskAdmin)
\ No newline at end of file
+admin.site.register(Task, TaskAdmin)
diff --git a/tasks/forms.py b/tasks/forms.py
index 4f39411b..c057038a 100644
--- a/tasks/forms.py
+++ b/tasks/forms.py
@@ -1,7 +1,14 @@
-from django.forms import ModelForm
+from django import forms
+from django.contrib.auth.models import User
from .models import Task
-class TaskForm(ModelForm):
+class TaskForm(forms.ModelForm):
+ shared_with = forms.ModelMultipleChoiceField(
+ queryset=User.objects.all(),
+ widget=forms.CheckboxSelectMultiple,
+ required=False,
+ )
+
class Meta:
model = Task
- fields = ['title', 'description', 'important']
\ No newline at end of file
+ fields = ['title', 'description', 'important', 'fecha_limite', 'public', 'shared' ]
diff --git a/tasks/migrations/0003_task_due_date.py b/tasks/migrations/0003_task_due_date.py
new file mode 100644
index 00000000..08a31c29
--- /dev/null
+++ b/tasks/migrations/0003_task_due_date.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1 on 2023-12-18 14:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0002_rename_datedcompleted_task_datecompleted'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='task',
+ name='due_date',
+ field=models.DateField(blank=True, null=True),
+ ),
+ ]
diff --git a/tasks/migrations/0003_task_public.py b/tasks/migrations/0003_task_public.py
new file mode 100644
index 00000000..f44956f4
--- /dev/null
+++ b/tasks/migrations/0003_task_public.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.7 on 2023-12-17 02:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0002_rename_datedcompleted_task_datecompleted'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='task',
+ name='public',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/tasks/migrations/0003_task_shared_task_shared_with.py b/tasks/migrations/0003_task_shared_task_shared_with.py
new file mode 100644
index 00000000..fa14001c
--- /dev/null
+++ b/tasks/migrations/0003_task_shared_task_shared_with.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.2.7 on 2023-12-27 00:59
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('tasks', '0002_rename_datedcompleted_task_datecompleted'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='task',
+ name='shared',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='shared_with',
+ field=models.ManyToManyField(blank=True, related_name='shared_tasks', to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/tasks/migrations/0004_comment.py b/tasks/migrations/0004_comment.py
new file mode 100644
index 00000000..91db6682
--- /dev/null
+++ b/tasks/migrations/0004_comment.py
@@ -0,0 +1,26 @@
+# Generated by Django 4.2.7 on 2023-12-17 15:47
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('tasks', '0003_task_public'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Comment',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('text', models.TextField(max_length=500)),
+ ('created', models.DateTimeField(auto_now_add=True)),
+ ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.task')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/tasks/migrations/0004_rename_due_date_task_fecha_limite.py b/tasks/migrations/0004_rename_due_date_task_fecha_limite.py
new file mode 100644
index 00000000..1d5448e3
--- /dev/null
+++ b/tasks/migrations/0004_rename_due_date_task_fecha_limite.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1 on 2023-12-18 14:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0003_task_due_date'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='task',
+ old_name='due_date',
+ new_name='fecha_limite',
+ ),
+ ]
diff --git a/tasks/migrations/0005_alter_comment_task.py b/tasks/migrations/0005_alter_comment_task.py
new file mode 100644
index 00000000..46be96f6
--- /dev/null
+++ b/tasks/migrations/0005_alter_comment_task.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.2.7 on 2023-12-19 03:15
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0004_comment'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='comment',
+ name='task',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='tasks.task'),
+ ),
+ ]
diff --git a/tasks/migrations/0006_delete_comment.py b/tasks/migrations/0006_delete_comment.py
new file mode 100644
index 00000000..9bcf2ec1
--- /dev/null
+++ b/tasks/migrations/0006_delete_comment.py
@@ -0,0 +1,16 @@
+# Generated by Django 4.2.7 on 2023-12-26 03:28
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0005_alter_comment_task'),
+ ]
+
+ operations = [
+ migrations.DeleteModel(
+ name='Comment',
+ ),
+ ]
diff --git a/tasks/migrations/0007_merge_20231226_1934.py b/tasks/migrations/0007_merge_20231226_1934.py
new file mode 100644
index 00000000..471e9b05
--- /dev/null
+++ b/tasks/migrations/0007_merge_20231226_1934.py
@@ -0,0 +1,14 @@
+# Generated by Django 4.2.7 on 2023-12-27 00:34
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0004_rename_due_date_task_fecha_limite'),
+ ('tasks', '0006_delete_comment'),
+ ]
+
+ operations = [
+ ]
diff --git a/tasks/migrations/0008_merge_20231226_2012.py b/tasks/migrations/0008_merge_20231226_2012.py
new file mode 100644
index 00000000..0f58e5cb
--- /dev/null
+++ b/tasks/migrations/0008_merge_20231226_2012.py
@@ -0,0 +1,14 @@
+# Generated by Django 4.2.7 on 2023-12-27 01:12
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0003_task_shared_task_shared_with'),
+ ('tasks', '0007_merge_20231226_1934'),
+ ]
+
+ operations = [
+ ]
diff --git a/tasks/migrations/0009_remove_task_shared_remove_task_shared_with.py b/tasks/migrations/0009_remove_task_shared_remove_task_shared_with.py
new file mode 100644
index 00000000..2823542d
--- /dev/null
+++ b/tasks/migrations/0009_remove_task_shared_remove_task_shared_with.py
@@ -0,0 +1,21 @@
+# Generated by Django 4.2.7 on 2023-12-27 01:30
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tasks', '0008_merge_20231226_2012'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='task',
+ name='shared',
+ ),
+ migrations.RemoveField(
+ model_name='task',
+ name='shared_with',
+ ),
+ ]
diff --git a/tasks/migrations/0010_task_shared_task_shared_with.py b/tasks/migrations/0010_task_shared_task_shared_with.py
new file mode 100644
index 00000000..31875429
--- /dev/null
+++ b/tasks/migrations/0010_task_shared_task_shared_with.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.2.7 on 2023-12-27 01:36
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('tasks', '0009_remove_task_shared_remove_task_shared_with'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='task',
+ name='shared',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='task',
+ name='shared_with',
+ field=models.ManyToManyField(blank=True, related_name='shared_tasks', to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/tasks/models.py b/tasks/models.py
index ea0cbfe5..002235e5 100644
--- a/tasks/models.py
+++ b/tasks/models.py
@@ -1,15 +1,18 @@
from django.db import models
from django.contrib.auth.models import User
-# Create your models here.
-
class Task(models.Model):
- title = models.CharField(max_length=200)
- description = models.TextField(max_length=1000)
- created = models.DateTimeField(auto_now_add=True)
- datecompleted = models.DateTimeField(null=True, blank=True)
- important = models.BooleanField(default=False)
- user = models.ForeignKey(User, on_delete=models.CASCADE)
+ title = models.CharField(max_length=200)
+ description = models.TextField(max_length=1000)
+ created = models.DateTimeField(auto_now_add=True)
+ datecompleted = models.DateTimeField(null=True, blank=True)
+ important = models.BooleanField(default=False)
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ fecha_limite = models.DateField(null=True, blank=True)
+ public = models.BooleanField(default=False)
+ shared = models.BooleanField(default=False)
+ shared_with = models.ManyToManyField(User, related_name='shared_tasks', blank=True)
- def __str__(self):
- return self.title + ' - ' + self.user.username
+ def _str_(self):
+ return self.title + ' - ' + self.user.username
+# Create your models here.
diff --git a/tasks/templates/_navbar.html b/tasks/templates/_navbar.html
index db0e7f67..12c66d02 100644
--- a/tasks/templates/_navbar.html
+++ b/tasks/templates/_navbar.html
@@ -26,6 +26,9 @@