1
Fork 0
mirror of https://github.com/Steffo99/greed.git synced 2024-11-25 07:14:18 +00:00

Add and edit products

This commit is contained in:
Steffo 2018-02-01 10:06:40 +01:00
parent b7bdae4678
commit c97dcaeef4
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS.
GPG key ID: C27544372FBB445D
3 changed files with 111 additions and 26 deletions

View file

@ -1,10 +1,11 @@
from sqlalchemy import create_engine, Column, ForeignKey, UniqueConstraint, CheckConstraint from sqlalchemy import create_engine, Column, ForeignKey, UniqueConstraint, CheckConstraint
from sqlalchemy import Integer, BigInteger, String, Numeric, Text from sqlalchemy import Integer, BigInteger, String, Text, LargeBinary
from sqlalchemy.orm import sessionmaker, relationship from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
import configloader import configloader
import telegram import telegram
import strings import strings
import requests
from html import escape from html import escape
# Create a (lazy) database engine # Create a (lazy) database engine
@ -67,8 +68,8 @@ class Product(TableDeclarativeBase):
description = Column(Text) description = Column(Text)
# Product price, if null product is not for sale # Product price, if null product is not for sale
price = Column(Integer) price = Column(Integer)
# Image filename # Image data
image = Column(String) image = Column(LargeBinary)
# Stock quantity, if null product has infinite stock # Stock quantity, if null product has infinite stock
stock = Column(Integer) stock = Column(Integer)
@ -79,15 +80,29 @@ class Product(TableDeclarativeBase):
def __str__(self): def __str__(self):
"""Return the product details formatted with Telegram HTML. The image is omitted.""" """Return the product details formatted with Telegram HTML. The image is omitted."""
return f"<b>{escape(self.name)}</b>\n" \ return f"{escape(self.name)}\n" \
f"{escape(self.description)}\n" \ f"{escape(self.description)}\n\n" \
f"<i>{strings.in_stock_format_string.format(quantity=self.stock) if self.stock is not None else ''}</i>\n" \ f"{strings.in_stock_format_string.format(quantity=self.stock) if self.stock is not None else ''}\n" \
f"{strings.currency_format_string.format(symbol=strings.currency_symbol, value=self.price)}" f"{strings.currency_format_string.format(symbol=strings.currency_symbol, value=self.price)}"
def __repr__(self): def __repr__(self):
return f"<Product {self.name}>" return f"<Product {self.name}>"
# TODO: add get image (and set image?) method(s) def send_as_message(self, chat_id: int) -> dict:
"""Send a message containing the product data."""
r = requests.post(f"https://api.telegram.org/bot{configloader.config['Telegram']['token']}/sendPhoto",
files={"photo": self.image},
params={"chat_id": chat_id,
"caption": str(self)})
return r
def set_image(self, file: telegram.File):
"""Download an image from Telegram and store it in the image column.
This is a slow blocking function. Try to avoid calling it directly, use a thread if possible."""
# Download the photo through a get request
r = requests.get(file.file_path)
# Store the photo in the database record
self.image = r.content
class Transaction(TableDeclarativeBase): class Transaction(TableDeclarativeBase):

View file

@ -75,7 +75,20 @@ ask_product_name = "Come si deve chiamare il prodotto?"
ask_product_description = "Quale deve essere la descrizione del prodotto?" ask_product_description = "Quale deve essere la descrizione del prodotto?"
# Add product: price? # Add product: price?
ask_product_price = "Quanto deve costare il prodotto?" ask_product_price = "Quanto deve costare il prodotto?\n" \
"Scrivi <code>skip</code> se vuoi che il prodotto non sia ancora in vendita."
# Add product: image?
ask_product_image = "Che immagine vuoi che abbia il prodotto?"
# Thread has started downloading an image and might be unresponsive
downloading_image = "Sto scaricando la tua foto!\n" \
"Potrei metterci un po'... Abbi pazienza!\n" \
"Non sarò in grado di risponderti durante il download."
# Edit product: current value
edit_current_value = "Il valore attuale è:\n" \
"<pre>{value}</pre>"
# Payment: cash payment info # Payment: cash payment info
payment_cash = "Puoi pagare in contanti alla sede fisica del negozio.\n" \ payment_cash = "Puoi pagare in contanti alla sede fisica del negozio.\n" \
@ -105,6 +118,9 @@ bot_info = 'Questo bot utilizza <a href="https://github.com/Steffo99/greed">gree
# Success: product has been added to the database # Success: product has been added to the database
success_product_added = "✅ Il prodotto è stato aggiunto con successo!" success_product_added = "✅ Il prodotto è stato aggiunto con successo!"
# Success: product has been added to the database
success_product_edited = "✅ Il prodotto è stato modificato con successo!"
# Error: message received not in a private chat # Error: message received not in a private chat
error_nonprivate_chat = "⚠️ Questo bot funziona solo in chat private." error_nonprivate_chat = "⚠️ Questo bot funziona solo in chat private."

View file

