Coverage for apps/accounts/admin_schemas.py: 100%

271 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-04 17:24 +0000

1"""Ninja schemas for the accounts self (/me) + RBAC admin API. 

2 

3Hand-written ``Schema``s for the principal and role views (the principal is not an ORM row); 

4safe-field user output never carries the password hash or the raw Cognito subject. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Any 

10 

11from ninja import ModelSchema, Schema 

12 

13from apps.accounts.models import License 

14 

15 

16class RoleBindingOut(Schema): 

17 role: str 

18 org_unit_id: int | None = None 

19 

20 

21class MeOut(Schema): 

22 user_id: int 

23 email: str 

24 first_name: str 

25 last_name: str 

26 is_hq: bool 

27 permissions: list[str] 

28 org_unit_ids: list[int] 

29 roles: list[RoleBindingOut] 

30 

31 

32class OrgUnitOut(Schema): 

33 id: int 

34 name: str 

35 parent_id: int | None = None 

36 nmls_id: str = "" 

37 is_headquarters: bool = False 

38 

39 

40class OrgUnitCreate(Schema): 

41 name: str 

42 parent_id: int | None = None 

43 nmls_id: str = "" 

44 is_headquarters: bool = False 

45 

46 

47class RoleOut(Schema): 

48 name: str 

49 permissions: list[str] 

50 

51 

52class PermissionOut(Schema): 

53 code: str 

54 label: str = "" 

55 

56 

57class PermissionRowOut(Schema): 

58 permission: str 

59 roles: list[str] 

60 branch_override: bool 

61 admin_role_check_reqd: bool 

62 

63 

64class RoleMatrixOut(Schema): 

65 roles: list[str] 

66 permissions: list[PermissionRowOut] 

67 

68 

69class RoleAssignmentOut(Schema): 

70 id: int 

71 role: str 

72 org_unit_id: int | None = None 

73 

74 

75class RoleAssignmentCreate(Schema): 

76 role_name: str 

77 org_unit_id: int | None = None 

78 

79 

80class LicenseOut(ModelSchema): 

81 class Meta: 

82 model = License 

83 exclude = ["user"] 

84 

85 

86class LicenseCreate(Schema): 

87 state_code: str 

88 nmls_id: str = "" 

89 active: bool = True 

90 sponsored: bool = False 

91 

92 

93class UserSummaryOut(Schema): 

94 id: int 

95 email: str 

96 first_name: str 

97 last_name: str 

98 title: str = "" 

99 phone: str = "" 

100 nmls_id: str = "" 

101 org_unit_id: int | None = None 

102 is_active: bool 

103 

104 

105class UserDetailOut(UserSummaryOut): 

106 roles: list[RoleAssignmentOut] = [] 

107 licenses: list[LicenseOut] = [] 

108 

109 

110class UserActiveUpdate(Schema): 

111 is_active: bool 

112 

113 

114class RoleAssignmentReplace(Schema): 

115 roles: list[RoleAssignmentCreate] 

116 

117 

118class UserInviteInput(Schema): 

119 name: str 

120 email: str 

121 role_name: str 

122 org_unit_id: int | None = None 

123 nmls_id: str = "" 

124 

125 

126class UserInviteOut(Schema): 

127 user: UserDetailOut 

128 # The temporary password goes only to the new user by email (add-sendgrid-email). 

129 email_sent: bool 

130 # Development (DEBUG) only, for local testing without opening the email. 

131 devTempPassword: str | None = None 

132 

133 

134class CompanyOut(Schema): 

135 name: str 

136 nmls_id: str 

137 type: str 

138 dba: str | None = None 

139 phone: str | None = None 

140 mers_id: str | None = None 

141 address_line: str | None = None 

142 city: str | None = None 

143 state_code: str | None = None 

144 postal_code: str | None = None 

145 business_hours: str | None = None 

146 

147 

148class CompanyPatch(Schema): 

149 name: str | None = None 

150 nmls_id: str | None = None 

151 type: str | None = None 

152 dba: str | None = None 

153 phone: str | None = None 

154 mers_id: str | None = None 

155 address_line: str | None = None 

156 city: str | None = None 

157 state_code: str | None = None 

158 postal_code: str | None = None 

159 business_hours: str | None = None 

160 

161 

162class OrgUnitSummaryOut(Schema): 

163 id: str 

164 label: str 

165 

166 

167class LoanOfficerSummaryOut(Schema): 

168 id: int 

169 name: str 

170 nmls_id: str 

171 states: list[str] 

172 

173 

174class OrgInfoOut(Schema): 

175 company: CompanyOut 

176 org_units: list[OrgUnitSummaryOut] 

177 user_count: int 

178 loan_officers: list[LoanOfficerSummaryOut] 

179 

180 

181class FieldConfigOut(Schema): 

182 name: str 

183 display_name: str 

184 value_type: str 

185 required: bool 

186 visible: bool 

187 order: int 

188 category: str 

189 default_value: Any | None = None 

190 

191 

192class RequiredToProcessingOut(Schema): 

193 Purchase: list[str] 

194 Refinance: list[str] 

195 

196 

197class FieldConfigsOut(Schema): 

198 stages: list[str] 

199 required_to_processing: RequiredToProcessingOut 

200 fields: list[FieldConfigOut] 

201 

202 

203class PosFeatures(Schema): 

204 credit_auth_required: bool = True 

205 allow_submit_without_credit_consent: bool = False 

206 generate_needs_on_submit: bool = True 

207 co_borrower_invite: bool = True 

208 doc_upload: bool = True 

209 e_consent: bool = True 

210 

211 

212class PosBranding(Schema): 

213 dba: str | None = None 

214 use_dba_in_disclosures: bool = False 

215 

216 

217class PosConfigOut(Schema): 

218 host: str = "posapi.my1003app.com" 

219 sections: list[str] 

220 languages: list[str] 

221 default_language: str = "English" 

222 programs: list[str] 

223 sections_by_program: dict[str, list[str]] 

224 features: PosFeatures 

225 branding: PosBranding 

226 

227 

228class PosConfigPatch(Schema): 

229 default_language: str | None = None 

230 credit_auth_required: bool | None = None 

231 allow_submit_without_credit_consent: bool | None = None 

232 generate_needs_on_submit: bool | None = None 

233 

234 

235class LeadFormConfigOut(Schema): 

236 sources: list[str] 

237 statuses: list[str] 

238 tags: list[str] 

239 required_fields: list[str] 

240 

241 

242class LeadFormConfigPatch(Schema): 

243 sources: list[str] | None = None 

244 statuses: list[str] | None = None 

245 tags: list[str] | None = None 

246 required_fields: list[str] | None = None 

247 

248 

249class AgentConfigOut(Schema): 

250 agent_portal_enabled: bool = True 

251 co_brand_letters: bool = False 

252 show_agent_on_letter: bool = True 

253 realtor_domain: str | None = None 

254 

255 

256class AgentConfigPatch(Schema): 

257 agent_portal_enabled: bool | None = None 

258 co_brand_letters: bool | None = None 

259 show_agent_on_letter: bool | None = None 

260 realtor_domain: str | None = None 

261 

262 

263class NonDelegatedConfigOut(Schema): 

264 enabled: bool = False 

265 sponsor_name: str | None = None 

266 sponsor_ein: str | None = None 

267 underwriting_contact: str | None = None 

268 

269 

270class NonDelegatedConfigPatch(Schema): 

271 enabled: bool | None = None 

272 sponsor_name: str | None = None 

273 sponsor_ein: str | None = None 

274 underwriting_contact: str | None = None 

275 

276 

277class ApiClientOut(Schema): 

278 client_id: str 

279 org_id: str 

280 label: str | None = None 

281 has_key: bool = True 

282 

283 

284class ApiClientCreate(Schema): 

285 label: str | None = "Zapier" 

286 

287 

288class NewApiCredentialsOut(Schema): 

289 client_id: str 

290 client_secret: str 

291 api_key: str 

292 

293 

294class StateComplianceOut(Schema): 

295 state_code: str 

296 state_name: str 

297 regulator: str 

298 company_license: str 

299 mlo_license: str 

300 mlo_state_education_hours: int 

301 ce_hours_annual: int 

302 surety_bond_min: int 

303 mcr_frequency: str 

304 renewal_window: str 

305 independent_processor_licensed: bool 

306 w2_processor_exempt: bool 

307 state_disclosures: list[str] 

308 notes: str | None = None 

309 

310 

311class ComplianceIssueOut(Schema): 

312 severity: str 

313 code: str 

314 message: str 

315 

316 

317class LoanOfficerComplianceOut(Schema): 

318 id: int | None = None 

319 name: str | None = None 

320 issues: list[ComplianceIssueOut] = [] 

321 

322 

323class ProcessorComplianceOut(Schema): 

324 type: str | None = None 

325 issues: list[ComplianceIssueOut] = [] 

326 

327 

328class ComplianceCheckOut(Schema): 

329 state_code: str 

330 state_name: str 

331 compliant: bool 

332 loan_officer: LoanOfficerComplianceOut 

333 processor: ProcessorComplianceOut 

334 rule: StateComplianceOut 

335 

336 

337class ProcessorInput(Schema): 

338 type: str 

339 licensed_states: list[str] = [] 

340 

341 

342class ComplianceCheckInput(Schema): 

343 state_code: str 

344 loan_officer_id: int | None = None 

345 processor: ProcessorInput | None = None 

346 

347 

348class BillingOut(Schema): 

349 provider: str 

350 plan: str 

351 status: str 

352 seats: int 

353 price_per_seat: int 

354 mrr: int 

355 next_invoice_date: str 

356 features: list[str] 

357 

358 

359class ProvisionPlanOut(Schema): 

360 company: str 

361 nmls_id: str 

362 branches: int 

363 staff: int 

364 loan_officers: int 

365 licenses: int 

366 licensed_states: list[str] 

367 products: int 

368 rules: int 

369 templates: int 

370 api_client: bool 

371 warnings: list[str] 

372 

373 

374class ProvisionPreviewInput(Schema): 

375 config: dict[str, Any] 

376 

377 

378class ProvisionApplyInput(Schema): 

379 config: dict[str, Any] 

380 clear_loans: bool = False 

381 

382 

383class ProvisionApplyOut(Schema): 

384 plan: ProvisionPlanOut