Coverage for sm / models.py: 92%
51 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 13:46 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 13:46 +0000
1from django.db import models
2from django.contrib.auth.models import Group, User
3from django.db.models.signals import post_save
4from django.dispatch import receiver
5from django.utils import timezone
6from typing import Any
7import uuid
10class GroupProfile(models.Model):
11 group = models.OneToOneField(
12 Group, on_delete=models.CASCADE, related_name="profile"
13 )
14 owner = models.ForeignKey(
15 User,
16 on_delete=models.SET_NULL,
17 null=True,
18 blank=True,
19 related_name="owned_groups",
20 )
21 max_items = models.PositiveIntegerField(
22 default=200,
23 help_text="Maximum number of items across all models for this group.",
24 )
25 max_users = models.PositiveIntegerField(
26 default=2, help_text="Maximum number of users allowed in this group."
27 )
29 def __str__(self) -> str:
30 return f"Profile for {self.group.name}"
32 class Meta:
33 verbose_name = "Group Profile"
34 verbose_name_plural = "Group Profiles"
37@receiver(post_save, sender=Group)
38def create_group_profile(
39 sender: Any, instance: Group, created: bool, **kwargs: Any
40) -> None:
41 if created:
42 GroupProfile.objects.get_or_create(group=instance)
43 from .utils_permissions import sync_group_permissions
45 # Default to view-only, GroupCreateView will upgrade this if needed
46 sync_group_permissions(instance, grant_all=False)
49@receiver(post_save, sender=User)
50def create_personal_group(
51 sender: Any, instance: User, created: bool, **kwargs: Any
52) -> None:
53 """
54 Ensures every new user has their own personal group to start with.
55 """
56 if created:
57 group_name = f"{instance.username}'s Workspace"
58 # Ensure name uniqueness
59 if Group.objects.filter(name=group_name).exists():
60 group_name = f"{instance.username}'s Workspace ({uuid.uuid4().hex[:4]})"
62 group = Group.objects.create(name=group_name)
63 # Profile is already created by create_group_profile signal
64 profile = group.profile
65 profile.owner = instance
66 profile.save()
68 instance.groups.add(group)
70 # Grant full permissions for personal workspace
71 from .utils_permissions import sync_group_permissions
73 sync_group_permissions(group, grant_all=True)
76class Invitation(models.Model):
77 email = models.EmailField()
78 group = models.ForeignKey(
79 Group, on_delete=models.CASCADE, related_name="invitations"
80 )
81 token = models.UUIDField(default=uuid.uuid4, unique=True)
82 created_at = models.DateTimeField(default=timezone.now)
83 created_by = models.ForeignKey(
84 User, on_delete=models.SET_NULL, null=True, related_name="invitations_sent"
85 )
86 accepted_at = models.DateTimeField(null=True, blank=True)
88 class Meta:
89 verbose_name = "Invitation"
90 verbose_name_plural = "Invitations"
91 unique_together = ["email", "group"]
93 def __str__(self) -> str:
94 return f"Invitation for {self.email} to {self.group.name}"
96 def is_expired(self) -> bool:
97 return timezone.now() - self.created_at > timezone.timedelta(hours=24)