From 2634181d5ef301c736cda58ff0c72230635967bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Gr=C3=B6ger?= Date: Mon, 22 Nov 2021 15:07:16 +0100 Subject: [PATCH] [balance] add serverside pagination --- flaschengeist/controller/userController.py | 14 +- flaschengeist/models/user.py | 5 + .../plugins/balance/balance_controller.py | 130 +++++++++++++++--- flaschengeist/plugins/balance/routes.py | 16 ++- flaschengeist/plugins/users/__init__.py | 5 +- 5 files changed, 140 insertions(+), 30 deletions(-) diff --git a/flaschengeist/controller/userController.py b/flaschengeist/controller/userController.py index 7427ac1..7c7c237 100644 --- a/flaschengeist/controller/userController.py +++ b/flaschengeist/controller/userController.py @@ -118,8 +118,12 @@ def modify_user(user, password, new_password=None): messageController.send_message(messageController.Message(user, text, subject)) -def get_users(): - return User.query.all() +def get_users(userids=None): + query = User.query + if userids: + query.filter(User.userid in userids) + query = query.order_by(User.lastname.asc(), User.firstname.asc()) + return query.all() def get_user_by_role(role: Role): @@ -175,8 +179,8 @@ def register(data): allowed_keys = User().serialize().keys() values = {key: value for key, value in data.items() if key in allowed_keys} roles = values.pop("roles", []) - if "birthday" in values: - values["birthday"] = from_iso_format(values["birthday"]).date() + if "birthday" in data: + values["birthday"] = from_iso_format(data["birthday"]).date() user = User(**values) set_roles(user, roles) @@ -195,6 +199,8 @@ def register(data): ) messageController.send_message(messageController.Message(user, text, subject)) + find_user(user.userid) + return user diff --git a/flaschengeist/models/user.py b/flaschengeist/models/user.py index 2f3ac67..095ba12 100644 --- a/flaschengeist/models/user.py +++ b/flaschengeist/models/user.py @@ -104,6 +104,11 @@ class User(db.Model, ModelSerializeMixin): def has_permission(self, permission): return permission in self.get_permissions() + def __repr__(self): + return ( + f"User({self.userid}, {self.firstname}, {self.lastname}, {self.mail}, {self.display_name}, {self.birthday})" + ) + class _UserAttribute(db.Model, ModelSerializeMixin): __tablename__ = "user_attribute" diff --git a/flaschengeist/plugins/balance/balance_controller.py b/flaschengeist/plugins/balance/balance_controller.py index 1d6174f..9925b44 100644 --- a/flaschengeist/plugins/balance/balance_controller.py +++ b/flaschengeist/plugins/balance/balance_controller.py @@ -3,12 +3,13 @@ # English: Debit -> from account # Credit -> to account -from sqlalchemy import func +from sqlalchemy import func, case, and_ +from sqlalchemy.ext.hybrid import hybrid_property from datetime import datetime from werkzeug.exceptions import BadRequest, NotFound, Conflict from flaschengeist.database import db -from flaschengeist.models.user import User +from flaschengeist.models.user import User, _UserAttribute from .models import Transaction from . import permissions, BalancePlugin @@ -38,27 +39,114 @@ def get_balance(user, start: datetime = None, end: datetime = None): return credit, debit, credit - debit -def get_balances(start: datetime = None, end: datetime = None): - debit = db.session.query(Transaction.sender_id, func.sum(Transaction.amount)).filter(Transaction.sender_ != None) - credit = db.session.query(Transaction.receiver_id, func.sum(Transaction.amount)).filter( - Transaction.receiver_ != None - ) - if start: - debit = debit.filter(start <= Transaction.time) - credit = credit.filter(start <= Transaction.time) - if end: - debit = debit.filter(Transaction.time <= end) - credit = credit.filter(Transaction.time <= end) +def get_balances(start: datetime = None, end: datetime = None, limit=None, offset=None, descending=None, sortBy=None): + class _User(User): + _debit = db.relationship(Transaction, back_populates="sender_", foreign_keys=[Transaction._sender_id]) + _credit = db.relationship(Transaction, back_populates="receiver_", foreign_keys=[Transaction._receiver_id]) - debit = debit.group_by(Transaction._sender_id).all() - credit = credit.group_by(Transaction._receiver_id).all() + @hybrid_property + def debit(self): + return sum([cred.amount for cred in self._debit]) + + @debit.expression + def debit(cls): + a = ( + db.select(func.sum(Transaction.amount)) + .where(cls.id_ == Transaction._sender_id, Transaction.amount) + .scalar_subquery() + ) + return case([(a, a)], else_=0) + + @hybrid_property + def credit(self): + return sum([cred.amount for cred in self._credit]) + + @credit.expression + def credit(cls): + b = ( + db.select(func.sum(Transaction.amount)) + .where(cls.id_ == Transaction._receiver_id, Transaction.amount) + .scalar_subquery() + ) + return case([(b, b)], else_=0) + + @hybrid_property + def limit(self): + return self.get_attribute("balance_limit", None) + + @limit.expression + def limit(cls): + return ( + db.select(_UserAttribute.value) + .where(and_(cls.id_ == _UserAttribute.user, _UserAttribute.name == "balance_limit")) + .scalar_subquery() + ) + + def get_debit(self, start: datetime = None, end: datetime = None): + if start and end: + return sum([deb.amount for deb in self._debit if start <= deb.time and deb.time <= end]) + if start: + return sum([deb.amount for deb in self._dedit if start <= deb.time]) + if end: + return sum([deb.amount for deb in self._dedit if deb.time <= end]) + return self.debit + + def get_credit(self, start: datetime = None, end: datetime = None): + if start and end: + return sum([cred.amount for cred in self._credit if start <= cred.time and cred.time <= end]) + if start: + return sum([cred.amount for cred in self._credit if start <= cred.time]) + if end: + return sum([cred.amount for cred in self._credit if cred.time <= end]) + return self.credit + + query = _User.query + + if start: + q1 = query.join(_User._credit).filter(start <= Transaction.time) + q2 = query.join(_User._debit).filter(start <= Transaction.time) + query = q1.union(q2) + if end: + q1 = query.join(_User._credit).filter(Transaction.time <= end) + q2 = query.join(_User._debit).filter(Transaction.time <= end) + query = q1.union(q2) + + if sortBy == "balance": + if descending: + query = query.order_by((_User.credit - _User.debit).desc(), _User.lastname.asc(), _User.firstname.asc()) + else: + query = query.order_by((_User.credit - _User.debit).asc(), _User.lastname.asc(), _User.firstname.asc()) + elif sortBy == "limit": + if descending: + query = query.order_by(_User.limit.desc(), User.lastname.asc(), User.firstname.asc()) + else: + query = query.order_by(_User.limit.asc(), User.lastname.asc(), User.firstname.asc()) + elif sortBy == "firstname": + if descending: + query = query.order_by(User.firstname.desc(), User.lastname.desc()) + else: + query = query.order_by(User.firstname.asc(), User.lastname.asc()) + elif sortBy == "lastname": + if descending: + query = query.order_by(User.lastname.desc(), User.firstname.desc()) + else: + query = query.order_by(User.lastname.asc(), User.firstname.asc()) + + count = None + if limit: + count = query.count() + query = query.limit(limit) + if offset: + query = query.offset(offset) + users = query all = {} - for uid, cred in credit: - all[uid] = [cred, 0] - for uid, deb in debit: - all.setdefault(uid, [0, 0]) - all[uid][1] = deb - return all + + for user in users: + + all[user.userid] = [user.get_credit(start, end), 0] + all[user.userid][1] = user.get_debit(start, end) + + return all, count def send(sender: User, receiver, amount: float, author: User): diff --git a/flaschengeist/plugins/balance/routes.py b/flaschengeist/plugins/balance/routes.py index b0ce3c9..11c7e33 100644 --- a/flaschengeist/plugins/balance/routes.py +++ b/flaschengeist/plugins/balance/routes.py @@ -110,8 +110,10 @@ def limits(current_session: Session): Returns: JSON encoded array of userid with limit or HTTP-error """ - - users = userController.get_users() + userids = None + if "userids" in request.args: + [x for x in request.args.get("userids").split(",") if x] + users = userController.get_users(userids=userids) if request.method == "GET": return jsonify([{"userid": user.userid, "limit": user.get_attribute("balance_limit")} for user in users]) @@ -311,5 +313,11 @@ def get_balances(current_session: Session): Returns: JSON Array containing credit, debit and userid for each user or HTTP error """ - balances = balance_controller.get_balances() - return jsonify([{"userid": u, "credit": v[0], "debit": v[1]} for u, v in balances.items()]) + limit = request.args.get("limit", type=int) + offset = request.args.get("offset", type=int) + descending = request.args.get("descending", False, type=bool) + sortBy = request.args.get("sortBy", type=str) + balances, count = balance_controller.get_balances(limit=limit, offset=offset, descending=descending, sortBy=sortBy) + return jsonify( + {"balances": [{"userid": u, "credit": v[0], "debit": v[1]} for u, v in balances.items()], "count": count} + ) diff --git a/flaschengeist/plugins/users/__init__.py b/flaschengeist/plugins/users/__init__.py index d0f929e..a8c87c8 100644 --- a/flaschengeist/plugins/users/__init__.py +++ b/flaschengeist/plugins/users/__init__.py @@ -69,7 +69,10 @@ def list_users(current_session): JSON encoded array of `flaschengeist.models.user.User` or HTTP error """ logger.debug("Retrieve list of all users") - users = userController.get_users() + userids = None + if "userids" in request.args: + userids = [x for x in request.args.get("userids").split(",") if x] + users = userController.get_users(userids=userids) return jsonify(users)