Compare commits
7 Commits
feat/creat
...
feat/add-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
72fca4228c
|
|||
|
9f779407af
|
|||
|
f2f594afb6
|
|||
|
95ab896e5f
|
|||
|
4f58cb0320
|
|||
|
294ea9a28b
|
|||
|
e56aff1a5c
|
25
home/migrations/0018_courseindexpage.py
Normal file
25
home/migrations/0018_courseindexpage.py
Normal 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',),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
home/migrations/0019_coursepage_description.py
Normal file
18
home/migrations/0019_coursepage_description.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -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"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
52
home/templates/home/course_index_page.html
Normal file
52
home/templates/home/course_index_page.html
Normal 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 %}
|
||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
Reference in New Issue
Block a user