@ -8,6 +8,8 @@ import sys
import queue as queuem import queue as queuem
import database as db import database as db
import re import re
import requests
from html import escape
class StopSignal: class StopSignal:
"""A data class that should be sent to the worker when the conversation has to be stopped abnormally.""" """A data class that should be sent to the worker when the conversation has to be stopped abnormally."""
@ -144,6 +146,20 @@ class ChatWorker(threading.Thread):
# Return the successfulpayment # Return the successfulpayment
return update.message.successful_payment return update.message.successful_payment
def __wait_for_photo(self) -> typing.List[telegram.PhotoSize]:
"""Continue getting updates until a photo is received, then download and return it."""
while True:
# Get the next update
update = self.__receive_next_update()
# Ensure the update contains a message
if update.message is None:
continue
# Ensure the message contains a photo
if update.message.photo is None:
continue
# Return the photo array
return update.message.photo
def __user_menu(self): def __user_menu(self):
"""Function called from the run method when the user is not an administrator. """Function called from the run method when the user is not an administrator.
Normal bot actions should be placed here.""" Normal bot actions should be placed here."""
@ -339,47 +355,85 @@ class ChatWorker(threading.Thread):
# If the user has selected the Add Product option... # If the user has selected the Add Product option...
if selection == strings.menu_add_product: if selection == strings.menu_add_product:
# Open the add product menu # Open the add product menu
self.__add_product_menu() self.__edit_product_menu()
# If the user has selected a product # If the user has selected a product
else: else:
# Find the selected product
product = self.session.query(db.Product).filter_by(name=selection).one()
# Open the edit menu for that specific product # Open the edit menu for that specific product
self.__edit_product_menu(selection) self.__edit_product_menu(product=product)
def __add_product_menu(self): def __edit_product_menu(self, product: typing.Optional[db.Product]=None):
"""Add a product to the database.""" """Add a product to the database or edit an existing one."""
# Ask for the product name until a valid product name is specified # Ask for the product name until a valid product name is specified
while True: while True:
# Ask the question to the user # Ask the question to the user
self.bot.send_message(self.chat.id, strings.ask_product_name) self.bot.send_message(self.chat.id, strings.ask_product_name)
# Display the current name if you're editing an existing product
if product:
self.bot.send_message(self.chat.id, strings.edit_current_value.format(value=escape(product.name)), parse_mode="HTML")
# Wait for an answer # Wait for an answer
name = self.__wait_for_regex(r"(.*)") name = self.__wait_for_regex(r"(.*)")
# Ensure a product with that name doesn't already exist # Ensure a product with that name doesn't already exist
if self.session.query(db.Product).filter_by(name=name).one_or_none() is None: if self.session.query(db.Product).filter_by(name=name).one_or_none() in [None, product]:
# Exit the loop # Exit the loop
break break
self.bot.send_message(self.chat.id, strings.error_duplicate_name) self.bot.send_message(self.chat.id, strings.error_duplicate_name)
# Ask for the product description # Ask for the product description
self.bot.send_message(self.chat.id, strings.ask_product_description) self.bot.send_message(self.chat.id, strings.ask_product_description)
# Display the current description if you're editing an existing product
if product:
self.bot.send_message(self.chat.id, strings.edit_current_value.format(value=escape(product.description)), parse_mode="HTML")
# Wait for an answer # Wait for an answer
description = self.__wait_for_regex(r"(.*)") description = self.__wait_for_regex(r"(.*)")
# Ask for the product price # Ask for the product price
self.bot.send_message(self.chat.id, strings.ask_product_price) self.bot.send_message(self.chat.id, strings.ask_product_price)
# Display the current name if you're editing an existing product
if product:
self.bot.send_message(self.chat.id, strings.edit_current_value.format(value=(strings.currency_format_string.format(symbol=strings.currency_symbol, value=(product.price / (10 ** int(configloader.config["Payments"]["currency_exp"]))))) if product.price is not None else 'Non in vendita'), parse_mode="HTML")
# Wait for an answer # Wait for an answer
price = int(self.__wait_for_regex(r"([0-9]{1,3}(?:[.,][0-9]{1,2})?)").replace(".", "").replace(",", "")) * ( price = self.__wait_for_regex(r"([0-9]{1,3}(?:[.,][0-9]{1,2})?|[Ss][Kk][Ii][Pp])")
10 ** int(configloader.config["Payments"]["currency_exp"])) # If the price is skipped
# TODO: ask for product image if price.lower() == "skip":
# Create the db record for the product price = None
product = db.Product(name=name, else:
description=description, price = int(price.replace(".", "").replace(",", "")) * (10 ** int(configloader.config["Payments"]["currency_exp"]))
price=price) # Ask for the product image
# Add the record to the session, then commit self.bot.send_message(self.chat.id, strings.ask_product_image)
self.session.add(product) # Wait for an answer
photo_list = self.__wait_for_photo()
# If a new product is being added...
if not product:
# Create the db record for the product
product = db.Product(name=name,
description=description,
price=price)
# Add the record to the database
self.session.add(product)
# If a product is being edited...
else:
# Edit the record with the new values
product.name = name
product.description = description
product.price = price
# Find the largest photo id
largest_photo = photo_list[0]
for photo in photo_list[1:]:
if photo.width > largest_photo.width:
largest_photo = photo
# Get the file object associated with the photo
photo_file = self.bot.get_file(largest_photo.file_id)
# Notify the user that the bot is downloading the image and might be inactive for a while
self.bot.send_message(self.chat.id, strings.downloading_image)
self.bot.send_chat_action(self.chat.id, action="upload_photo")
# Set the image for that product
product.set_image(photo_file)
self.session.commit() self.session.commit()
# Notify the user # Notify the user
self.bot.send_message(self.chat.id, strings.success_product_added) if product:
self.bot.send_message(self.chat.id, strings.success_product_edited)
def __edit_product_menu(self, name: str): else:
raise NotImplementedError() self.bot.send_message(self.chat.id, strings.success_product_added)
def __orders_menu(self): def __orders_menu(self):
raise NotImplementedError() raise NotImplementedError()