diff --git a/app/callbacks/menu.py b/app/callbacks/menu.py
index 92ac2ef..43eadea 100644
--- a/app/callbacks/menu.py
+++ b/app/callbacks/menu.py
@@ -1,7 +1,6 @@
__all__ = ("router",)
from aiogram import F, Router
-from aiogram.exceptions import TelegramBadRequest
from aiogram.filters import StateFilter
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message
@@ -35,11 +34,6 @@ async def profile_callback(callback: CallbackQuery) -> None:
)
await callback.answer()
- try:
- await callback.message.delete()
- except TelegramBadRequest:
- pass
-
@router.callback_query(
F.data == "menu_create_travel",
@@ -63,11 +57,6 @@ async def create_travel_callback(
await callback.answer()
- try:
- await callback.message.delete()
- except TelegramBadRequest:
- pass
-
@router.callback_query(
F.data == "menu_travels",
@@ -101,9 +90,4 @@ async def travels_callback(
),
)
- try:
- await callback.message.delete()
- except TelegramBadRequest:
- pass
-
await callback.answer()
diff --git a/app/callbacks/profile.py b/app/callbacks/profile.py
index 5866616..7087ff1 100644
--- a/app/callbacks/profile.py
+++ b/app/callbacks/profile.py
@@ -102,7 +102,7 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
if column == "username":
try:
- validated_value = User().validate_username(
+ validated_title = User().validate_username(
key="username",
value=value,
)
@@ -111,7 +111,7 @@ async def profile_change_entered(message: Message, state: FSMContext) -> None:
return
- await state.update_data(value=validated_value, successfully=True)
+ await state.update_data(value=validated_title, successfully=True)
elif column == "age":
try:
validated_age = User().validate_age(key="age", value=value)
diff --git a/app/callbacks/travels.py b/app/callbacks/travels.py
index 4a6dd31..fdb5eac 100644
--- a/app/callbacks/travels.py
+++ b/app/callbacks/travels.py
@@ -3,20 +3,54 @@ __all__ = ("router",)
from aiogram import F, Router
from aiogram.exceptions import TelegramBadRequest
from aiogram.filters import StateFilter
+from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message
-from app import messages
+from app import messages, session
from app.config import Config
-from app.filters.user import RegisteredCallback
+from app.filters.user import Registered, RegisteredCallback
from app.keyboards.builders import travels_keyboard
from app.keyboards.travel import get
from app.models.travel import Travel
from app.models.user import User
+from app.states.travel import TravelAlteringState
+from app.utils.states import delete_message_from_state, handle_validation_error
router = Router(name="menu_callback")
+@router.callback_query(
+ F.data == "travels",
+ RegisteredCallback(),
+ StateFilter(None),
+)
+async def travels_index_callback(callback: CallbackQuery) -> None:
+ page = 0
+
+ if callback.from_user is None or not isinstance(callback.message, Message):
+ return
+
+ user = User().get_user_by_telegram_id(callback.from_user.id)
+
+ travels = user.get_user_travels()
+
+ if not travels or travels == []:
+ await callback.message.edit_text(messages.NO_TRAVELS)
+ else:
+ pages = (len(travels) + Config.PAGE_SIZE - 1) // Config.PAGE_SIZE
+
+ await callback.message.edit_text(
+ messages.TRAVELS,
+ reply_markup=travels_keyboard(
+ travels,
+ page,
+ pages,
+ user.telegram_id,
+ ),
+ )
+
+
@router.callback_query(
F.data.startswith("travels_page"),
RegisteredCallback(),
@@ -71,3 +105,138 @@ async def travel_detail_callback(callback: CallbackQuery) -> None:
travel.get_travel_text(),
reply_markup=get(travel_id),
)
+
+
+@router.callback_query(
+ F.data.startswith("travel_change"),
+ RegisteredCallback(),
+ StateFilter(None),
+)
+async def travel_change_callback(
+ callback: CallbackQuery,
+ state: FSMContext,
+) -> None:
+ if (
+ callback.data is None
+ or callback.message is None
+ or not isinstance(callback.message, Message)
+ ):
+ return
+
+ travel_id, column = callback.data.replace("travel_change_", "").split("_")
+
+ if column == "title":
+ message = await callback.message.answer(
+ f"{messages.INPUT_TRAVEL_TITLE}\n{messages.CANCEL_CHANGE}",
+ )
+ elif column == "description":
+ message = await callback.message.answer(
+ f"{messages.EDIT_TRAVEL_DESCRIPTION}\n{messages.CANCEL_CHANGE}",
+ )
+
+ await state.update_data(
+ column=column,
+ travel_message_id=callback.message.message_id,
+ input_message_id=message.message_id,
+ travel_id=travel_id,
+ )
+ await state.set_state(TravelAlteringState.value)
+
+ await callback.answer()
+
+
+@router.message(TravelAlteringState.value, F.text, Registered())
+async def travel_change_entered(message: Message, state: FSMContext) -> None:
+ if (
+ message.text is None
+ or message.from_user is None
+ or message.bot is None
+ ):
+ return
+
+ data = await state.get_data()
+
+ column = data["column"]
+ travel_id = data["travel_id"]
+ value = message.text.strip()
+
+ if value == "/cancel":
+ await message.answer(
+ messages.CHANGE_CANCELED,
+ )
+
+ await state.update_data(successfully=True)
+ await message.delete()
+ await delete_message_from_state(
+ state,
+ message.chat.id,
+ message.bot,
+ )
+ await state.clear()
+
+ return
+
+ if column == "title":
+ try:
+ validated_title = Travel().validate_title(
+ key="title",
+ value=value,
+ )
+ except AssertionError as e:
+ await handle_validation_error(message, state, e)
+
+ return
+
+ await state.update_data(value=validated_title, successfully=True)
+ elif column == "description":
+ if value == "/skip":
+ await state.update_data(value=None, successfully=True)
+ await delete_message_from_state(
+ state,
+ message.chat.id,
+ message.bot,
+ )
+ else:
+ try:
+ validated_description = Travel().validate_description(
+ key="description", value=value,
+ )
+ except AssertionError as e:
+ await handle_validation_error(message, state, e)
+
+ return
+
+ await state.update_data(
+ value=validated_description, successfully=True,
+ )
+
+ await message.delete()
+ await delete_message_from_state(state, message.chat.id, message.bot)
+
+ state_data = await state.get_data()
+
+ travel = Travel().get_travel_queryset_by_id(travel_id)
+
+ data = {state_data["column"]: state_data["value"]}
+ travel.update(data)
+
+ session.commit()
+
+ travel = travel.first()
+ session.refresh(travel)
+
+ try:
+ await message.bot.edit_message_text(
+ travel.get_travel_text(),
+ message.chat.id,
+ state_data["travel_message_id"],
+ reply_markup=get(travel_id),
+ )
+ except TelegramBadRequest:
+ pass
+
+ await message.answer(
+ messages.TRAVEL_UPDATED,
+ )
+
+ await state.clear()
diff --git a/app/handlers/travels_command.py b/app/handlers/travels_command.py
index daf826d..a27af10 100644
--- a/app/handlers/travels_command.py
+++ b/app/handlers/travels_command.py
@@ -15,7 +15,7 @@ router = Router(name="travels_command")
@router.message(Command("travels"), Registered(), StateFilter(None))
-async def command_help_handler(message: Message) -> None:
+async def command_travels_handler(message: Message) -> None:
page = 0
if message.from_user is None:
diff --git a/app/keyboards/travel.py b/app/keyboards/travel.py
index 7a005a7..7cb57bb 100644
--- a/app/keyboards/travel.py
+++ b/app/keyboards/travel.py
@@ -17,6 +17,16 @@ def get(travel_id: int):
callback_data=f"travel_change_{travel_id}_description",
),
)
+ builder.row(
+ types.InlineKeyboardButton(
+ text="πΊοΈ Locations",
+ callback_data=f"travel_locations_{travel_id}",
+ ),
+ types.InlineKeyboardButton(
+ text="π€ Users",
+ callback_data=f"travel_users_{travel_id}",
+ ),
+ )
builder.row(
types.InlineKeyboardButton(
text="β Add location",
@@ -30,7 +40,7 @@ def get(travel_id: int):
builder.row(
types.InlineKeyboardButton(
text="β¬
οΈ",
- callback_data="menu_travels",
+ callback_data="travels",
),
)
diff --git a/app/messages.py b/app/messages.py
index 15db843..e4bce33 100644
--- a/app/messages.py
+++ b/app/messages.py
@@ -7,6 +7,8 @@ NO_TRAVELS = "No travels yet. You can create one with /create_travel command."
CREATE_TRAVEL = (
"π§³ Let's create new travel!\nEnter /cancel to cancel creating."
)
+TRAVEL_UPDATED = "β
Travel updated"
+EDIT_TRAVEL_DESCRIPTION = "Enter travel description (enter /skip if you want to set it to None):\nMaximum length: 100 characters"
INPUT_TRAVEL_TITLE = (
"Enter travel title:\nMaximum length: 30 characters"
)
diff --git a/app/models/travel.py b/app/models/travel.py
index 04bd229..509cd48 100644
--- a/app/models/travel.py
+++ b/app/models/travel.py
@@ -74,13 +74,19 @@ class Travel(Base):
def get_travel_text(self):
return messages.TRAVEL_DETAIL.format(
title=self.title,
- description=self.description,
+ description=(
+ self.description if self.description else messages.NOT_SET
+ ),
)
@classmethod
def get_travel_by_id(cls, travel_id):
return session.query(Travel).filter(Travel.id == travel_id).first()
+ @classmethod
+ def get_travel_queryset_by_id(cls, travel_id):
+ return session.query(Travel).filter(Travel.id == travel_id)
+
class Location(Base):
__tablename__ = "locations"
diff --git a/app/models/user.py b/app/models/user.py
index 3f34719..e97b12c 100644
--- a/app/models/user.py
+++ b/app/models/user.py
@@ -98,7 +98,8 @@ class User(Base):
return normalized_value
def get_user_travels(self) -> list:
- return self.owned_travels + self.travels
+ all_travels = self.owned_travels + self.travels
+ return sorted(all_travels, key=lambda travel: travel.id)
def get_human_readable_datejoined(self) -> str:
return self.date_joined.strftime("%Y-%m-%d %H:%M:%S")
diff --git a/app/states/travel.py b/app/states/travel.py
index 6ed1f8b..3543954 100644
--- a/app/states/travel.py
+++ b/app/states/travel.py
@@ -7,3 +7,13 @@ class TravelCreationState(StatesGroup):
error_message_id = State()
title = State()
description = State()
+
+
+class TravelAlteringState(StatesGroup):
+ travel_message_id = State()
+ input_message_id = State()
+ error_message_id = State()
+ successfully = State()
+ travel_id = State()
+ column = State()
+ value = State()