Coverage for users/models/users.py: 79%

84 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 18:35 +0000

1from django.contrib.auth.base_user import AbstractBaseUser 

2from django.contrib.auth.models import ( 

3 GroupManager as DjangoGroupManager, 

4) 

5from django.contrib.auth.models import ( 

6 Permission, 

7 PermissionsMixin, 

8) 

9from django.contrib.auth.models import ( 

10 UserManager as DjangoUserManager, 

11) 

12from django.contrib.auth.validators import UnicodeUsernameValidator 

13from django.core.exceptions import ValidationError 

14from django.core.mail import send_mail 

15from django.db import models 

16from django.urls import reverse 

17from django.utils import timezone 

18from django.utils.translation import gettext_lazy as _ 

19 

20from utilities.querysets import RestrictedQuerySet 

21 

22__all__ = ( 

23 'Group', 

24 'GroupManager', 

25 'User', 

26 'UserManager', 

27) 

28 

29 

30class GroupManager(DjangoGroupManager.from_queryset(RestrictedQuerySet)): 

31 pass 

32 

33 

34class Group(models.Model): 

35 name = models.CharField( 

36 verbose_name=_('name'), 

37 max_length=150, 

38 unique=True 

39 ) 

40 description = models.CharField( 

41 verbose_name=_('description'), 

42 max_length=200, 

43 blank=True 

44 ) 

45 object_permissions = models.ManyToManyField( 

46 to='users.ObjectPermission', 

47 blank=True, 

48 related_name='groups' 

49 ) 

50 

51 # Replicate legacy Django permissions support from stock Group model 

52 # to ensure authentication backend compatibility 

53 permissions = models.ManyToManyField( 

54 Permission, 

55 verbose_name=_("permissions"), 

56 blank=True, 

57 related_name='groups', 

58 related_query_name='group' 

59 ) 

60 

61 objects = GroupManager() 

62 

63 class Meta: 

64 ordering = ('name',) 

65 verbose_name = _('group') 

66 verbose_name_plural = _('groups') 

67 

68 def __str__(self): 

69 return self.name 

70 

71 def get_absolute_url(self): 

72 return reverse('users:group', args=[self.pk]) 

73 

74 def natural_key(self): 

75 return (self.name,) 

76 

77 

78class UserManager(DjangoUserManager.from_queryset(RestrictedQuerySet)): 

79 

80 def create_user(self, username, email=None, password=None, **extra_fields): 

81 extra_fields.setdefault('is_superuser', False) 

82 return self._create_user(username, email, password, **extra_fields) 

83 

84 create_user.alters_data = True 

85 

86 async def acreate_user(self, username, email=None, password=None, **extra_fields): 

87 extra_fields.setdefault('is_superuser', False) 

88 return await self._acreate_user(username, email, password, **extra_fields) 

89 

90 acreate_user.alters_data = True 

91 

92 def create_superuser(self, username, email=None, password=None, **extra_fields): 

93 extra_fields.setdefault('is_superuser', True) 

94 

95 if extra_fields.get('is_superuser') is not True: 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true

96 raise ValueError('Superuser must have is_superuser=True.') 

97 

98 return self._create_user(username, email, password, **extra_fields) 

99 

100 create_superuser.alters_data = True 

101 

102 async def acreate_superuser(self, username, email=None, password=None, **extra_fields): 

103 extra_fields.setdefault('is_superuser', True) 

104 

105 if extra_fields.get('is_superuser') is not True: 

106 raise ValueError('Superuser must have is_superuser=True.') 

107 

108 return await self._acreate_user(username, email, password, **extra_fields) 

109 

110 acreate_superuser.alters_data = True 

111 

112 

113class User(AbstractBaseUser, PermissionsMixin): 

114 username = models.CharField( 

115 _("username"), 

116 max_length=150, 

117 unique=True, 

118 help_text=_("Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."), 

119 validators=[UnicodeUsernameValidator()], 

120 error_messages={ 

121 "unique": _("A user with that username already exists."), 

122 }, 

123 ) 

124 first_name = models.CharField( 

125 _("first name"), 

126 max_length=150, 

127 blank=True, 

128 ) 

129 last_name = models.CharField( 

130 _("last name"), 

131 max_length=150, 

132 blank=True, 

133 ) 

134 email = models.EmailField( 

135 _("email address"), 

136 blank=True, 

137 ) 

138 is_active = models.BooleanField( 

139 _("active"), 

140 default=True, 

141 help_text=_( 

142 "Designates whether this user should be treated as active. Unselect this instead of deleting accounts." 

143 ), 

144 ) 

145 date_joined = models.DateTimeField( 

146 _("date joined"), 

147 default=timezone.now, 

148 ) 

149 groups = models.ManyToManyField( 

150 to='users.Group', 

151 verbose_name=_('groups'), 

152 blank=True, 

153 related_name='users', 

154 related_query_name='user' 

155 ) 

156 object_permissions = models.ManyToManyField( 

157 to='users.ObjectPermission', 

158 blank=True, 

159 related_name='users' 

160 ) 

161 

162 objects = UserManager() 

163 

164 # Ensure compatibility with Django's stock User model 

165 EMAIL_FIELD = "email" 

166 USERNAME_FIELD = "username" 

167 REQUIRED_FIELDS = ["email"] 

168 

169 class Meta: 

170 ordering = ('username',) 

171 verbose_name = _('user') 

172 verbose_name_plural = _('users') 

173 

174 def get_absolute_url(self): 

175 return reverse('users:user', args=[self.pk]) 

176 

177 def clean(self): 

178 super().clean() 

179 

180 # Normalize email address 

181 self.email = self.__class__.objects.normalize_email(self.email) 

182 

183 # Check for any existing Users with names that differ only in case 

184 model = self._meta.model 

185 if model.objects.exclude(pk=self.pk).filter(username__iexact=self.username).exists(): 185 ↛ 186line 185 didn't jump to line 186 because the condition on line 185 was never true

186 raise ValidationError(_("A user with this username already exists.")) 

187 

188 def get_full_name(self): 

189 """ 

190 Return the first_name plus the last_name, with a space in between. 

191 """ 

192 full_name = f"{self.first_name} {self.last_name}" 

193 return full_name.strip() 

194 

195 def get_short_name(self): 

196 """Return the short name for the user.""" 

197 return self.first_name 

198 

199 def email_user(self, subject, message, from_email=None, **kwargs): 

200 """Send an email to this user.""" 

201 send_mail(subject, message, from_email, [self.email], **kwargs)