Skip to content
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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

GabrielCastelo-31
Copy link
Collaborator

@GabrielCastelo-31 GabrielCastelo-31 commented Aug 15, 2024

Descrição

closes #209
Foi implementado um filtro extra de pesquisa de disciplinas baseado no horário de aula desejado pelo usuário e o departamento que deseja encontrar uma turma. O usuário pode passar os seguintes parâmetros:

  • name: Termo de pesquisa que pode ser nome da disciplina ou professor
  • departament: Código do departamento para filtrar as disciplinas
  • schedule: Horário de aula pretendido.

É importante notar o seguinte:

  • É possível filtrar por horário e departamento conjuntamente ao nome.
    • Vale notar que podem acontecer erros de utilização pelo usuário. Exemplo name= “Cálculo Númerico” e department= ”código_departamento_de_música”. O resultado dessa pesquisa será vazio.
      • Porém isto não se apresenta como um problema relevante, uma vez que os benefícios dessa funcionalidade superam os casos de uso errôneos.
  • Caso o nome não seja passado, obrigatoriamente deve ser passado schedule e department.
    • Isto é necessário, pois:
      • A pesquisa não pode ser completamente vazia
      • Caso a pesquisa fosse apenas por horário, seriam retornadas milhares de disciplinas, o que perde o sentido de especificidade da pesquisa.
    • Portanto, a pesquisa por schedule_only necessita do código do departamento desejado.

Tipo de alteração

  • Bug fix (alteração que corrige um problema)
  • New feature (alteração que adiciona funcionalidade)
  • Breaking change (correção ou funcionalidade que altera o comportamento de outras partes do sistema de maneira significativa)
  • Documentation update (alteração na documentação)

Como isso foi testado?

A funcionalidade foi implementada utilizando a metodologia TDD, logo cada funcionalidade implementada está coberta por testes.

  • Teste de pesquisa correta com apenas horário e departamento
  • Teste de pesquisa por nome e horário.
  • Teste de pesquisa com nome e horário sem turmas.

Checklist

  • Meu código segue as diretrizes de contribuição deste projeto
  • Realizei uma revisão pessoal do meu código
  • Comentei meu código, especialmente em áreas de difícil compreensão
  • Fiz alterações correspondentes na documentação
  • Minhas alterações não geram novos warnings ou erros
  • Adicionei testes que comprovam que minha correção é eficaz ou que minha funcionalidade está funcionando corretamente
  • Todos os testes unitários novos e existentes passam localmente com minhas alterações

Copy link

vercel bot commented Aug 15, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
suagradeunb ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 15, 2024 2:15am

@GabrielCastelo-31 GabrielCastelo-31 self-assigned this Aug 15, 2024
@GabrielCastelo-31 GabrielCastelo-31 added feature request New feature or request back-end related to back-end development API related to API communication labels Aug 15, 2024
@@ -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:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function get_serialized_data has 5 arguments (exceeds 4 allowed). Consider refactoring.

Copy link

codecov bot commented Aug 15, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (b3e8235) to head (28c794d).

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #234   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           58        58           
  Lines         2016      2086   +70     
=========================================
+ Hits          2016      2086   +70     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

codeclimate bot commented Aug 15, 2024

Code Climate has analyzed commit 28c794d and detected 2 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 2

View more on Code Climate.

Copy link
Collaborator

@caio-felipee caio-felipee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boa noite! Realizei algumas considerações a respeito do sistema e do código. Quando puder, poderia dar uma olhada?

@@ -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"
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Comment on lines +143 to +148
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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:

  • Cálculo 1
  • 234N23
    e não exista nenhuma turma com esse horário. A ideia seria não retornar o objeto de Disciplines com a lista de classes vazia, pois pode comprometer a experiência do usuário.

Caso a pesquisa acima fosse modificada para

  • Calc
  • 234N23
    existe a possibilidade de outras matérias possuírem turmas neste horário e, caso a matéria de Cálculo 1 não fosse removida do data, o usuário iria receber uma lista de disciplinas onde uma delas não possui turmas.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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))

Comment on lines +126 to +130
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
Copy link
Collaborator

@caio-felipee caio-felipee Aug 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O search_by_schedule = True aparece duas vezes. Acho que poderia remover esses trechos do escopo local das estruturas de condição e adicionar o atributo da variável apenas uma vez em um escopo mais geral. O que acha?

Comment on lines +74 to +76
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")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Não entendi muito bem o uso do filter_disciplines_by_schedule_and_department_code em conjunto com o filter_classes_by_schedule. Esse Q(classes__schedule__icontains=schedule) não filtra as aulas através do código? Logo em seguida, é utilizada a outra função. Algum dos dois são inutilizados, não?

Copy link
Collaborator Author

@GabrielCastelo-31 GabrielCastelo-31 Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Olá, Caio! A função filter_disciplines_by_schedule_and_department_code() serve para encontrar disciplinas que possuam alguma turma com o horário especificado. Uma vez feito isso, antes de retornar a disciplina para o front-end, é necessário filtrar, dentre as turmas do objeto Discipline em questão, as turmas que coincidem com o horário especificado.

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 classes relacionados ao objeto discipline diretamente na filtragem de objetos Discipline utilizando o ORM padrão do Djano. Talvez seja interessante utilizar uma query em SQL que já realize a filtragem completa.

O que acha?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Entendi. Se já temos um sistema dessa forma, vamos manter então (pelo menos por enquanto).
Nunca tinha reparado, não me parece ser tão interessante seguir dessa maneira. Não estamos verificando esses atributos duas vezes?

Talvez seja interessante utilizar uma query em SQL que já realize a filtragem completa.

O que acha?

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 filter_classes_by_schedule. Valeu!

Comment on lines +84 to +86
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)
Copy link
Collaborator

@caio-felipee caio-felipee Aug 18, 2024

Choose a reason for hiding this comment

The 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 4M34, receberei apenas a primeira matéria, sendo que a segunda também possui o mesmo padrão.

Essa pesquisa é funcional, mas se limita a uma substring de horário.

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 4M34, pode ter qualquer coisa antes do primeiro 4 e depois também. Logo após isso, vem o M e também pode ter qualquer coisa entre o 3 e 4.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.

return response.Response(data[:MAXIMUM_RETURNED_DISCIPLINES], status.HTTP_200_OK)


class YearPeriod(APIView):

@swagger_auto_schema(
@ swagger_auto_schema(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Não utilizamos esse padrão de separação de decorators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API related to API communication back-end related to back-end development feature request New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feature (busca por horário): Função de busca de disciplina por horário e departamento.
2 participants