Coverage for backend / app / data_tables / schemas.py: 100%

157 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-17 21:34 +0000

1"""Schemas for the JAM database 

2Create schemas should be used to create entries in the database. 

3Out schemas should be used to return data to the user. 

4Min schemas should be used to return minimal data to the user (enough to display the entry as a badge) and should not 

5contain reference to other tables. 

6Update schemas should be used to update existing entries in the database.""" 

7 

8from datetime import datetime 

9 

10from pydantic import BaseModel, field_validator 

11 

12from app.base_schemas import OwnedOut, EmailField, serialise_relationships 

13 

14 

15# ------------------------------------------------------- KEYWORD ------------------------------------------------------ 

16 

17 

18class KeywordCreate(BaseModel): 

19 """Keyword create schema""" 

20 

21 name: str 

22 

23 

24class KeywordOut(KeywordCreate, OwnedOut): 

25 """Keyword output schema with full job data""" 

26 

27 jobs: list[OwnedOut] = [] 

28 

29 

30class KeywordUpdate(KeywordCreate): 

31 """Keyword update schema""" 

32 

33 name: str | None = None 

34 

35 

36# ----------------------------------------------------- AGGREGATOR ----------------------------------------------------- 

37 

38 

39class AggregatorCreate(BaseModel): 

40 """Aggregator create schema""" 

41 

42 name: str 

43 url: str | None = None 

44 

45 

46class AggregatorOut(AggregatorCreate, OwnedOut): 

47 """Aggregator output schema with full job data and job applications""" 

48 

49 jobs: list[OwnedOut] = [] 

50 job_applications: list[OwnedOut] = [] 

51 

52 

53class AggregatorUpdate(AggregatorCreate): 

54 """Aggregator update schema""" 

55 

56 name: str | None = None 

57 

58 

59# ------------------------------------------------------- COMPANY ------------------------------------------------------ 

60 

61 

62class CompanyCreate(BaseModel): 

63 """Company create schema""" 

64 

65 name: str 

66 description: str | None = None 

67 url: str | None = None 

68 

69 

70class CompanyOut(CompanyCreate, OwnedOut): 

71 """Company output schema with job data and individuals""" 

72 

73 jobs: list[OwnedOut] = [] 

74 persons: list[OwnedOut] = [] 

75 recruited_jobs: list[OwnedOut] = [] 

76 

77 

78class CompanyUpdate(CompanyCreate): 

79 """Company update schema""" 

80 

81 name: str | None = None 

82 

83 

84# ------------------------------------------------------ GEOLOCATION ------------------------------------------------------ 

85 

86 

87class GeolocationOut(BaseModel): 

88 """Geolocation output schema""" 

89 

90 query: str 

91 latitude: float | None = None 

92 longitude: float | None = None 

93 postcode: str | None = None 

94 city: str | None = None 

95 country: str | None = None 

96 formatted_address: str | None = None 

97 

98 

99# ------------------------------------------------------ LOCATION ------------------------------------------------------ 

100 

101 

102class LocationCreate(BaseModel): 

103 """Location create schema""" 

104 

105 postcode: str | None = None 

106 city: str | None = None 

107 country: str | None = None 

108 

109 

110class LocationOut(LocationCreate, OwnedOut): 

111 """Location output schema with job and interview data""" 

112 

113 name: str | None = None 

114 geolocation: GeolocationOut | None = None 

115 jobs: list[OwnedOut] = [] 

116 interviews: list[OwnedOut] = [] 

117 

118 

119class LocationUpdate(LocationCreate): 

120 """Location update schema""" 

121 

122 pass 

123 

124 

125# -------------------------------------------------------- FILES ------------------------------------------------------- 

126 

127 

128class FileCreate(BaseModel): 

129 """File create schema""" 

130 

131 filename: str 

132 type: str 

133 content: str 

134 size: int 

135 

136 

137class FileOut(FileCreate, OwnedOut): 

138 """File output schema""" 

139 

140 pass 

141 

142 

143class FileUpdate(FileCreate): 

144 """File update schema""" 

145 

146 filename: str | None = None 

147 type: str | None = None 

148 content: str | None = None 

149 size: int | None = None 

150 

151 

152# ------------------------------------------------------- PERSON ------------------------------------------------------- 

153 

154 

155class PersonCreate(BaseModel): 

156 """Person create schema""" 

157 

158 first_name: str 

159 last_name: str 

160 email: EmailField | None = None 

161 phone: str | None = None 

162 linkedin_url: str | None = None 

163 role: str | None = None 

164 is_recruiter: bool = False 

165 

166 # Foreign keys 

167 company_id: int | None = None 

168 

169 

170class PersonOut(PersonCreate, OwnedOut): 

171 """Person out schema with job data and bare interview data""" 

172 

173 interviews: list[OwnedOut] = [] 

174 jobs: list[OwnedOut] = [] 

