Coverage for backend/tests/utils/table_data.py: 100%
49 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-22 15:38 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-22 15:38 +0000
1"""Centralised test data for both conftest.py and seed_database.py"""
3from datetime import datetime, timedelta, timezone
4from itertools import groupby
6from tests.utils.files import load_all_resource_files
8RESOURCE_FILES = load_all_resource_files()
11current_date = datetime.now(timezone.utc)
12DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z"
13DATE_FORMAT = "%Y-%m-%d"
16USER_DATA = [
17 {
18 "email": "test_user@test.com",
19 "password": "test_password",
20 "is_admin": True,
21 },
22 {
23 "email": "emmanuelpean@gmail.com",
24 "password": "password2",
25 },
26 {
27 "email": "jessicaaggood@live.co.uk",
28 "password": "password3",
29 },
30 {
31 "email": "sarah.wilson@example.com",
32 "password": "password4",
33 },
34 {
35 "email": "admin@example.com",
36 "password": "password5",
37 },
38 {
39 "email": "developer@techstartup.com",
40 "password": "password6",
41 },
42]
45SETTINGS_DATA = [
46 {
47 "name": "allowlist",
48 "value": ",".join([data["email"] for data in USER_DATA] + ["newuser@user.com"]),
49 "description": "Emails allowed to sign up",
50 }
51]
54COMPANY_DATA = [
55 {
56 "name": "Tech Corp",
57 "description": "A leading technology company specializing in web applications",
58 "url": "https://techcorp.com",
59 "owner_id": 1,
60 },
61 {
62 "name": "StartupXYZ",
63 "description": "An innovative startup focused on AI-driven solutions",
64 "url": "https://startupxyz.com",
65 "owner_id": 1,
66 },
67 {
68 "name": "Oxford PV",
69 "description": "Oxford-based company specializing in photovoltaic technology",
70 "url": "https://oxfordpv.com",
71 "owner_id": 1,
72 },
73 {
74 "name": "WebSolutions Ltd",
75 "description": "Full-service web development and digital marketing agency",
76 "url": "https://websolutions.com",
77 "owner_id": 1,
78 },
79 {
80 "name": "DataTech Industries",
81 "description": "Big data analytics and business intelligence solutions",
82 "url": "https://datatech.com",
83 "owner_id": 1,
84 },
85 {
86 "name": "CloudFirst Inc",
87 "description": None,
88 "url": "https://cloudfirst.io",
89 "owner_id": 1,
90 },
91 {
92 "name": "Minimal Corp",
93 "owner_id": 1, # Only required fields
94 },
95 {
96 "name": "No URL Company",
97 "description": "Company without website",
98 "owner_id": 1,
99 },
100 {
101 "name": "Enterprise Solutions",
102 "description": "Large enterprise software solutions provider with comprehensive digital transformation services",
103 "url": "https://enterprise-solutions.com",
104 "owner_id": 1,
105 },
106 {
107 "name": "LocalBiz",
108 "description": "Small local business",
109 "url": "https://localbiz.com",
110 "owner_id": 1,
111 },
112]
115LOCATION_DATA = [
116 {
117 "postcode": "10001",
118 "city": "New York",
119 "country": "United States",
120 "owner_id": 1,
121 },
122 {
123 "postcode": "90210",
124 "city": "Beverly Hills",
125 "country": "United States",
126 "owner_id": 1,
127 },
128 {
129 "postcode": "SW1A 1AA",
130 "city": "London",
131 "country": "United Kingdom",
132 "owner_id": 1,
133 },
134 {
135 "city": "San Francisco",
136 "country": "United States",
137 "owner_id": 1,
138 },
139 {
140 "country": "Germany",
141 "owner_id": 1,
142 },
143 {
144 "postcode": "OX1 2JD",
145 "city": "Oxford",
146 "country": "United Kingdom",
147 "owner_id": 1,
148 },
149 {
150 "country": "Canada",
151 "owner_id": 1,
152 },
153 {
154 "postcode": "75001",
155 "city": "Paris",
156 "country": "France",
157 "owner_id": 1,
158 },
159 {
160 "postcode": "10115",
161 "country": "Germany",
162 "owner_id": 1,
163 },
164 {
165 "city": "Tokyo",
166 "country": "Japan",
167 "owner_id": 1,
168 },
169 {
170 "postcode": "M5V 3A8",
171 "city": "Toronto",
172 "country": "Canada",
173 "owner_id": 1,
174 },
175 {
176 "city": "Amsterdam",
177 "country": "Netherlands",
178 "owner_id": 1,
179 },
180 {
181 "country": "Brazil",
182 "owner_id": 1,
183 },
184]
187AGGREGATOR_DATA = [
188 {
189 "name": "LinkedIn",
190 "url": "https://linkedin.com/jobs",
191 "owner_id": 1,
192 },
193 {
194 "name": "Indeed",
195 "url": "https://indeed.com",
196 "owner_id": 1,
197 },
198 {
199 "name": "Glassdoor",
200 "url": "https://glassdoor.com",
201 "owner_id": 1,
202 },
203 {
204 "name": "AngelList",
205 "url": "https://angel.co",
206 "owner_id": 1,
207 },
208 {
209 "name": "Stack Overflow Jobs",
210 "url": "https://stackoverflow.com/jobs",
211 "owner_id": 1,
212 },
213 {
214 "name": "RemoteOK",
215 "url": "https://remoteok.io",
216 "owner_id": 1,
217 },
218 {
219 "name": "WeWorkRemotely",
220 "url": "https://weworkremotely.com",
221 "owner_id": 1,
222 },
223 {
224 "name": "Upwork",
225 "url": "https://upwork.com",
226 "owner_id": 1,
227 },
228 {
229 "name": "Freelancer",
230 "url": "https://freelancer.com",
231 "owner_id": 1,
232 },
233 {
234 "name": "ZipRecruiter",
235 "url": "https://ziprecruiter.com",
236 "owner_id": 1,
237 },
238]
241KEYWORD_DATA = [
242 {
243 "name": "Python",
244 "owner_id": 1,
245 },
246 {
247 "name": "JavaScript",
248 "owner_id": 1,
249 },
250 {
251 "name": "React",
252 "owner_id": 1,
253 },
254 {
255 "name": "Node.js",
256 "owner_id": 1,
257 },
258 {
259 "name": "TypeScript",
260 "owner_id": 1,
261 },
262 {
263 "name": "PostgreSQL",
264 "owner_id": 1,
265 },
266 {
267 "name": "FastAPI",
268 "owner_id": 1,
269 },
270 {
271 "name": "Machine Learning",
272 "owner_id": 1,
273 },
274 {
275 "name": "DevOps",
276 "owner_id": 1,
277 },
278 {
279 "name": "Docker",
280 "owner_id": 1,
281 },
282 {
283 "name": "Kubernetes",
284 "owner_id": 1,
285 },
286 {
287 "name": "AWS",
288 "owner_id": 1,
289 },
290 {
291 "name": "REST API",
292 "owner_id": 1,
293 },
294 {
295 "name": "Git",
296 "owner_id": 1,
297 },
298 {
299 "name": "Agile",
300 "owner_id": 1,
301 },
302 {
303 "name": "Vue.js",
304 "owner_id": 1,
305 },
306 {
307 "name": "Angular",
308 "owner_id": 1,
309 },
310 {
311 "name": "MongoDB",
312 "owner_id": 1,
313 },
314 {
315 "name": "Redis",
316 "owner_id": 1,
317 },
318 {
319 "name": "GraphQL",
320 "owner_id": 1,
321 },
322 {
323 "name": "Microservices",
324 "owner_id": 1,
325 },
326 {
327 "name": "CI/CD",
328 "owner_id": 1,
329 },
330 {
331 "name": "Terraform",
332 "owner_id": 1,
333 },
334 {
335 "name": "Jenkins",
336 "owner_id": 1,
337 },
338 {
339 "name": "Scrum",
340 "owner_id": 1,
341 },
342]
345PERSON_DATA = [
346 {
347 "first_name": "John",
348 "last_name": "Doe",
349 "email": "john.doe@techcorp.com",
350 "phone": "1234567890",
351 "linkedin_url": "https://linkedin.com/in/johndoe",
352 "role": "Senior Engineering Manager",
353 "company_id": 1, # Tech Corp
354 "owner_id": 1,
355 },
356 {
357 "first_name": "Jane",
358 "last_name": "Smith",
359 "email": "jane.smith@startupxyz.com",
360 "linkedin_url": "https://linkedin.com/in/janesmith",
361 "role": "Product Manager",
362 "company_id": 2, # StartupXYZ
363 "owner_id": 1,
364 },
365 {
366 "first_name": "Mike",
367 "last_name": "Taylor",
368 "phone": "9876543210",
369 "role": "Lead Developer",
370 "company_id": 1, # Tech Corp
371 "owner_id": 1,
372 },
373 {
374 "first_name": "Emily",
375 "last_name": "Davis",
376 "email": "emily.davis@startupxyz.com",
377 "role": "DevOps Engineer",
378 "company_id": 2, # StartupXYZ
379 "owner_id": 1,
380 },
381 {
382 "first_name": "Chris",
383 "last_name": "Brown",
384 "linkedin_url": "https://linkedin.com/in/chrisbrown",
385 "role": "Data Science Manager",
386 "company_id": 1, # Tech Corp
387 "owner_id": 1,
388 },
389 {
390 "first_name": "Sarah",
391 "last_name": "Wilson",
392 "email": "sarah.wilson@oxfordpv.com",
393 "role": "Technical Recruiter",
394 "company_id": 3, # Oxford PV
395 "owner_id": 1,
396 },
397 {
398 "first_name": "Anonymous",
399 "last_name": "Recruiter",
400 "company_id": None,
401 "owner_id": 1,
402 },
403 {
404 "first_name": "Tech",
405 "last_name": "Recruiter",
406 "role": "Talent Acquisition",
407 "company_id": 3, # Oxford PV
408 "owner_id": 1,
409 },
410 {
411 "first_name": "Alex",
412 "last_name": "Johnson",
413 "email": "alex@cloudfirst.io",
414 "phone": "5551234567",
415 "linkedin_url": "https://linkedin.com/in/alexjohnson",
416 "role": "CTO",
417 "company_id": 6, # CloudFirst Inc
418 "owner_id": 1,
419 },
420 {
421 "first_name": "Maria",
422 "last_name": "Garcia",
423 "email": "maria.garcia@enterprise.com",
424 "role": "HR Director",
425 "company_id": 9, # Enterprise Solutions
426 "owner_id": 1,
427 },
428 {
429 "first_name": "David",
430 "last_name": "Kim",
431 "phone": "5559876543",
432 "linkedin_url": "https://linkedin.com/in/davidkim",
433 "company_id": None, # Freelancer/Independent
434 "owner_id": 1,
435 },
436 {
437 "first_name": "Lisa",
438 "last_name": "Chen",
439 "email": "lisa@localbiz.com",
440 "role": "Founder",
441 "company_id": 10, # LocalBiz
442 "owner_id": 1,
443 },
444 {
445 "first_name": "Robert",
446 "last_name": "Anderson",
447 "linkedin_url": "https://linkedin.com/in/robertanderson",
448 "role": "Senior Developer",
449 "company_id": 7, # Minimal Corp
450 "owner_id": 1,
451 },
452 {
453 "first_name": "Jennifer",
454 "last_name": "Brown",
455 "phone": "5555551234",
456 "role": "Product Owner",
457 "company_id": 8, # No URL Company
458 "owner_id": 1,
459 },
460 {
461 "first_name": "Michael",
462 "last_name": "Wilson",
463 "email": "m.wilson@datatech.com",
464 "phone": "5557778888",
465 "linkedin_url": "https://linkedin.com/in/michaelwilson",
466 "role": "Data Scientist",
467 "company_id": 5, # DataTech Industries
468 "owner_id": 1,
469 },
470 {
471 "first_name": "Freelance",
472 "last_name": "Developer",
473 "email": "freelance@dev.com",
474 "owner_id": 1, # No company, minimal info
475 },
476]
479JOB_DATA = [
480 {
481 "title": "Senior Python Developer",
482 "salary_min": 80000,
483 "salary_max": 130000,
484 "description": "Lead backend development using Python and modern frameworks. Work with a talented team to build scalable web applications.",
485 "personal_rating": 5,
486 "url": "https://techcorp.com/jobs/senior_python_developer",
487 "company_id": 1,
488 "location_id": 2,
489 "note": "Excellent opportunity for senior developer",
490 "attendance_type": "hybrid",
491 "owner_id": 1,
492 "source_id": 1,
493 "application_date": "2024-01-15T10:00:00",
494 "application_url": "https://techcorp.com/apply/senior-python",
495 "application_status": "applied",
496 "applied_via": "aggregator",
497 "application_note": "Submitted application with cover letter",
498 "cv_id": 3,
499 "cover_letter_id": 3,
500 "application_aggregator_id": 1,
501 },
502 {
503 "title": "Full Stack JavaScript Developer",
504 "salary_min": 65000,
505 "salary_max": 105000,
506 "description": "Build end-to-end web applications with React and Node.js. Join our dynamic startup environment.",
507 "personal_rating": 4,
508 "url": "https://startupxyz.com/jobs/fullstack_js_developer",
509 "company_id": 2,
510 "location_id": 1,
511 "note": "Great team culture mentioned in reviews",
512 "attendance_type": "on-site",
513 "owner_id": 1,
514 "source_id": 2,
515 "application_date": "2024-01-16T14:30:00",
516 "application_url": "https://startupxyz.com/apply/fullstack-js",
517 "application_status": "interview",
518 "applied_via": "aggregator",
519 "application_note": "Phone screening scheduled for next week",
520 "cv_id": 1,
521 "cover_letter_id": None,
522 "application_aggregator_id": 5,
523 },
524 {
525 "title": "Remote React Developer",
526 "salary_min": 60000,
527 "salary_max": 95000,
528 "description": "Build modern React applications for distributed team. Full remote position with flexible hours.",
529 "personal_rating": 3,
530 "url": "https://techcorp.com/jobs/remote_react_developer",
531 "company_id": 1,
532 "location_id": 4,
533 "owner_id": 1,
534 "source_id": 2,
535 "application_date": "2024-01-17T09:15:00",
536 "application_url": None,
537 "application_status": "applied",
538 "applied_via": "aggregator",
539 "application_note": "Applied through LinkedIn",
540 "cv_id": None,
541 "cover_letter_id": 2,
542 "application_aggregator_id": 4,
543 },
544 {
545 "title": "Cloud Engineer",
546 "salary_min": 75000,
547 "salary_max": 120000,
548 "description": "Design and maintain AWS cloud infrastructure. Experience with Kubernetes and Docker required.",
549 "personal_rating": 4,
550 "url": "https://startupxyz.com/jobs/cloud_engineer",
551 "company_id": 2,
552 "location_id": 3,
553 "attendance_type": "hybrid",
554 "owner_id": 1,
555 "source_id": 2,
556 "application_date": "2024-01-18T16:45:00",
557 "application_url": "https://startupxyz.com/careers/cloud-engineer",
558 "application_status": "rejected",
559 "applied_via": "aggregator",
560 "application_note": "Not enough cloud experience",
561 "cv_id": 4,
562 "cover_letter_id": 4,
563 "application_aggregator_id": 7,
564 },
565 {
566 "title": "Frontend Developer",
567 "salary_min": 55000,
568 "salary_max": 85000,
569 "description": "Create beautiful user interfaces with React and JavaScript. Focus on user experience and modern design.",
570 "personal_rating": 2,
571 "url": "https://techcorp.com/jobs/frontend_developer",
572 "company_id": 1,
573 "location_id": 5,
574 "attendance_type": "on-site",
575 "owner_id": 1,
576 "source_id": 4,
577 "application_date": None,
578 "application_url": None,
579 "application_status": None,
580 "applied_via": None,
581 "application_note": None,
582 "cv_id": None,
583 "cover_letter_id": None,
584 "application_aggregator_id": None,
585 },
586 {
587 "title": "Backend Developer",
588 "description": "Looking for a backend developer with Python experience. FastAPI knowledge preferred.",
589 "location_id": 4,
590 "attendance_type": "hybrid",
591 "owner_id": 1,
592 "source_id": 1,
593 "application_date": "2024-01-20T13:30:00",
594 "application_url": None,
595 "application_status": "applied",
596 "applied_via": None,
597 "application_note": "Quick application through company form",
598 "cv_id": None,
599 "cover_letter_id": None,
600 "application_aggregator_id": None,
601 },
602 {
603 "title": "Software Engineer Intern",
604 "description": "Summer internship opportunity for computer science students. Great learning environment.",
605 "personal_rating": 1,
606 "company_id": 3,
607 "owner_id": 1,
608 "source_id": 7,
609 "application_date": None,
610 "application_url": None,
611 "application_status": None,
612 "applied_via": None,
613 "application_note": None,
614 "cv_id": None,
615 "cover_letter_id": None,
616 "application_aggregator_id": None,
617 },
618 {
619 "title": "Developer Position",
620 "company_id": 3,
621 "location_id": 5,
622 "attendance_type": "hybrid",
623 "owner_id": 1,
624 "source_id": 9,
625 "application_date": None,
626 "application_url": None,
627 "application_status": None,
628 "applied_via": None,
629 "application_note": None,
630 "cv_id": None,
631 "cover_letter_id": None,
632 "application_aggregator_id": None,
633 "deadline": (current_date + timedelta(days=10)).strftime(DATE_FORMAT),
634 },
635 {
636 "title": "DevOps Engineer",
637 "salary_min": 90000,
638 "salary_max": 140000,
639 "description": "Build and maintain CI/CD pipelines, manage cloud infrastructure",
640 "personal_rating": 5,
641 "url": "https://cloudfirst.io/careers/devops",
642 "company_id": 6,
643 "location_id": 8,
644 "note": "Strong DevOps culture, great tools",
645 "attendance_type": "hybrid",
646 "owner_id": 1,
647 "source_id": 9,
648 "application_date": "2024-01-21T09:00:00",
649 "application_url": "https://cloudfirst.io/apply/devops",
650 "application_status": "interview",
651 "applied_via": None,
652 "application_note": "Technical interview scheduled",
653 "cv_id": 8,
654 "cover_letter_id": 10,
655 "application_aggregator_id": None,
656 },
657 {
658 "title": "Data Scientist",
659 "salary_min": 85000,
660 "salary_max": 125000,
661 "description": "Work with big data to derive insights and build ML models",
662 "personal_rating": 4,
663 "company_id": 5,
664 "location_id": 9,
665 "attendance_type": "remote",
666 "owner_id": 1,
667 "source_id": 1,
668 "application_date": "2024-01-22T11:15:00",
669 "application_url": None,
670 "application_status": "applied",
671 "applied_via": None,
672 "application_note": None,
673 "cv_id": 9,
674 "cover_letter_id": None,
675 "application_aggregator_id": None,
676 },
677 {
678 "title": "Vue.js Frontend Developer",
679 "salary_min": 50000,
680 "salary_max": 80000,
681 "description": "Build modern SPAs with Vue.js and TypeScript",
682 "url": "https://enterprise-solutions.com/jobs/vue-dev",
683 "company_id": 9,
684 "location_id": 10,
685 "attendance_type": "on-site",
686 "owner_id": 1,
687 "source_id": 5,
688 "application_date": "2024-01-23T15:45:00",
689 "application_url": "https://enterprise-solutions.com/apply/vue-dev",
690 "application_status": "rejected",
691 "applied_via": None,
692 "application_note": "Position filled internally",
693 "cv_id": 6,
694 "cover_letter_id": 12,
695 "application_aggregator_id": None,
696 },
697 {
698 "title": "Remote Full Stack Engineer",
699 "salary_min": 70000,
700 "salary_max": 110000,
701 "description": "Work remotely on full stack applications",
702 "personal_rating": 3,
703 "url": "https://localbiz.com/jobs/fullstack",
704 "company_id": 10,
705 "location_id": 11,
706 "note": "Small team, lots of autonomy",
707 "attendance_type": "remote",
708 "owner_id": 1,
709 "source_id": 7,
710 "application_date": "2024-01-24T08:30:00",
711 "application_url": None,
712 "application_status": "applied",
713 "applied_via": None,
714 "application_note": "Applied directly through website",
715 "cv_id": 3,
716 "cover_letter_id": None,
717 "application_aggregator_id": None,
718 },
719 {
720 "title": "Junior Developer",
721 "salary_min": 40000,
722 "salary_max": 60000,
723 "description": "Entry-level position for new graduates",
724 "personal_rating": 2,
725 "company_id": 7,
726 "location_id": 12,
727 "attendance_type": "on-site",
728 "owner_id": 1,
729 "source_id": 8,
730 "application_date": "2024-01-25T10:00:00",
731 "application_url": None,
732 "application_status": "interview",
733 "applied_via": None,
734 "application_note": None,
735 "cv_id": 1,
736 "cover_letter_id": 7,
737 "application_aggregator_id": None,
738 "deadline": (current_date + timedelta(days=10)).strftime(DATE_FORMAT),
739 },
740 {
741 "title": "Freelance Web Developer",
742 "description": "Contract position for web development projects",
743 "location_id": 13,
744 "note": "Flexible hours, project-based",
745 "attendance_type": "remote",
746 "owner_id": 1,
747 "application_date": None,
748 "application_url": None,
749 "application_status": None,
750 "applied_via": None,
751 "application_note": None,
752 "cv_id": None,
753 "cover_letter_id": None,
754 "application_aggregator_id": None,
755 },
756 {
757 "title": "Mobile App Developer",
758 "salary_min": 60000,
759 "salary_max": 100000,
760 "description": "Develop iOS and Android applications",
761 "personal_rating": 4,
762 "url": "https://websolutions.com/jobs/mobile-dev",
763 "company_id": 4,
764 "location_id": 12,
765 "attendance_type": "hybrid",
766 "owner_id": 1,
767 "application_date": "2024-01-26T16:00:00",
768 "application_url": "https://websolutions.com/apply/mobile-dev",
769 "application_status": "applied",
770 "applied_via": None,
771 "application_note": "Excited about mobile development opportunity",
772 "cv_id": 11,
773 "cover_letter_id": None,
774 "application_aggregator_id": None,
775 },
776 {
777 "title": "Minimum Required Job",
778 "attendance_type": "on-site",
779 "owner_id": 1,
780 "application_date": "2024-01-27T12:00:00",
781 "application_url": None,
782 "application_status": "applied",
783 "applied_via": None,
784 "application_note": None,
785 "cv_id": None,
786 "cover_letter_id": None,
787 "application_aggregator_id": None,
788 },
789 {
790 "title": "High Salary Position",
791 "salary_min": 150000,
792 "salary_max": 250000,
793 "description": "Senior leadership role with high compensation",
794 "personal_rating": 5,
795 "company_id": 9,
796 "location_id": 1,
797 "attendance_type": "hybrid",
798 "owner_id": 1,
799 "application_date": "2024-01-28T14:20:00",
800 "application_url": "https://enterprise-solutions.com/apply/high-salary",
801 "application_status": "interview",
802 "applied_via": None,
803 "application_note": "Executive-level interview process",
804 "cv_id": 8,
805 "cover_letter_id": 2,
806 "application_aggregator_id": None,
807 },
808]
810JOB_APPLICATION_DATETIME = [current_date - timedelta(weeks=i) for i in range(len(JOB_DATA))]
811for job_application, date in zip(JOB_DATA, JOB_APPLICATION_DATETIME):
812 if job_application.get("application_date"):
813 job_application["application_date"] = date.strftime(DATETIME_FORMAT)
816FILE_DATA = [
817 {
818 "filename": "john_doe_cv_2024.pdf",
819 "owner_id": 1,
820 **RESOURCE_FILES["CV.pdf"],
821 },
822 {
823 "filename": "cover_letter_senior_python.docx",
824 "owner_id": 1,
825 **RESOURCE_FILES["Cover Letter.docx"],
826 },
827 {
828 "filename": "fullstack_developer_cv.pdf",
829 "owner_id": 1,
830 **RESOURCE_FILES["CV.pdf"],
831 },
832 {
833 "filename": "portfolio_cover_letter.txt",
834 "owner_id": 1,
835 **RESOURCE_FILES["Cover Letter.txt"],
836 },
837 {
838 "filename": "junior_cloud_cv.docx",
839 "owner_id": 1,
840 **RESOURCE_FILES["Cover Letter.docx"],
841 },
842 {
843 "filename": "frontend_specialist_cv.pdf",
844 "owner_id": 1,
845 **RESOURCE_FILES["CV.pdf"],
846 },
847 {
848 "filename": "frontend_developer_cover_letter.docx",
849 "owner_id": 1,
850 **RESOURCE_FILES["Cover Letter.docx"],
851 },
852 {
853 "filename": "devops_engineer_cv.pdf",
854 "owner_id": 1,
855 **RESOURCE_FILES["CV.pdf"],
856 },
857 {
858 "filename": "data_scientist_resume.pdf",
859 "owner_id": 1,
860 **RESOURCE_FILES["CV.pdf"],
861 },
862 {
863 "filename": "vue_developer_cover_letter.txt",
864 "owner_id": 1,
865 **RESOURCE_FILES["Cover Letter.txt"],
866 },
867 {
868 "filename": "mobile_dev_portfolio.pdf",
869 "owner_id": 1,
870 **RESOURCE_FILES["CV.pdf"],
871 },
872 {
873 "filename": "generic_cover_letter.docx",
874 "owner_id": 1,
875 **RESOURCE_FILES["Cover Letter.docx"],
876 },
877]
880INTERVIEW_DATA = [
881 {
882 "date": "2024-01-20T09:30:00",
883 "type": "HR",
884 "location_id": 1,
885 "job_id": 1,
886 "note": "First round technical interview",
887 "attendance_type": "on-site",
888 "owner_id": 1,
889 },
890 {
891 "date": "2024-01-21T14:00:00",
892 "type": "Technical",
893 "location_id": 2,
894 "job_id": 2,
895 "note": "HR screening call",
896 "attendance_type": "remote",
897 "owner_id": 1,
898 },
899 {
900 "date": "2024-01-22T10:15:00",
901 "type": "Management",
902 "location_id": 4, # Remote
903 "job_id": 3,
904 "note": "Remote technical assessment",
905 "attendance_type": "remote",
906 "owner_id": 1,
907 },
908 {
909 "date": "2024-01-23T16:30:00",
910 "type": "HR",
911 "location_id": 1,
912 "job_id": 4,
913 "note": "Final round with team lead",
914 "attendance_type": "on-site",
915 "owner_id": 1,
916 },
917 {
918 "date": "2024-01-24T11:45:00",
919 "type": "Other",
920 "location_id": 3,
921 "job_id": 5,
922 "note": "Cultural fit interview",
923 "attendance_type": "on-site",
924 "owner_id": 1,
925 },
926 {
927 "date": "2024-01-26T09:00:00",
928 "type": "HR",
929 "location_id": 7, # Canada location
930 "job_id": 1, # Same application, second interview
931 "note": None,
932 "attendance_type": "on-site",
933 "owner_id": 1,
934 },
935 {
936 "date": "2024-01-29T10:30:00",
937 "type": "Technical",
938 "location_id": 8, # Paris
939 "job_id": 6, # DevOps application
940 "note": "Deep technical dive into infrastructure",
941 "attendance_type": "on-site",
942 "owner_id": 1,
943 },
944 {
945 "date": "2024-01-30T15:00:00",
946 "type": "Management",
947 "location_id": 11, # Australia (remote)
948 "job_id": 8, # Remote Full Stack application
949 "note": "Meeting with team leads",
950 "attendance_type": "remote",
951 "owner_id": 1,
952 },
953 {
954 "date": "2024-02-01T09:45:00",
955 "type": "HR",
956 "location_id": 12, # Toronto
957 "job_id": 10, # Junior Developer application
958 "note": "Initial screening for junior position",
959 "attendance_type": "on-site",
960 "owner_id": 1,
961 },
962 {
963 "date": "2024-02-02T11:00:00",
964 "type": "Technical",
965 "location_id": 12, # Brazil (remote)
966 "job_id": 11, # Mobile App Developer
967 "note": "Technical skills assessment for mobile development",
968 "attendance_type": "remote",
969 "owner_id": 1,
970 },
971 {
972 "date": "2024-02-03T14:15:00",
973 "type": "Other",
974 "location_id": 1, # New York
975 "job_id": 13, # High Salary Position
976 "note": "Panel interview with executives",
977 "attendance_type": "on-site",
978 "owner_id": 1,
979 },
980 {
981 "date": "2024-02-04T16:30:00",
982 "type": "Management",
983 "location_id": 5, # Germany (remote)
984 "job_id": 7, # Data Scientist application
985 "attendance_type": "remote",
986 "owner_id": 1, # Minimal interview info
987 },
988]
989interviews_sorted = sorted(INTERVIEW_DATA, key=lambda x: x["job_id"])
990grouped = {k: list(v) for k, v in groupby(interviews_sorted, key=lambda x: x["job_id"])}
991for update_key, date in zip(grouped, JOB_APPLICATION_DATETIME):
992 for i, update in enumerate(grouped[update_key]):
993 update["date"] = (date + timedelta(weeks=4) * (i + 1)).strftime(DATETIME_FORMAT)
996JOB_APPLICATION_UPDATE_DATA = [
997 {
998 "date": "2024-01-15 14:30:00",
999 "job_id": 1, # Tech startup application
1000 "note": "Received automated confirmation email",
1001 "type": "received",
1002 "owner_id": 1,
1003 },
1004 {
1005 "date": "2024-01-18 09:15:00",
1006 "job_id": 1,
1007 "note": "HR recruiter called to schedule phone screening",
1008 "type": "received",
1009 "owner_id": 1,
1010 },
1011 {
1012 "date": "2024-01-22 16:45:00",
1013 "job_id": 2, # Marketing agency application
1014 "note": "Application status changed to 'Under Review'",
1015 "type": "received",
1016 "owner_id": 1,
1017 },
1018 {
1019 "date": "2024-01-25 11:20:00",
1020 "job_id": 3, # Finance corp application
1021 "note": "Received rejection email - position filled internally",
1022 "type": "received",
1023 "owner_id": 1,
1024 },
1025 {
1026 "date": "2024-01-28 13:10:00",
1027 "job_id": 4, # Healthcare application
1028 "note": "Invited to complete online assessment",
1029 "type": "received",
1030 "owner_id": 1,
1031 },
1032 {
1033 "date": "2024-02-01 08:30:00",
1034 "job_id": 2,
1035 "note": "Scheduled for first round interview next week",
1036 "type": "received",
1037 "owner_id": 1,
1038 },
1039 {
1040 "date": "2024-02-03 15:45:00",
1041 "job_id": 5, # Remote consulting application
1042 "note": "Application acknowledgment received",
1043 "type": "received",
1044 "owner_id": 1,
1045 },
1046 {
1047 "date": "2024-02-05 10:15:00",
1048 "job_id": 4,
1049 "note": "Completed technical assessment - awaiting results",
1050 "type": "sent",
1051 "owner_id": 1,
1052 },
1053 {
1054 "date": "2024-02-08 14:20:00",
1055 "job_id": 6, # Another application
1056 "note": "Phone screening scheduled for tomorrow",
1057 "type": "sent",
1058 "owner_id": 1,
1059 },
1060 {
1061 "date": "2024-02-12 11:45:00",
1062 "job_id": 7,
1063 "note": "Application moved to final review stage",
1064 "type": "sent",
1065 "owner_id": 1,
1066 },
1067 {
1068 "date": "2024-02-15 09:30:00",
1069 "job_id": 8,
1070 "note": "Hiring manager wants to schedule video call",
1071 "type": "sent",
1072 "owner_id": 1,
1073 },
1074 {
1075 "date": "2024-02-18 16:10:00",
1076 "job_id": 9,
1077 "note": "Received offer letter - salary negotiation in progress",
1078 "type": "sent",
1079 "owner_id": 1,
1080 },
1081 {
1082 "date": "2024-02-20 13:25:00",
1083 "job_id": 10,
1084 "note": "Application automatically withdrawn due to inactivity",
1085 "type": "sent",
1086 "owner_id": 1,
1087 },
1088 {
1089 "date": "2024-02-22 10:40:00",
1090 "job_id": 2,
1091 "note": "Second round interview scheduled - panel interview",
1092 "type": "sent",
1093 "owner_id": 1,
1094 },
1095 {
1096 "date": "2024-02-25 14:55:00",
1097 "job_id": 11,
1098 "note": "Reference check completed",
1099 "type": "sent",
1100 "owner_id": 1,
1101 },
1102]
1103job_application_updates_sorted = sorted(JOB_APPLICATION_UPDATE_DATA, key=lambda x: x["job_id"])
1104grouped = {k: list(v) for k, v in groupby(job_application_updates_sorted, key=lambda x: x["job_id"])}
1105for update_key, date in zip(grouped, JOB_APPLICATION_DATETIME):
1106 for i, update in enumerate(grouped[update_key]):
1107 update["date"] = (date + timedelta(weeks=4) * (i + 1)).strftime(DATETIME_FORMAT)
1110JOB_KEYWORD_MAPPINGS = [
1111 {"job_id": 1, "keyword_ids": [1, 2, 6, 7]}, # Senior Python Developer - Python, JavaScript, PostgreSQL, FastAPI
1112 {"job_id": 2, "keyword_ids": [2, 3, 4, 13]}, # Full Stack JS Developer - JavaScript, React, Node.js, REST API
1113 {"job_id": 3, "keyword_ids": [3, 2, 14]}, # Remote React Developer - React, JavaScript, Git
1114 {"job_id": 4, "keyword_ids": [12, 10, 11, 9]}, # Cloud Engineer - AWS, Docker, Kubernetes, DevOps
1115 {"job_id": 5, "keyword_ids": [2, 3, 14, 15]}, # Frontend Developer - JavaScript, React, Git, Agile
1116 {"job_id": 9, "keyword_ids": [9, 10, 11, 22, 23]}, # DevOps Engineer - DevOps, Docker, Kubernetes, CI/CD, Terraform
1117 {"job_id": 10, "keyword_ids": [8, 1, 18]}, # Data Scientist - Machine Learning, Python, MongoDB
1118 {"job_id": 11, "keyword_ids": [16, 5, 2]}, # Vue.js Developer - Vue.js, TypeScript, JavaScript
1119 {"job_id": 12, "keyword_ids": [2, 3, 4, 1]}, # Remote Full Stack - JavaScript, React, Node.js, Python
1120 {"job_id": 13, "keyword_ids": [2, 14, 15]}, # Junior Developer - JavaScript, Git, Agile
1121 {"job_id": 15, "keyword_ids": [2, 3, 17]}, # Mobile App Developer - JavaScript, React, Angular
1122]
1125JOB_CONTACT_MAPPINGS = [
1126 {"job_id": 1, "person_ids": [1, 3]}, # Senior Python Developer - John Doe, Mike Taylor
1127 {"job_id": 2, "person_ids": [2, 4]}, # Full Stack JS Developer - Jane Smith, Emily Davis
1128 {"job_id": 3, "person_ids": [1]}, # Remote React Developer - John Doe
1129 {"job_id": 4, "person_ids": [4]}, # Cloud Engineer - Emily Davis
1130 {"job_id": 5, "person_ids": [5]}, # Frontend Developer - Chris Brown
1131 {"job_id": 9, "person_ids": [9]}, # DevOps Engineer - Alex Johnson
1132 {"job_id": 10, "person_ids": [15]}, # Data Scientist - Michael Wilson
1133 {"job_id": 11, "person_ids": [10]}, # Vue.js Developer - Maria Garcia
1134 {"job_id": 12, "person_ids": [12]}, # Remote Full Stack - Lisa Chen
1135 {"job_id": 13, "person_ids": [13]}, # Junior Developer - Robert Anderson
1136 {"job_id": 15, "person_ids": [11, 16]}, # Mobile App Developer - David Kim, Freelance Developer
1137]
1140INTERVIEW_INTERVIEWER_MAPPINGS = [
1141 {"interview_id": 1, "person_ids": [1]}, # First round - John Doe
1142 {"interview_id": 2, "person_ids": [2]}, # HR screening - Jane Smith
1143 {"interview_id": 3, "person_ids": [3, 5]}, # Remote assessment - Mike Taylor, Chris Brown
1144 {"interview_id": 4, "person_ids": [1]}, # Final round - John Doe
1145 {"interview_id": 5, "person_ids": [4]}, # Cultural fit - Emily Davis
1146 {"interview_id": 7, "person_ids": [9]}, # DevOps technical - Alex Johnson
1147 {"interview_id": 8, "person_ids": [12, 11]}, # Remote Full Stack - Lisa Chen, David Kim
1148 {"interview_id": 9, "person_ids": [13]}, # Junior Developer - Robert Anderson
1149 {"interview_id": 10, "person_ids": [16]}, # Mobile App - Freelance Developer
1150 {"interview_id": 11, "person_ids": [10, 15]}, # High Salary Position - Maria Garcia, Michael Wilson
1151 {"interview_id": 12, "person_ids": [15]}, # Data Scientist - Michael Wilson
1152]
1155JOB_ALERT_EMAIL_DATA = [
1156 {
1157 "owner_id": 1,
1158 "external_email_id": "linkedin_alert_001",
1159 "subject": "10 new jobs matching Python Developer",
1160 "sender": "jobs-noreply@linkedin.com",
1161 "date_received": "2024-01-15 09:30:00",
1162 "platform": "linkedin",
1163 "service_log_id": 1,
1164 "body": """
1165 Hi there,
1167 We found 10 new jobs that match your preferences:
1169 1. Senior Python Developer at TechCorp
1170 https://www.linkedin.com/jobs/view/3789012345
1172 2. Python Backend Engineer at StartupInc
1173 https://www.linkedin.com/jobs/view/3789012346
1175 3. Full Stack Python Developer at DataSoft
1176 https://linkedin.com/comm/jobs/view/3789012347
1178 Best regards,
1179 LinkedIn Jobs Team
1180 """,
1181 },
1182 {
1183 "owner_id": 1,
1184 "external_email_id": "indeed_alert_001",
1185 "subject": "New job alerts for Software Engineer",
1186 "sender": "noreply@indeed.com",
1187 "date_received": "2024-01-16 14:45:00",
1188 "platform": "indeed",
1189 "service_log_id": 2,
1190 "body": """
1191 New jobs matching your search criteria:
1193 Software Engineer - Remote
1194 Apply here: https://indeed.com/pagead/clk/dl?mo=r&ad=job123456789&source=email
1196 Senior Software Engineer - London
1197 View job: https://uk.indeed.com/rc/clk/dl?jk=job987654321&from=email
1199 Python Developer - Manchester
1200 https://indeed.com/viewjob?jk=job555666777
1202 Don't miss out on these opportunities!
1203 Indeed Team
1204 """,
1205 },
1206 {
1207 "owner_id": 2,
1208 "external_email_id": "linkedin_alert_002",
1209 "subject": "Data Scientist positions you might like",
1210 "sender": "jobs-noreply@linkedin.com",
1211 "date_received": "2024-01-17 11:20:00",
1212 "platform": "linkedin",
1213 "service_log_id": 3,
1214 "body": """
1215 Hello,
1217 Check out these Data Scientist roles:
1219 Machine Learning Engineer
1220 https://www.linkedin.com/jobs/view/3801234567
1222 Senior Data Scientist at FinTech Ltd
1223 https://linkedin.com/comm/jobs/view/3801234568
1225 AI Research Scientist
1226 https://www.linkedin.com/jobs/view/3801234569
1228 Happy job hunting!
1229 LinkedIn
1230 """,
1231 },
1232 {
1233 "owner_id": 2,
1234 "external_email_id": "indeed_alert_002",
1235 "subject": "Your weekly job digest - 5 new matches",
1236 "sender": "alerts@indeed.com",
1237 "date_received": "2024-01-18 08:15:00",
1238 "platform": "indeed",
1239 "service_log_id": 4,
1240 "body": """
1241 Your weekly job digest is here!
1243 Data Analyst - Birmingham
1244 https://indeed.com/pagead/clk/dl?mo=r&ad=data123&ref=email
1246 Business Intelligence Developer
1247 https://uk.indeed.com/viewjob?jk=bi456789&utm_source=email
1249 Senior Data Engineer
1250 https://indeed.com/rc/clk/dl?jk=eng999888&campaign=weekly
1252 Python Data Scientist - Edinburgh
1253 https://uk.indeed.com/pagead/clk/dl?mo=r&ad=sci777666&source=digest
1255 ML Engineer - Glasgow
1256 https://indeed.com/viewjob?jk=ml444333&ref=weekly_digest
1258 Best of luck with your job search!
1259 Indeed
1260 """,
1261 },
1262 {
1263 "owner_id": 1,
1264 "external_email_id": "linkedin_alert_003",
1265 "subject": "3 jobs similar to ones you've viewed",
1266 "sender": "jobs-noreply@linkedin.com",
1267 "date_received": "2024-01-19 16:10:00",
1268 "platform": "linkedin",
1269 "service_log_id": 5,
1270 "body": """
1271 Based on your recent activity, here are some similar opportunities:
1273 DevOps Engineer at CloudTech
1274 https://www.linkedin.com/jobs/view/3812345678
1276 Site Reliability Engineer
1277 https://linkedin.com/comm/jobs/view/3812345679
1279 Infrastructure Engineer - Remote
1280 https://www.linkedin.com/jobs/view/3812345680
1282 View more jobs on LinkedIn
1283 """,
1284 },
1285 {
1286 "owner_id": 2,
1287 "external_email_id": "indeed_alert_003",
1288 "subject": "Frontend Developer jobs in your area",
1289 "sender": "job-alerts@indeed.co.uk",
1290 "date_received": "2024-01-20 12:30:00",
1291 "platform": "indeed",
1292 "service_log_id": 4,
1293 "body": """
1294 New Frontend Developer opportunities:
1296 React Developer - London
1297 https://uk.indeed.com/pagead/clk/dl?mo=r&ad=react123&loc=london
1299 Vue.js Developer - Manchester
1300 https://indeed.com/viewjob?jk=vue456789&location=manchester
1302 Angular Developer - Bristol
1303 https://uk.indeed.com/rc/clk/dl?jk=ng789012&city=bristol
1305 Full Stack JavaScript Developer
1306 https://indeed.com/pagead/clk/dl?mo=r&ad=js345678&type=fullstack
1308 Keep applying!
1309 Indeed UK
1310 """,
1311 },
1312]
1315JOB_SCRAPED_DATA = [
1316 {
1317 "external_job_id": "3789012345",
1318 "owner_id": 1,
1319 "is_scraped": True,
1320 "is_failed": False,
1321 "title": "Senior Python Developer",
1322 "description": "We are looking for an experienced Python developer to join our team...",
1323 "company": "TechCorp Inc",
1324 "location": "San Francisco, CA",
1325 "salary_min": 120000.0,
1326 "salary_max": 160000.0,
1327 "url": "https://linkedin.com/jobs/view/3789012345",
1328 "scrape_datetime": "2025-08-15T14:32:18.123456+00:00",
1329 },
1330 {
1331 "external_job_id": "987654321",
1332 "owner_id": 1,
1333 "is_scraped": True,
1334 "is_failed": False,
1335 "title": "Full Stack Engineer",
1336 "description": "Join our growing startup as a full stack engineer...",
1337 "company": "StartupXYZ",
1338 "location": "Remote",
1339 "salary_min": 90000.0,
1340 "salary_max": 130000.0,
1341 "url": "https://indeed.com/viewjob?jk=987654321",
1342 "scrape_datetime": "2025-08-22T09:45:32.789012+00:00",
1343 },
1344 {
1345 "external_job_id": "1122334455",
1346 "owner_id": 1,
1347 "is_scraped": True,
1348 "is_failed": False,
1349 "title": "DevOps Engineer",
1350 "description": "Looking for a DevOps engineer with AWS experience...",
1351 "company": "CloudTech Solutions",
1352 "location": "New York, NY",
1353 "salary_min": 110000.0,
1354 "salary_max": 150000.0,
1355 "url": "https://linkedin.com/jobs/view/1122334455",
1356 "scrape_datetime": "2025-08-28T16:20:45.456789+00:00",
1357 },
1358 {
1359 "external_job_id": "5566778899",
1360 "owner_id": 1,
1361 "is_scraped": True,
1362 "is_failed": False,
1363 "title": "Software Engineer",
1364 "scrape_datetime": "2025-08-30T11:15:22.234567+00:00",
1365 },
1366 {
1367 "external_job_id": "1357924680",
1368 "owner_id": 1,
1369 "is_scraped": True,
1370 "is_failed": False,
1371 "title": "Backend Developer",
1372 "scrape_datetime": "2025-08-25T13:42:17.345678+00:00",
1373 },
1374 {
1375 "external_job_id": "2468135790",
1376 "owner_id": 1,
1377 "is_scraped": True,
1378 "is_failed": True,
1379 "scrape_error": "Page not found - job posting may have been removed",
1380 "title": "Data Engineer",
1381 "scrape_datetime": "2025-08-18T08:30:55.567890+00:00",
1382 },
1383 {
1384 "external_job_id": "9988776655",
1385 "owner_id": 1,
1386 "is_scraped": True,
1387 "is_failed": True,
1388 "scrape_error": "Scraping blocked - rate limit exceeded",
1389 "title": "ML Engineer",
1390 "scrape_datetime": "2025-08-20T19:25:08.678901+00:00",
1391 },
1392]
1395SERVICE_LOG_DATA = [
1396 {
1397 "name": "Email Scraper Service",
1398 "run_duration": 45.2,
1399 "run_datetime": "2024-01-15 08:30:00",
1400 "is_success": True,
1401 "error_message": None,
1402 "job_success_n": 25,
1403 "job_fail_n": 2,
1404 },
1405 {
1406 "name": "Email Scraper Service",
1407 "run_duration": 123.8,
1408 "run_datetime": "2024-01-15 09:15:00",
1409 "is_success": True,
1410 "error_message": None,
1411 "job_success_n": 89,
1412 "job_fail_n": 5,
1413 },
1414 {
1415 "name": "Email Scraper Service",
1416 "run_duration": 67.4,
1417 "run_datetime": "2024-01-15 10:00:00",
1418 "is_success": False,
1419 "error_message": "Rate limit exceeded after 30 requests",
1420 "job_success_n": 15,
1421 "job_fail_n": 45,
1422 },
1423 {
1424 "name": "Email Scraper Service",
1425 "run_duration": 89.1,
1426 "run_datetime": "2024-01-15 11:30:00",
1427 "is_success": True,
1428 "error_message": None,
1429 "job_success_n": 73,
1430 "job_fail_n": 8,
1431 },
1432 {
1433 "name": "Email Scraper Service",
1434 "run_duration": 12.3,
1435 "run_datetime": "2024-01-15 12:00:00",
1436 "is_success": True,
1437 "error_message": None,
1438 "job_success_n": None,
1439 "job_fail_n": None,
1440 },
1441 {
1442 "name": "Email Scraper Service",
1443 "run_duration": 3.7,
1444 "run_datetime": "2024-01-15 13:45:00",
1445 "is_success": False,
1446 "error_message": "SMTP server connection timeout",
1447 "job_success_n": 0,
1448 "job_fail_n": 12,
1449 },
1450 {
1451 "name": "Email Scraper Service",
1452 "run_duration": 156.9,
1453 "run_datetime": "2024-01-15 14:20:00",
1454 "is_success": True,
1455 "error_message": None,
1456 "job_success_n": 234,
1457 "job_fail_n": 18,
1458 },
1459 {
1460 "name": "Email Scraper Service",
1461 "run_duration": 78.5,
1462 "run_datetime": "2024-01-15 15:30:00",
1463 "is_success": False,
1464 "error_message": "PDF parsing library crashed on corrupted file",
1465 "job_success_n": 45,
1466 "job_fail_n": 67,
1467 },
1468 {
1469 "name": "Email Scraper Service",
1470 "run_duration": 34.2,
1471 "run_datetime": "2024-01-16 08:00:00",
1472 "is_success": True,
1473 "error_message": None,
1474 "job_success_n": 156,
1475 "job_fail_n": 3,
1476 },
1477]
1478SERVICE_LOG_DATETIME = [current_date - timedelta(days=i) for i in range(len(SERVICE_LOG_DATA))]
1479for service_log, date in zip(SERVICE_LOG_DATA, SERVICE_LOG_DATETIME):
1480 service_log["run_datetime"] = date.strftime(DATETIME_FORMAT)
1483EMAIL_SCRAPEDJOB_MAPPINGS = [
1484 {"email_id": 1, "scraped_job_ids": [1, 2, 4]}, # Mix of scraped and unscraped jobs
1485 {"email_id": 2, "scraped_job_ids": [3, 5]}, # One scraped, one unscraped
1486 {"email_id": 3, "scraped_job_ids": [6, 7]}, # Both failed scraping attempts
1487]
1490def add_mappings(
1491 primary_data: list,
1492 secondary_data: list,
1493 mapping_data: list,
1494 primary_key: str,
1495 secondary_key: str,
1496 relationship_attr: str,
1497) -> None:
1498 """Generic function to add many-to-many relationships between data objects.
1499 :param primary_data: List of primary objects (e.g. jobs, interviews)
1500 :param secondary_data: List of secondary objects (e.g. keywords, persons)
1501 :param mapping_data: List of mapping dictionaries
1502 :param primary_key: Key name for primary object ID in mapping (e.g. "job_id", "interview_id")
1503 :param secondary_key: Key name for secondary object IDs in mapping (e.g. "keyword_ids", "person_ids")
1504 :param relationship_attr: Attribute name on a primary object for the relationship (e.g. "keywords", "contacts")"""
1506 for mapping in mapping_data:
1507 primary_obj = primary_data[mapping[primary_key] - 1] # Convert to 0-based index
1508 secondary_ids = mapping[secondary_key]
1510 for secondary_id in secondary_ids:
1511 secondary_obj = secondary_data[secondary_id - 1] # Convert to 0-based index
1512 getattr(primary_obj, relationship_attr).append(secondary_obj)