Start User management
This commit is contained in:
26
src/allmende_payment_system/api/admin.py
Normal file
26
src/allmende_payment_system/api/admin.py
Normal 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},
|
||||
)
|
||||
@@ -4,6 +4,7 @@ from fastapi import Depends, FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
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.shop import shop_router
|
||||
|
||||
@@ -20,3 +21,4 @@ app.mount(
|
||||
|
||||
app.include_router(root_router)
|
||||
app.include_router(shop_router)
|
||||
app.include_router(admin_router)
|
||||
|
||||
@@ -55,6 +55,12 @@ class User(Base):
|
||||
)
|
||||
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
|
||||
def shopping_cart(self):
|
||||
for order in self.orders:
|
||||
@@ -68,6 +74,52 @@ class User(Base):
|
||||
|
||||
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):
|
||||
__tablename__ = TABLE_PREFIX + "area"
|
||||
|
||||
@@ -29,16 +29,13 @@
|
||||
Einkaufen
|
||||
</a>
|
||||
</li>
|
||||
{# <li class="nav-item">#}
|
||||
{# <a href="#" class="nav-link">#}
|
||||
{# Lorem#}
|
||||
{# </a>#}
|
||||
{# </li>#}
|
||||
{# <li class="nav-item">#}
|
||||
{# <a href="#" class="nav-link">#}
|
||||
{# Ipsum#}
|
||||
{# </a>#}
|
||||
{# </li>#}
|
||||
{% if request.state.user.has_permission("user", "edit") %}
|
||||
<li class="nav-item">
|
||||
<a href="/admin/users" class="nav-link{% if request.url.path.startswith("/admin/users")%} active{% endif %}">
|
||||
Nutzerverwaltung
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<!-- Shopping Cart at Bottom -->
|
||||
|
||||
39
src/allmende_payment_system/templates/users.html.jinja
Normal file
39
src/allmende_payment_system/templates/users.html.jinja
Normal 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 %}
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from allmende_payment_system.models import Account, User
|
||||
from allmende_payment_system.models import Account, Permission, User, UserGroup
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@@ -26,3 +26,21 @@ def test_user_shopping_cart_new(test_db, test_user):
|
||||
cart = test_user.shopping_cart
|
||||
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user