This repository has been archived on 2026-07-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
cloud-reader/backend/apps/groups/models.py
Marko d5def297b9 feat: implement group creation and management (US #28)
Backend:
- Add groups Django app with models: Group, GroupMember, GroupInvite, JoinRequest
- Create serializers with business rule validation
- Implement GroupViewSet with full CRUD + custom actions (members, invites, roles, leave, join requests)
- Add JoinGroupViewSet for invite-based joining flow
- Register app in Django config and URL routing

Frontend:
- Add shared types for groups to @cloud-reader/shared
- Create groups API client (groupsApi)
- Build GroupsListPage, GroupDetailPage (member mgmt, invites, role transfer)
- Build CreateGroupPage and JoinGroupPage
- Add lazy-loaded routes to App.tsx with ProtectedRoute
- Add navigation links to Library header

Ref: #28
2026-06-20 19:25:46 +00:00

130 lines
3.6 KiB
Python

import uuid
from django.conf import settings
from django.db import models
class GroupRole(models.TextChoices):
ADMIN = "admin", "Admin"
MEMBER = "member", "Member"
class JoinRequestStatus(models.TextChoices):
PENDING = "pending", "Pending"
APPROVED = "approved", "Approved"
REJECTED = "rejected", "Rejected"
class Group(models.Model):
name = models.CharField(max_length=256, db_index=True)
description = models.TextField(blank=True, default="")
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="created_groups",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "groups_group"
verbose_name = "Group"
verbose_name_plural = "Groups"
ordering = ["-created_at"]
def __str__(self) -> str:
return self.name
class GroupMember(models.Model):
group = models.ForeignKey(
Group,
on_delete=models.CASCADE,
related_name="memberships",
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="group_memberships",
)
role = models.CharField(
max_length=16,
choices=GroupRole.choices,
default=GroupRole.MEMBER,
)
joined_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "groups_member"
verbose_name = "Group Member"
verbose_name_plural = "Group Members"
ordering = ["joined_at"]
unique_together = [("group", "user")]
def __str__(self) -> str:
return f"{self.user} in {self.group} ({self.role})"
class GroupInvite(models.Model):
group = models.ForeignKey(
Group,
on_delete=models.CASCADE,
related_name="invites",
)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="created_invites",
)
code = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True)
max_uses = models.PositiveIntegerField(default=0, help_text="0 = unlimited")
use_count = models.PositiveIntegerField(default=0)
is_active = models.BooleanField(default=True, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "groups_invite"
verbose_name = "Group Invite"
verbose_name_plural = "Group Invites"
ordering = ["-created_at"]
def __str__(self) -> str:
return f"Invite for {self.group.name} ({self.code})"
class JoinRequest(models.Model):
group = models.ForeignKey(
Group,
on_delete=models.CASCADE,
related_name="join_requests",
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name="join_requests",
)
invite = models.ForeignKey(
GroupInvite,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="join_requests",
)
status = models.CharField(
max_length=16,
choices=JoinRequestStatus.choices,
default=JoinRequestStatus.PENDING,
db_index=True,
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = "groups_join_request"
verbose_name = "Join Request"
verbose_name_plural = "Join Requests"
ordering = ["-created_at"]
unique_together = [("group", "user")]
def __str__(self) -> str:
return f"{self.user}{self.group.name} ({self.status})"