From f4681d97e52bd61f5fb0b14d19861cb550b96b71 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 13 Nov 2023 21:53:51 -0300 Subject: [PATCH 01/18] db(makemigrations): install unaccent extension --- api/api/migrations/0002_auto_20231113_2029.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 api/api/migrations/0002_auto_20231113_2029.py diff --git a/api/api/migrations/0002_auto_20231113_2029.py b/api/api/migrations/0002_auto_20231113_2029.py new file mode 100644 index 00000000..ae26c263 --- /dev/null +++ b/api/api/migrations/0002_auto_20231113_2029.py @@ -0,0 +1,15 @@ +# Generated by Django 4.2.5 on 2023-11-13 23:29 + +from django.db import migrations +from django.contrib.postgres.operations import UnaccentExtension + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0001_initial'), + ] + + operations = [ + UnaccentExtension() + ] From edbedb9bf29af321fd74827f3fb12f9d37d9753a Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 13 Nov 2023 21:54:52 -0300 Subject: [PATCH 02/18] api(serializers): add serializers for models --- api/api/serializers.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 api/api/serializers.py diff --git a/api/api/serializers.py b/api/api/serializers.py new file mode 100644 index 00000000..3fb60be8 --- /dev/null +++ b/api/api/serializers.py @@ -0,0 +1,20 @@ +from rest_framework.serializers import ModelSerializer +from api.models import Department, Discipline, Class + +class DepartmentSerializer(ModelSerializer): + class Meta: + model = Department + fields = ('code', 'year', 'period') + +class ClassSerializer(ModelSerializer): + class Meta: + model = Class + fields = ('workload', 'teachers', 'classroom', 'schedule', 'days', '_class') + +class DisciplineSerializer(ModelSerializer): + department = DepartmentSerializer() + classes = ClassSerializer(many=True) + + class Meta: + model = Discipline + fields = ('name', 'code', 'department', 'classes') \ No newline at end of file From c1f9d9a6a63f4903c89cf3b05ec3053373d38d95 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 13 Nov 2023 21:55:19 -0300 Subject: [PATCH 03/18] api(urls): add api urls and search api endpoint --- api/api/urls.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 api/api/urls.py diff --git a/api/api/urls.py b/api/api/urls.py new file mode 100644 index 00000000..b613f9ac --- /dev/null +++ b/api/api/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from api import views + +app_name = 'api' + +urlpatterns = [ + path('', views.Search.as_view(), name="search") +] \ No newline at end of file From 5df50810e877e73852d30ef40adc8d417f3a9553 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 13 Nov 2023 21:55:57 -0300 Subject: [PATCH 04/18] api(views): create search api endpoint --- api/api/views.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/api/api/views.py b/api/api/views.py index 91ea44a2..e1808e33 100644 --- a/api/api/views.py +++ b/api/api/views.py @@ -1,3 +1,27 @@ -from django.shortcuts import render +from utils.db_handler import filter_disciplines_by_name, filter_disciplines_by_code, filter_disciplines_by_year_and_period +from rest_framework.decorators import APIView +from .serializers import DisciplineSerializer +from rest_framework.response import Response +from rest_framework.request import Request +from rest_framework import status -# Create your views here. +MAXIMUM_RETURNED_DISCIPLINES = 5 + +class Search(APIView): + def get(self, request: Request, *args, **kwargs) -> Response: + name = request.GET.get('search', None) + year = request.GET.get('year', None) + period = request.GET.get('period', None) + + if name is None or year is None or period is None: + return Response( + { + "errors": "no valid argument found for 'search', 'year' or 'period'" + }, status.HTTP_400_BAD_REQUEST) + + disciplines = filter_disciplines_by_name(name=name) | filter_disciplines_by_code(code=name) + filtered_disciplines = filter_disciplines_by_year_and_period(year=year, period=period, disciplines=disciplines) + data = DisciplineSerializer(filtered_disciplines, many=True).data + + return Response(data[:MAXIMUM_RETURNED_DISCIPLINES], status.HTTP_200_OK) + \ No newline at end of file From 0580842d2f4d1b6a0fbb4c9c92ef9256e0a0bae1 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 13 Nov 2023 21:56:25 -0300 Subject: [PATCH 05/18] api(urls): add api.urls to project urls core --- api/core/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/core/urls.py b/api/core/urls.py index 671b882b..4a3bed89 100644 --- a/api/core/urls.py +++ b/api/core/urls.py @@ -21,6 +21,7 @@ urlpatterns = [ path('admin/', admin.site.urls), path('users/', include('users.urls')), + path('courses/', include('api.urls')) ] if 'test' in sys.argv: From 7cb43f156d428029cafb20e5f425154bbde29ad9 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 13 Nov 2023 21:59:08 -0300 Subject: [PATCH 06/18] django(base): install app django.contrib.postgres --- api/core/settings/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/core/settings/base.py b/api/core/settings/base.py index 0757b410..72e6afeb 100644 --- a/api/core/settings/base.py +++ b/api/core/settings/base.py @@ -41,6 +41,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django.contrib.postgres', 'corsheaders', 'api', 'users', From 7f3beb1e1b8bf56228c9719fd643b1b7d8e2ba34 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 13 Nov 2023 21:59:41 -0300 Subject: [PATCH 07/18] db(handler): add filtering model functions --- api/utils/db_handler.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/api/utils/db_handler.py b/api/utils/db_handler.py index c87b4557..36a42ca0 100644 --- a/api/utils/db_handler.py +++ b/api/utils/db_handler.py @@ -1,4 +1,5 @@ from api.models import Discipline, Department, Class +from django.db.models.query import QuerySet """ Este módulo lida com as operações de banco de dados.""" @@ -27,5 +28,15 @@ def delete_all_departments_using_year_and_period(year: str, period: str) -> None """Deleta um departamento de um periodo especifico.""" Department.objects.filter(year=year, period=period).delete() +def filter_disciplines_by_name(name: str, disciplines: Discipline = Discipline.objects) -> QuerySet: + """Filtra as disciplinas pelo nome.""" + return disciplines.filter(name__unaccent__icontains=name) +def filter_disciplines_by_code(code: str, disciplines: Discipline = Discipline.objects) -> QuerySet: + """Filtra as disciplinas pelo código.""" + return disciplines.filter(code__icontains=code) + +def filter_disciplines_by_year_and_period(year: str, period: str, disciplines: Discipline = Discipline.objects) -> QuerySet: + """Filtra as disciplinas pelo ano e período.""" + return disciplines.filter(department__year=year, department__period=period) From 43f202eab3ba9e6ab02e32e8160dc8d67ae22870 Mon Sep 17 00:00:00 2001 From: Caio Date: Wed, 15 Nov 2023 17:26:42 -0300 Subject: [PATCH 08/18] tests(db-handler): add tests for db handler --- api/utils/tests.py | 166 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/api/utils/tests.py b/api/utils/tests.py index b2606bf8..873e95b9 100644 --- a/api/utils/tests.py +++ b/api/utils/tests.py @@ -8,6 +8,8 @@ import random import json +from . import db_handler as dbh +from api.models import Department, Discipline, Class class WebScrapingTest(APITestCase): @@ -143,3 +145,167 @@ def test_get_previous_period_with_year(self): year, period = sns.get_previous_period(date) self.assertEqual(str(date.year - (period == '2')), year) + +class DatabaseHandlerTest(APITestCase): + def test_create_department(self): + department = dbh.get_or_create_department( + code = 'TCC', + year = '2025', + period = '1' + ) + + self.assertEqual(department.code, 'TCC') + self.assertEqual(department.year, '2025') + self.assertEqual(department.period, '1') + + def test_create_discipline(self): + department = dbh.get_or_create_department( + code = 'SGU', + year = '2023', + period = '2' + ) + + discipline = dbh.get_or_create_discipline( + name = 'Front-End II', + code = 'SGU1234', + department = department + ) + + self.assertEqual(discipline.name, 'Front-End II') + self.assertEqual(discipline.code, 'SGU1234') + self.assertTrue(discipline.department == department) + + def test_create_class(self): + department = dbh.get_or_create_department( + code = 'SGU', + year = '2023', + period = '2' + ) + + discipline = dbh.get_or_create_discipline( + name = 'Front-End II', + code = 'SGU1234', + department = department + ) + + _class = dbh.create_class( + workload = 60, + teachers = ['Mateus Vieira'], + classroom = 'Gather Town', + schedule = '46M34', + days = ['Quarta-Feira 10:00 às 11:50', 'Sexta-Feira 10:00 às 11:50'], + _class = "1", + discipline = discipline + ) + + self.assertEqual(_class.workload, 60) + self.assertEqual(_class.teachers, ['Mateus Vieira']) + self.assertEqual(_class.classroom, 'Gather Town') + self.assertEqual(_class.schedule, '46M34') + self.assertEqual(_class.days, ['Quarta-Feira 10:00 às 11:50', 'Sexta-Feira 10:00 às 11:50']) + self.assertEqual(_class._class, "1") + self.assertTrue(_class.discipline == discipline) + + def test_delete_classes_from_discipline(self): + department = dbh.get_or_create_department( + code = 'HCQ', + year = '2023', + period = '2' + ) + + discipline = dbh.get_or_create_discipline( + name = 'Lingua Portuguesa (Portugal)', + code = 'HCQ1234', + department = department + ) + + _class = dbh.create_class( + workload = 60, + teachers = ['Henrique Camelo'], + classroom = 'Gather Town', + schedule = '46M34', + days = ['Quarta-Feira 10:00 às 11:50', 'Sexta-Feira 10:00 às 11:50'], + _class = "1", + discipline = discipline + ) + + dbh.delete_classes_from_discipline( + discipline = discipline + ) + + self.assertFalse(len(discipline.classes.all())) + + def test_delete_all_departments_using_year_and_period(self): + department = dbh.get_or_create_department( + code = 'MDS', + year = '2025', + period = '2' + ) + + dbh.delete_all_departments_using_year_and_period( + year = '2025', + period = '2' + ) + + self.assertFalse(len(Department.objects.all())) + + def test_filter_disciplines_by_name(self): + department = dbh.get_or_create_department( + code = 'CFH', + year = '2023', + period = '2' + ) + + discipline = dbh.get_or_create_discipline( + name = 'Aprendizado de organização de faltas', + code = 'CFH1234', + department = department + ) + + disciplines = dbh.filter_disciplines_by_name( + name = 'Aprendizado de organização de faltas' + ) + + self.assertTrue(len(disciplines)) + self.assertTrue(discipline in disciplines) + + def test_filter_disciplines_by_code(self): + department = dbh.get_or_create_department( + code = 'FGA', + year = '2023', + period = '2' + ) + + discipline = dbh.get_or_create_discipline( + name = 'Tópicos Especiais em Programação', + code = 'FGA0053', + department = department + ) + + disciplines = dbh.filter_disciplines_by_code( + code = 'FGA0053' + ) + + self.assertTrue(len(disciplines)) + self.assertTrue(discipline in disciplines) + + def test_filter_disciplines_by_year_and_period(self): + department = dbh.get_or_create_department( + code = 'THZ', + year = '2027', + period = '1' + ) + + discipline = dbh.get_or_create_discipline( + name = 'Redes', + code = 'THZ1004', + department = department + ) + + disciplines = dbh.filter_disciplines_by_year_and_period( + year = '2027', + period = '1' + ) + + self.assertTrue(len(disciplines)) + self.assertTrue(discipline in disciplines) \ No newline at end of file From 3e12ad7d61143761f6b918ac1f8868570589c887 Mon Sep 17 00:00:00 2001 From: GabrielCastelo-31 Date: Wed, 15 Nov 2023 20:31:50 -0300 Subject: [PATCH 09/18] Fix error of empty string passed as parameter Co-authored-by: Caio Felipe --- api/api/views.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/api/api/views.py b/api/api/views.py index e1808e33..08390e1f 100644 --- a/api/api/views.py +++ b/api/api/views.py @@ -6,22 +6,25 @@ from rest_framework import status MAXIMUM_RETURNED_DISCIPLINES = 5 +ERROR_MESSAGE = "no valid argument found for 'search', 'year' or 'period'" + class Search(APIView): def get(self, request: Request, *args, **kwargs) -> Response: name = request.GET.get('search', None) year = request.GET.get('year', None) period = request.GET.get('period', None) - - if name is None or year is None or period is None: + + if name is None or len(name) == 0 or year is None or len(year) == 0 or period is None or len(period) == 0: return Response( { - "errors": "no valid argument found for 'search', 'year' or 'period'" + "errors": ERROR_MESSAGE }, status.HTTP_400_BAD_REQUEST) - disciplines = filter_disciplines_by_name(name=name) | filter_disciplines_by_code(code=name) - filtered_disciplines = filter_disciplines_by_year_and_period(year=year, period=period, disciplines=disciplines) + disciplines = filter_disciplines_by_name( + name=name) | filter_disciplines_by_code(code=name) + filtered_disciplines = filter_disciplines_by_year_and_period( + year=year, period=period, disciplines=disciplines) data = DisciplineSerializer(filtered_disciplines, many=True).data return Response(data[:MAXIMUM_RETURNED_DISCIPLINES], status.HTTP_200_OK) - \ No newline at end of file From f97b67451d478951026946d796f521e87655b4dd Mon Sep 17 00:00:00 2001 From: GabrielCastelo-31 Date: Wed, 15 Nov 2023 20:33:43 -0300 Subject: [PATCH 10/18] Create tests for API View - All scenarios were tested. Co-authored-by: Caio Felipe --- api/api/tests.py | 220 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 202 insertions(+), 18 deletions(-) diff --git a/api/api/tests.py b/api/api/tests.py index 0d818ea3..6f0ab6e3 100644 --- a/api/api/tests.py +++ b/api/api/tests.py @@ -1,32 +1,37 @@ from django.test import TestCase -from .models import Department, Discipline,Class +from rest_framework.test import APITestCase +from .models import Department, Discipline, Class +from utils.db_handler import get_or_create_department, get_or_create_discipline, create_class +from .views import Search, ERROR_MESSAGE +import json + class DisciplineModelsTest(TestCase): def setUp(self): self.department = Department.objects.create( - code = 'INF', - year = "2023", - period = "2" + code='INF', + year="2023", + period="2" ) self.discipline = Discipline.objects.create( - name = 'Métodos de Desenvolvimento de Software', - code = 'MDS1010', - department = self.department + name='Métodos de Desenvolvimento de Software', + code='MDS1010', + department=self.department ) self._class = Class.objects.create( - workload = 60, - teachers = ['Professor 1', 'Professor 2'], - classroom = 'MOCAP', - schedule = '46M34', - days = ['Quarta-Feira 10:00 às 11:50', 'Sexta-Feira 10:00 às 11:50'], - _class = "1", - discipline = self.discipline + workload=60, + teachers=['Professor 1', 'Professor 2'], + classroom='MOCAP', + schedule='46M34', + days=['Quarta-Feira 10:00 às 11:50', 'Sexta-Feira 10:00 às 11:50'], + _class="1", + discipline=self.discipline ) - def test_create_discipline(self): - self.assertEqual(self.discipline.name, 'Métodos de Desenvolvimento de Software') + self.assertEqual(self.discipline.name, + 'Métodos de Desenvolvimento de Software') self.assertEqual(self.discipline.code, 'MDS1010') self.assertEqual(self.discipline.department, self.department) @@ -35,7 +40,8 @@ def test_create_class(self): self.assertEqual(self._class.teachers, ['Professor 1', 'Professor 2']) self.assertEqual(self._class.classroom, 'MOCAP') self.assertEqual(self._class.schedule, '46M34') - self.assertEqual(self._class.days, ['Quarta-Feira 10:00 às 11:50', 'Sexta-Feira 10:00 às 11:50']) + self.assertEqual(self._class.days, [ + 'Quarta-Feira 10:00 às 11:50', 'Sexta-Feira 10:00 às 11:50']) self.assertEqual(self._class._class, "1") self.assertEqual(self._class.discipline, self.discipline) @@ -46,9 +52,187 @@ def test_create_department(self): def test_str_method_of_discipline(self): self.assertEqual(str(self.discipline), self.discipline.name) - + def test_str_method_of_class(self): self.assertEqual(str(self._class), self._class._class) def test_str_method_of_department(self): self.assertEqual(str(self.department), self.department.code) + + +class Testsad(APITestCase): + def setUp(self) -> None: + self.department = get_or_create_department( + code='518', year='2023', period='2') + self.discipline_1 = get_or_create_discipline( + name='CÁLCULO 1', code='MAT518', department=self.department) + self.discipline_2 = get_or_create_discipline( + name='CÁLCULO 2', code='MAT519', department=self.department) + self._class_1 = create_class(workload=60, teachers=['RICARDO FRAGELLI'], classroom='MOCAP', schedule='46M34', + days=['Quarta-Feira 10:00 às 11:50', 'Sexta-Feira 10:00 às 11:50'], _class="1", discipline=self.discipline_1) + self._class_2 = create_class(workload=60, teachers=['VINICIUS RISPOLI'], classroom='S1', schedule='24M34', days=[ + 'Segunda-Feira 10:00 às 11:50', 'Quarta-Feira 10:00 às 11:50'], _class="1", discipline=self.discipline_2) + + def test_with_complete_correct_search(self): + """ + Testa a busca por disciplinas com o nome completo e todos os parâmetros corretos + + Testes: + - Status code (200 OK) + - Quantidade de disciplinas retornadas + - Código do departamento + - Nome da disciplina + - Professores da disciplina + """ + response_for_discipline_1 = self.client.get( + '/courses/?search=calculo+1&year=2023&period=2') + response_for_discipline_2 = self.client.get( + '/courses/?search=calculo+2&year=2023&period=2') + content_1 = json.loads(response_for_discipline_1.content) + content_2 = json.loads(response_for_discipline_2.content) + + # Testes da disciplina 1 + self.assertEqual(response_for_discipline_1.status_code, 200) + self.assertEqual(len(content_1), 1) + self.assertEqual(content_1[0]['department'] + ['code'], self.department.code) + self.assertEqual(content_1[0]['name'], self.discipline_1.name) + self.assertEqual(content_1[0]['classes'][0] + ['teachers'], self._class_1.teachers) + + # Testes da disciplina 2 + self.assertEqual(response_for_discipline_2.status_code, 200) + self.assertEqual(len(content_2), 1) + self.assertEqual(content_2[0]['department'] + ['code'], self.department.code) + self.assertEqual(content_2[0]['name'], self.discipline_2.name) + self.assertEqual(content_2[0]['classes'][0] + ['teachers'], self._class_2.teachers) + + def test_with_incomplete_correct_search(self): + """ + Testa a busca por disciplinas com nome incompleto e todos os parâmetros corretos + + Testes: + - Status code (200 OK) + - Quantidade de disciplinas retornadas + - Código do departamento + - Nome da disciplina + - Professores da disciplina + """ + response_for_disciplines = self.client.get( + '/courses/?search=calculo&year=2023&period=2') + content = json.loads(response_for_disciplines.content) + + # Testes da disciplina 1 + self.assertEqual(response_for_disciplines.status_code, 200) + self.assertEqual(len(content), 2) + self.assertEqual(content[0]['department'] + ['code'], self.department.code) + self.assertEqual(content[0]['name'], self.discipline_1.name) + self.assertEqual(content[0]['classes'][0] + ['teachers'], self._class_1.teachers) + # Testes da disciplina 2 + self.assertEqual(content[1]['department'] + ['code'], self.department.code) + self.assertEqual(content[1]['name'], self.discipline_2.name) + self.assertEqual(content[1]['classes'][0] + ['teachers'], self._class_2.teachers) + + def test_with_bad_url_search_missing_year(self): + """ + Testa a busca por disciplinas sem os parâmetros de ano, como None e como string vazia + Testes: + - Status code (400 BAD REQUEST) + - Quantidade de resposta no JSON + - Mensagem de erro + """ + response_1 = self.client.get( + '/courses/?search=calculo&') + response_2 = self.client.get( + '/courses/?search=calculo&year=&period=2') + content_1 = json.loads(response_1.content) + content_2 = json.loads(response_2.content) + + # Testes com ano None + self.assertEqual(response_1.status_code, 400) + self.assertEqual(len(content_1), 1) + self.assertEqual(content_1['errors'], ERROR_MESSAGE) + + # Testes com ano string vazia + self.assertEqual(response_2.status_code, 400) + self.assertEqual(len(content_2), 1) + self.assertEqual(content_2['errors'], ERROR_MESSAGE) + + def test_with_bad_url_search_missing_period(self): + """ + Testa a busca por disciplinas sem os parâmetros de período, como None e como string vazia + Testes: + - Status code (400 BAD REQUEST) + - Quantidade de resposta no JSON + - Mensagem de erro + """ + response_1 = self.client.get( + '/courses/?search=calculo&year=2023') + response_2 = self.client.get( + '/courses/?search=calculo&year=2023&period=') + content_1 = json.loads(response_1.content) + content_2 = json.loads(response_2.content) + + # Testes com período None + self.assertEqual(response_1.status_code, 400) + self.assertEqual(len(content_1), 1) + self.assertEqual(content_1['errors'], ERROR_MESSAGE) + + # Testes com período string vazia + self.assertEqual(response_2.status_code, 400) + self.assertEqual(len(content_2), 1) + self.assertEqual(content_2['errors'], ERROR_MESSAGE) + + def test_with_bad_url_search_missing_name(self): + """ + Testa a busca por disciplinas sem os parâmetros de nome, como None e como string vazia + Testes: + - Status code (400 BAD REQUEST) + - Quantidade de resposta no JSON + - Mensagem de erro + """ + response_1 = self.client.get( + '/courses/?year=2023&period=2') + response_2 = self.client.get( + '/courses/?search=&year=2023&period=2') + content_1 = json.loads(response_1.content) + content_2 = json.loads(response_2.content) + + # Testes com nome None + self.assertEqual(response_1.status_code, 400) + self.assertEqual(len(content_1), 1) + self.assertEqual(content_1['errors'], ERROR_MESSAGE) + + # Testes com nome string vazia + self.assertEqual(response_2.status_code, 400) + self.assertEqual(len(content_2), 1) + self.assertEqual(content_2['errors'], ERROR_MESSAGE) + + def test_with_bad_url_search_missing_all_parameters(self): + """ + Testa a busca por disciplinas sem nenhum parâmetro, como None e como string vazia + Testes: + - Status code (400 BAD REQUEST) + - Quantidade de resposta no JSON + - Mensagem de erro + """ + response_1 = self.client.get('/courses/') + response_2 = self.client.get('/courses/?search=&year=&period=') + content_1 = json.loads(response_1.content) + content_2 = json.loads(response_2.content) + + # Testes com todos os parâmetros None + self.assertEqual(response_1.status_code, 400) + self.assertEqual(len(content_1), 1) + self.assertEqual(content_1['errors'], ERROR_MESSAGE) + + # Testes com todos os parâmetros string vazia + self.assertEqual(response_2.status_code, 400) + self.assertEqual(len(content_2), 1) + self.assertEqual(content_2['errors'], ERROR_MESSAGE) From 591142d8973c17114541536e50b0e15cfab86fbf Mon Sep 17 00:00:00 2001 From: GabrielCastelo-31 Date: Wed, 15 Nov 2023 20:45:01 -0300 Subject: [PATCH 11/18] Fix class test name --- api/api/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api/tests.py b/api/api/tests.py index 6f0ab6e3..c65c9d1f 100644 --- a/api/api/tests.py +++ b/api/api/tests.py @@ -60,7 +60,7 @@ def test_str_method_of_department(self): self.assertEqual(str(self.department), self.department.code) -class Testsad(APITestCase): +class TestSearchAPI(APITestCase): def setUp(self) -> None: self.department = get_or_create_department( code='518', year='2023', period='2') From 171ad01de7a11022ede85d4f9d55918ed8a3e2b9 Mon Sep 17 00:00:00 2001 From: Caio Date: Wed, 15 Nov 2023 22:03:47 -0300 Subject: [PATCH 12/18] views(courses): fix search api to be more accurate Co-authored-by: HenrikhKenino <110349110+HenrikhKenino@users.noreply.github.com> Co-authored-by: mateusvrs --- api/api/views.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/api/views.py b/api/api/views.py index 08390e1f..f69d67eb 100644 --- a/api/api/views.py +++ b/api/api/views.py @@ -4,6 +4,7 @@ from rest_framework.response import Response from rest_framework.request import Request from rest_framework import status +from django.db.models import QuerySet MAXIMUM_RETURNED_DISCIPLINES = 5 ERROR_MESSAGE = "no valid argument found for 'search', 'year' or 'period'" @@ -21,8 +22,18 @@ def get(self, request: Request, *args, **kwargs) -> Response: "errors": ERROR_MESSAGE }, status.HTTP_400_BAD_REQUEST) - disciplines = filter_disciplines_by_name( - name=name) | filter_disciplines_by_code(code=name) + name = name.split() + disciplines = filter_disciplines_by_name(name=name[0]) + + for term in name[1:]: + disciplines &= filter_disciplines_by_name(name=term) + + if not disciplines.count(): + disciplines = filter_disciplines_by_code(code=name[0]) + + for term in name[1:]: + disciplines &= filter_disciplines_by_code(code=term) + filtered_disciplines = filter_disciplines_by_year_and_period( year=year, period=period, disciplines=disciplines) data = DisciplineSerializer(filtered_disciplines, many=True).data From b1e02aeeec4396edc60af2c71c13db89d921b541 Mon Sep 17 00:00:00 2001 From: Caio Date: Wed, 15 Nov 2023 23:23:40 -0300 Subject: [PATCH 13/18] tests(views): add missing tests for search api --- api/api/tests.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/api/api/tests.py b/api/api/tests.py index c65c9d1f..5f2488b2 100644 --- a/api/api/tests.py +++ b/api/api/tests.py @@ -139,6 +139,52 @@ def test_with_incomplete_correct_search(self): self.assertEqual(content[1]['classes'][0] ['teachers'], self._class_2.teachers) + def test_with_code_search(self): + """ + Testa a busca por disciplinas através do código da matéria + Testes: + - Status code (200 OK) + - Quantidade de disciplinas retornadas + """ + + response_for_discipline_1 = self.client.get( + '/courses/?search=MAT518&year=2023&period=2') + response_for_discipline_2 = self.client.get( + '/courses/?search=MAT519&year=2023&period=2') + content_1 = json.loads(response_for_discipline_1.content) + content_2 = json.loads(response_for_discipline_2.content) + + # Testes da disciplina 1 + self.assertEqual(response_for_discipline_1.status_code, 200) + self.assertEqual(len(content_1), 1) + + # Testes da disciplina 2 + self.assertEqual(response_for_discipline_2.status_code, 200) + self.assertEqual(len(content_2), 1) + + def test_with_code_search_spaced(self): + """ + Testa a busca por disciplinas através do código da matéria com espaços + Testes: + - Status code (200 OK) + - Quantidade de disciplinas retornadas + """ + + response_for_discipline_1 = self.client.get( + '/courses/?search=MAT+518&year=2023&period=2') + response_for_discipline_2 = self.client.get( + '/courses/?search=MAT+519&year=2023&period=2') + content_1 = json.loads(response_for_discipline_1.content) + content_2 = json.loads(response_for_discipline_2.content) + + # Testes da disciplina 1 + self.assertEqual(response_for_discipline_1.status_code, 200) + self.assertEqual(len(content_1), 1) + + # Testes da disciplina 2 + self.assertEqual(response_for_discipline_2.status_code, 200) + self.assertEqual(len(content_2), 1) + def test_with_bad_url_search_missing_year(self): """ Testa a busca por disciplinas sem os parâmetros de ano, como None e como string vazia From f5fa825dd7168037a203c1828f843d8b546a4d56 Mon Sep 17 00:00:00 2001 From: Caio Date: Fri, 17 Nov 2023 17:02:37 -0300 Subject: [PATCH 14/18] api(serializers): change serializer fields to all --- api/api/serializers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/api/api/serializers.py b/api/api/serializers.py index 3fb60be8..ef30f00b 100644 --- a/api/api/serializers.py +++ b/api/api/serializers.py @@ -4,12 +4,11 @@ class DepartmentSerializer(ModelSerializer): class Meta: model = Department - fields = ('code', 'year', 'period') - + fields = '__all__' class ClassSerializer(ModelSerializer): class Meta: model = Class - fields = ('workload', 'teachers', 'classroom', 'schedule', 'days', '_class') + fields = '__all__' class DisciplineSerializer(ModelSerializer): department = DepartmentSerializer() @@ -17,4 +16,4 @@ class DisciplineSerializer(ModelSerializer): class Meta: model = Discipline - fields = ('name', 'code', 'department', 'classes') \ No newline at end of file + fields = '__all__' \ No newline at end of file From 540deb3c6cc8b4157d51cebdb841a2bb3dc0fbf6 Mon Sep 17 00:00:00 2001 From: Caio Date: Fri, 17 Nov 2023 17:03:27 -0300 Subject: [PATCH 15/18] api(tests): remove unused import --- api/api/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api/tests.py b/api/api/tests.py index 5f2488b2..19799990 100644 --- a/api/api/tests.py +++ b/api/api/tests.py @@ -2,7 +2,7 @@ from rest_framework.test import APITestCase from .models import Department, Discipline, Class from utils.db_handler import get_or_create_department, get_or_create_discipline, create_class -from .views import Search, ERROR_MESSAGE +from .views import ERROR_MESSAGE import json From e8bd2aa879ee2b0d68aeaf381c49bd67eda6cd57 Mon Sep 17 00:00:00 2001 From: Caio Date: Fri, 17 Nov 2023 17:03:40 -0300 Subject: [PATCH 16/18] api(views): remove unused import --- api/api/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/api/views.py b/api/api/views.py index f69d67eb..bd5e70c6 100644 --- a/api/api/views.py +++ b/api/api/views.py @@ -4,7 +4,6 @@ from rest_framework.response import Response from rest_framework.request import Request from rest_framework import status -from django.db.models import QuerySet MAXIMUM_RETURNED_DISCIPLINES = 5 ERROR_MESSAGE = "no valid argument found for 'search', 'year' or 'period'" From 00a3dd206d7720ad14ef5cdb7c2bb9ea28fb740a Mon Sep 17 00:00:00 2001 From: Caio Date: Fri, 17 Nov 2023 17:15:55 -0300 Subject: [PATCH 17/18] api(views): add function treat_string --- api/api/views.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/api/api/views.py b/api/api/views.py index bd5e70c6..0aa07fa9 100644 --- a/api/api/views.py +++ b/api/api/views.py @@ -10,10 +10,16 @@ class Search(APIView): + def treat_string(self, string: str | None) -> str | None: + if string is not None: + string = string.strip() + + return string + def get(self, request: Request, *args, **kwargs) -> Response: - name = request.GET.get('search', None) - year = request.GET.get('year', None) - period = request.GET.get('period', None) + name = self.treat_string(request.GET.get('search', None)) + year = self.treat_string(request.GET.get('year', None)) + period = self.treat_string(request.GET.get('period', None)) if name is None or len(name) == 0 or year is None or len(year) == 0 or period is None or len(period) == 0: return Response( From ceb2ff9e999dd7080dacf0fdf48885557a79ee1b Mon Sep 17 00:00:00 2001 From: Caio Date: Fri, 17 Nov 2023 17:16:16 -0300 Subject: [PATCH 18/18] api(tests): add test for empty spaced parameters --- api/api/tests.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/api/api/tests.py b/api/api/tests.py index 19799990..5be1808d 100644 --- a/api/api/tests.py +++ b/api/api/tests.py @@ -282,3 +282,29 @@ def test_with_bad_url_search_missing_all_parameters(self): self.assertEqual(response_2.status_code, 400) self.assertEqual(len(content_2), 1) self.assertEqual(content_2['errors'], ERROR_MESSAGE) + + def test_with_only_spaces(self): + """ + Testa a busca por disciplinas com apenas espaços nos parâmetros + Testes: + - Status code (400 BAD REQUEST) + """ + + response_1 = self.client.get('/courses/?search= &year=2023&period=2') + response_2 = self.client.get('/courses/?search=calculo&year= &period=2') + response_3 = self.client.get('/courses/?search=calculo&year=2023&period= ') + content_1 = json.loads(response_1.content) + content_2 = json.loads(response_2.content) + content_3 = json.loads(response_3.content) + + self.assertEqual(response_1.status_code, 400) + self.assertEqual(len(content_1), 1) + self.assertEqual(content_1['errors'], ERROR_MESSAGE) + + self.assertEqual(response_2.status_code, 400) + self.assertEqual(len(content_2), 1) + self.assertEqual(content_2['errors'], ERROR_MESSAGE) + + self.assertEqual(response_3.status_code, 400) + self.assertEqual(len(content_3), 1) + self.assertEqual(content_3['errors'], ERROR_MESSAGE) \ No newline at end of file