[balance] add serverside pagination

This commit is contained in:
Tim Gröger 2021-11-22 15:07:16 +01:00
parent 0b94177235
commit 9ab84073d0
5 changed files with 140 additions and 30 deletions

View File

@ -118,8 +118,12 @@ def modify_user(user, password, new_password=None):
messageController.send_message(messageController.Message(user, text, subject)) messageController.send_message(messageController.Message(user, text, subject))
def get_users(): def get_users(userids=None):
return User.query.all() 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): def get_user_by_role(role: Role):
@ -175,8 +179,8 @@ def register(data):
allowed_keys = User().serialize().keys() allowed_keys = User().serialize().keys()
values = {key: value for key, value in data.items() if key in allowed_keys} values = {key: value for key, value in data.items() if key in allowed_keys}
roles = values.pop("roles", []) roles = values.pop("roles", [])
if "birthday" in values: if "birthday" in data:
values["birthday"] = from_iso_format(values["birthday"]).date() values["birthday"] = from_iso_format(data["birthday"]).date()
user = User(**values) user = User(**values)
set_roles(user, roles) set_roles(user, roles)
@ -195,6 +199,8 @@ def register(data):
) )
messageController.send_message(messageController.Message(user, text, subject)) messageController.send_message(messageController.Message(user, text, subject))
find_user(user.userid)
return user return user

View File

@ -104,6 +104,11 @@ class User(db.Model, ModelSerializeMixin):
def has_permission(self, permission): def has_permission(self, permission):
return permission in self.get_permissions() 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): class _UserAttribute(db.Model, ModelSerializeMixin):
__tablename__ = "user_attribute" __tablename__ = "user_attribute"

View File

@ -3,12 +3,13 @@
# English: Debit -> from account # English: Debit -> from account
# Credit -> to 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 datetime import datetime
from werkzeug.exceptions import BadRequest, NotFound, Conflict from werkzeug.exceptions import BadRequest, NotFound, Conflict
from flaschengeist.database import db from flaschengeist.database import db
from flaschengeist.models.user import User from flaschengeist.models.user import User, _UserAttribute
from .models import Transaction from .models import Transaction
from . import permissions, BalancePlugin from . import permissions, BalancePlugin
@ -38,27 +39,114 @@ def get_balance(user, start: datetime = None, end: datetime = None):
return credit, debit, credit - debit return credit, debit, credit - debit
def get_balances(start: datetime = None, end: datetime = None): def get_balances(start: datetime = None, end: datetime = None, limit=None, offset=None, descending=None, sortBy=None):
debit = db.session.query(Transaction.sender_id, func.sum(Transaction.amount)).filter(Transaction.sender_ != None) class _User(User):
credit = db.session.query(Transaction.receiver_id, func.sum(Transaction.amount)).filter( _debit = db.relationship(Transaction, back_populates="sender_", foreign_keys=[Transaction._sender_id])
Transaction.receiver_ != None _credit = db.relationship(Transaction, back_populates="receiver_", foreign_keys=[Transaction._receiver_id])
)
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)
debit = debit.group_by(Transaction._sender_id).all() @hybrid_property
credit = credit.group_by(Transaction._receiver_id).all() 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 = {} all = {}
for uid, cred in credit:
all[uid] = [cred, 0] for user in users:
for uid, deb in debit:
all.setdefault(uid, [0, 0]) all[user.userid] = [user.get_credit(start, end), 0]
all[uid][1] = deb all[user.userid][1] = user.get_debit(start, end)
return all
return all, count
def send(sender: User, receiver, amount: float, author: User): def send(sender: User, receiver, amount: float, author: User):

View File

@ -110,8 +110,10 @@ def limits(current_session: Session):
Returns: Returns:
JSON encoded array of userid with limit or HTTP-error JSON encoded array of userid with limit or HTTP-error
""" """
userids = None
users = userController.get_users() 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": if request.method == "GET":
return jsonify([{"userid": user.userid, "limit": user.get_attribute("balance_limit")} for user in users]) 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: Returns:
JSON Array containing credit, debit and userid for each user or HTTP error JSON Array containing credit, debit and userid for each user or HTTP error
""" """
balances = balance_controller.get_balances() limit = request.args.get("limit", type=int)
return jsonify([{"userid": u, "credit": v[0], "debit": v[1]} for u, v in balances.items()]) 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}
)

View File

@ -69,7 +69,10 @@ def list_users(current_session):
JSON encoded array of `flaschengeist.models.user.User` or HTTP error JSON encoded array of `flaschengeist.models.user.User` or HTTP error
""" """
logger.debug("Retrieve list of all users") 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) return jsonify(users)