175 recruited_jobs: list[OwnedOut] = [] 

176 name: str | None = None 

177 

178 

179class PersonUpdate(PersonCreate): 

180 """Person update schema""" 

181 

182 first_name: str | None = None 

183 last_name: str | None = None 

184 

185 

186# --------------------------------------------------------- JOB -------------------------------------------------------- 

187 

188 

189class JobCreate(BaseModel): 

190 """Job create schema""" 

191 

192 title: str 

193 description: str | None = None 

194 salary_min: float | None = None 

195 salary_max: float | None = None 

196 salary_currency: str | None = None 

197 personal_rating: int | None = None 

198 url: str | None = None 

199 deadline: datetime | None = None 

200 note: str | None = None 

201 attendance_type: str | None = None 

202 application_date: datetime | None = None 

203 application_url: str | None = None 

204 application_status: str | None = None 

205 application_note: str | None = None 

206 applied_via: str | None = None 

207 source_type: str | None = None 

208 followup_snooze_datetime: datetime | None = None 

209 

210 # Foreign keys 

211 company_id: int | None = None 

212 location_id: int | None = None 

213 duplicate_id: int | None = None 

214 source_aggregator_id: int | None = None 

215 application_aggregator_id: int | None = None 

216 recruiter_id: int | None = None 

217 recruitment_company_id: int | None = None 

218 cv_id: int | None = None 

219 cover_letter_id: int | None = None 

220 keywords: list[int] = [] 

221 contacts: list[int] = [] 

222 

223 

224class JobOut(JobCreate, OwnedOut): 

225 """Job output schema with IDs of related entities""" 

226 

227 keywords: list[int] = [] 

228 contacts: list[int] = [] 

229 interviews: list[OwnedOut] = [] 

230 updates: list[OwnedOut] = [] 

231 

232 @field_validator("keywords", "contacts", mode="before") 

233 @classmethod 

234 def serialize_relationships(cls, value) -> list[int]: 

235 """Serialize relationships to list of IDs""" 

236 return serialise_relationships(value) 

237 

238 

239class JobUpdate(JobCreate): 

240 """Job update schema""" 

241 

242 title: str | None = None 

243 

244 

245# ------------------------------------------------------ INTERVIEW ----------------------------------------------------- 

246 

247 

248class InterviewCreate(BaseModel): 

249 """Interview create schema""" 

250 

251 date: datetime 

252 type: str 

253 job_id: int 

254 attendance_type: str | None = None 

255 location_id: int | None = None 

256 note: str | None = None 

257 interviewers: list[int] | None = None 

258 

259 

260class InterviewOut(InterviewCreate, OwnedOut): 

261 """Interview output with bare location and person data, and job data""" 

262 

263 interviewers: list[int] = [] 

264 

265 @field_validator("interviewers", mode="before") 

266 @classmethod 

267 def serialize_relationships(cls, value) -> list[int]: 

268 """Serialize relationships to list of IDs""" 

269 return serialise_relationships(value) 

270 

271 

272class InterviewUpdate(InterviewCreate): 

273 """Interview update schema""" 

274 

275 date: datetime | None = None 

276 type: str | None = None 

277 job_id: int | None = None 

278 

279 

280# ----------------------------------------------- JOB APPLICATION UPDATE ----------------------------------------------- 

281 

282 

283class JobApplicationUpdateCreate(BaseModel): 

284 """Job Application Update create schema""" 

285 

286 date: datetime 

287 type: str 

288 job_id: int 

289 note: str | None = None 

290 

291 

292class JobApplicationUpdateOut(JobApplicationUpdateCreate, OwnedOut): 

293 """Job Application Update output schema with job data""" 

294 

295 pass 

296 

297 

298class JobApplicationUpdateUpdate(JobApplicationUpdateCreate): 

299 """Job Application Update update schema""" 

300 

301 date: datetime | None = None 

302 type: str | None = None 

303 job_id: int | None = None 

304 

305 

306# ----------------------------------------------- SPECULATIVE APPLICATION ---------------------------------------------- 

307 

308 

309class SpeculativeApplicationCreate(BaseModel): 

310 """Speculative application create schema""" 

311 

312 date: datetime | None = None 

313 note: str | None = None 

314 contact_email: str | None = None 

315 

316 # Foreign keys 

317 company_id: int 

318 contacts: list[int] = [] 

319 

320 

321class SpeculativeApplicationOut(SpeculativeApplicationCreate, OwnedOut): 

322 """Speculative application output schema""" 

323 

324 @field_validator("contacts", mode="before") 

325 @classmethod 

326 def serialize_relationships(cls, value) -> list[int]: 

327 """Serialize relationships to list of IDs""" 

328 return serialise_relationships(value) 

329 

330 

331class SpeculativeApplicationUpdate(SpeculativeApplicationCreate): 

332 """Speculative application update schema""" 

333 

334 company_id: int | None = None