Start User management

This commit is contained in:
2025-12-13 15:15:18 +01:00
parent a5ac52a387
commit bafca3c291
6 changed files with 145 additions and 11 deletions

View File

@@ -0,0 +1,26 @@
from decimal import Decimal
from fastapi import APIRouter, HTTPException, Request
from sqlalchemy import select
from starlette import status
from starlette.responses import RedirectResponse
from allmende_payment_system.api.dependencies import SessionDep, UserDep
from allmende_payment_system.models import Area, Order, OrderItem, Product, User
from allmende_payment_system.tools import get_jinja_renderer
admin_router = APIRouter(prefix="/admin")
@admin_router.get("/users")
async def user_list(request: Request, session: SessionDep, user: UserDep):
if not user.has_permission("user", "edit"):
raise HTTPException(status_code=403, detail="Insufficient permissions")
query = select(User)
users = session.scalars(query).all()
templates = get_jinja_renderer()
return templates.TemplateResponse(
"users.html.jinja",
context={"request": request, "users": users},
)

View File

@@ -4,6 +4,7 @@ from fastapi import Depends, FastAPI
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from allmende_payment_system.api import root_router from allmende_payment_system.api import root_router
from allmende_payment_system.api.admin import admin_router
from allmende_payment_system.api.dependencies import get_user_object from allmende_payment_system.api.dependencies import get_user_object
from allmende_payment_system.api.shop import shop_router from allmende_payment_system.api.shop import shop_router
@@ -20,3 +21,4 @@ app.mount(
app.include_router(root_router) app.include_router(root_router)
app.include_router(shop_router) app.include_router(shop_router)
app.include_router(admin_router)

View File

@@ -55,6 +55,12 @@ class User(Base):
) )
orders: Mapped[list["Order"]] = relationship("Order", back_populates="user") orders: Mapped[list["Order"]] = relationship("Order", back_populates="user")
user_groups: Mapped[list["UserGroup"]] = relationship(
"UserGroup",
secondary=TABLE_PREFIX + "user_user_group_association",
back_populates="users",
)
@property @property
def shopping_cart(self): def shopping_cart(self):
for order in self.orders: for order in self.orders:
@@ -68,6 +74,52 @@ class User(Base):
return cart return cart
def has_permission(self, scope: str, action: str) -> bool:
for group in self.user_groups:
for permission in group.permissions:
if permission.scope == scope and permission.action == action:
return True
return False
class UserGroup(Base):
__tablename__ = TABLE_PREFIX + "user_group"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(nullable=False, unique=True)
description: Mapped[str] = mapped_column(nullable=True)
permissions = relationship("Permission", back_populates="user_group")
users: Mapped[list["User"]] = relationship(
"User",
secondary=TABLE_PREFIX + "user_user_group_association",
back_populates="user_groups",
)
user_group_association = Table(
TABLE_PREFIX + "user_user_group_association",
Base.metadata,
Column("user_id", ForeignKey(TABLE_PREFIX + "user.id"), primary_key=True),
Column(
"user_group_id", ForeignKey(TABLE_PREFIX + "user_group.id"), primary_key=True
),
)
class Permission(Base):
__tablename__ = TABLE_PREFIX + "permission"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
scope: Mapped[str] = mapped_column(nullable=False)
action: Mapped[str] = mapped_column(nullable=False)
user_group_id: Mapped[int] = mapped_column(
ForeignKey(TABLE_PREFIX + "user_group.id")
)
user_group: Mapped["UserGroup"] = relationship(
"UserGroup", back_populates="permissions"
)
class Area(Base): class Area(Base):
__tablename__ = TABLE_PREFIX + "area" __tablename__ = TABLE_PREFIX + "area"

View File

@@ -29,16 +29,13 @@
Einkaufen Einkaufen
</a> </a>
</li> </li>
{# <li class="nav-item">#} {% if request.state.user.has_permission("user", "edit") %}
{# <a href="#" class="nav-link">#} <li class="nav-item">
{# Lorem#} <a href="/admin/users" class="nav-link{% if request.url.path.startswith("/admin/users")%} active{% endif %}">
{# </a>#} Nutzerverwaltung
{# </li>#} </a>
{# <li class="nav-item">#} </li>
{# <a href="#" class="nav-link">#} {% endif %}
{# Ipsum#}
{# </a>#}
{# </li>#}
</ul> </ul>
<!-- Shopping Cart at Bottom --> <!-- Shopping Cart at Bottom -->

View File

@@ -0,0 +1,39 @@
{% extends "base.html.jinja" %}
{% block content %}
<div class="mb-4">
<h2 class="h4 mb-3">Benutzer verwalten</h2>
</div>
{% if users|length == 0 %}
<div class="alert alert-info">Keine Benutzer vorhanden.</div>
{% else %}
<div class="table-responsive">
<table class="table align-middle">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Azeigename</th>
<th>Gruppen</th>
<th></th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.display_name }}</td>
<td>{{ user.user_groups | map(attribute='name') | join(', ') }}</td>
<td class="text-end">
<a href="/users/edit/{{ user.id }}" class="btn btn-sm btn-outline-primary me-1">Bearbeiten</a>
<a href="/users/delete/{{ user.id }}" class="btn btn-sm btn-outline-danger">Löschen</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% endblock %}

View File

@@ -1,6 +1,6 @@
import pytest import pytest
from allmende_payment_system.models import Account, User from allmende_payment_system.models import Account, Permission, User, UserGroup
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
@@ -26,3 +26,21 @@ def test_user_shopping_cart_new(test_db, test_user):
cart = test_user.shopping_cart cart = test_user.shopping_cart
assert len(cart.items) == 0 assert len(cart.items) == 0
def test_user_permissions(test_db):
user = User(username="normie", display_name="Normal User")
admin = User(username="admin", display_name="Admin User")
test_db.add(user)
group = UserGroup(name="Admins", description="A group for admins")
group.permissions.append(Permission(scope="area", action="edit"))
test_db.add(group)
admin.user_groups.append(group)
test_db.flush()
assert len(admin.user_groups) == 1
assert not user.has_permission("area", "edit")
assert admin.has_permission("area", "edit")