Archived
Backend (apps/groups):
- ReadingGroup model: named groups linked to EBooks with creator tracking
- GroupMembership model: user-group association with member/admin roles
- MemberProgress model: per-member progress with section tracking,
percentage, time_spent, privacy toggles, and device-position preservation
- ReadingGroupViewSet: CRUD, join/leave, members list, progress endpoints
- GET /api/groups/{id}/members/progress/ — all members' progress
(public: section label only; own: full detail; private: stubbed)
- PATCH /api/groups/{id}/progress/ — update own progress with auto-
percentage calculation from section boundaries
- GET /api/groups/{id}/progress/summary/ — admin dashboard with
averages, started/finished counts, and per-member details
- Permissions: IsGroupMember, IsGroupAdmin
Frontend:
- Groups list page (/groups) with create modal and book selector
- Group detail page (/groups/:id) with progress and admin summary tabs
- Progress bar visualization per member with privacy-aware display
- Admin stat cards (total members, started, finished, avg progress, avg time)
- Navigation link from Library header
Shared:
- ReadingGroupSummary and MemberProgressPublic types
- API endpoint constants for groups routes
Closes #30
76 lines
4.0 KiB
Python
76 lines
4.0 KiB
Python
# Generated by Django 5.1.7 on 2026-06-20 19:21
|
|
|
|
import django.db.models.deletion
|
|
from django.conf import settings
|
|
from django.db import migrations, models
|
|
|
|
|
|
class Migration(migrations.Migration):
|
|
|
|
initial = True
|
|
|
|
dependencies = [
|
|
('books', '0003_readingprogress_epub_location'),
|
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
]
|
|
|
|
operations = [
|
|
migrations.CreateModel(
|
|
name='ReadingGroup',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('name', models.CharField(db_index=True, max_length=256)),
|
|
('description', models.TextField(blank=True, default='')),
|
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
('updated_at', models.DateTimeField(auto_now=True)),
|
|
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_groups', to=settings.AUTH_USER_MODEL)),
|
|
('ebook', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reading_groups', to='books.ebook')),
|
|
],
|
|
options={
|
|
'verbose_name': 'Reading Group',
|
|
'verbose_name_plural': 'Reading Groups',
|
|
'db_table': 'groups_reading_group',
|
|
'ordering': ['-created_at'],
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name='GroupMembership',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('role', models.CharField(choices=[('member', 'Member'), ('admin', 'Admin')], default='member', max_length=16)),
|
|
('joined_at', models.DateTimeField(auto_now_add=True)),
|
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='group_memberships', to=settings.AUTH_USER_MODEL)),
|
|
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberships', to='groups.readinggroup')),
|
|
],
|
|
options={
|
|
'verbose_name': 'Group Membership',
|
|
'verbose_name_plural': 'Group Memberships',
|
|
'db_table': 'groups_membership',
|
|
'ordering': ['joined_at'],
|
|
'unique_together': {('group', 'user')},
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name='MemberProgress',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('current_section', models.PositiveIntegerField(default=0)),
|
|
('percentage', models.FloatField(default=0.0)),
|
|
('time_spent_seconds', models.PositiveIntegerField(default=0)),
|
|
('last_position', models.JSONField(blank=True, default=dict)),
|
|
('is_public', models.BooleanField(default=True)),
|
|
('updated_at', models.DateTimeField(auto_now=True)),
|
|
('membership', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='progress', to='groups.groupmembership')),
|
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='member_progress', to=settings.AUTH_USER_MODEL)),
|
|
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='member_progress', to='groups.readinggroup')),
|
|
],
|
|
options={
|
|
'verbose_name': 'Member Progress',
|
|
'verbose_name_plural': 'Member Progress',
|
|
'db_table': 'groups_member_progress',
|
|
'indexes': [models.Index(fields=['group', '-percentage'], name='groups_memb_group_i_42e333_idx'), models.Index(fields=['group', 'user'], name='groups_memb_group_i_f50793_idx')],
|
|
'unique_together': {('group', 'user')},
|
|
},
|
|
),
|
|
]
|