Merge pull request 'Lists with more functionality' (#3) from checklist into main
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #3
This commit is contained in:
Remy Moll 2023-06-23 11:02:23 +00:00
commit 86a9762f39
2 changed files with 126 additions and 69 deletions

View File

@ -1,16 +1,13 @@
import os import os
from pathlib import Path from pathlib import Path
from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler from telegram.ext import ConversationHandler, CommandHandler, MessageHandler, filters, CallbackQueryHandler, CallbackContext
from telegram import InlineKeyboardButton, InlineKeyboardMarkup from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from .models import ListModel, set_db, db from .models import ListModel, set_db, db
MEDIA_DIR = Path(os.getenv("MEDIA_DIR")) PERSISTENCE_DIR = Path(os.getenv("PERSISTENCE_DIR"))
DB_DIR = MEDIA_DIR / "lists_db"
DB_DIR.mkdir(parents=True, exist_ok=True)
NAME, NEW, ACTION, ITEMADD, ITEMREMOVE, ITEMTOGGLE = range(6)
NAME, NEW, ACTION, ITEMADD, ITEMREMOVE = range(5)
from ..basehandler import BaseHandler from ..basehandler import BaseHandler
@ -19,8 +16,20 @@ class ListHandler(BaseHandler):
"""Create and edit lists""" """Create and edit lists"""
def __init__(self, entry_string, models): def __init__(self, entry_string, models):
self.journal_models = models # not needed here del models # not needed here, but part of the template
self.entry_string = entry_string self.entry_string = entry_string
set_db(PERSISTENCE_DIR / "lists.sqlite")
self.list_overview_keyboard = [
[InlineKeyboardButton("Add item", callback_data="add")],
[InlineKeyboardButton("Toggle item", callback_data="toggle")],
[InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Delete list", callback_data="delete")],
]
self.handler = ConversationHandler( self.handler = ConversationHandler(
entry_points=[CommandHandler(entry_string, self.entry_point)], entry_points=[CommandHandler(entry_string, self.entry_point)],
states={ states={
@ -31,6 +40,7 @@ class ListHandler(BaseHandler):
NEW : [MessageHandler(filters.TEXT, callback=self.new_listname)], NEW : [MessageHandler(filters.TEXT, callback=self.new_listname)],
ACTION: [ ACTION: [
CallbackQueryHandler(self.list_add, pattern="^add$"), CallbackQueryHandler(self.list_add, pattern="^add$"),
CallbackQueryHandler(self.list_toggle, pattern="^toggle$"),
CallbackQueryHandler(self.list_remove, pattern="^remove$"), CallbackQueryHandler(self.list_remove, pattern="^remove$"),
CallbackQueryHandler(self.list_clear, pattern="^clear$"), CallbackQueryHandler(self.list_clear, pattern="^clear$"),
CallbackQueryHandler(self.list_delete, pattern="^delete$"), CallbackQueryHandler(self.list_delete, pattern="^delete$"),
@ -38,6 +48,7 @@ class ListHandler(BaseHandler):
CallbackQueryHandler(self.list_menu, pattern="^overview$"), CallbackQueryHandler(self.list_menu, pattern="^overview$"),
], ],
ITEMADD : [MessageHandler(filters.TEXT, callback=self.list_add_item)], ITEMADD : [MessageHandler(filters.TEXT, callback=self.list_add_item)],
ITEMTOGGLE: [CallbackQueryHandler(self.list_toggle_index)],
ITEMREMOVE : [CallbackQueryHandler(self.list_remove_index)] ITEMREMOVE : [CallbackQueryHandler(self.list_remove_index)]
}, },
fallbacks=[CommandHandler('list', self.entry_point)], fallbacks=[CommandHandler('list', self.entry_point)],
@ -46,10 +57,9 @@ class ListHandler(BaseHandler):
async def entry_point(self, update, context) -> None: async def entry_point(self, update, context) -> None:
await super().entry_point(update, context) await super().entry_point(update, context)
set_db(DB_DIR / f"chat_{update.message.chat_id}.db")
with db: with db:
lists = ListModel.select() lists = ListModel.select().where(ListModel.chat_id == update.effective_chat.id)
keyboard = [[InlineKeyboardButton(k.name, callback_data=f"list-{k.name}")] for k in lists] + \ keyboard = [[InlineKeyboardButton(k.name, callback_data=f"list-{k.id}")] for k in lists] + \
[[InlineKeyboardButton("New list", callback_data="new")]] [[InlineKeyboardButton("New list", callback_data="new")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
@ -57,23 +67,16 @@ class ListHandler(BaseHandler):
return NAME return NAME
async def choose_list(self, update, context) -> None: async def choose_list(self, update, context: CallbackContext) -> None:
query = update.callback_query query = update.callback_query
data = query.data data = query.data
name = data.replace("list-","") id = data.replace("list-","")
await query.answer() await query.answer()
self.current_name = name context.user_data["current_list"] = ListModel.get(id = id)
keyboard = [ reply_markup = InlineKeyboardMarkup(self.list_overview_keyboard)
[InlineKeyboardButton("Add item", callback_data="add")],
[InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Delete list", callback_data="delete")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Very well. For " + name + " the following actions are available:", reply_markup=reply_markup) await query.edit_message_text(f"Using {context.user_data['current_list'].name}. Available actions:", reply_markup=reply_markup)
return ACTION return ACTION
@ -81,16 +84,9 @@ class ListHandler(BaseHandler):
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
keyboard = [ reply_markup = InlineKeyboardMarkup(self.list_overview_keyboard)
[InlineKeyboardButton("Add item", callback_data="add")],
[InlineKeyboardButton("Remove item", callback_data="remove")],
[InlineKeyboardButton("Clear list", callback_data="clear")],
[InlineKeyboardButton("Print list", callback_data="print")],
[InlineKeyboardButton("Delete list", callback_data="delete")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Very well. For " + self.current_name + " the following actions are available:", reply_markup=reply_markup) await query.edit_message_text(f"Using {context.user_data['current_list'].name}. Available actions:", reply_markup=reply_markup)
return ACTION return ACTION
@ -105,10 +101,9 @@ class ListHandler(BaseHandler):
name = update.message.text name = update.message.text
try: try:
with db: with db:
ListModel.create(name = name) context.user_data["current_list"] = ListModel.create(name = name, chat_id=update.effective_chat.id)
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]] keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("To the menu!", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
self.current_name = name
await update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup) await update.message.reply_text("Thanks. List " + name + " was successfully created.", reply_markup=reply_markup)
return ACTION return ACTION
except Exception as e: except Exception as e:
@ -123,13 +118,24 @@ class ListHandler(BaseHandler):
return ITEMADD return ITEMADD
async def list_toggle(self, update, context) -> None:
query = update.callback_query
await query.answer()
list_object = context.user_data["current_list"]
keyboard = [[InlineKeyboardButton(v, callback_data=k)] for k,v in list_object.content.items()]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Which item would you like to toggle?", reply_markup = reply_markup)
return ITEMTOGGLE
async def list_remove(self, update, context) -> None: async def list_remove(self, update, context) -> None:
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
with db:
list_object = ListModel.get(name = self.current_name)
keyboard = [[InlineKeyboardButton(k, callback_data=i)] for i,k in enumerate(list_object.content_list)] list_object = context.user_data["current_list"]
keyboard = [[InlineKeyboardButton(v, callback_data=k)] for k,v in list_object.content.items()]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Which item would you like to remove?", reply_markup = reply_markup) await query.edit_message_text("Which item would you like to remove?", reply_markup = reply_markup)
@ -139,47 +145,77 @@ class ListHandler(BaseHandler):
async def list_clear(self, update, context) -> None: async def list_clear(self, update, context) -> None:
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
with db:
ListModel.get(name = self.current_name).content_list = [] list_object = context.user_data["current_list"]
list_object.content = {}
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("List " + self.current_name + " cleared", reply_markup=reply_markup) await query.edit_message_text(f"List {list_object.name} cleared", reply_markup=reply_markup)
return ACTION return ACTION
async def list_delete(self, update, context) -> None: async def list_delete(self, update, context) -> None:
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
with db: list_object = context.user_data["current_list"]
ListModel.get(name = self.current_name).delete_instance() list_object.delete_instance()
await query.edit_message_text("List " + self.current_name + " deleted") await query.edit_message_text(f"List {list_object.name} deleted")
return ConversationHandler.END return ConversationHandler.END
async def list_print(self, update, context) -> None: async def list_print(self, update, context) -> None:
query = update.callback_query query = update.callback_query
await query.answer() await query.answer()
with db: list_object = context.user_data["current_list"]
it = ListModel.get(name = self.current_name).content_list
if it: content_it = list_object.content.values()
content = "·" + "\n· ".join(it) done_it = [
"· " if e is None \
else "" if e \
else "" \
for e in list_object.done_dict.values()]
if content_it:
msg_content = "\n".join([f"{d} {c}" for d, c in zip(done_it, content_it)])
else: else:
content = "List empty" msg_content = "List empty"
keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] keyboard = [[InlineKeyboardButton("Add an item", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Content of " + self.current_name + ":\n" + content, reply_markup=reply_markup) await query.edit_message_text(f"Content of {list_object.name}:\n{msg_content}", reply_markup=reply_markup)
return ACTION return ACTION
async def list_add_item(self, update, context) -> None: async def list_add_item(self, update, context) -> None:
item = update.message.text item = update.message.text
with db: list_object = context.user_data["current_list"]
ListModel.get(name = self.current_name).content_list = ListModel.get(name = self.current_name).content_list + [item] new = list_object.content
new.update({"random_key": item})
list_object.content = new
# TODO test me! # TODO test me!
keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] keyboard = [[InlineKeyboardButton("Add some more", callback_data="add"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
await update.message.reply_text("Added " + item, reply_markup=reply_markup) await update.message.reply_text(f"Added {item}", reply_markup=reply_markup)
return ACTION
async def list_toggle_index(self, update, context) -> None:
query = update.callback_query
toggle_key = int(query.data)
await query.answer()
list_object = context.user_data["current_list"]
old = list_object.done_dict[toggle_key]
# if all None or all False (first toggle or all false) then set all dones to False
if not any(list_object.done_dict.values()):
new_done_dict = dict.fromkeys(list_object.done_dict, False)
else: new_done_dict = list_object.done_dict
new_done_dict[toggle_key] = not old
list_object.done_dict = new_done_dict
keyboard = [[InlineKeyboardButton("Toggle another", callback_data="toggle"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text(f"Toggled {list_object.content[toggle_key]}", reply_markup=reply_markup)
return ACTION return ACTION
@ -188,14 +224,13 @@ class ListHandler(BaseHandler):
ind = int(query.data) ind = int(query.data)
await query.answer() await query.answer()
with db: list_object = context.user_data["current_list"]
list_object = ListModel.get(name = self.current_name) old = list_object.content
old = list_object.content_list
name = old.pop(ind) name = old.pop(ind)
list_object.content_list = old list_object.content = old
keyboard = [[InlineKeyboardButton("Remove another", callback_data="remove"), InlineKeyboardButton("Back to the menu", callback_data="overview")]] keyboard = [[InlineKeyboardButton("Remove another", callback_data="remove"), InlineKeyboardButton("Back to the menu", callback_data="overview")]]
reply_markup = InlineKeyboardMarkup(keyboard) reply_markup = InlineKeyboardMarkup(keyboard)
await query.edit_message_text("Removed " + name, reply_markup=reply_markup) await query.edit_message_text(f"Removed {name}", reply_markup=reply_markup)
return ACTION return ACTION

View File

@ -1,30 +1,52 @@
from peewee import * from peewee import *
import json
db = DatabaseProxy() db = DatabaseProxy()
class BaseModel(Model): class BaseModel(Model):
class Meta: class Meta:
database = db database = db
db_table = 'lists'
class ListModel(BaseModel): class ListModel(BaseModel):
name = CharField(unique=True) name = CharField(default="")
content = TextField(default="") # unlimited length, use to serialise list into chat_id = IntegerField()
@property @property
def content_list(self): def content(self) -> dict:
return json.loads(self.content or '[]') return {e.id: e.entry for e in self.entries}
@content_list.setter @content.setter
def content_list(self, list_content): def content(self, new_content: dict):
self.content = json.dumps(list_content) old_content = self.content
with db: if len(old_content) < len(new_content):
self.save() # we assume: only 1 item added (last item)
new_item = list(new_content.values())[-1]
ListEntryModel.create(list_model=self, entry = new_item)
elif len(old_content) > len(new_content):
to_delete_ids = set(old_content.keys()) - set(new_content.keys())
ListEntryModel.delete().where(ListEntryModel.id.in_(list(to_delete_ids))).execute()
@property
def done_dict(self):
return {e.id: e.done for e in self.entries}
@done_dict.setter
def done_dict(self, new_done: dict):
old_done_dict = self.done_dict
for k,d in new_done.items():
if d != old_done_dict[k]:
ListEntryModel.update(done = d).where(ListEntryModel.id == k).execute()
class ListEntryModel(BaseModel):
list_model = ForeignKeyField(ListModel, backref="entries", on_delete="CASCADE")
entry = TextField(default="")
done = BooleanField(default=None, null=True)
def set_db(db_path): def set_db(db_path):
db.initialize(SqliteDatabase(db_path)) db.initialize(SqliteDatabase(db_path))
with db: with db:
db.create_tables([ListModel], safe=True) db.create_tables([ListModel, ListEntryModel], safe=True)