Skip to content

Commit

Permalink
Merge pull request #73 from unb-mds/task/db
Browse files Browse the repository at this point in the history
task(db): test da models e função para salvar os dados no db
  • Loading branch information
mateusvrs authored Nov 6, 2023
2 parents d23a206 + 15b7e5a commit 4d2384f
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 32 deletions.
22 changes: 21 additions & 1 deletion api/api/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
from django.contrib import admin
from .models import Department, Discipline, Class

# Register your models here.

@admin.register(Department)
class DepartmentAdmin(admin.ModelAdmin):
list_display = ['code', 'year', 'period']
search_fields = ['code']
ordering = ['year', 'period']


@admin.register(Discipline)
class DisciplineAdmin(admin.ModelAdmin):
list_display = ['name', 'code']
search_fields = ['name', 'code']
ordering = ['name']


@admin.register(Class)
class ClassAdmin(admin.ModelAdmin):
list_display = ['discipline', 'classroom', 'schedule']
search_fields = ['discipline__name']
ordering = ['discipline__name']
66 changes: 66 additions & 0 deletions api/api/management/commands/updatedb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import Any
from django.core.management.base import BaseCommand
from api.utils import sessions, web_scraping, db_handler
from time import time

class Command(BaseCommand):
"""Comando para atualizar o banco de dados."""

help = "Atualiza o banco de dados com as disciplinas do SIGAA e suas respectivas turmas."

def handle(self, *args: Any, **options: Any):
departments_ids = web_scraping.get_list_of_departments()

if departments_ids is None:
self.display_error_message("department_ids")
return

print("Atualizando o banco de dados...")

# Obtem o ano e o período atual e o ano e o período seguinte
previous_period_year, previous_period = sessions.get_previous_period()
current_year, current_period = sessions.get_current_year_and_period()
next_period_year, next_period = sessions.get_next_period()

# Apaga as disciplinas do período interior
db_handler.delete_all_departments_using_year_and_period(previous_period_year, previous_period)

# Atualiza as disciplinas do período atual
start_time = time()
self.update_departments(departments_ids=departments_ids, year=current_year, period=current_period)
self.display_success_message(operation=f"{current_year}/{current_period}", start_time=start_time)

# Atualiza as disciplinas do período seguinte
start_time = time()
self.update_departments(departments_ids=departments_ids, year=next_period_year, period=next_period)
self.display_success_message(operation=f"{next_period_year}/{next_period}", start_time=start_time)

def update_departments(self, departments_ids: list, year: str, period: str) -> None:
for department_id in departments_ids:
disciplines_list = web_scraping.get_department_disciplines(department_id=department_id, current_year=year, current_period=period)
department = db_handler.get_or_create_department(code=department_id, year=year, period=period)

# Para cada disciplina do período atual, deleta as turmas previamente cadastradas e cadastra novas turmas no banco de dados
for discipline_code in disciplines_list:
classes_info = disciplines_list[discipline_code]

# Cria ou pega a disciplina
discipline = db_handler.get_or_create_discipline(name=classes_info[0]["name"], code=discipline_code, department=department)
# Deleta as turmas previamente cadastradas
db_handler.delete_classes_from_discipline(discipline=discipline)

# Cadastra as novas turmas
for class_info in classes_info:
db_handler.create_class(workload=class_info["workload"], teachers=class_info["teachers"],
classroom=class_info["classroom"], schedule=class_info["schedule"],
days=class_info["days"], _class=class_info["class_code"], discipline=discipline)

def display_error_message(self, operation: str) -> None:
print("Não foi possível realizar a operação de atualização do banco de dados.")
print("Verifique se o SIGAA está funcionando corretamente.")
print(f"Falha em {operation}", end="\n\n")

def display_success_message(self, operation: str, start_time: float) -> None:
print("Operação de atualização do banco de dados realizada com sucesso.")
print(f"Sucesso em {operation}")
print(f"Tempo de execução: {(time() - start_time):.1f}s", end="\n\n")
47 changes: 47 additions & 0 deletions api/api/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 4.2.5 on 2023-11-06 16:42

import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Department',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=10)),
('year', models.CharField(default='0000', max_length=4)),
('period', models.CharField(default='1', max_length=1)),
],
),
migrations.CreateModel(
name='Discipline',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('code', models.CharField(max_length=20)),
('department', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='disciplines', to='api.department')),
],
),
migrations.CreateModel(
name='Class',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('workload', models.IntegerField()),
('teachers', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), size=None)),
('classroom', models.CharField(max_length=60)),
('schedule', models.CharField(max_length=60)),
('days', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=60), size=None)),
('_class', models.CharField(max_length=30)),
('discipline', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='classes', to='api.discipline')),
],
),
]
48 changes: 47 additions & 1 deletion api/api/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
from django.db import models
from django.contrib.postgres.fields import ArrayField

# Create your models here.
class Department(models.Model):
"""Classe que representa um departamento.
code:str -> Código do departamento
year:str -> Ano do departamento
period:str -> Período do departamento
"""
code = models.CharField(max_length=10)
year = models.CharField(max_length=4, default='0000')
period = models.CharField(max_length=1, default='1')

def __str__(self):
return self.code

class Discipline(models.Model):
"""Classe que representa uma disciplina.
name:str -> Nome da disciplina
code:str -> Código da disciplina
department:Department -> Departamento da disciplina
"""
name = models.CharField(max_length=100)
code = models.CharField(max_length=20)
department = models.ForeignKey(Department, on_delete=models.CASCADE, related_name='disciplines')

def __str__(self):
return self.name

