diff --git a/CHANGELOG.md b/CHANGELOG.md
index 19cc617..eb5d524 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Matemat Changelog
+
+## Version 0.3.10
+
+Add option to log out users automatically after completing a purchase
+
+### Changes
+
+
+- Add option to log out users automatically after completing a purchase
+
+
+
+
## Version 0.3.9
diff --git a/matemat/db/facade.py b/matemat/db/facade.py
index 522dfd4..9ed5b15 100644
--- a/matemat/db/facade.py
+++ b/matemat/db/facade.py
@@ -84,7 +84,7 @@ class MatematDatabase(object):
users: List[User] = []
with self.db.transaction(exclusive=False) as c:
for row in c.execute('''
- SELECT user_id, username, email, is_admin, is_member, balance, receipt_pref
+ SELECT user_id, username, email, is_admin, is_member, balance, receipt_pref, logout_after_purchase
FROM users
WHERE touchkey IS NOT NULL OR NOT :must_have_touchkey
ORDER BY username COLLATE NOCASE ASC
@@ -92,12 +92,13 @@ class MatematDatabase(object):
'must_have_touchkey': with_touchkey
}):
# Decompose each row and put the values into a User object
- user_id, username, email, is_admin, is_member, balance, receipt_p = row
+ user_id, username, email, is_admin, is_member, balance, receipt_p, logout_after_purchase = row
try:
receipt_pref: ReceiptPreference = ReceiptPreference(receipt_p)
except ValueError:
raise DatabaseConsistencyError(f'{receipt_p} is not a valid ReceiptPreference')
- users.append(User(user_id, username, balance, email, is_admin, is_member, receipt_pref))
+ users.append(User(user_id, username, balance, email, is_admin, is_member, receipt_pref,
+ logout_after_purchase))
return users
def get_user(self, uid: int) -> User:
@@ -108,7 +109,7 @@ class MatematDatabase(object):
with self.db.transaction(exclusive=False) as c:
# Fetch all values to construct the user
c.execute('''
- SELECT user_id, username, email, is_admin, is_member, balance, receipt_pref
+ SELECT user_id, username, email, is_admin, is_member, balance, receipt_pref, logout_after_purchase
FROM users
WHERE user_id = ?
''',
@@ -117,19 +118,20 @@ class MatematDatabase(object):
if row is None:
raise ValueError(f'No user with user ID {uid} exists.')
# Unpack the row and construct the user
- user_id, username, email, is_admin, is_member, balance, receipt_p = row
+ user_id, username, email, is_admin, is_member, balance, receipt_p, logout_after_purchase = row
try:
receipt_pref: ReceiptPreference = ReceiptPreference(receipt_p)
except ValueError:
raise DatabaseConsistencyError(f'{receipt_p} is not a valid ReceiptPreference')
- return User(user_id, username, balance, email, is_admin, is_member, receipt_pref)
+ return User(user_id, username, balance, email, is_admin, is_member, receipt_pref, logout_after_purchase)
def create_user(self,
username: str,
password: str,
email: Optional[str] = None,
admin: bool = False,
- member: bool = True) -> User:
+ member: bool = True,
+ logout_after_purchase: bool = False) -> User:
"""
Create a new user.
:param username: The name of the new user.
@@ -137,6 +139,7 @@ class MatematDatabase(object):
:param email: The user's email address, defaults to None.
:param admin: Whether the user is an administrator, defaults to False.
:param member: Whether the user is a member, defaults to True.
+ :param logout_after_purchase: Whether the user should be logged out after completing a purchase.
:return: A User object representing the created user.
:raises ValueError: If a user with the same name already exists.
"""
@@ -150,8 +153,10 @@ class MatematDatabase(object):
raise ValueError(f'A user with the name \'{username}\' already exists.')
# Insert the user into the database.
c.execute('''
- INSERT INTO users (username, email, password, balance, is_admin, is_member, lastchange, created)
- VALUES (:username, :email, :pwhash, 0, :admin, :member, STRFTIME('%s', 'now'), STRFTIME('%s', 'now'))
+ INSERT INTO users (username, email, password, balance, is_admin, is_member, lastchange, created,
+ logout_after_purchase)
+ VALUES (:username, :email, :pwhash, 0, :admin, :member, STRFTIME('%s', 'now'), STRFTIME('%s', 'now'),
+ logout_after_purchase)
''', {
'username': username,
'email': email,
@@ -179,14 +184,15 @@ class MatematDatabase(object):
raise ValueError('Exactly one of password and touchkey must be provided')
with self.db.transaction(exclusive=False) as c:
c.execute('''
- SELECT user_id, username, email, password, touchkey, is_admin, is_member, balance, receipt_pref
+ SELECT user_id, username, email, password, touchkey, is_admin, is_member, balance, receipt_pref,
+ logout_after_purchase
FROM users
WHERE username = ?
''', [username])
row = c.fetchone()
if row is None:
raise AuthenticationError('User does not exist')
- user_id, username, email, pwhash, tkhash, admin, member, balance, receipt_p = row
+ user_id, username, email, pwhash, tkhash, admin, member, balance, receipt_p, logout_after_purchase = row
if password is not None and not compare_digest(crypt.crypt(password, pwhash), pwhash):
raise AuthenticationError('Password mismatch')
elif touchkey is not None \
@@ -199,7 +205,7 @@ class MatematDatabase(object):
receipt_pref: ReceiptPreference = ReceiptPreference(receipt_p)
except ValueError:
raise DatabaseConsistencyError(f'{receipt_p} is not a valid ReceiptPreference')
- return User(user_id, username, balance, email, admin, member, receipt_pref)
+ return User(user_id, username, balance, email, admin, member, receipt_pref, logout_after_purchase)
def change_password(self, user: User, oldpass: str, newpass: str, verify_password: bool = True) -> None:
"""
@@ -280,6 +286,7 @@ class MatematDatabase(object):
balance: int = kwargs['balance'] if 'balance' in kwargs else user.balance
balance_reason: Optional[str] = kwargs['balance_reason'] if 'balance_reason' in kwargs else None
receipt_pref: ReceiptPreference = kwargs['receipt_pref'] if 'receipt_pref' in kwargs else user.receipt_pref
+ logout_after_purchase: bool = kwargs['logout_after_purchase'] if 'logout_after_purchase' in kwargs else user.logout_after_purchase
with self.db.transaction() as c:
c.execute('SELECT balance FROM users WHERE user_id = :user_id', {'user_id': user.id})
row = c.fetchone()
@@ -312,7 +319,8 @@ class MatematDatabase(object):
is_admin = :is_admin,
is_member = :is_member,
receipt_pref = :receipt_pref,
- lastchange = STRFTIME('%s', 'now')
+ lastchange = STRFTIME('%s', 'now'),
+ logout_after_purchase = :logout_after_purchase
WHERE user_id = :user_id
''', {
'user_id': user.id,
@@ -321,7 +329,8 @@ class MatematDatabase(object):
'balance': balance,
'is_admin': is_admin,
'is_member': is_member,
- 'receipt_pref': receipt_pref.value
+ 'receipt_pref': receipt_pref.value,
+ 'logout_after_purchase': logout_after_purchase
})
# Only update the actual user object after the changes in the database succeeded
user.name = name
@@ -329,6 +338,7 @@ class MatematDatabase(object):
user.balance = balance
user.is_admin = is_admin
user.is_member = is_member
+ user.logout_after_purchase = user.logout_after_purchase
user.receipt_pref = receipt_pref
def delete_user(self, user: User) -> None:
diff --git a/matemat/db/migrations.py b/matemat/db/migrations.py
index 8617b12..bbdff55 100644
--- a/matemat/db/migrations.py
+++ b/matemat/db/migrations.py
@@ -276,3 +276,11 @@ def migrate_schema_5_to_6(c: sqlite3.Cursor):
ALTER TABLE products ADD COLUMN
custom_price INTEGER(1) DEFAULT 0;
''')
+
+
+def migrate_schema_6_to_7(c: sqlite3.Cursor):
+ # Add custom_price column
+ c.execute('''
+ ALTER TABLE users ADD COLUMN
+ logout_after_purchase INTEGER(1) DEFAULT 0;
+ ''')
diff --git a/matemat/db/primitives/User.py b/matemat/db/primitives/User.py
index ce203d7..6febc40 100644
--- a/matemat/db/primitives/User.py
+++ b/matemat/db/primitives/User.py
@@ -26,7 +26,8 @@ class User:
email: Optional[str] = None,
is_admin: bool = False,
is_member: bool = False,
- receipt_pref: ReceiptPreference = ReceiptPreference.NONE) -> None:
+ receipt_pref: ReceiptPreference = ReceiptPreference.NONE,
+ logout_after_purchase: bool = False) -> None:
self.id: int = _id
self.name: str = name
self.balance: int = balance
@@ -34,6 +35,7 @@ class User:
self.is_admin: bool = is_admin
self.is_member: bool = is_member
self.receipt_pref: ReceiptPreference = receipt_pref
+ self.logout_after_purchase: bool = logout_after_purchase
def __eq__(self, other) -> bool:
if not isinstance(other, User):
@@ -44,7 +46,9 @@ class User:
self.email == other.email and \
self.is_admin == other.is_admin and \
self.is_member == other.is_member and \
- self.receipt_pref == other.receipt_pref
+ self.receipt_pref == other.receipt_pref and \
+ self.logout_after_purchase == other.logout_after_purchase
def __hash__(self) -> int:
- return hash((self.id, self.name, self.balance, self.email, self.is_admin, self.is_member, self.receipt_pref))
+ return hash((self.id, self.name, self.balance, self.email, self.is_admin, self.is_member, self.receipt_pref,
+ self.logout_after_purchase))
diff --git a/matemat/db/schemas.py b/matemat/db/schemas.py
index 436c102..d5ec75e 100644
--- a/matemat/db/schemas.py
+++ b/matemat/db/schemas.py
@@ -413,3 +413,84 @@ SCHEMAS[6] = [
ON DELETE SET NULL ON UPDATE CASCADE
);
''']
+
+
+SCHEMAS[7] = [
+ '''
+ CREATE TABLE users (
+ user_id INTEGER PRIMARY KEY,
+ username TEXT UNIQUE NOT NULL,
+ email TEXT DEFAULT NULL,
+ password TEXT NOT NULL,
+ touchkey TEXT DEFAULT NULL,
+ is_admin INTEGER(1) NOT NULL DEFAULT 0,
+ is_member INTEGER(1) NOT NULL DEFAULT 1,
+ balance INTEGER(8) NOT NULL DEFAULT 0,
+ lastchange INTEGER(8) NOT NULL DEFAULT 0,
+ receipt_pref INTEGER(1) NOT NULL DEFAULT 0,
+ created INTEGER(8) NOT NULL DEFAULT 0,
+ logout_after_purchase INTEGER(1) DEFAULT 0
+ );
+ ''',
+ '''
+ CREATE TABLE products (
+ product_id INTEGER PRIMARY KEY,
+ name TEXT UNIQUE NOT NULL,
+ stock INTEGER(8) DEFAULT 0,
+ stockable INTEGER(1) DEFAULT 1,
+ price_member INTEGER(8) NOT NULL,
+ price_non_member INTEGER(8) NOT NULL,
+ custom_price INTEGER(1) DEFAULT 0
+ );
+ ''',
+ '''
+ CREATE TABLE transactions ( -- "superclass" of the following 3 tables
+ ta_id INTEGER PRIMARY KEY,
+ user_id INTEGER DEFAULT NULL,
+ value INTEGER(8) NOT NULL,
+ old_balance INTEGER(8) NOT NULL,
+ date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')),
+ FOREIGN KEY (user_id) REFERENCES users(user_id)
+ ON DELETE SET NULL ON UPDATE CASCADE
+ );
+ ''',
+ '''
+ CREATE TABLE consumptions ( -- transactions involving buying a product
+ ta_id INTEGER PRIMARY KEY,
+ product TEXT NOT NULL,
+ FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
+ ON DELETE CASCADE ON UPDATE CASCADE
+ );
+ ''',
+ '''
+ CREATE TABLE deposits ( -- transactions involving depositing cash
+ ta_id INTEGER PRIMARY KEY,
+ FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
+ ON DELETE CASCADE ON UPDATE CASCADE
+ );
+ ''',
+ '''
+ CREATE TABLE modifications ( -- transactions involving balance modification by an admin
+ ta_id INTEGER NOT NULL,
+ agent TEXT NOT NULL,
+ reason TEXT DEFAULT NULL,
+ PRIMARY KEY (ta_id),
+ FOREIGN KEY (ta_id) REFERENCES transactions(ta_id)
+ ON DELETE CASCADE ON UPDATE CASCADE
+ );
+ ''',
+ '''
+ CREATE TABLE receipts ( -- receipts sent to the users
+ receipt_id INTEGER PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ first_ta_id INTEGER DEFAULT NULL,
+ last_ta_id INTEGER DEFAULT NULL,
+ date INTEGER(8) DEFAULT (STRFTIME('%s', 'now')),
+ FOREIGN KEY (user_id) REFERENCES users(user_id)
+ ON DELETE CASCADE ON UPDATE CASCADE,
+ FOREIGN KEY (first_ta_id) REFERENCES transactions(ta_id)
+ ON DELETE SET NULL ON UPDATE CASCADE,
+ FOREIGN KEY (last_ta_id) REFERENCES transactions(ta_id)
+ ON DELETE SET NULL ON UPDATE CASCADE
+ );
+ ''']
diff --git a/matemat/db/wrapper.py b/matemat/db/wrapper.py
index 8f4354e..1c8695b 100644
--- a/matemat/db/wrapper.py
+++ b/matemat/db/wrapper.py
@@ -40,7 +40,7 @@ class DatabaseTransaction(object):
class DatabaseWrapper(object):
- SCHEMA_VERSION = 6
+ SCHEMA_VERSION = 7
def __init__(self, filename: str) -> None:
self._filename: str = filename
@@ -89,6 +89,8 @@ class DatabaseWrapper(object):
migrate_schema_4_to_5(c)
if from_version <= 5 and to_version >= 6:
migrate_schema_5_to_6(c)
+ if from_version <= 6 and to_version >= 7:
+ migrate_schema_6_to_7(c)
def connect(self) -> None:
if self.is_connected():
diff --git a/matemat/webserver/pagelets/admin.py b/matemat/webserver/pagelets/admin.py
index 6a73da5..49fc35e 100644
--- a/matemat/webserver/pagelets/admin.py
+++ b/matemat/webserver/pagelets/admin.py
@@ -75,6 +75,7 @@ def handle_change(args: FormsDict, files: FormsDict, user: User, db: MatematData
return
username = str(args.username)
email = str(args.email)
+ logout_after_purchase = 'logout_after_purchase' in args
# An empty e-mail field should be interpreted as NULL
if len(email) == 0:
email = None
@@ -84,7 +85,8 @@ def handle_change(args: FormsDict, files: FormsDict, user: User, db: MatematData
return
# Attempt to update username, e-mail and receipt preference
try:
- db.change_user(user, agent=None, name=username, email=email, receipt_pref=receipt_pref)
+ db.change_user(user, agent=None, name=username, email=email, receipt_pref=receipt_pref,
+ logout_after_purchase=logout_after_purchase)
except DatabaseConsistencyError:
return
@@ -176,8 +178,10 @@ def handle_admin_change(args: FormsDict, files: FormsDict, db: MatematDatabase):
password = str(args.password)
is_member = 'ismember' in args
is_admin = 'isadmin' in args
+ logout_after_purchase = 'logout_after_purchase' in args
# Create the user in the database
- newuser: User = db.create_user(username, password, email, member=is_member, admin=is_admin)
+ newuser: User = db.create_user(username, password, email, member=is_member, admin=is_admin,
+ logout_after_purchase=logout_after_purchase)
# If a default avatar is set, copy it to the user's avatar path
diff --git a/matemat/webserver/pagelets/buy.py b/matemat/webserver/pagelets/buy.py
index 796458d..a01bb45 100644
--- a/matemat/webserver/pagelets/buy.py
+++ b/matemat/webserver/pagelets/buy.py
@@ -16,6 +16,7 @@ def buy():
# If no user is logged in, redirect to the main page, as a purchase must always be bound to a user
if not session.has(session_id, 'authenticated_user'):
redirect('/')
+ authlevel: int = session.get(session_id, 'authentication_level')
# Connect to the database
with MatematDatabase(config['DatabaseFile']) as db:
# Fetch the authenticated user from the database
@@ -34,6 +35,9 @@ def buy():
stock_provider = c.get_stock_provider()
if stock_provider.needs_update():
stock_provider.update_stock(product, -1)
+ # Logout user if configured, logged in via touchkey and no price entry input was shown
+ if user.logout_after_purchase and authlevel < 2 and not product.custom_price:
+ redirect('/logout')
# Redirect to the main page (where this request should have come from)
redirect(f'/?lastaction=buy&lastproduct={pid}&lastprice={price}')
redirect('/')
diff --git a/matemat/webserver/pagelets/moduser.py b/matemat/webserver/pagelets/moduser.py
index dd212ab..3251e97 100644
--- a/matemat/webserver/pagelets/moduser.py
+++ b/matemat/webserver/pagelets/moduser.py
@@ -111,6 +111,7 @@ def handle_change(args: FormsDict, files: FormsDict, user: User, authuser: User,
balance_reason = None
is_member = 'ismember' in args
is_admin = 'isadmin' in args
+ logout_after_purchase = 'logout_after_purchase' in args
# An empty e-mail field should be interpreted as NULL
if len(email) == 0:
email = None
@@ -121,7 +122,8 @@ def handle_change(args: FormsDict, files: FormsDict, user: User, authuser: User,
db.change_password(user, '', password, verify_password=False)
# Write the user detail changes
db.change_user(user, agent=authuser, name=username, email=email, is_member=is_member, is_admin=is_admin,
- balance=balance, balance_reason=balance_reason, receipt_pref=receipt_pref)
+ balance=balance, balance_reason=balance_reason, receipt_pref=receipt_pref,
+ logout_after_purchase=logout_after_purchase)
except DatabaseConsistencyError:
return
# If a new avatar was uploaded, process it
diff --git a/templates/admin_all.html b/templates/admin_all.html
index 93b688b..e21fbb9 100644
--- a/templates/admin_all.html
+++ b/templates/admin_all.html
@@ -23,6 +23,9 @@
+
+
+
diff --git a/templates/admin_restricted.html b/templates/admin_restricted.html
index b5e64d3..fedfdfb 100644
--- a/templates/admin_restricted.html
+++ b/templates/admin_restricted.html
@@ -17,6 +17,9 @@
+
+
+
diff --git a/templates/moduser.html b/templates/moduser.html
index b71d100..3bfa7b7 100644
--- a/templates/moduser.html
+++ b/templates/moduser.html
@@ -35,6 +35,9 @@
+
+
+
CHF