Coverage for sm / models.py: 92%

51 statements  

« 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 

8 

9 

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 ) 

28 

29 def __str__(self) -> str: 

30 return f"Profile for {self.group.name}" 

31 

32 class Meta: 

33 verbose_name = "Group Profile" 

34 verbose_name_plural = "Group Profiles" 

35 

36 

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 

44 

45 # Default to view-only, GroupCreateView will upgrade this if needed 

46 sync_group_permissions(instance, grant_all=False) 

47 

48 

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]})" 

61 

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() 

67 

68 instance.groups.add(group) 

69 

70 # Grant full permissions for personal workspace 

71 from .utils_permissions import sync_group_permissions 

72 

73 sync_group_permissions(group, grant_all=True) 

74 

75 

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) 

87 

88 class Meta: 

89 verbose_name = "Invitation" 

90 verbose_name_plural = "Invitations" 

91 unique_together = ["email", "group"] 

92 

93 def __str__(self) -> str: 

94 return f"Invitation for {self.email} to {self.group.name}" 

95 

96 def is_expired(self) -> bool: 

97 return timezone.now() - self.created_at > timezone.timedelta(hours=24)