class Class(models.Model):
"""Classe que representa uma turma.
workload:int -> Carga horária da turma
teachers:list -> Lista de professores da turma
classroom:str -> Sala da turma
schedule:str -> Horário da turma
days:list -> Dias da semana da turma
_class:str -> Turma da disciplina
discipline:Discipline -> Disciplina da turma
"""
workload = models.IntegerField()
teachers = ArrayField(models.CharField(max_length=100))
classroom = models.CharField(max_length=60)
schedule = models.CharField(max_length=60)
days = ArrayField(models.CharField(max_length=60))
_class = models.CharField(max_length=30)
discipline = models.ForeignKey(Discipline, on_delete=models.CASCADE, related_name='classes')

def __str__(self):
return self._class
44 changes: 43 additions & 1 deletion api/api/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,45 @@
from django.test import TestCase
from .models import Department, Discipline,Class

# Create your tests here.
class DisciplineModelsTest(TestCase):
def setUp(self):
self.department = Department.objects.create(
code = 'INF',
year = "2023",
period = "2"
)

self.discipline = Discipline.objects.create(
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
)


def test_create_discipline(self):
self.assertEqual(self.discipline.name, 'Métodos de Desenvolvimento de Software')
self.assertEqual(self.discipline.code, 'MDS1010')
self.assertEqual(self.discipline.department, self.department)

def test_create_class(self):
self.assertEqual(self._class.workload, 60)
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._class, "1")
self.assertEqual(self._class.discipline, self.discipline)

def test_create_department(self):
self.assertEqual(self.department.code, 'INF')
self.assertEqual(self.department.year, '2023')
self.assertEqual(self.department.period, '2')
31 changes: 31 additions & 0 deletions api/api/utils/db_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from api.models import Discipline, Department, Class

""" Este módulo lida com as operações de banco de dados."""


def get_or_create_department(code: str, year: str, period: str) -> Department:
"""Cria um departamento."""
return Department.objects.get_or_create(code=code, year=year, period=period)[0]


def get_or_create_discipline(name: str, code: str, department: Department) -> Discipline:
"""Cria uma disciplina."""
return Discipline.objects.get_or_create(name=name, code=code, department=department)[0]


def create_class(workload: int, teachers: list, classroom: str, schedule: str,
days: list, _class: str, discipline: Discipline) -> Class:
"""Cria uma turma de uma disciplina."""
return Class.objects.create(workload=workload, teachers=teachers, classroom=classroom, schedule=schedule,
days=days, _class=_class, discipline=discipline)

def delete_classes_from_discipline(discipline: Discipline) -> None:
"""Deleta todas as turmas de uma disciplina."""
Class.objects.filter(discipline=discipline).delete()

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



51 changes: 42 additions & 9 deletions api/api/utils/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
from datetime import datetime
from typing import List

"""Este módulo contém funções necessárias para realizar uma requsição ao SIGAA
corretamente.
"""

URL = "https://sigaa.unb.br/sigaa/public/turmas/listar.jsf"
HEADERS = {
'Access-Control-Allow-Origin': '*',
Expand All @@ -13,7 +17,7 @@
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0'
}

'''Create a request session with retry and backoff_factor'''
"""Cria uma sessão de requisição e retorna um objeto Session."""
def create_request_session() -> Session:
session = Session() # Create a persistent request session
retry = Retry(connect=3, backoff_factor=0.5) # Create a retry object
Expand All @@ -22,29 +26,58 @@ def create_request_session() -> Session:

return session

'''Get the response from the request session'''
"""Obtem a resposta da requisição ao SIGAA e retorna um objeto Response."""
def get_response(session: Session) -> Response:
response = session.get(url=URL, headers=HEADERS) # Make a get request to the url

return response

"""Obtem o cookie da sessão de requisição necessário para acessar a pagina de turmas
e retorna um cookie jar."""
def get_session_cookie(session: Session) -> cookies.RequestsCookieJar:
'''Get the cookie from the request session'''
response = get_response(session) # Get the response from the request session
cookie = response.cookies.get_dict() # Get the cookie from the response
cookie_jar = cookies.RequestsCookieJar() # Create a cookie jar
cookie_jar.update(cookie) # Update the cookie jar with the cookie

return cookie_jar

def get_current_year_and_period() -> List[int | str]:
# Pega o ano e o período atual
current_date = datetime.now()
"""Obtem o ano e o período atual e retorna uma lista com esses valores."""
def get_current_year_and_period() -> List[str | str]:
current_date = datetime.now()
current_year = current_date.year
period = "1"

# Se a data atual estiver entre 1 de maio e 30 de outubro, o período é 2.
if datetime(current_year, 5, 1) <= current_date <= datetime(current_year, 10, 30):
# Se a data atual estiver entre 1 de maio e 30 de dezembro, o período é 2.
if datetime(current_year, 5, 1) <= current_date <= datetime(current_year, 12, 30):
period = "2"
# elif current_date > datetime(current_year, 10, 30):
# current_year += 1
# period = "1"

return [str(current_year), period]

"""Obtem o ano e o período seguinte e retorna uma lista com esses valores."""
def get_next_period() -> List[str | str]:
date = get_current_year_and_period()

if date[1] == "1":
date[1] = "2"
return date

date[0] = str(int(date[0]) + 1)
date[1] = "1"

return date

def get_previous_period() -> List[str | str]:
date = get_current_year_and_period()

if date[1] == "2":
date[1] = "1"
return date

date[0] = str(int(date[0]) - 1)
date[1] = "2"

return [current_year, period]
return date
Loading

0 comments on commit 4d2384f

Please sign in to comment.