from django.contrib.auth.models import Group from django.db import models from django.forms import CheckboxSelectMultiple from wagtail import blocks from wagtail.admin.panels import FieldPanel from wagtail.fields import RichTextField, StreamField from wagtail.images.blocks import ImageBlock from wagtail.models import Page from wagtail.models.copying import ParentalManyToManyField from purchase.models import CoursePurchase class EmptyPage(Page): pass class HomePage(Page): body = RichTextField(blank=True) content_panels = Page.content_panels + ["body"] class BlogIndexPage(Page): subpage_types = ["home.BlogPage"] 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 BlogPage(Page): author = models.CharField(max_length=255) image = models.ForeignKey( "wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) body = StreamField( [ ("heading", blocks.CharBlock(classname="title")), ("paragraph", blocks.RichTextBlock()), ("image", ImageBlock()), ] ) content_panels = Page.content_panels + [ FieldPanel("author"), FieldPanel("image"), FieldPanel("body"), ] parent_page_types = ["home.BlogIndexPage"] class CoursePage(Page): course_image = models.ForeignKey( "wagtailimages.Image", null=True, blank=True, on_delete=models.SET_NULL, related_name="+", ) description = models.CharField(max_length=255, blank=True) body = RichTextField(blank=True) allowed_groups = ParentalManyToManyField( Group, related_name="course_pages", help_text="Additional groups that should have access to this course, e.g. Editors. NOTE: Users who purchase the course will be automatically added to a dedicated access group for this course, so you don't need to add that group here.", ) repository_url = models.URLField( null=True, blank=True, ) def _user_has_access(self, user): if not user.is_authenticated: return False user_group_ids = user.groups.values_list("id", flat=True) if self.allowed_groups.filter(id__in=user_group_ids).exists(): return True return CoursePurchase.objects.filter( user=user, course=self, refunded=False ).exists() def _user_purchase_id(self, user): if not user.is_authenticated: return None purchase = CoursePurchase.objects.filter( user=user, course=self, refunded=False ).first() print(f"User {user} purchase for course {self}: {purchase}") return purchase.id if purchase else None def mock_purchase(self, user): """Mock method to simulate purchasing this course for a user.""" if not user.is_authenticated: return False obj, created = CoursePurchase.objects.get_or_create( user=user, course=self, refunded=False ) # Add user to dedicated access group for this course group_name = f"course_{self.id}_access" group, _ = Group.objects.get_or_create(name=group_name) user.groups.add(group) # Ensure allowed_groups only includes this access group if not self.allowed_groups.filter(id=group.id).exists(): self.allowed_groups.add(group) return created def save(self, *args, **kwargs): if self.id is not None: group_name = f"course_{self.id}_access" group, created = Group.objects.get_or_create(name=group_name) if not self.allowed_groups.filter(id=group.id).exists(): self.allowed_groups.add(group) super().save(*args, **kwargs) def get_context(self, request): context = super().get_context(request) context["user_has_access"] = self._user_has_access(request.user) context["user_purchase_id"] = self._user_purchase_id(request.user) return context content_panels = Page.content_panels + [ FieldPanel("course_image"), FieldPanel("description"), FieldPanel("body"), FieldPanel("allowed_groups", widget=CheckboxSelectMultiple), FieldPanel( "repository_url", read_only=True, heading="Repository URL (auto-generated)", ), ] parent_page_types = ["home.CourseIndexPage"] subpage_types = ["home.CourseModulePage"] class CourseModulePage(Page): body = RichTextField(blank=True) @property def course(self): if hasattr(self, "get_parent"): parent = self.get_parent() if parent and hasattr(parent, "specific"): return parent.specific return None @property def full_title(self): course = self.course if course: return f"{course.title} - {self.title}" return self.title content_panels = Page.content_panels + ["body"] subpage_types = ["home.ModuleLessonPage"] parent_page_types = ["home.CoursePage"] class ModuleLessonPage(Page): body = RichTextField(blank=True) create_gitea_repo = models.BooleanField( default=False, help_text="If enabled, a Gitea repository will be automatically created for this module when the module is published.", ) gitea_repo_url = models.URLField( null=True, blank=True, help_text="URL of the Gitea repository for this lesson (auto-generated if 'create_gitea_repo' is enabled)", ) @property def module(self): if hasattr(self, "get_parent"): parent = self.get_parent() if parent and hasattr(parent, "specific"): return parent.specific return None @property def full_title(self): module = self.module if module: return f"{module.full_title} - {self.title}" return self.title content_panels = Page.content_panels + [ FieldPanel("body"), FieldPanel("create_gitea_repo"), FieldPanel( "gitea_repo_url", read_only=True, heading="Gitea Repository URL", ), ] parent_page_types = ["home.CourseModulePage"]