from __future__ import annotations from django.conf import settings from django.db import models class FrequencyChoice(models.TextChoices): DAILY = "daily", "Daily" WEEKLY = "weekly", "Weekly" class MembershipRole(models.TextChoices): ADMIN = "admin", "Admin" MEMBER = "member", "Member" class NotificationStatus(models.TextChoices): PENDING = "pending", "Pending" SENT = "sent", "Sent" DISMISSED = "dismissed", "Dismissed" class ReadingGroup(models.Model): """A group of users reading a book together with scheduled meetings.""" name = models.CharField(max_length=256, db_index=True) description = models.TextField(blank=True, default="") book = models.ForeignKey( "books.Book", on_delete=models.SET_NULL, null=True, blank=True, related_name="reading_groups", ) ebook = models.ForeignKey( "books.EBook", on_delete=models.SET_NULL, null=True, blank=True, related_name="reading_groups", ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="created_groups", ) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "groups_reading_group" verbose_name = "Reading Group" verbose_name_plural = "Reading Groups" ordering = ["-created_at"] def __str__(self) -> str: return self.name class GroupMembership(models.Model): """User membership in a reading group.""" user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="group_memberships", ) group = models.ForeignKey( ReadingGroup, on_delete=models.CASCADE, related_name="memberships", ) role = models.CharField( max_length=16, choices=MembershipRole.choices, default=MembershipRole.MEMBER, ) joined_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "groups_membership" verbose_name = "Group Membership" verbose_name_plural = "Group Memberships" unique_together = [("user", "group")] indexes = [models.Index(fields=["user", "group"])] def __str__(self) -> str: return f"{self.user} in {self.group}" class GroupMeeting(models.Model): """A scheduled meeting for a reading group with a target section.""" group = models.ForeignKey( ReadingGroup, on_delete=models.CASCADE, related_name="meetings", ) title = models.CharField(max_length=512, blank=True, default="") scheduled_at = models.DateTimeField(db_index=True) target_section = models.PositiveIntegerField( default=0, help_text="Target chapter/section index to reach by this meeting", ) target_section_label = models.CharField( max_length=512, blank=True, default="", help_text="Human-readable label for the target section (e.g. 'Section 3')", ) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "groups_meeting" verbose_name = "Group Meeting" verbose_name_plural = "Group Meetings" ordering = ["scheduled_at"] def __str__(self) -> str: return f"{self.group.name} — {self.title or self.target_section_label} ({self.scheduled_at.date()})" class NotificationPreference(models.Model): """Per-user notification settings for reading pace alerts.""" user = models.OneToOneField( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="notification_preference", primary_key=True, ) pace_notifications_enabled = models.BooleanField(default=True) reminder_enabled = models.BooleanField(default=True) frequency = models.CharField( max_length=16, choices=FrequencyChoice.choices, default=FrequencyChoice.DAILY, ) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "groups_notification_preference" verbose_name = "Notification Preference" verbose_name_plural = "Notification Preferences" def __str__(self) -> str: return f"{self.user} — pace={'on' if self.pace_notifications_enabled else 'off'}, freq={self.frequency}" class PaceNotification(models.Model): """Tracks reading pace notifications sent to users to avoid duplicates.""" user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="pace_notifications", ) meeting = models.ForeignKey( GroupMeeting, on_delete=models.CASCADE, related_name="pace_notifications", ) ebook = models.ForeignKey( "books.EBook", on_delete=models.CASCADE, related_name="pace_notifications", null=True, blank=True, ) status = models.CharField( max_length=16, choices=NotificationStatus.choices, default=NotificationStatus.PENDING, ) current_section = models.PositiveIntegerField(default=0) target_section = models.PositiveIntegerField(default=0) ahead_behind_delta = models.IntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) class Meta: db_table = "groups_pace_notification" verbose_name = "Pace Notification" verbose_name_plural = "Pace Notifications" ordering = ["-created_at"] indexes = [ models.Index(fields=["user", "meeting", "ebook"]), ] def __str__(self) -> str: return f"{self.user} — meeting {self.meeting_id}: {self.get_status_display()}"