Kursliste und Kurs-Seiten
This commit is contained in:
parent
a0057fca32
commit
7a4a2b5fe6
@ -210,7 +210,45 @@ def register_visit(route_name: str) -> int:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
def get_available_courses_for_user():
|
||||||
|
mandant_level = session.get("mandant_level")
|
||||||
|
|
||||||
|
if mandant_level is None:
|
||||||
|
current_mandant_id = session.get("mandant_id")
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT level
|
||||||
|
FROM mandant
|
||||||
|
WHERE id = %s
|
||||||
|
""", (current_mandant_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
mandant_level = row[0] if row else 0
|
||||||
|
session["mandant_level"] = mandant_level
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT id, code, title, description, video_file, min_level, sort_order
|
||||||
|
FROM course
|
||||||
|
WHERE is_active = TRUE
|
||||||
|
AND min_level <= %s
|
||||||
|
ORDER BY sort_order, code
|
||||||
|
""", (mandant_level,))
|
||||||
|
|
||||||
|
courses = fetchall_dict(cur)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return courses
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -941,4 +979,76 @@ def useradmin_user_edit(user_id):
|
|||||||
form_error=form_error,
|
form_error=form_error,
|
||||||
success_message=success_message,
|
success_message=success_message,
|
||||||
**get_current_user()
|
**get_current_user()
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route("/courses")
|
||||||
|
@login_required
|
||||||
|
def course_list():
|
||||||
|
courses = get_available_courses_for_user()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"course_list.html",
|
||||||
|
page_title="Kurse",
|
||||||
|
active_page="courses",
|
||||||
|
courses=courses,
|
||||||
|
**get_current_user()
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route("/course/<int:course_id>")
|
||||||
|
@login_required
|
||||||
|
def course_start(course_id):
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT *
|
||||||
|
FROM course
|
||||||
|
WHERE id = %s
|
||||||
|
""", (course_id,))
|
||||||
|
course = fetchone_dict(cur)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
"course_video.html",
|
||||||
|
course=course,
|
||||||
|
**get_current_user()
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.route("/course/<int:course_id>/page/<int:page_number>")
|
||||||
|
@login_required
|
||||||
|
def course_page(course_id, page_number):
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT *
|
||||||
|
FROM course_page
|
||||||
|
WHERE course_id = %s
|
||||||
|
AND page_number = %s
|
||||||
|
""", (course_id, page_number))
|
||||||
|
page = fetchone_dict(cur)
|
||||||
|
|
||||||
|
if not page:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
# Fortschritt speichern
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO user_course_progress (user_id, course_id, last_page)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
ON CONFLICT (user_id, course_id)
|
||||||
|
DO UPDATE SET last_page = EXCLUDED.last_page
|
||||||
|
""", (session["user_id"], course_id, page_number))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
page["template_name"],
|
||||||
|
course_id=course_id,
|
||||||
|
page_number=page_number,
|
||||||
|
**get_current_user()
|
||||||
)
|
)
|
||||||
@ -12,8 +12,8 @@ class Config:
|
|||||||
|
|
||||||
LOG_DIR = os.getenv("LOG_DIR", "./logs")
|
LOG_DIR = os.getenv("LOG_DIR", "./logs")
|
||||||
|
|
||||||
COUNTRY_VAT_LABELS = {
|
COUNTRY_VAT_LABELS = {
|
||||||
"DE": "inkl. 19% USt",
|
"DE": "inkl. 19% USt",
|
||||||
"AT": "inkl. 20% USt",
|
"AT": "inkl. 20% USt",
|
||||||
"CH": "exkl. MwSt",
|
"CH": "exkl. MwSt",
|
||||||
}
|
}
|
||||||
@ -15,4 +15,25 @@ def is_video_allowed_for_level(filename: str, mandant_level: int | None) -> bool
|
|||||||
if mandant_level == 3:
|
if mandant_level == 3:
|
||||||
return first_char == "A"
|
return first_char == "A"
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_available_courses_for_user():
|
||||||
|
level = session.get("mandant_level", 0)
|
||||||
|
|
||||||
|
conn = get_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
SELECT id, code, title, description, video_file
|
||||||
|
FROM course
|
||||||
|
WHERE is_active = TRUE
|
||||||
|
AND min_level <= %s
|
||||||
|
ORDER BY sort_order, code
|
||||||
|
""", (level,))
|
||||||
|
|
||||||
|
courses = fetchall_dict(cur)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return courses
|
||||||
@ -23,6 +23,12 @@
|
|||||||
<a href="/preise" class="{% if active_page == 'preise' %}active{% endif %}">Preise</a>
|
<a href="/preise" class="{% if active_page == 'preise' %}active{% endif %}">Preise</a>
|
||||||
<a href="/allgemein" class="{% if active_page == 'allgemein' %}active{% endif %}">Allgemein</a>
|
<a href="/allgemein" class="{% if active_page == 'allgemein' %}active{% endif %}">Allgemein</a>
|
||||||
|
|
||||||
|
{% if is_logged_in %}
|
||||||
|
<a href="/courses" class="{% if active_page == 'courses' %}active{% endif %}">
|
||||||
|
Kurse
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if is_logged_in %}
|
{% if is_logged_in %}
|
||||||
<div class="user-menu">
|
<div class="user-menu">
|
||||||
<button class="user-menu-toggle" type="button">{{ user_name }} ▾</button>
|
<button class="user-menu-toggle" type="button">{{ user_name }} ▾</button>
|
||||||
|
|||||||
16
app/flask-postgres/app/templates/course/A1_page1.html
Normal file
16
app/flask-postgres/app/templates/course/A1_page1.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>AI Governance Basics</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
KI-Systeme müssen nachvollziehbar, sicher und kontrollierbar sein...
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="course-nav">
|
||||||
|
<a href="/course/{{ course_id }}" class="btn-secondary">Zurück</a>
|
||||||
|
<a href="/course/{{ course_id }}/page/2" class="btn-primary">Weiter</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
16
app/flask-postgres/app/templates/course/A1_page2.html
Normal file
16
app/flask-postgres/app/templates/course/A1_page2.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>AI Governance Basics</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
KI-Systeme müssen einen hübschen Avatar haben sein...
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="course-nav">
|
||||||
|
<a href="/course/{{ course_id }}/page/1" class="btn-secondary">Zurück</a>
|
||||||
|
<a href="/courses" class="btn-primary">Fertig</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
16
app/flask-postgres/app/templates/course/B1_page1.html
Normal file
16
app/flask-postgres/app/templates/course/B1_page1.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>B1 AI Privacy Basics</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
KI-Systeme müssen die Privatsphäre schützen sein...
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="course-nav">
|
||||||
|
<a href="/course/{{ course_id }}" class="btn-secondary">Zurück</a>
|
||||||
|
<a href="/course/{{ course_id }}/page/2" class="btn-primary">Weiter</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
16
app/flask-postgres/app/templates/course/B1_page2.html
Normal file
16
app/flask-postgres/app/templates/course/B1_page2.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h2>B1 AI Privacy Basics</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
KI-Systeme müssen die Privatsphäre schützen sein...
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="course-nav">
|
||||||
|
<a href="/course/{{ course_id }}/page/1" class="btn-secondary">Zurück</a>
|
||||||
|
<a href="/courses" class="btn-primary">Fertig</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
22
app/flask-postgres/app/templates/course_list.html
Normal file
22
app/flask-postgres/app/templates/course_list.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Lerneinheiten</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="course-grid">
|
||||||
|
{% for course in courses %}
|
||||||
|
<div class="course-card">
|
||||||
|
<h3>{{ course.title }}</h3>
|
||||||
|
<p>{{ course.description }}</p>
|
||||||
|
|
||||||
|
<a href="/course/{{ course.id }}" class="btn-primary">
|
||||||
|
Starten
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
17
app/flask-postgres/app/templates/course_video.html
Normal file
17
app/flask-postgres/app/templates/course_video.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>{{ course.title }}</h1>
|
||||||
|
|
||||||
|
<video controls width="100%">
|
||||||
|
<source src="/videos/{{ course.video_file }}" type="video/mp4">
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<div class="course-actions">
|
||||||
|
<a href="/course/{{ course.id }}/page/1" class="btn-primary">
|
||||||
|
Weiter
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@ -694,4 +694,28 @@ button {
|
|||||||
outline: none;
|
outline: none;
|
||||||
border-color: #8fb6e2;
|
border-color: #8fb6e2;
|
||||||
box-shadow: 0 0 0 3px rgba(143, 182, 226, 0.2);
|
box-shadow: 0 0 0 3px rgba(143, 182, 226, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Kurse
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
|
||||||
|
.course-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 20px;
|
||||||
|
box-shadow: 0 10px 20px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.course-nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user