Coverage for src/accounts/managers/user_credential.py: 47%

60 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-08-04 12:59 +0300

1""" 

2This module defines the custom user manager for the UserCredential model. 

3The manager extends Django's BaseUserManager to provide custom user creation 

4methods that work with our email-based authentication system. 

5""" 

6 

7from django.contrib.auth.base_user import BaseUserManager 

8from django.contrib.auth.hashers import make_password 

9from django.contrib import auth 

10 

11 

12class UserCredentialManager(BaseUserManager): 

13 """ 

14 Custom user manager for the UserCredential model. 

15 

16 This manager extends Django's BaseUserManager to provide custom user creation 

17 methods that work with our email-based authentication system. It handles: 

18 - User creation with email as primary identifier 

19 - Password hashing and security 

20 

21 The manager is used by Django's authentication system and provides 

22 the methods needed for user creation, management, and queries. 

23 """ 

24 

25 use_in_migrations = True 

26 

27 def _create_user_object(self, email, password, **extra_fields): 

28 """ 

29 Create a user object without saving it to the database. 

30 

31 This is a private method that creates the user object in memory 

32 but doesn't save it. It's used by other methods that need to 

33 create users with different save strategies (sync/async). 

34 

35 Args: 

36 email: User's email address (primary identifier) 

37 password: User's password (will be hashed) 

38 **extra_fields: Additional fields for the user model 

39 

40 Returns: 

41 UserCredential object (not saved to database) 

42 

43 Raises: 

44 ValueError: If email is not provided 

45 """ 

46 if not email: 

47 raise ValueError('The given email must be set') 

48 

49 email = self.normalize_email(email) 

50 user = self.model(email=email, **extra_fields) 

51 user.password = make_password(password) 

52 

53 return user 

54 

55 def _create_user(self, email, password, **extra_fields): 

56 """ 

57 Create and save a regular user to the database. 

58 

59 This is a private method that creates a user object and saves it 

60 to the database synchronously. It's used by create_user(). 

61 

62 Args: 

63 email: User's email address 

64 password: User's password 

65 **extra_fields: Additional fields for the user model 

66 

67 Returns: 

68 Saved UserCredential object 

69 """ 

70 user = self._create_user_object(email, password, **extra_fields) 

71 user.save(using=self._db) 

72 

73 return user 

74 

75 async def _acreate_user(self, email, password, **extra_fields): 

76 """ 

77 Create and save a regular user to the database asynchronously. 

78 

79 This is an async version of _create_user for use in async Django 

80 applications. It provides the same functionality but uses async/await. 

81 

82 Args: 

83 email: User's email address 

84 password: User's password 

85 **extra_fields: Additional fields for the user model 

86 

87 Returns: 

88 Saved UserCredential object 

89 """ 

90 user = self._create_user_object(email, password, **extra_fields) 

91 await user.asave(using=self._db) 

92 

93 return user 

94 

95 def create_user(self, email, password, **extra_fields): 

96 """ 

97 Create and save a regular user. 

98 

99 This is the main method for creating regular users. It sets default 

100 values for staff and superuser flags and calls the private creation method. 

101 

102 Args: 

103 email: User's email address 

104 password: User's password 

105 **extra_fields: Additional fields for the user model 

106 

107 Returns: 

108 Saved UserCredential object 

109 

110 Example: 

111 user = UserCredential.objects.create_user( 

112 email='user@example.com', 

113 password='secure_password', 

114 username='john_doe' 

115 ) 

116 """ 

117 extra_fields.setdefault('is_staff', False) 

118 extra_fields.setdefault('is_superuser', False) 

119 

120 return self._create_user(email, password, **extra_fields) 

121 

122 create_user.alters_data = True 

123 

124 async def acreate_user(self, email, password, **extra_fields): 

