Update redirect paths and enhance user context in templates and views

This commit is contained in:
Christian Krakau-Louis
2025-04-14 05:54:21 +02:00
parent 690065f788
commit e952630295
8 changed files with 189 additions and 37 deletions
+2 -2
View File
@@ -90,10 +90,10 @@ app.include_router(leaderboard.router, prefix="/leaderboard", tags=["Leaderboard
app.include_router(dashboard.router, prefix="/dashboard", tags=["Dashboard"]) app.include_router(dashboard.router, prefix="/dashboard", tags=["Dashboard"])
app.include_router(static.router, tags=["Static"]) # Include the static router app.include_router(static.router, tags=["Static"]) # Include the static router
# Add a direct route for /scan that redirects to /redeem/scan # Add a direct route for /scan that redirects to /dashboard/scan
@app.get("/scan") @app.get("/scan")
async def scan_redirect(): async def scan_redirect():
return RedirectResponse("/redeem/scan", status_code=303) return RedirectResponse("/dashboard/scan", status_code=303)
# Add convenience routes for auth paths # Add convenience routes for auth paths
@app.get("/login") @app.get("/login")
+3 -3
View File
@@ -68,7 +68,7 @@
<a href="/" class="hover:text-golden-ale transition">Home</a> <a href="/" class="hover:text-golden-ale transition">Home</a>
<a href="/teams" class="hover:text-golden-ale transition">Teams</a> <a href="/teams" class="hover:text-golden-ale transition">Teams</a>
<a href="/leaderboard" class="hover:text-golden-ale transition">Leaderboard</a> <a href="/leaderboard" class="hover:text-golden-ale transition">Leaderboard</a>
<a href="/scan" class="hover:text-golden-ale transition">Scan QR Code</a> <a href="/dashboard/scan" class="hover:text-golden-ale transition">Scan QR Code</a>
{% if user %} {% if user %}
<div class="relative dropdown"> <div class="relative dropdown">
@@ -114,7 +114,7 @@
<a href="/" class="hover:text-golden-ale transition py-2">Home</a> <a href="/" class="hover:text-golden-ale transition py-2">Home</a>
<a href="/teams" class="hover:text-golden-ale transition py-2">Teams</a> <a href="/teams" class="hover:text-golden-ale transition py-2">Teams</a>
<a href="/leaderboard" class="hover:text-golden-ale transition py-2">Leaderboard</a> <a href="/leaderboard" class="hover:text-golden-ale transition py-2">Leaderboard</a>
<a href="/scan" class="hover:text-golden-ale transition py-2">Scan QR Code</a> <a href="/dashboard/scan" class="hover:text-golden-ale transition py-2">Scan QR Code</a>
{% if user %} {% if user %}
<a href="/profile" class="hover:text-golden-ale transition py-2">Profile</a> <a href="/profile" class="hover:text-golden-ale transition py-2">Profile</a>
<a href="/dashboard" class="hover:text-golden-ale transition py-2">Dashboard</a> <a href="/dashboard" class="hover:text-golden-ale transition py-2">Dashboard</a>
@@ -154,7 +154,7 @@
<li><a href="/" class="hover:text-golden-ale transition">Home</a></li> <li><a href="/" class="hover:text-golden-ale transition">Home</a></li>
<li><a href="/teams" class="hover:text-golden-ale transition">Teams</a></li> <li><a href="/teams" class="hover:text-golden-ale transition">Teams</a></li>
<li><a href="/leaderboard" class="hover:text-golden-ale transition">Leaderboard</a></li> <li><a href="/leaderboard" class="hover:text-golden-ale transition">Leaderboard</a></li>
<li><a href="/scan" class="hover:text-golden-ale transition">Scan QR Code</a></li> <li><a href="/dashboard/scan" class="hover:text-golden-ale transition">Scan QR Code</a></li>
</ul> </ul>
</div> </div>
+4 -2
View File
@@ -1,6 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<div class="space-y-6"> <div class="max-w-4xl mx-auto space-y-6">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6"> <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6">
<div> <div>
<h1 class="text-2xl md:text-3xl font-garamond font-bold text-irish-green">League Leaderboard</h1> <h1 class="text-2xl md:text-3xl font-garamond font-bold text-irish-green">League Leaderboard</h1>
@@ -82,7 +82,9 @@
{% for team in teams %} {% for team in teams %}
<tr class="{% if team.rank == 1 %}bg-golden-ale bg-opacity-10{% endif %} hover:bg-gray-50"> <tr class="{% if team.rank == 1 %}bg-golden-ale bg-opacity-10{% endif %} hover:bg-gray-50">
<td class="py-3 px-4 font-bold">{{ team.rank }}</td> <td class="py-3 px-4 font-bold">{{ team.rank }}</td>
<td class="py-3 px-4">{{ team.name }}</td> <td class="py-3 px-4">
<a href="/teams/{{ team.id }}" class="text-irish-green hover:underline">{{ team.name }}</a>
</td>
<td class="py-3 px-4 text-right font-bold">{{ team.points }}</td> <td class="py-3 px-4 text-right font-bold">{{ team.points }}</td>
<td class="py-3 px-4 text-center"> <td class="py-3 px-4 text-center">
{% if team.change > 0 %} {% if team.change > 0 %}
+5 -5
View File
@@ -50,7 +50,7 @@
<!-- Next Steps --> <!-- Next Steps -->
<div class="bg-white rounded-lg shadow-md p-6"> <div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-semibold text-irish-green mb-4">What's Next?</h3> <h3 class="text-xl font-semibold text-irish-green mb-4">What's Next?</h3>
<div class="space-y-4"> <div class="space-y-3 mt-8">
<a href="/teams/{{ team.id }}" class="flex items-start hover:bg-gray-50 p-3 rounded-md cursor-pointer"> <a href="/teams/{{ team.id }}" class="flex items-start hover:bg-gray-50 p-3 rounded-md cursor-pointer">
<div class="bg-cream-white p-2 rounded-full mr-3"> <div class="bg-cream-white p-2 rounded-full mr-3">
<i class="fas fa-users text-irish-green"></i> <i class="fas fa-users text-irish-green"></i>
@@ -71,13 +71,13 @@
</div> </div>
</a> </a>
<a href="/scan" class="flex items-start hover:bg-gray-50 p-3 rounded-md cursor-pointer"> <a href="/dashboard/scan" class="flex items-start hover:bg-gray-50 p-3 rounded-md cursor-pointer">
<div class="bg-cream-white p-2 rounded-full mr-3"> <div class="bg-green-100 p-2 rounded-full mr-3">
<i class="fas fa-qrcode text-irish-green"></i> <i class="fas fa-qrcode text-green-600"></i>
</div> </div>
<div> <div>
<p class="font-medium">Scan Another QR Code</p> <p class="font-medium">Scan Another QR Code</p>
<p class="text-gray-600 text-sm">Got more codes to redeem? Scan them now.</p> <p class="text-sm text-gray-600">Find more QR codes to redeem</p>
</div> </div>
</a> </a>
</div> </div>
+47 -4
View File
@@ -67,12 +67,22 @@ def get_relationships(model_class: Type[Base]) -> Dict[str, str]:
return relationships return relationships
@router.get("/", response_class=HTMLResponse) @router.get("/", response_class=HTMLResponse)
async def admin_home(request: Request): async def admin_home(request: Request, db: Session = Depends(get_db)):
"""Admin dashboard home.""" """Admin dashboard home."""
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Check admin status
if not user or not user.is_admin:
raise HTTPException(status_code=403, detail="Forbidden: Admin access required")
model_list = [(key, name) for key, (_, name) in MODELS.items()] model_list = [(key, name) for key, (_, name) in MODELS.items()]
return templates.TemplateResponse( return templates.TemplateResponse(
"admin/index.html", "admin/index.html",
{"request": request, "models": model_list} {"request": request, "models": model_list, "user": user}
) )
@router.get("/{model_name}", response_class=HTMLResponse) @router.get("/{model_name}", response_class=HTMLResponse)
@@ -84,6 +94,16 @@ async def list_records(
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
"""List records for a model with pagination.""" """List records for a model with pagination."""
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Check admin status
if not user or not user.is_admin:
raise HTTPException(status_code=403, detail="Forbidden: Admin access required")
if model_name not in MODELS: if model_name not in MODELS:
raise HTTPException(status_code=404, detail=f"Model {model_name} not found") raise HTTPException(status_code=404, detail=f"Model {model_name} not found")
@@ -123,6 +143,7 @@ async def list_records(
"per_page": per_page, "per_page": per_page,
"total_pages": total_pages, "total_pages": total_pages,
"total_records": total_records, "total_records": total_records,
"user": user # Add user to the context
} }
) )
@@ -133,6 +154,16 @@ async def create_record_form(
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
"""Show form for creating a new record.""" """Show form for creating a new record."""
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Check admin status
if not user or not user.is_admin:
raise HTTPException(status_code=403, detail="Forbidden: Admin access required")
if model_name not in MODELS: if model_name not in MODELS:
raise HTTPException(status_code=404, detail=f"Model {model_name} not found") raise HTTPException(status_code=404, detail=f"Model {model_name} not found")
@@ -162,7 +193,8 @@ async def create_record_form(
"columns_info": columns_info, "columns_info": columns_info,
"record": None, # No record for new form "record": None, # No record for new form
"foreign_key_options": foreign_key_options, "foreign_key_options": foreign_key_options,
"is_new": True "is_new": True,
"user": user # Add user to the context
} }
) )
@@ -222,6 +254,16 @@ async def edit_record_form(
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
"""Show form for editing an existing record.""" """Show form for editing an existing record."""
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Check admin status
if not user or not user.is_admin:
raise HTTPException(status_code=403, detail="Forbidden: Admin access required")
if model_name not in MODELS: if model_name not in MODELS:
raise HTTPException(status_code=404, detail=f"Model {model_name} not found") raise HTTPException(status_code=404, detail=f"Model {model_name} not found")
@@ -261,7 +303,8 @@ async def edit_record_form(
"columns_info": columns_info, "columns_info": columns_info,
"record": record_data, "record": record_data,
"foreign_key_options": foreign_key_options, "foreign_key_options": foreign_key_options,
"is_new": False "is_new": False,
"user": user # Add user to the context
} }
) )
+52 -3
View File
@@ -92,6 +92,45 @@ def user_dashboard(request: Request, db: Session = Depends(get_db)):
.limit(5)\ .limit(5)\
.all() .all()
# Get user teams with enhanced data for display
user_teams_enhanced = []
if user_teams:
# Get all team points to calculate ranks
team_points_query = db.query(
models.Team.id,
func.sum(models.QRCode.points).label('total_points')
).outerjoin(
models.QRCode,
models.QRCode.redeemed_at_team == models.Team.id
).group_by(models.Team.id)
# Get results and sort by points in descending order
team_points = {row.id: row.total_points or 0 for row in team_points_query.all()}
ranked_teams = sorted(team_points.items(), key=lambda x: x[1], reverse=True)
# Create a dictionary mapping team ID to rank
team_ranks = {team_id: i+1 for i, (team_id, _) in enumerate(ranked_teams)}
# Enhance user teams with rank and points data
for team in user_teams:
points = team_points.get(team.id, 0)
rank = team_ranks.get(team.id, len(team_ranks) + 1)
user_teams_enhanced.append({
'id': team.id,
'name': team.name,
'points': points,
'rank': rank,
'description': getattr(team, 'description', None)
})
# Find best rank if the user has any teams
best_rank = None
if user_teams_enhanced:
ranks = [team['rank'] for team in user_teams_enhanced]
best_rank = min(ranks) if ranks else None
return templates.TemplateResponse( return templates.TemplateResponse(
"dashboard/index.html", "dashboard/index.html",
{ {
@@ -101,7 +140,8 @@ def user_dashboard(request: Request, db: Session = Depends(get_db)):
"total_points": total_points, "total_points": total_points,
"event_count": event_count, "event_count": event_count,
"recent_events": recent_events, "recent_events": recent_events,
"user_teams": user_teams "user_teams": user_teams_enhanced,
"best_rank": best_rank
} }
) )
except Exception as e: except Exception as e:
@@ -109,6 +149,15 @@ def user_dashboard(request: Request, db: Session = Depends(get_db)):
raise HTTPException(status_code=500, detail=f"Dashboard error: {str(e)}") raise HTTPException(status_code=500, detail=f"Dashboard error: {str(e)}")
@router.get("/scan", response_class=HTMLResponse) @router.get("/scan", response_class=HTMLResponse)
def scan_qr_page(request: Request): def scan_qr_page(request: Request, db: Session = Depends(get_db)):
"""Render the QR code scanning page.""" """Render the QR code scanning page."""
return templates.TemplateResponse("scan_qr.html", {"request": request}) # Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(models.User).get(user_id)
return templates.TemplateResponse("scan_qr.html", {
"request": request,
"user": user # Add user to the context
})
+25 -4
View File
@@ -20,7 +20,7 @@ from reportlab.lib.enums import TA_CENTER
from pydantic import BaseModel from pydantic import BaseModel
from ..db import SessionLocal from ..db import SessionLocal
from ..models import QRCode, QRSet, Event from ..models import QRCode, QRSet, Event, User
from ..templates_config import templates from ..templates_config import templates
router = APIRouter() router = APIRouter()
@@ -55,6 +55,12 @@ class QRCodeRequest(BaseModel):
@router.get("/", response_class=HTMLResponse) @router.get("/", response_class=HTMLResponse)
async def qr_dashboard(request: Request, db: Session = Depends(get_db)): async def qr_dashboard(request: Request, db: Session = Depends(get_db)):
"""QR code management dashboard.""" """QR code management dashboard."""
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Get all QR sets # Get all QR sets
qr_sets = db.query(QRSet).all() qr_sets = db.query(QRSet).all()
@@ -64,7 +70,8 @@ async def qr_dashboard(request: Request, db: Session = Depends(get_db)):
return templates.TemplateResponse("qr/dashboard.html", { return templates.TemplateResponse("qr/dashboard.html", {
"request": request, "request": request,
"qr_sets": qr_sets, "qr_sets": qr_sets,
"events": events "events": events,
"user": user # Add user to the context
}) })
@@ -94,6 +101,12 @@ async def create_qr_set(
@router.get("/sets/{set_id}", response_class=HTMLResponse) @router.get("/sets/{set_id}", response_class=HTMLResponse)
async def view_qr_set(request: Request, set_id: int, db: Session = Depends(get_db)): async def view_qr_set(request: Request, set_id: int, db: Session = Depends(get_db)):
"""View details of a QR set.""" """View details of a QR set."""
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
qr_set = db.query(QRSet).filter(QRSet.id == set_id).first() qr_set = db.query(QRSet).filter(QRSet.id == set_id).first()
if not qr_set: if not qr_set:
raise HTTPException(status_code=404, detail="QR Set not found") raise HTTPException(status_code=404, detail="QR Set not found")
@@ -105,7 +118,8 @@ async def view_qr_set(request: Request, set_id: int, db: Session = Depends(get_d
"request": request, "request": request,
"qr_set": qr_set, "qr_set": qr_set,
"qr_codes": qr_codes, "qr_codes": qr_codes,
"base_url": BASE_URL "base_url": BASE_URL,
"user": user # Add user to the context
}) })
@@ -208,6 +222,12 @@ def generate_admin_qr(set_id: int, db: Session = Depends(get_db)):
@router.get("/admin-link/{admin_code}", response_class=HTMLResponse) @router.get("/admin-link/{admin_code}", response_class=HTMLResponse)
async def admin_link_page(request: Request, admin_code: str, db: Session = Depends(get_db)): async def admin_link_page(request: Request, admin_code: str, db: Session = Depends(get_db)):
"""Page for linking QR sets to events via admin code.""" """Page for linking QR sets to events via admin code."""
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Extract set_id from admin code # Extract set_id from admin code
try: try:
set_id = int(admin_code.split("-")[1]) set_id = int(admin_code.split("-")[1])
@@ -225,7 +245,8 @@ async def admin_link_page(request: Request, admin_code: str, db: Session = Depen
"request": request, "request": request,
"qr_set": qr_set, "qr_set": qr_set,
"admin_code": admin_code, "admin_code": admin_code,
"events": events "events": events,
"user": user # Add user to the context
}) })
+51 -14
View File
@@ -29,6 +29,12 @@ def redeem_code(code: str, request: Request, db: Session = Depends(get_db)):
Display a page to let the user choose which team to apply points to. Display a page to let the user choose which team to apply points to.
No login required. No login required.
""" """
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Find the QR code record # Find the QR code record
qr_code = db.query(QRCode).filter_by(code=code).first() qr_code = db.query(QRCode).filter_by(code=code).first()
@@ -39,7 +45,8 @@ def redeem_code(code: str, request: Request, db: Session = Depends(get_db)):
{ {
"request": request, "request": request,
"error_title": "Invalid Code", "error_title": "Invalid Code",
"error_message": "This QR code is invalid or does not exist." "error_message": "This QR code is invalid or does not exist.",
"user": user # Add user to the context
} }
) )
@@ -49,7 +56,8 @@ def redeem_code(code: str, request: Request, db: Session = Depends(get_db)):
{ {
"request": request, "request": request,
"error_title": "Code Already Used", "error_title": "Code Already Used",
"error_message": "This QR code has already been redeemed." "error_message": "This QR code has already been redeemed.",
"user": user # Add user to the context
} }
) )
@@ -60,7 +68,8 @@ def redeem_code(code: str, request: Request, db: Session = Depends(get_db)):
{ {
"request": request, "request": request,
"error_title": "Expired Code", "error_title": "Expired Code",
"error_message": "This QR code has expired and can no longer be redeemed." "error_message": "This QR code has expired and can no longer be redeemed.",
"user": user # Add user to the context
} }
) )
@@ -72,16 +81,24 @@ def redeem_code(code: str, request: Request, db: Session = Depends(get_db)):
"ticket": qr_code, # Using the same template variable name for compatibility "ticket": qr_code, # Using the same template variable name for compatibility
"user_teams": all_teams, "user_teams": all_teams,
"has_achievement": bool(qr_code.achievement_name), "has_achievement": bool(qr_code.achievement_name),
"base_url": BASE_URL "base_url": BASE_URL,
"user": user # Add user to the context
}) })
@router.get("/scan", response_class=HTMLResponse) @router.get("/scan", response_class=HTMLResponse)
def scan_qr_code(request: Request): def scan_qr_code(request: Request, db: Session = Depends(get_db)):
""" """
Display the QR code scanner page Display the QR code scanner page
""" """
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
return templates.TemplateResponse("scan_qr.html", { return templates.TemplateResponse("scan_qr.html", {
"request": request "request": request,
"user": user # Add user to the context
}) })
@router.post("/apply/{code}") @router.post("/apply/{code}")
@@ -93,6 +110,12 @@ async def apply_code(
""" """
Apply the QR code to a selected team and award points and/or achievements Apply the QR code to a selected team and award points and/or achievements
""" """
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Get form data # Get form data
form_data = await request.form() form_data = await request.form()
team_id = int(form_data.get("team_id", 0)) team_id = int(form_data.get("team_id", 0))
@@ -103,7 +126,8 @@ async def apply_code(
{ {
"request": request, "request": request,
"error_title": "Team Selection Required", "error_title": "Team Selection Required",
"error_message": "Please select a team to redeem this code." "error_message": "Please select a team to redeem this code.",
"user": user # Add user to the context
} }
) )
@@ -115,7 +139,8 @@ async def apply_code(
{ {
"request": request, "request": request,
"error_title": "Invalid Code", "error_title": "Invalid Code",
"error_message": "This QR code is invalid or does not exist." "error_message": "This QR code is invalid or does not exist.",
"user": user # Add user to the context
} }
) )
@@ -126,7 +151,8 @@ async def apply_code(
{ {
"request": request, "request": request,
"error_title": "Code Already Used", "error_title": "Code Already Used",
"error_message": "This QR code has already been redeemed." "error_message": "This QR code has already been redeemed.",
"user": user # Add user to the context
} }
) )
@@ -137,7 +163,8 @@ async def apply_code(
{ {
"request": request, "request": request,
"error_title": "Expired Code", "error_title": "Expired Code",
"error_message": "This QR code has expired and can no longer be redeemed." "error_message": "This QR code has expired and can no longer be redeemed.",
"user": user # Add user to the context
} }
) )
@@ -148,7 +175,8 @@ async def apply_code(
{ {
"request": request, "request": request,
"error_title": "Team Not Found", "error_title": "Team Not Found",
"error_message": "The selected team could not be found." "error_message": "The selected team could not be found.",
"user": user # Add user to the context
} }
) )
@@ -180,7 +208,8 @@ async def apply_code(
"achievement": qr_code.achievement_name if qr_code.achievement_name else None, "achievement": qr_code.achievement_name if qr_code.achievement_name else None,
"team": team, "team": team,
"event": qr_code.event if qr_code.event else None, "event": qr_code.event if qr_code.event else None,
"base_url": BASE_URL "base_url": BASE_URL,
"user": user # Add user to the context
} }
) )
@@ -194,6 +223,12 @@ async def manual_code_entry(
Handle manual code entry from the form. Handle manual code entry from the form.
This redirects to the normal redeem flow after validating the code. This redirects to the normal redeem flow after validating the code.
""" """
# Get user from session for navbar
user = None
user_id = request.session.get("user_id")
if user_id:
user = db.query(User).get(user_id)
# Check if the code exists # Check if the code exists
qr_code = db.query(QRCode).filter_by(code=code).first() qr_code = db.query(QRCode).filter_by(code=code).first()
@@ -203,7 +238,8 @@ async def manual_code_entry(
{ {
"request": request, "request": request,
"error_title": "Invalid Code", "error_title": "Invalid Code",
"error_message": "The code you entered is invalid or does not exist." "error_message": "The code you entered is invalid or does not exist.",
"user": user # Add user to the context
} }
) )
@@ -214,7 +250,8 @@ async def manual_code_entry(
{ {
"request": request, "request": request,
"error_title": "Code Already Used", "error_title": "Code Already Used",
"error_message": "This code has already been redeemed." "error_message": "This code has already been redeemed.",
"user": user # Add user to the context
} }
) )