macao-pos/pos/database.py

175 lines
5.7 KiB
Python
Raw Permalink Normal View History

from datetime import datetime, timedelta
from uuid import uuid4
2017-03-21 11:26:25 +01:00
from contextlib import contextmanager
import sqlalchemy as sa
import sqlalchemy.ext.declarative
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import Column, ForeignKey
2017-03-21 11:26:25 +01:00
from sqlalchemy import Integer, String, Boolean, DateTime
from sqlalchemy.orm import relationship
from sqlalchemy_utils import force_auto_coercion
from sqlalchemy_utils.types.password import PasswordType
from pos.logging import get_logger
log = get_logger('database')
2017-03-21 11:26:25 +01:00
# The database URL must follow RFC 1738 in the form
# dialect+driver://username:password@host:port/database
ENGINE_GENERIC = "{engine}://{user}:{password}@{host}:{port}/{database}"\
"?charset=utf8"
ENGINE_SQLITE = "sqlite:///{path}"
ENGINE_SQLITE_MEMORY = "sqlite://"
PASSWORD_SCHEMES = ['pbkdf2_sha512']
Base = sqlalchemy.ext.declarative.declarative_base()
class Database:
"""
Handle database operations."
"""
Session = None
engine = None
def __init__(self, **kwargs):
"""
Initialize database connection.
:param engine: The SQLAlchemy database backend in the form
dialect+driver where dialect is the name of a SQLAlchemy
dialect (sqlite, mysql, postgresql, oracle or mssql) and
driver is the name of the DBAPI in all lowercase
letters. If driver is not specified the default DBAPI
will be imported if available.
:param path: Only for SQLite. Path to database. If not specified the
database will be kept in memory (should be used only for
testing).
:param host:
:param port:
:param database:
:param user:
:param password:
"""
if kwargs['engine'] == 'sqlite':
if 'path' in kwargs:
url = ENGINE_SQLITE.format(path=kwargs['path'])
else:
url = ENGINE_SQLITE_MEMORY
else:
url = ENGINE_GENERIC.format(**kwargs)
self.engine = sa.create_engine(url)
self.Session = sa.orm.sessionmaker(
bind=self.engine,
expire_on_commit=False
)
Base.metadata.create_all(self.engine)
force_auto_coercion()
2017-03-21 11:26:25 +01:00
@contextmanager
def get_session(self):
session = self.Session()
try:
yield session
except SQLAlchemyError as e:
log.critical("Error performing transaction: {}".format(e))
2017-03-21 11:26:25 +01:00
session.rollback()
else:
session.commit()
finally:
session.close()
class User(Base):
__tablename__ = 'users'
uid = Column(Integer, primary_key=True)
username = Column(String, nullable=False, unique=True)
password = Column(PasswordType(schemes=PASSWORD_SCHEMES), nullable=False)
is_active = Column(Boolean, nullable=False, server_default='1')
created_at = Column(DateTime, nullable=False, default=datetime.now)
2017-03-21 11:26:25 +01:00
class Event(Base):
__tablename__ = 'events'
uid = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
starts_at = Column(DateTime, nullable=False, default=datetime.now)
ends_at = Column(DateTime)
created_at = Column(DateTime, nullable=False, default=datetime.now)
2017-03-21 11:26:25 +01:00
transactions = relationship('Transaction', lazy='joined')
2017-03-21 11:26:25 +01:00
class ProductCategory(Base):
__tablename__ = 'product_categories'
uid = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
sort = Column(Integer, nullable=False, server_default='0')
created_at = Column(DateTime, nullable=False, default=datetime.now)
products = relationship('Product', lazy='joined')
2017-03-21 11:26:25 +01:00
class Product(Base):
__tablename__ = 'products'
uid = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
price = Column(Integer, nullable=False)
sort = Column(Integer, nullable=False, server_default='0')
category_uid = Column(Integer, ForeignKey('product_categories.uid'),
nullable=False)
2017-03-21 11:26:25 +01:00
is_active = Column(Boolean, nullable=False, server_default='1')
created_at = Column(DateTime, nullable=False, default=datetime.now)
2017-03-21 11:26:25 +01:00
category = relationship('ProductCategory', lazy='joined')
2017-03-21 11:26:25 +01:00
class Transaction(Base):
__tablename__ = 'transactions'
2017-03-21 11:26:25 +01:00
uid = Column(Integer, primary_key=True)
event_uid = Column(Integer, ForeignKey('events.uid'), nullable=False)
created_at = Column(DateTime, nullable=False, default=datetime.now)
2017-03-21 11:26:25 +01:00
event = relationship('Event', lazy='joined')
orders = relationship('Order', lazy='joined')
2017-03-21 11:26:25 +01:00
class Order(Base):
__tablename__ = 'orders'
2017-03-21 11:26:25 +01:00
uid = Column(Integer, primary_key=True)
product_uid = Column(Integer, ForeignKey('products.uid'), nullable=False)
quantity = Column(Integer, nullable=False)
transaction_uid = Column(Integer, ForeignKey('transactions.uid'),
nullable=False)
2017-03-21 11:26:25 +01:00
product = relationship('Product', lazy='joined')
transaction = relationship('Transaction', lazy='joined')
class AccessToken(Base):
__tablename__ = 'access_tokens'
uid = Column(Integer, primary_key=True)
user_uid = Column(Integer, ForeignKey('users.uid'), nullable=False)
token = Column(String(36), nullable=False, default=str(uuid4()))
is_active = Column(Boolean, nullable=False, server_default='1')
created_at = Column(DateTime, nullable=False, default=datetime.now)
expires_at = Column(DateTime, nullable=False,
default=(datetime.now() + timedelta(days=2)))
user = relationship('User', lazy='joined')
2017-09-25 21:22:41 +02:00
def is_valid(self):
return all([
self.is_active,
self.created_at < datetime.now(),
self.expires_at > datetime.now()
])