You've already forked Travel-Agent
feat: Added travel deletion and added ability to add locations
This commit is contained in:
+375
-26
@@ -1,5 +1,7 @@
|
|||||||
__all__ = ("router",)
|
__all__ = ("router",)
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
from aiogram import F, Router
|
from aiogram import F, Router
|
||||||
from aiogram.exceptions import TelegramBadRequest
|
from aiogram.exceptions import TelegramBadRequest
|
||||||
from aiogram.filters import StateFilter
|
from aiogram.filters import StateFilter
|
||||||
@@ -10,10 +12,11 @@ from app import messages, session
|
|||||||
from app.config import Config
|
from app.config import Config
|
||||||
from app.filters.user import Registered, RegisteredCallback
|
from app.filters.user import Registered, RegisteredCallback
|
||||||
from app.keyboards.builders import travels_keyboard
|
from app.keyboards.builders import travels_keyboard
|
||||||
from app.keyboards.travel import get
|
from app.keyboards.confirm_location import get as confirm_location_get
|
||||||
from app.models.travel import Travel
|
from app.keyboards.travel import get as travel_get
|
||||||
|
from app.models.travel import Location, Travel
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
from app.states.travel import TravelAlteringState
|
from app.states.travel import CreateLocationState, TravelAlteringState
|
||||||
from app.utils.states import delete_message_from_state, handle_validation_error
|
from app.utils.states import delete_message_from_state, handle_validation_error
|
||||||
|
|
||||||
|
|
||||||
@@ -36,19 +39,25 @@ async def travels_index_callback(callback: CallbackQuery) -> None:
|
|||||||
travels = user.get_user_travels()
|
travels = user.get_user_travels()
|
||||||
|
|
||||||
if not travels or travels == []:
|
if not travels or travels == []:
|
||||||
await callback.message.edit_text(messages.NO_TRAVELS)
|
try:
|
||||||
|
await callback.message.edit_text(messages.NO_TRAVELS)
|
||||||
|
except TelegramBadRequest:
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
pages = (len(travels) + Config.PAGE_SIZE - 1) // Config.PAGE_SIZE
|
pages = (len(travels) + Config.PAGE_SIZE - 1) // Config.PAGE_SIZE
|
||||||
|
|
||||||
await callback.message.edit_text(
|
try:
|
||||||
messages.TRAVELS,
|
await callback.message.edit_text(
|
||||||
reply_markup=travels_keyboard(
|
messages.TRAVELS,
|
||||||
travels,
|
reply_markup=travels_keyboard(
|
||||||
page,
|
travels,
|
||||||
pages,
|
page,
|
||||||
user.telegram_id,
|
pages,
|
||||||
),
|
user.telegram_id,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
except TelegramBadRequest:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
@@ -74,15 +83,18 @@ async def travels_callback(callback: CallbackQuery) -> None:
|
|||||||
else:
|
else:
|
||||||
pages = (len(travels) + Config.PAGE_SIZE - 1) // Config.PAGE_SIZE
|
pages = (len(travels) + Config.PAGE_SIZE - 1) // Config.PAGE_SIZE
|
||||||
|
|
||||||
await callback.message.edit_text(
|
try:
|
||||||
messages.TRAVELS,
|
await callback.message.edit_text(
|
||||||
reply_markup=travels_keyboard(
|
messages.TRAVELS,
|
||||||
travels,
|
reply_markup=travels_keyboard(
|
||||||
page,
|
travels,
|
||||||
pages,
|
page,
|
||||||
user.telegram_id,
|
pages,
|
||||||
),
|
user.telegram_id,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
|
except TelegramBadRequest:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(
|
@router.callback_query(
|
||||||
@@ -103,7 +115,7 @@ async def travel_detail_callback(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
travel.get_travel_text(),
|
travel.get_travel_text(),
|
||||||
reply_markup=get(travel_id),
|
reply_markup=travel_get(travel_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -125,6 +137,11 @@ async def travel_change_callback(
|
|||||||
|
|
||||||
travel_id, column = callback.data.replace("travel_change_", "").split("_")
|
travel_id, column = callback.data.replace("travel_change_", "").split("_")
|
||||||
|
|
||||||
|
travel = Travel().get_travel_by_id(travel_id)
|
||||||
|
|
||||||
|
if not travel:
|
||||||
|
return
|
||||||
|
|
||||||
if column == "title":
|
if column == "title":
|
||||||
message = await callback.message.answer(
|
message = await callback.message.answer(
|
||||||
f"{messages.INPUT_TRAVEL_TITLE}\n{messages.CANCEL_CHANGE}",
|
f"{messages.INPUT_TRAVEL_TITLE}\n{messages.CANCEL_CHANGE}",
|
||||||
@@ -199,7 +216,8 @@ async def travel_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
validated_description = Travel().validate_description(
|
validated_description = Travel().validate_description(
|
||||||
key="description", value=value,
|
key="description",
|
||||||
|
value=value,
|
||||||
)
|
)
|
||||||
except AssertionError as e:
|
except AssertionError as e:
|
||||||
await handle_validation_error(message, state, e)
|
await handle_validation_error(message, state, e)
|
||||||
@@ -207,7 +225,8 @@ async def travel_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
await state.update_data(
|
await state.update_data(
|
||||||
value=validated_description, successfully=True,
|
value=validated_description,
|
||||||
|
successfully=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
await message.delete()
|
await message.delete()
|
||||||
@@ -230,7 +249,7 @@ async def travel_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
travel.get_travel_text(),
|
travel.get_travel_text(),
|
||||||
message.chat.id,
|
message.chat.id,
|
||||||
state_data["travel_message_id"],
|
state_data["travel_message_id"],
|
||||||
reply_markup=get(travel_id),
|
reply_markup=travel_get(travel_id),
|
||||||
)
|
)
|
||||||
except TelegramBadRequest:
|
except TelegramBadRequest:
|
||||||
pass
|
pass
|
||||||
@@ -240,3 +259,333 @@ async def travel_change_entered(message: Message, state: FSMContext) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(
|
||||||
|
F.data.startswith("travel_add_location"),
|
||||||
|
RegisteredCallback(),
|
||||||
|
StateFilter(None),
|
||||||
|
)
|
||||||
|
async def add_travel_location_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 = int(callback.data.replace("travel_add_location_", ""))
|
||||||
|
|
||||||
|
travel = Travel().get_travel_by_id(travel_id)
|
||||||
|
|
||||||
|
if not travel:
|
||||||
|
return
|
||||||
|
|
||||||
|
await state.update_data(travel_id=travel_id)
|
||||||
|
await state.set_state(CreateLocationState.temp_location)
|
||||||
|
|
||||||
|
await callback.message.answer(
|
||||||
|
messages.CREATE_LOCATION,
|
||||||
|
)
|
||||||
|
await callback.message.answer(
|
||||||
|
messages.ENTER_LOCATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(CreateLocationState.temp_location, F.text, Registered())
|
||||||
|
async def location_entered(message: Message, state: FSMContext) -> None:
|
||||||
|
if (
|
||||||
|
message.text is None
|
||||||
|
or message.from_user is None
|
||||||
|
or message.bot is None
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
location = message.text.strip()
|
||||||
|
|
||||||
|
if location == "/cancel":
|
||||||
|
await message.answer(
|
||||||
|
messages.ACTION_CANCELED,
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.update_data()
|
||||||
|
await message.delete()
|
||||||
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
validated_location = Location().validate_location(
|
||||||
|
key="location",
|
||||||
|
value=location,
|
||||||
|
)
|
||||||
|
except AssertionError as e:
|
||||||
|
await handle_validation_error(message, state, e)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
|
await state.update_data(
|
||||||
|
temp_location=validated_location,
|
||||||
|
temp_location_message_id=message.message_id,
|
||||||
|
)
|
||||||
|
await state.set_state(CreateLocationState.location)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
messages.CONFIRM_LOCATION.format(location=validated_location),
|
||||||
|
reply_markup=confirm_location_get(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(
|
||||||
|
F.data.in_(["confirm_location", "cancel_location"]),
|
||||||
|
RegisteredCallback(),
|
||||||
|
StateFilter(CreateLocationState.location),
|
||||||
|
)
|
||||||
|
async def confirm_location(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
state: FSMContext,
|
||||||
|
) -> None:
|
||||||
|
if (
|
||||||
|
not callback.message
|
||||||
|
or not isinstance(callback.message, Message)
|
||||||
|
or callback.bot is None
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
|
location = data.get("temp_location")
|
||||||
|
|
||||||
|
if callback.data == "confirm_location":
|
||||||
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
callback.from_user.id,
|
||||||
|
callback.bot,
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.update_data(location=location)
|
||||||
|
await state.set_state(CreateLocationState.date_start)
|
||||||
|
|
||||||
|
await callback.message.answer(
|
||||||
|
messages.INPUT_TRAVEL_CALLBACK.format(
|
||||||
|
key="location",
|
||||||
|
value=location,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
await callback.message.answer(
|
||||||
|
messages.ENTER_LOCATION_DATE_START,
|
||||||
|
)
|
||||||
|
elif callback.data == "cancel_location":
|
||||||
|
error_message = await callback.message.answer(
|
||||||
|
messages.CONFIRMATION_REEJECTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.bot.delete_message(
|
||||||
|
callback.from_user.id,
|
||||||
|
data["temp_location_message_id"],
|
||||||
|
)
|
||||||
|
except TelegramBadRequest:
|
||||||
|
pass
|
||||||
|
|
||||||
|
await state.set_state(CreateLocationState.temp_location)
|
||||||
|
await state.update_data(error_message_id=error_message.message_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await callback.message.delete()
|
||||||
|
except TelegramBadRequest:
|
||||||
|
pass
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(CreateLocationState.date_start, F.text, Registered())
|
||||||
|
async def location_date_start_entered(
|
||||||
|
message: Message,
|
||||||
|
state: FSMContext,
|
||||||
|
) -> None:
|
||||||
|
if (
|
||||||
|
message.text is None
|
||||||
|
or message.from_user is None
|
||||||
|
or message.bot is None
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
date_start = message.text.strip()
|
||||||
|
|
||||||
|
if date_start == "/cancel":
|
||||||
|
await message.answer(
|
||||||
|
messages.ACTION_CANCELED,
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.update_data()
|
||||||
|
await message.delete()
|
||||||
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
validated_date_start = Location().validate_date_end(
|
||||||
|
key="date_start",
|
||||||
|
value=date_start,
|
||||||
|
)
|
||||||
|
except AssertionError as e:
|
||||||
|
await handle_validation_error(message, state, e)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
|
await state.update_data(
|
||||||
|
date_start=datetime.datetime.strftime(
|
||||||
|
validated_date_start,
|
||||||
|
"%Y-%m-%d %H:%M:%S",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
await state.set_state(CreateLocationState.date_end)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
messages.INPUT_TRAVEL_CALLBACK.format(
|
||||||
|
key="start date",
|
||||||
|
value=date_start,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
await message.answer(
|
||||||
|
messages.ENTER_LOCATION_DATE_END,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(CreateLocationState.date_end, F.text, Registered())
|
||||||
|
async def location_date_end_entered(
|
||||||
|
message: Message,
|
||||||
|
state: FSMContext,
|
||||||
|
) -> None:
|
||||||
|
if (
|
||||||
|
message.text is None
|
||||||
|
or message.from_user is None
|
||||||
|
or message.bot is None
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
date_end = message.text.strip()
|
||||||
|
|
||||||
|
if date_end == "/cancel":
|
||||||
|
await message.answer(
|
||||||
|
messages.ACTION_CANCELED,
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.update_data()
|
||||||
|
await message.delete()
|
||||||
|
await delete_message_from_state(
|
||||||
|
state,
|
||||||
|
message.chat.id,
|
||||||
|
message.bot,
|
||||||
|
)
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
validated_date_end = Location().validate_date_end(
|
||||||
|
key="date_end",
|
||||||
|
value=date_end,
|
||||||
|
)
|
||||||
|
except AssertionError as e:
|
||||||
|
await handle_validation_error(message, state, e)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
date_start = (await state.get_data()).get("date_start")
|
||||||
|
|
||||||
|
if validated_date_end <= datetime.datetime.strptime(
|
||||||
|
str(date_start),
|
||||||
|
"%Y-%m-%d %H:%M:%S",
|
||||||
|
).replace(tzinfo=datetime.UTC):
|
||||||
|
await handle_validation_error(
|
||||||
|
message,
|
||||||
|
state,
|
||||||
|
messages.INVALID_DATE_END,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
await delete_message_from_state(state, message.chat.id, message.bot)
|
||||||
|
|
||||||
|
await state.update_data(
|
||||||
|
date_end=datetime.datetime.strftime(
|
||||||
|
validated_date_end,
|
||||||
|
"%Y-%m-%d %H:%M:%S",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
|
|
||||||
|
if "temp_location" in data:
|
||||||
|
del data["temp_location"]
|
||||||
|
|
||||||
|
if "temp_location_message_id" in data:
|
||||||
|
del data["temp_location_message_id"]
|
||||||
|
|
||||||
|
if "error_message_id" in data:
|
||||||
|
del data["error_message_id"]
|
||||||
|
|
||||||
|
data["date_start"] = datetime.datetime.strptime(
|
||||||
|
data["date_start"],
|
||||||
|
"%Y-%m-%d %H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
|
data["date_end"] = datetime.datetime.strptime(
|
||||||
|
data["date_end"],
|
||||||
|
"%Y-%m-%d %H:%M:%S",
|
||||||
|
)
|
||||||
|
|
||||||
|
session.add(Location(**data))
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
messages.LOCATION_ADDED,
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router.callback_query(
|
||||||
|
F.data.startswith("travel_delete"),
|
||||||
|
RegisteredCallback(),
|
||||||
|
StateFilter(None),
|
||||||
|
)
|
||||||
|
async def delete_travel_callback(
|
||||||
|
callback: CallbackQuery,
|
||||||
|
):
|
||||||
|
if callback.data is None or not isinstance(callback.message, Message):
|
||||||
|
return
|
||||||
|
|
||||||
|
travel_id = int(callback.data.replace("travel_delete_", ""))
|
||||||
|
|
||||||
|
travel = Travel.get_travel_queryset_by_id(travel_id)
|
||||||
|
|
||||||
|
travel.delete()
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
await callback.message.answer(messages.DELETED_TRAVEL)
|
||||||
|
|
||||||
|
await callback.message.delete()
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
__all__ = ("get",)
|
||||||
|
|
||||||
|
from aiogram import types
|
||||||
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
|
|
||||||
|
def get():
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
builder.row(
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="Yes",
|
||||||
|
callback_data="confirm_location",
|
||||||
|
),
|
||||||
|
types.InlineKeyboardButton(text="No", callback_data="cancel_location"),
|
||||||
|
)
|
||||||
|
|
||||||
|
return builder.as_markup()
|
||||||
+21
-5
@@ -23,18 +23,34 @@ def get(travel_id: int):
|
|||||||
callback_data=f"travel_locations_{travel_id}",
|
callback_data=f"travel_locations_{travel_id}",
|
||||||
),
|
),
|
||||||
types.InlineKeyboardButton(
|
types.InlineKeyboardButton(
|
||||||
text="👤 Users",
|
text="➕ Add location",
|
||||||
callback_data=f"travel_users_{travel_id}",
|
callback_data=f"travel_add_location_{travel_id}",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
types.InlineKeyboardButton(
|
types.InlineKeyboardButton(
|
||||||
text="➕ Add location",
|
text="👤 Users",
|
||||||
callback_data=f"travel_add_{travel_id}_location",
|
callback_data=f"travel_users_{travel_id}",
|
||||||
),
|
),
|
||||||
types.InlineKeyboardButton(
|
types.InlineKeyboardButton(
|
||||||
text="➕ Add user",
|
text="➕ Add user",
|
||||||
callback_data=f"travel_add_{travel_id}_user",
|
callback_data=f"travel_add_user_{travel_id}",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
builder.row(
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="📝 Notes",
|
||||||
|
callback_data=f"travel_notes_{travel_id}",
|
||||||
|
),
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="➕ Add note",
|
||||||
|
callback_data=f"travel_add_note_{travel_id}",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
builder.row(
|
||||||
|
types.InlineKeyboardButton(
|
||||||
|
text="❌ Delete travel",
|
||||||
|
callback_data=f"travel_delete_{travel_id}",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
builder.row(
|
builder.row(
|
||||||
|
|||||||
@@ -2,6 +2,18 @@
|
|||||||
|
|
||||||
MENU = "<b>Menu:</b>"
|
MENU = "<b>Menu:</b>"
|
||||||
|
|
||||||
|
CREATE_LOCATION = "✈️ Lets create new location!"
|
||||||
|
ENTER_LOCATION = "Enter location:"
|
||||||
|
CONFIRM_LOCATION = "Is this location correct: <b>{location}</b>?"
|
||||||
|
CONFIRMATION_REEJECTED = (
|
||||||
|
"❌ Confirmation rejected. Please re-enter the location."
|
||||||
|
)
|
||||||
|
ENTER_LOCATION_DATE_START = "Enter location start datetime(in UTC) in this format:\n<i>Format: YYYY-MM-DD HH:MM</i>\n<i>Example: 2022-01-01 00:00</i>"
|
||||||
|
ENTER_LOCATION_DATE_END = "Enter location end datetime(in UTC) in this format:\n<i>Format: YYYY-MM-DD HH:MM</i>\n<i>Example: 2022-01-01 00:00</i>"
|
||||||
|
INVALID_DATE_END = "End date can't be earlier or equal to start date."
|
||||||
|
LOCATION_ADDED = "✅ Location added"
|
||||||
|
|
||||||
|
DELETED_TRAVEL = "✅ Travel deleted"
|
||||||
TRAVELS = "📃 <b>Travels:</b>\n<i>👑 - owner</i>"
|
TRAVELS = "📃 <b>Travels:</b>\n<i>👑 - owner</i>"
|
||||||
NO_TRAVELS = "No travels yet. You can create one with /create_travel command."
|
NO_TRAVELS = "No travels yet. You can create one with /create_travel command."
|
||||||
CREATE_TRAVEL = (
|
CREATE_TRAVEL = (
|
||||||
@@ -21,6 +33,7 @@ TRAVEL_CREATED = "Travel <b>{title}</b> successfully created! You can now view a
|
|||||||
ACTION_CANCELED = "❌ Action canceled"
|
ACTION_CANCELED = "❌ Action canceled"
|
||||||
TRAVEL_DETAIL = (
|
TRAVEL_DETAIL = (
|
||||||
"📝 <b>Travel detail</b>\n\n"
|
"📝 <b>Travel detail</b>\n\n"
|
||||||
|
"\tID: <b>{travel_id}</b>\n"
|
||||||
"\tTitle: <b>{title}</b>\n"
|
"\tTitle: <b>{title}</b>\n"
|
||||||
"\tDescription: <b>{description}</b>\n"
|
"\tDescription: <b>{description}</b>\n"
|
||||||
)
|
)
|
||||||
|
|||||||
+6
-6
@@ -1,8 +1,8 @@
|
|||||||
"""Added travel models
|
"""Added travel models
|
||||||
|
|
||||||
Revision ID: fe4ace4196fb
|
Revision ID: 4dea8f302149
|
||||||
Revises: 4914f00ae14a
|
Revises: 4914f00ae14a
|
||||||
Create Date: 2024-03-22 19:19:36.662090
|
Create Date: 2024-03-24 17:56:20.975589
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = 'fe4ace4196fb'
|
revision: str = '4dea8f302149'
|
||||||
down_revision: Union[str, None] = '4914f00ae14a'
|
down_revision: Union[str, None] = '4914f00ae14a'
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
@@ -38,9 +38,9 @@ def upgrade() -> None:
|
|||||||
op.create_table(
|
op.create_table(
|
||||||
'locations',
|
'locations',
|
||||||
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
|
||||||
sa.Column('name', sa.Text(), nullable=False),
|
sa.Column('location', sa.Text(), nullable=False),
|
||||||
sa.Column('date_start', sa.Date(), nullable=False),
|
sa.Column('date_start', sa.DateTime(timezone=True), nullable=False),
|
||||||
sa.Column('date_end', sa.Date(), nullable=False),
|
sa.Column('date_end', sa.DateTime(timezone=True), nullable=False),
|
||||||
sa.Column('travel_id', sa.Integer(), nullable=False),
|
sa.Column('travel_id', sa.Integer(), nullable=False),
|
||||||
sa.ForeignKeyConstraint(
|
sa.ForeignKeyConstraint(
|
||||||
['travel_id'],
|
['travel_id'],
|
||||||
+53
-3
@@ -1,11 +1,14 @@
|
|||||||
__all__ = ("Travel", "Location")
|
__all__ = ("Travel", "Location")
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.orm import relationship, validates
|
from sqlalchemy.orm import relationship, validates
|
||||||
|
|
||||||
from app import messages, session
|
from app import messages, session
|
||||||
from app.models import Base
|
from app.models import Base
|
||||||
from app.models.user import User
|
from app.models.user import User
|
||||||
|
from app.utils.geo import get_location_by_name
|
||||||
|
|
||||||
|
|
||||||
association_table = sa.Table(
|
association_table = sa.Table(
|
||||||
@@ -73,6 +76,7 @@ class Travel(Base):
|
|||||||
|
|
||||||
def get_travel_text(self):
|
def get_travel_text(self):
|
||||||
return messages.TRAVEL_DETAIL.format(
|
return messages.TRAVEL_DETAIL.format(
|
||||||
|
travel_id=self.id,
|
||||||
title=self.title,
|
title=self.title,
|
||||||
description=(
|
description=(
|
||||||
self.description if self.description else messages.NOT_SET
|
self.description if self.description else messages.NOT_SET
|
||||||
@@ -98,9 +102,15 @@ class Location(Base):
|
|||||||
autoincrement=True,
|
autoincrement=True,
|
||||||
index=True,
|
index=True,
|
||||||
)
|
)
|
||||||
name = sa.Column(sa.Text, nullable=False)
|
location = sa.Column(sa.Text, nullable=False)
|
||||||
date_start = sa.Column(sa.Date(), nullable=False)
|
date_start = sa.Column(
|
||||||
date_end = sa.Column(sa.Date(), nullable=False)
|
sa.DateTime(timezone=True),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
date_end = sa.Column(
|
||||||
|
sa.DateTime(timezone=True),
|
||||||
|
nullable=False,
|
||||||
|
)
|
||||||
|
|
||||||
travel_id = sa.Column(
|
travel_id = sa.Column(
|
||||||
sa.Integer,
|
sa.Integer,
|
||||||
@@ -108,6 +118,46 @@ class Location(Base):
|
|||||||
nullable=False,
|
nullable=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@validates("location")
|
||||||
|
def validate_location(self, key, value):
|
||||||
|
geocoder = get_location_by_name(value)
|
||||||
|
|
||||||
|
assert geocoder[0], "Invalid location."
|
||||||
|
|
||||||
|
return geocoder[1].raw["display_name"]
|
||||||
|
|
||||||
|
def validate_date_start(self, key, value):
|
||||||
|
try:
|
||||||
|
value_datetime = datetime.datetime.strptime(
|
||||||
|
value,
|
||||||
|
"%Y-%m-%d %H:%M",
|
||||||
|
)
|
||||||
|
value_datetime = value_datetime.replace(tzinfo=datetime.UTC)
|
||||||
|
except ValueError:
|
||||||
|
raise AssertionError("Invalid datetime format.")
|
||||||
|
|
||||||
|
assert value_datetime >= datetime.datetime.now(
|
||||||
|
datetime.UTC,
|
||||||
|
), "Invalid datetime."
|
||||||
|
|
||||||
|
return value_datetime
|
||||||
|
|
||||||
|
def validate_date_end(self, key, value):
|
||||||
|
try:
|
||||||
|
value_datetime = datetime.datetime.strptime(
|
||||||
|
value,
|
||||||
|
"%Y-%m-%d %H:%M",
|
||||||
|
)
|
||||||
|
value_datetime = value_datetime.replace(tzinfo=datetime.UTC)
|
||||||
|
except ValueError:
|
||||||
|
raise AssertionError("Invalid datetime format.")
|
||||||
|
|
||||||
|
assert value_datetime >= datetime.datetime.now(
|
||||||
|
datetime.UTC,
|
||||||
|
), "Invalid datetime."
|
||||||
|
|
||||||
|
return value_datetime
|
||||||
|
|
||||||
|
|
||||||
class Note(Base):
|
class Note(Base):
|
||||||
__tablename__ = "notes"
|
__tablename__ = "notes"
|
||||||
|
|||||||
@@ -17,3 +17,14 @@ class TravelAlteringState(StatesGroup):
|
|||||||
travel_id = State()
|
travel_id = State()
|
||||||
column = State()
|
column = State()
|
||||||
value = State()
|
value = State()
|
||||||
|
|
||||||
|
|
||||||
|
class CreateLocationState(StatesGroup):
|
||||||
|
temp_location_message_id = State()
|
||||||
|
error_message_id = State()
|
||||||
|
travel_id = State()
|
||||||
|
location = State()
|
||||||
|
temp_location = State()
|
||||||
|
location = State()
|
||||||
|
date_start = State()
|
||||||
|
date_end = State()
|
||||||
|
|||||||
+22
-1
@@ -1,5 +1,5 @@
|
|||||||
# type: ignore
|
# type: ignore
|
||||||
__all__ = ("validate_country", "validate_city")
|
__all__ = ("validate_country", "validate_city", "get_location_by_name")
|
||||||
|
|
||||||
from geopy.exc import GeocoderTimedOut
|
from geopy.exc import GeocoderTimedOut
|
||||||
from geopy.geocoders import Nominatim
|
from geopy.geocoders import Nominatim
|
||||||
@@ -72,3 +72,24 @@ def validate_city(city: str, country: str):
|
|||||||
return True, normalized_country
|
return True, normalized_country
|
||||||
|
|
||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
|
def get_location_by_name(location: str) -> None:
|
||||||
|
geolocator = Nominatim(user_agent="travel_agent_bot")
|
||||||
|
|
||||||
|
for _ in range(3):
|
||||||
|
try:
|
||||||
|
geocode = geolocator.geocode(
|
||||||
|
location,
|
||||||
|
featuretype="city",
|
||||||
|
)
|
||||||
|
break
|
||||||
|
except GeocoderTimedOut:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
if not geocode:
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
return True, geocode
|
||||||
|
|||||||
+1
-1
@@ -15,4 +15,4 @@ REDIS_PORT = <port_to_be_forwared> # default: 6379
|
|||||||
|
|
||||||
PGADMIN_PORT = <port_to_be_forwared> # default: 5050
|
PGADMIN_PORT = <port_to_be_forwared> # default: 5050
|
||||||
PGADMIN_EMAIL = <email> # default: admin@mail.com
|
PGADMIN_EMAIL = <email> # default: admin@mail.com
|
||||||
PGADMIN_PASSWORD <password> # default: admin
|
PGADMIN_PASSWORD = <password> # default: admin
|
||||||
|
|||||||
Reference in New Issue
Block a user