7 Commits

6 changed files with 131 additions and 4 deletions

View File

@@ -0,0 +1,25 @@
# Generated by Django 6.0.3 on 2026-03-19 14:41
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0017_chatmessage'),
('wagtailcore', '0096_referenceindex_referenceindex_source_object_and_more'),
]
operations = [
migrations.CreateModel(
name='CourseIndexPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 6.0.3 on 2026-03-19 15:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0018_courseindexpage'),
]
operations = [
migrations.AddField(
model_name='coursepage',
name='description',
field=models.CharField(blank=True, max_length=255),
),
]

View File

@@ -25,8 +25,29 @@ class HomePage(Page):
content_panels = Page.content_panels + ["body"] content_panels = Page.content_panels + ["body"]
class CourseIndexPage(Page):
subpage_types = ["home.CoursePage"]
def get_context(self, request):
context = super().get_context(request)
all_courses = self.get_children().live()
purchased_courses = set()
other_courses = set()
for course in all_courses:
if course.specific._user_has_access(request.user):
purchased_courses.add(course)
else:
other_courses.add(course)
context["purchased_courses"] = sorted(
purchased_courses, key=lambda c: c.title.lower()
)
context["other_courses"] = sorted(other_courses, key=lambda c: c.title.lower())
return context
class CoursePage(Page): class CoursePage(Page):
body = RichTextField(blank=True)
course_image = models.ForeignKey( course_image = models.ForeignKey(
"wagtailimages.Image", "wagtailimages.Image",
null=True, null=True,
@@ -34,6 +55,8 @@ class CoursePage(Page):
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
related_name="+", related_name="+",
) )
description = models.CharField(max_length=255, blank=True)
body = RichTextField(blank=True)
allowed_groups = ParentalManyToManyField( allowed_groups = ParentalManyToManyField(
Group, Group,
related_name="course_pages", related_name="course_pages",
@@ -53,9 +76,11 @@ class CoursePage(Page):
content_panels = Page.content_panels + [ content_panels = Page.content_panels + [
FieldPanel("course_image"), FieldPanel("course_image"),
FieldPanel("description"),
FieldPanel("body"), FieldPanel("body"),
FieldPanel("allowed_groups", widget=CheckboxSelectMultiple), FieldPanel("allowed_groups", widget=CheckboxSelectMultiple),
] ]
parent_page_types = ["home.CourseIndexPage"]
subpage_types = ["home.CourseModulePage"] subpage_types = ["home.CourseModulePage"]

View File

@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% load static i18n wagtailcore_tags wagtailimages_tags %}
{% block title %}{% trans "Courses" %}{% endblock title %}
{% block body_class %}template-courseindex{% endblock body_class %}
{% block content %}
<h1 class="text-3xl font-bold mb-6 text-center">{% trans "Courses" %}</h1>
<h2 class="text-2xl font-semibold mb-4">{% trans "Purchased Courses" %}</h2>
<div class="flex flex-wrap -mx-4">
{% for course in purchased_courses %}
<div class="w-full md:w-1/2 lg:w-1/3 px-4 mb-8">
<a href="{{ course.url }}" class="block bg-green-50 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
{% image course.specific.course_image original %}
<div class="p-4">
<div class="flex items-center justify-between mb-2">
<h2 class="text-xl font-semibold">{{ course.specific.title }}</h2>
<div class="relative w-8 h-8 rounded-full bg-green-500">
<i class="fi fi-br-lock-open-alt leading-0 absolute left-0 top-1/2 translate-x-1/2 -translate-y-1/2 text-white" title="{% trans "Purchased" %}"></i>
</div>
</div>
<p class="text-gray-600">{{ course.specific.description|truncatewords:20 }}</p>
</div>
</a>
</div>
{% endfor %}
</div>
<h2 class="text-2xl font-semibold mb-4">{% trans "Available Courses" %}</h2>
<div class="flex flex-wrap -mx-4">
{% for course in other_courses %}
<div class="w-full md:w-1/2 lg:w-1/3 px-4 mb-8">
<a href="{{ course.url }}" class="block bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300">
{% image course.specific.course_image original %}
<div class="p-4">
<div class="flex items-center justify-between mb-2">
<h2 class="text-xl font-semibold">{{ course.specific.title }}</h2>
<div class="relative w-8 h-8 rounded-full bg-gray-200">
<i class="fi fi-br-shopping-basket leading-0 absolute left-0 top-1/2 translate-x-1/2 -translate-y-1/2 text-gray-700" title="{% trans "Not Purchased" %}"></i>
</div>
</div>
<p class="text-gray-600">{{ course.specific.description|truncatewords:20 }}</p>
</div>
</a>
</div>
{% endfor %}
</div>
{% endblock content %}

View File

@@ -17,9 +17,13 @@
</h1> </h1>
{% if page.course_image %} {% if page.course_image %}
{% image page.course_image original alt=page.title class="w-full h-auto rounded-lg mb-6" %} {% image page.course_image original alt=page.title class="w-full h-auto rounded-lg mb-4" %}
{% endif %} {% endif %}
<p class="not-prose text-gray-600 mb-6 text-lg">
{{ page.description }}
</p>
{{ page.body|richtext }} {{ page.body|richtext }}
{% if user_has_access %} {% if user_has_access %}

View File

@@ -2,10 +2,13 @@
<header class="bg-blue-900 text-white shadow-md relative"> <header class="bg-blue-900 text-white shadow-md relative">
<div class="container mx-auto flex items-center justify-between py-4 px-6"> <div class="container mx-auto flex items-center justify-between py-4 px-6">
{% wagtail_site as current_site %} {% wagtail_site as current_site %}
<a class="text-xl font-bold" href="/">{{ current_site.site_name }}</a> <div class="flex items-center gap-4">
<a class="text-xl font-bold" href="/">{{ current_site.site_name }}</a>
<a href="{% slugurl 'courses' %}" class="hover:underline">{% trans "Courses" %}</a>
<a href="{% url 'calendar' %}" class="hover:underline">{% trans "Course Calendar" %}</a>
</div>
<nav class="flex items-center gap-4"> <nav class="flex items-center gap-4">
<a href="{% url 'calendar' %}" class="hover:underline">{% trans "Course Calendar" %}</a>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a href="{% url 'account_logout' %}" class="hover:underline">{% trans "Logout" %}</a> <a href="{% url 'account_logout' %}" class="hover:underline">{% trans "Logout" %}</a>
{% else %} {% else %}