mirror of https://github.com/stijndcl/didier
Displaying deadlines
parent
a510e2fe4a
commit
107e4fb580
|
@ -0,0 +1,39 @@
|
||||||
|
"""Deadlines
|
||||||
|
|
||||||
|
Revision ID: 08d21b2d1a0a
|
||||||
|
Revises: 3962636f3a3d
|
||||||
|
Create Date: 2022-08-12 23:44:13.947011
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = "08d21b2d1a0a"
|
||||||
|
down_revision = "3962636f3a3d"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table(
|
||||||
|
"deadlines",
|
||||||
|
sa.Column("deadline_id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("course_id", sa.Integer(), nullable=True),
|
||||||
|
sa.Column("name", sa.Text(), nullable=False),
|
||||||
|
sa.Column("deadline", sa.DateTime(timezone=True), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["course_id"],
|
||||||
|
["ufora_courses.course_id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("deadline_id"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table("deadlines")
|
||||||
|
# ### end Alembic commands ###
|
|
@ -0,0 +1,31 @@
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
|
from dateutil.parser import parse
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
|
from database.schemas.relational import Deadline
|
||||||
|
|
||||||
|
__all__ = ["add_deadline", "get_deadlines"]
|
||||||
|
|
||||||
|
|
||||||
|
async def add_deadline(session: AsyncSession, course_id: int, name: str, date_str: str):
|
||||||
|
"""Add a new deadline"""
|
||||||
|
date_dt = parse(date_str).replace(tzinfo=ZoneInfo("Europe/Brussels"))
|
||||||
|
|
||||||
|
if date_dt.hour == date_dt.minute == date_dt.second == 0:
|
||||||
|
date_dt.replace(hour=23, minute=59, second=59)
|
||||||
|
|
||||||
|
deadline = Deadline(course_id=course_id, name=name, deadline=date_dt)
|
||||||
|
session.add(deadline)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_deadlines(session: AsyncSession) -> list[Deadline]:
|
||||||
|
"""Get a list of all deadlines that are currently known
|
||||||
|
|
||||||
|
This includes deadlines that have passed already
|
||||||
|
"""
|
||||||
|
statement = select(Deadline).options(selectinload(Deadline.course))
|
||||||
|
return (await session.execute(statement)).scalars().all()
|
|
@ -28,6 +28,7 @@ __all__ = [
|
||||||
"CustomCommand",
|
"CustomCommand",
|
||||||
"CustomCommandAlias",
|
"CustomCommandAlias",
|
||||||
"DadJoke",
|
"DadJoke",
|
||||||
|
"Deadline",
|
||||||
"Link",
|
"Link",
|
||||||
"NightlyData",
|
"NightlyData",
|
||||||
"Task",
|
"Task",
|
||||||
|
@ -110,6 +111,19 @@ class DadJoke(Base):
|
||||||
joke: str = Column(Text, nullable=False)
|
joke: str = Column(Text, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Deadline(Base):
|
||||||
|
"""A deadline for a university project"""
|
||||||
|
|
||||||
|
__tablename__ = "deadlines"
|
||||||
|
|
||||||
|
deadline_id: int = Column(Integer, primary_key=True)
|
||||||
|
course_id: int = Column(Integer, ForeignKey("ufora_courses.course_id"))
|
||||||
|
name: str = Column(Text, nullable=False)
|
||||||
|
deadline: datetime = Column(DateTime(timezone=True), nullable=False)
|
||||||
|
|
||||||
|
course: UforaCourse = relationship("UforaCourse", back_populates="deadlines", uselist=False, lazy="selectin")
|
||||||
|
|
||||||
|
|
||||||
class Link(Base):
|
class Link(Base):
|
||||||
"""Useful links that go useful places"""
|
"""Useful links that go useful places"""
|
||||||
|
|
||||||
|
@ -160,6 +174,9 @@ class UforaCourse(Base):
|
||||||
aliases: list[UforaCourseAlias] = relationship(
|
aliases: list[UforaCourseAlias] = relationship(
|
||||||
"UforaCourseAlias", back_populates="course", cascade="all, delete-orphan", lazy="selectin"
|
"UforaCourseAlias", back_populates="course", cascade="all, delete-orphan", lazy="selectin"
|
||||||
)
|
)
|
||||||
|
deadlines: list[Deadline] = relationship(
|
||||||
|
"Deadline", back_populates="course", cascade="all, delete-orphan", lazy="selectin"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UforaCourseAlias(Base):
|
class UforaCourseAlias(Base):
|
||||||
|
|
|
@ -5,7 +5,9 @@ from discord import app_commands
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from database.crud import ufora_courses
|
from database.crud import ufora_courses
|
||||||
|
from database.crud.deadlines import get_deadlines
|
||||||
from didier import Didier
|
from didier import Didier
|
||||||
|
from didier.data.embeds.deadlines import Deadlines
|
||||||
from didier.utils.discord.flags.school import StudyGuideFlags
|
from didier.utils.discord.flags.school import StudyGuideFlags
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,6 +29,15 @@ class School(commands.Cog):
|
||||||
"""Remove the commands when the cog is unloaded"""
|
"""Remove the commands when the cog is unloaded"""
|
||||||
self.client.tree.remove_command(self._pin_ctx_menu.name, type=self._pin_ctx_menu.type)
|
self.client.tree.remove_command(self._pin_ctx_menu.name, type=self._pin_ctx_menu.type)
|
||||||
|
|
||||||
|
@commands.hybrid_command(name="deadlines", description="Show upcoming deadlines")
|
||||||
|
async def deadlines(self, ctx: commands.Context):
|
||||||
|
"""Show upcoming deadlines"""
|
||||||
|
async with self.client.postgres_session as session:
|
||||||
|
deadlines = await get_deadlines(session)
|
||||||
|
|
||||||
|
embed = await Deadlines(deadlines).to_embed()
|
||||||
|
await ctx.reply(embed=embed, mention_author=False, ephemeral=False)
|
||||||
|
|
||||||
@commands.command(name="Pin", usage="[Message]")
|
@commands.command(name="Pin", usage="[Message]")
|
||||||
async def pin(self, ctx: commands.Context, message: Optional[discord.Message] = None):
|
async def pin(self, ctx: commands.Context, message: Optional[discord.Message] = None):
|
||||||
"""Pin a message in the current channel"""
|
"""Pin a message in the current channel"""
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import itertools
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from overrides import overrides
|
||||||
|
|
||||||
|
from database.schemas.relational import Deadline
|
||||||
|
from didier.data.embeds.base import EmbedBaseModel
|
||||||
|
from didier.utils.types.datetime import tz_aware_now
|
||||||
|
from didier.utils.types.string import get_edu_year_name
|
||||||
|
|
||||||
|
__all__ = ["Deadlines"]
|
||||||
|
|
||||||
|
|
||||||
|
class Deadlines(EmbedBaseModel):
|
||||||
|
"""Embed that shows all the deadlines of a semester"""
|
||||||
|
|
||||||
|
deadlines: list[Deadline]
|
||||||
|
|
||||||
|
def __init__(self, deadlines: list[Deadline]):
|
||||||
|
self.deadlines = deadlines
|
||||||
|
self.deadlines.sort(key=lambda deadline: deadline.deadline)
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
async def to_embed(self, **kwargs: dict) -> discord.Embed:
|
||||||
|
embed = discord.Embed(colour=discord.Colour.dark_gold())
|
||||||
|
embed.set_author(name="Upcoming Deadlines")
|
||||||
|
now = tz_aware_now()
|
||||||
|
|
||||||
|
has_active_deadlines = False
|
||||||
|
deadlines_grouped: dict[int, list[str]] = {}
|
||||||
|
|
||||||
|
deadline: Deadline
|
||||||
|
for year, deadline in itertools.groupby(self.deadlines, key=lambda _deadline: _deadline.course.year):
|
||||||
|
if year not in deadlines_grouped:
|
||||||
|
deadlines_grouped[year] = []
|
||||||
|
|
||||||
|
passed = deadline.deadline <= now
|
||||||
|
if passed:
|
||||||
|
has_active_deadlines = True
|
||||||
|
|
||||||
|
deadline_str = (
|
||||||
|
f"{deadline.course.name} - {deadline.name}: <t:{round(datetime.timestamp(deadline.deadline))}:R>"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Strike through deadlines that aren't active anymore
|
||||||
|
deadlines_grouped[year].append(deadline_str if not passed else f"~~{deadline_str}~~")
|
||||||
|
|
||||||
|
if not has_active_deadlines:
|
||||||
|
embed.description = "There are currently no upcoming deadlines."
|
||||||
|
embed.set_image(url="https://c.tenor.com/RUzJ3lDGQUsAAAAC/iron-man-you-can-rest-now.gif")
|
||||||
|
return embed
|
||||||
|
|
||||||
|
for i in range(5):
|
||||||
|
if i not in deadlines_grouped:
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = get_edu_year_name(i)
|
||||||
|
description = "\n".join(deadlines_grouped[i])
|
||||||
|
|
||||||
|
embed.add_field(name=name, value=description, inline=False)
|
||||||
|
|
||||||
|
return embed
|
|
@ -1,7 +1,7 @@
|
||||||
import math
|
import math
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
__all__ = ["abbreviate", "leading", "pluralize"]
|
__all__ = ["abbreviate", "leading", "pluralize", "get_edu_year_name"]
|
||||||
|
|
||||||
|
|
||||||
def abbreviate(text: str, max_length: int) -> str:
|
def abbreviate(text: str, max_length: int) -> str:
|
||||||
|
@ -43,3 +43,10 @@ def pluralize(word: str, amount: int, plural_form: Optional[str] = None) -> str:
|
||||||
return word
|
return word
|
||||||
|
|
||||||
return plural_form or (word + "s")
|
return plural_form or (word + "s")
|
||||||
|
|
||||||
|
|
||||||
|
def get_edu_year_name(year: int) -> str:
|
||||||
|
"""Get the string representation of a university year"""
|
||||||
|
years = ["1st Bachelor", "2nd Bachelor", "3rd Bachelor", "1st Master", "2nd Master"]
|
||||||
|
|
||||||
|
return years[year]
|
||||||
|
|
Loading…
Reference in New Issue