125 """ 

126 Create and save a regular user asynchronously. 

127 

128 Async version of create_user for use in async Django applications. 

129 

130 Args: 

131 email: User's email address 

132 password: User's password 

133 **extra_fields: Additional fields for the user model 

134 

135 Returns: 

136 Saved UserCredential object 

137 """ 

138 extra_fields.setdefault('is_staff', False) 

139 extra_fields.setdefault('is_superuser', False) 

140 

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

142 

143 acreate_user.alters_data = True 

144 

145 def create_superuser(self, email, password, **extra_fields): 

146 """ 

147 Create and save a superuser. 

148 

149 This method creates a user with administrative privileges. It ensures 

150 that superusers have the proper staff and superuser flags set. 

151 

152 Args: 

153 email: User's email address 

154 password: User's password 

155 **extra_fields: Additional fields for the user model 

156 

157 Returns: 

158 Saved UserCredential object with admin privileges 

159 

160 Raises: 

161 ValueError: If is_staff or is_superuser is not True 

162 

163 Example: 

164 admin = UserCredential.objects.create_superuser( 

165 email='admin@example.com', 

166 password='admin_password' 

167 ) 

168 """ 

169 extra_fields.setdefault('is_staff', True) 

170 extra_fields.setdefault('is_superuser', True) 

171 

172 if extra_fields.get('is_staff') is not True: 

173 raise ValueError('Superuser must have is_staff=True.') 

174 

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

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

177 

178 return self._create_user(email, password, **extra_fields) 

179 

180 create_superuser.alters_data = True 

181 

182 async def acreate_superuser(self, email, password, **extra_fields): 

183 """ 

184 Create and save a superuser asynchronously. 

185 

186 Async version of create_superuser for use in async Django applications. 

187 

188 Args: 

189 email: User's email address 

190 password: User's password 

191 **extra_fields: Additional fields for the user model 

192 

193 Returns: 

194 Saved UserCredential object with admin privileges 

195 

196 Raises: 

197 ValueError: If is_staff or is_superuser is not True 

198 """ 

199 extra_fields.setdefault('is_staff', True) 

200 extra_fields.setdefault('is_superuser', True) 

201 

202 if extra_fields.get('is_staff') is not True: 

203 raise ValueError('Superuser must have is_staff=True.') 

204 

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

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

207 

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

209 

210 acreate_superuser.alters_data = True 

211 

212 def with_perm( 

213 self, 

214 perm, 

215 is_active=True, 

216 include_superusers=True, 

217 backend=None, 

218 obj=None, 

219 ): 

220 """ 

221 Return users with the specified permission. 

222 

223 This method filters users based on their permissions. It's useful 

224 for finding users with specific capabilities or roles. 

225 

226 Args: 

227 perm: Permission string (e.g., 'auth.add_user') 

228 is_active: Whether to include only active users 

229 include_superusers: Whether to include superusers 

230 backend: Authentication backend to use 

231 obj: Object to check permissions against 

232 

233 Returns: 

234 QuerySet of users with the specified permission 

235 

236 Raises: 

237 ValueError: If multiple backends are configured and backend not specified 

238 TypeError: If backend is not a string 

239 """ 

240 if backend is None: 

241 backends = auth._get_backends(return_tuples=True) 

242 

243 if len(backends) == 1: 

244 backend, _ = backends[0] 

245 else: 

246 raise ValueError( 

247 'You have multiple authentication backends configured and ' 

248 'therefore must provide the `backend` argument.' 

249 ) 

250 

251 elif not isinstance(backend, str): 

252 raise TypeError( 

253 'backend must be a dotted import path string (got %r).' 

254 % backend 

255 ) 

256 else: 

257 backend = auth.load_backend(backend) 

258 

259 if hasattr(backend, 'with_perm'): 

260 return backend.with_perm( 

261 perm, 

262 is_active=is_active, 

263 include_superusers=include_superusers, 

264 obj=obj, 

265 ) 

266 

267 return self.none()