-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
api: add search filter by class schedule and department #234
base: main
Are you sure you want to change the base?
Changes from all commits
34bf272
0f59d00
7ae6f1c
0a8250e
a0d0a93
2090201
6dc75a4
28c794d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
from ..models import Discipline | ||
from ..models import Discipline, Class | ||
|
||
from django.db.models.query import QuerySet | ||
|
||
|
@@ -10,7 +10,7 @@ | |
|
||
from utils.sessions import get_current_year_and_period, get_next_period | ||
from utils.schedule_generator import ScheduleGenerator | ||
from utils.db_handler import get_best_similarities_by_name, filter_disciplines_by_teacher, filter_disciplines_by_year_and_period, filter_disciplines_by_code | ||
from utils.db_handler import get_best_similarities_by_name, filter_disciplines_by_teacher, filter_disciplines_by_year_and_period, filter_disciplines_by_code, filter_disciplines_by_schedule_and_department_code | ||
from utils.search import SearchTool | ||
|
||
from .. import serializers | ||
|
@@ -21,7 +21,7 @@ | |
from traceback import print_exception | ||
|
||
MAXIMUM_RETURNED_DISCIPLINES = 15 | ||
ERROR_MESSAGE = "no valid argument found for 'search', 'year' or 'period'" | ||
ERROR_MESSAGE = "Bad search parameters or missing parameters" | ||
MINIMUM_SEARCH_LENGTH = 4 | ||
ERROR_MESSAGE_SEARCH_LENGTH = f"search must have at least {MINIMUM_SEARCH_LENGTH} characters" | ||
MAXIMUM_RETURNED_SCHEDULES = 5 | ||
|
@@ -38,11 +38,11 @@ def treat_string(self, string: str | None) -> str | None: | |
def filter_disciplines(self, request: request.Request, name: str) -> QuerySet[Discipline]: | ||
search_handler = SearchTool(Discipline) | ||
search_fields = ['unicode_name', 'code'] | ||
|
||
result = search_handler.filter_by_search_result( | ||
request = request, | ||
search_str = name, | ||
search_fields = search_fields | ||
request=request, | ||
search_str=name, | ||
search_fields=search_fields | ||
) | ||
|
||
return result | ||
|
@@ -51,10 +51,8 @@ def retrieve_disciplines_by_similarity(self, request: request.Request, name: str | |
disciplines = self.filter_disciplines(request, name) | ||
|
||
disciplines = get_best_similarities_by_name(name, disciplines) | ||
|
||
if not disciplines.count(): | ||
disciplines = filter_disciplines_by_code(code=name[0]) | ||
|
||
for term in name[1:]: | ||
disciplines &= filter_disciplines_by_code(code=term) | ||
|
||
|
@@ -70,12 +68,15 @@ def get_disciplines_and_search_flag(self, request, name): | |
search_by_teacher = True | ||
return disciplines, search_by_teacher | ||
|
||
def get_serialized_data(self, filter_params: dict, search_by_teacher: bool, name: str) -> list: | ||
def get_serialized_data(self, filter_params: dict, search_by_teacher: bool, name: str, schedule=None, search_by_schedule=False) -> list: | ||
filtered_disciplines = filter_disciplines_by_year_and_period( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function |
||
**filter_params) | ||
if search_by_teacher: | ||
data = serializers.DisciplineSerializer( | ||
filtered_disciplines, many=True, context={'teacher_name': name}).data | ||
elif search_by_schedule: | ||
data = serializers.DisciplineSerializer( | ||
filtered_disciplines, many=True, context={'schedule': schedule}).data | ||
else: | ||
data = serializers.DisciplineSerializer( | ||
filtered_disciplines, many=True).data | ||
|
@@ -85,48 +86,72 @@ def get_request_parameters(self, request): | |
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)) | ||
return name, year, period | ||
department_code = self.treat_string( | ||
request.GET.get('department_code', None)) | ||
schedule = self.treat_string(request.GET.get('schedule', None)) | ||
return name, year, period, schedule, department_code | ||
|
||
@swagger_auto_schema( | ||
@ swagger_auto_schema( | ||
operation_description="Busca disciplinas por nome ou código. O ano e período são obrigatórios.", | ||
security=[], | ||
manual_parameters=[ | ||
openapi.Parameter('search', openapi.IN_QUERY, | ||
description="Termo de pesquisa (Nome/Código)", type=openapi.TYPE_STRING), | ||
description="Termo de pesquisa (Nome/Código/Professor)", type=openapi.TYPE_STRING), | ||
openapi.Parameter('year', openapi.IN_QUERY, | ||
description="Ano", type=openapi.TYPE_INTEGER), | ||
openapi.Parameter('period', openapi.IN_QUERY, | ||
description="Período ", type=openapi.TYPE_INTEGER), | ||
openapi.Parameter('department_code', openapi.IN_QUERY, | ||
description="Código do departamento", type=openapi.TYPE_STRING), | ||
openapi.Parameter('schedule', openapi.IN_QUERY, | ||
description="Horário no formato 46M34", type=openapi.TYPE_STRING) | ||
], | ||
responses={ | ||
200: openapi.Response('OK', serializers.DisciplineSerializer), | ||
**Errors([400]).retrieve_erros() | ||
} | ||
) | ||
def get(self, request: request.Request, *args, **kwargs) -> response.Response: | ||
name, year, period = self.get_request_parameters(request) | ||
|
||
if not all((name, year, period)): | ||
name, year, period, schedule, department_code = self.get_request_parameters( | ||
request) | ||
if not all((year, period)): | ||
return handle_400_error(ERROR_MESSAGE) | ||
disciplines, search_by_teacher, search_by_schedule = None, False, False | ||
if name: | ||
if len(name) < MINIMUM_SEARCH_LENGTH: | ||
return handle_400_error(ERROR_MESSAGE_SEARCH_LENGTH) | ||
disciplines, search_by_teacher = self.get_disciplines_and_search_flag( | ||
request, name) | ||
if schedule: | ||
search_by_schedule = True | ||
elif schedule and department_code: | ||
disciplines = filter_disciplines_by_schedule_and_department_code( | ||
schedule=schedule, department_code=department_code) | ||
search_by_schedule = True | ||
else: | ||
Comment on lines
+126
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. O |
||
return handle_400_error(ERROR_MESSAGE) | ||
|
||
if len(name) < MINIMUM_SEARCH_LENGTH: | ||
return handle_400_error(ERROR_MESSAGE_SEARCH_LENGTH) | ||
|
||
disciplines, search_by_teacher = self.get_disciplines_and_search_flag( | ||
request, name) | ||
|
||
data = self.get_serialized_data( | ||
filter_params={'year': year, 'period': period, | ||
'disciplines': disciplines}, | ||
search_by_teacher=search_by_teacher, | ||
name=name | ||
search_by_schedule=search_by_schedule, | ||
name=name, | ||
schedule=schedule | ||
) | ||
|
||
data_aux = [] | ||
for i in range(len(data)): | ||
if data[i]['classes'] == []: | ||
data_aux.append(data[i]) | ||
for i in data_aux: | ||
data.remove(i) | ||
return response.Response(data[:MAXIMUM_RETURNED_DISCIPLINES], status.HTTP_200_OK) | ||
Comment on lines
+143
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Não entendi o propósito dessas linhas de código. Poderia elaborar um pouco sobre? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Olá, Caio! O propósito deste trecho de código é filtrar as disciplinas que por ventura tenham sido encontradas pelo nome, porém devido ao critério de horário, não possua nenhuma turma associada. Isso pode ocorrer caso o estudante pesquise por exemplo:
Caso a pesquisa acima fosse modificada para
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Entendi. Essa abordagem não é muito performática por conta das frequentes buscas lineares para remoção, e também das remoções (que será linear). Acho que seria melhor algo desse tipo: data = list(filter(lambda value: len(value) > 0, data)) |
||
|
||
|
||
class YearPeriod(APIView): | ||
|
||
@swagger_auto_schema( | ||
@ swagger_auto_schema( | ||
operation_description="Retorna o ano e período atual, e o próximo ano e período letivos válidos para pesquisa.", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Não utilizamos esse padrão de separação de decorators. |
||
security=[], | ||
responses={ | ||
|
@@ -159,7 +184,7 @@ def get(self, request: request.Request, *args, **kwargs) -> response.Response: | |
|
||
|
||
class GenerateSchedule(APIView): | ||
@swagger_auto_schema( | ||
@ swagger_auto_schema( | ||
operation_description="Gera possíveis horários de acordo com as aulas escolhidas com preferência de turno", | ||
security=[], | ||
request_body=openapi.Schema( | ||
|
@@ -245,7 +270,7 @@ def post(self, request: request.Request, *args, **kwargs) -> response.Response: | |
for schedule in schedules[:MAXIMUM_RETURNED_SCHEDULES]: | ||
data.append( | ||
list(map(lambda x: serializers.ClassSerializerSchedule(x).data, schedule))) | ||
|
||
return response.Response({ | ||
'message': message, | ||
'schedules': data | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ def create_class(teachers: list, classroom: str, schedule: str, | |
return Class.objects.create(teachers=teachers, classroom=classroom, schedule=schedule, | ||
days=days, _class=_class, special_dates=special_dates, discipline=discipline) | ||
|
||
|
||
@handle_cache_before_delete | ||
def delete_classes_from_discipline(discipline: Discipline) -> QuerySet: | ||
"""Deleta todas as turmas de uma disciplina.""" | ||
|
@@ -70,11 +71,21 @@ def filter_disciplines_by_teacher(name: str) -> QuerySet: | |
return search_disciplines | ||
|
||
|
||
def filter_disciplines_by_schedule_and_department_code(schedule: str, department_code: str) -> QuerySet: | ||
"""Filtra as disciplinas pelo horário.""" | ||
return Discipline.objects.filter(Q(classes__schedule__icontains=schedule) & Q(department__code=department_code)).distinct("id") | ||
Comment on lines
+74
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Não entendi muito bem o uso do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Olá, Caio! A função Algo parecido é feito na busca por professor, uma vez que encontramos disciplinas com turmas com o professor especificado, nós filtramos, dentre as turmas daquela disciplina, somente as turmas daquele professor. Essa filtragem de turmas é feita dentro serializer após obter os objetos disciplines em questão. class DisciplineSerializer(DisciplineSerializerSchedule):
classes = serializers.SerializerMethodField()
def get_classes(self, discipline: Discipline):
teacher_name = self.context.get('teacher_name')
schedule = self.context.get('schedule')
classes = discipline.classes.all() if hasattr(
discipline, 'classes') else Class.objects.none()
if teacher_name:
classes = dbh.filter_classes_by_teacher(
name=teacher_name, classes=classes)
if schedule:
classes = dbh.filter_classes_by_schedule(
schedule=schedule, classes=classes)
return ClassSerializer(classes, many=True).data Não conheço uma maneira de filtrar os objetos O que acha? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Entendi. Se já temos um sistema dessa forma, vamos manter então (pelo menos por enquanto).
Por enquanto, vamos manter do jeito em que está mesmo. Para utilizar uma query em SQL, temos que verificar as opções que o framework entrega pra gente (se é anti SQL Injection e tudo mais). Para essa thread, só basta a resolução do problema reportado que é o mesmo do |
||
|
||
|
||
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_classes_by_schedule(schedule: str, classes: BaseManager[Class] = Class.objects) -> QuerySet: | ||
"""Filtra as turmas pelo horário.""" | ||
return classes.filter(schedule__icontains=schedule) | ||
Comment on lines
+84
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A proposta por realizar um filtro através do horário é boa, mas acho que não é eficiente utilizando essa função. Pelo o que eu entendi, ela vai encontrar padrões que contém trechos diretos. Por exemplo: Imagine que temos uma lista de matérias distintas, com os seguintes horários: schedules = ['24M34', '46M34'] Se eu realizo uma query para receber matérias que possuem o horário Essa pesquisa é funcional, mas se limita a uma EDIT: A função icontains é equivalente: SELECT classes WHERE schedule ILIKE '%4M34%'; Se a query fosse desse tipo (abaixo), o problema seria resolvido: SELECT classes WHERE schedule ILIKE '%4%M%3%4%'; Ou seja: Com a query There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Excelente observação, Caio! Concordo com as sugestões e irei modificar o código para implementá-las. |
||
|
||
|
||
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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eu acredito que nesse caso, as mensagens de erro devem ser mais descritivas.
Ao realizar uma pesquisa, tive certa dificuldade em saber o que estava fazendo de errado.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Olá, modificarei as mensagens para torná-las mais descritivas.