From c1e1684efd17019e3365ce4241177c653621c95e Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Mon, 18 Dec 2017 10:46:48 +0100 Subject: [PATCH] Start work on the database code The code isn't finished yet and isn't ready for use! --- config/template_config.ini | 3 +- database.py | 114 ++++++++++++++++++++++++++++++------- strings.py | 9 +++ 3 files changed, 104 insertions(+), 22 deletions(-) diff --git a/config/template_config.ini b/config/template_config.ini index 5b045a4..be187ea 100644 --- a/config/template_config.ini +++ b/config/template_config.ini @@ -3,7 +3,7 @@ # Config file parameters [Config] ; Config file version. DO NOT EDIT THIS! -version = 3 +version = 4 ; Set this to no when you are done editing the file is_template = yes @@ -20,3 +20,4 @@ long_polling_timeout = 30 # Database parameters [Database] ; The database engine you want to use. Refer to http://docs.sqlalchemy.org/en/latest/core/engines.html for the possible settings. +engine = sqlite:// \ No newline at end of file diff --git a/database.py b/database.py index 8518c5a..f4947b6 100644 --- a/database.py +++ b/database.py @@ -1,27 +1,99 @@ -from sqlalchemy import Table, Column, BigInteger, Numeric, String, Text, MetaData, ForeignKey +from sqlalchemy import create_engine, Column, ForeignKey +from sqlalchemy import Integer, BigInteger, String, Numeric, Text +from sqlalchemy.ext.declarative import declarative_base +import configloader +import telegram import decimal +import strings +from html import escape -# Create a metadata object containing info about the tables in the database -metadata = MetaData() +# Create a (lazy) database engine +engine = create_engine(configloader.config["Database"]["engine"]) -# Metadata for the users table -# TODO: maybe add a 'credit' column -users = Table("users", metadata, - Column("telegram_user_id", BigInteger, primary_key=True), - Column("first_name", String, nullable=False), - Column("last_name", String), - Column("username", String)) +# Create a base class to define all the database subclasses +TableDeclarativeBase = declarative_base(bind=engine) -# Metadata for the admins table -# TODO: add columns for all the possible permissions -admins = Table("admins", metadata, - Column("telegram_user_id", ForeignKey("users.telegram_user_id"), primary_key=True)) +# Define all the database tables using the sqlalchemy declarative base +class User(TableDeclarativeBase): + """A Telegram user who used the bot at least once.""" -# Metadata for the products table -products = Table("products", metadata, - Column("product_name", String, primary_key=True), - Column("description", Text), - Column("price", Numeric(...))) + # Telegram data + user_id = Column(BigInteger, primary_key=True) + first_name = Column(String, nullable=False) + last_name = Column(String) + username = Column(String) -#TODO: many things are still missing... -raise NotImplementedError() \ No newline at end of file + # Current wallet credit + credit = Column(Numeric, nullable=False) + + # Extra table parameters + __tablename__ = "users" + + def __init__(self, telegram_user: telegram.User): + # Get the data from telegram + self.id = telegram_user.id + self.first_name = telegram_user.first_name + self.last_name = telegram_user.last_name + self.username = telegram_user.username + # The starting wallet value is 0 + self.credit = decimal.Decimal("0") + + def __str__(self): + """Describe the user in the best way possible given the available data.""" + if self.username is not None: + return f"@{self.username}" + elif self.last_name is not None: + return f"{self.first_name} {self.last_name}" + else: + return self.first_name + + def __repr__(self): + return f"" + + +class Product(TableDeclarativeBase): + """A purchasable product.""" + + # Product name + name = Column(String, primary_key=True) + # Product description + description = Column(Text) + # Product price, if null product is not for sale + price = Column(Numeric) + # Image filename + image = Column(String) + # Stock quantity, if null product has infinite stock + stock = Column(Integer) + + # Extra table parameters + __tablename__ = "product" + + # No __init__ is needed, the default one is sufficient + + def __str__(self): + """Return the product details formatted with Telegram HTML. The image is omitted.""" + return f"{escape(self.name)}\n" \ + f"{escape(self.description)}\n" \ + f"{format(strings.in_stock_format_string, quantity=self.stock) if self.stock is not None else ''}\n" \ + f"{format(strings.currency_format_string, symbol=strings.currency_symbol, value=self.price)}" + + def __repr__(self): + return f"" + + # TODO: add get image (and set image?) method(s) + + +class Transactions(TableDeclarativeBase): + """A greed wallet transaction. + Wallet credit ISN'T calculated from these, but they can be used to recalculate it.""" + + # The internal transaction ID + transaction_id = Column(BigInteger, primary_key=True) + # The user whose credit is affected by this transaction + user_id = Column(BigInteger, ForeignKey("users.id"), nullable=False) + # The value of this transaction. Can be both negative and positive. + value = Column(Numeric, nullable=False) + # Extra notes on the transaction + notes = Column(Text) + + # TODO: there still are some missing fields \ No newline at end of file diff --git a/strings.py b/strings.py index 747b844..bd7aef8 100644 --- a/strings.py +++ b/strings.py @@ -2,6 +2,15 @@ # Can be edited, but DON'T REMOVE THE REPLACEMENT FIELDS (words surrounded by {curly braces}) # TODO: maybe add a preformat to all strings in this file +# Currency symbol +currency_symbol = "€" + +# Positioning of the currency symbol +currency_format_string = "{symbol} {value}" + +# Quantity of a product in stock +in_stock_format_string = "{quantity} disponibili" + # Answer: the start command was sent and the bot should welcome the user conversation_after_start = "Ciao!\n" \ "Benvenuto su greed!"