From e6daa39f1ede6a31a6cbf359bd8ec6bcb61f0ddd Mon Sep 17 00:00:00 2001 From: Faheed Date: Tue, 21 Oct 2025 19:25:06 +0300 Subject: [PATCH] ... --- recruitment/__pycache__/views.cpython-312.pyc | Bin 92006 -> 93930 bytes recruitment/migrations/0001_initial.py | 478 ++++++++++++++++++ templates/includes/easy_logs.html | 2 +- 3 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 recruitment/migrations/0001_initial.py diff --git a/recruitment/__pycache__/views.cpython-312.pyc b/recruitment/__pycache__/views.cpython-312.pyc index 8fb9bd05de3dbf19f205b8bf2821cf0d10141ac1..19c121a751dcd8b7f816f306356a047b49b05730 100644 GIT binary patch delta 4997 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 delta 3688 zcmb7Hd014}6`%8l_XcMWsVw3ELeNhb1~rHvibQJxKX(wshzl773y7!}hz3WYiTh_# z$*tZdE}+J^p+%p`sEvTTiE%@k2rkj6Y1Am#V2x{g&Ksj9ZThA2$L~Amcb0qZdH3CS z=Eep2n-jA8vDU4PI{Zz}yO$lGTjuTyN!vr*TwsRU!{w?JoMp(IK3BJeJn)ykGpltb zdqJoZ+eN?Y_!HbrNV zdFtF4zHnZrD49hg%LWh#f*iIT;Iy+yw!M5D^^@xGwH10e z#hcgnRT_}EqgSIoOi?W7$@v1oMS@c5(N`p0BDhTOHNh3Wy)qWg@LQFBy{{6{8a*vW z_N(J5<`nP0wdXG{>0F2$os09eTNnJY_kR>PVjGv6G1^IY)_3@+e353I9)# z7L)VjoS;tp>OlUfBM7^D!|^+~%De8WMQqv??(ui3VKil0scnl}cbf-%i(;dDN|EvO)ttS%F4``IJGFhkbOt|a`L#vm+X#~@1Z_V zJ+ga{UZJhC2L4jD5AU+qEI&Z?3?H($y4`)0@c3(QMiPY{CAf#4G4dm{HLByQAL%{* zMe#Zb0SF|XyZ>k~iH&UvbFx^3vx1C*LdKrEN)q4R5WsiUyYblv+Q{zcen~Ama2H;B zLg{K~rSA|n>78BG_NAa#aVI`!zYpJgC|CAGHBb#dyaT|Ke|6*|*&B&)^}VB$B=CIq z*aq1T^`2_wv2`GKKyjd&dVC&$J8wMkh8%>%F!j}w(i*2T!7sHdsTbr}F2JgGhn+VTzc;j$I!O!aL2Og#kf;uIOS@W)q>zIXN9`z+CxbmgyLy+)wg|R-(Dy_P=aRU6lMoGU&Xo9 zz#hEi)(KN28rfsg`}5q#xA2g+70VA7}LiOCAMyXdNQxr{EW3p2TDChN_nzH5(yP)EQu$;%t_Pa)plV z$D?1PTTXsXVU8^?BhyxBV-qP~rkL&u`KIUH*~61>dy2=da1PQ09;Bh(+LGGaT0&)L z1&tQ5MhO1z`xMLw2_D+@tQ{1(qgj7(#0a5*&XQ>1JOb_ONToUVHAT3aV5Kpa2!e%< zohA_OiuQaXMY0(zinCHtYK90?4mqyC^o{<)!xj7`Hd!>9!3^WYT{HB8iK3ey#47pZ zHdRdXgN4d8lC9#2AB0GM193SFyi8gFS~p5WqCa#}v>lg-Y5w2~--t#25Dufo$NrFz z^7CC$wj$c!B?3lj3`x@wENx$Wwq$y~=jG(&+_)GP0Ns?=$zCYl3V<+=86?jnn1WEy zmiHU%%`*lo901?+6K81ILArf*8 z@6x(eI$rg@msk=Be)2LTt~n|rp(99Pz_(X-=2f2?jXw@^v-P~J#z$Nl3kGYOqKR-J2qDnkA}wkosn0^$98$4sA~g~` zM`#(I8>`S*U_c0R{@5h)E~5$vo}j6KzSpI?^Cth5sTC9IO_tL57DeY3cE;0MI^?Xu zU4-|9sk|w`$8oGD1iF}3Q}6^zQ!ehtLLYe@5KHc$ie#u3XI_IOvvy;ZQr4f}P$JF-gQ2rnWP^C)XB1DMb9`xo zVo)}qY&qx{H4)ajl+eEqwvT}RATXN#8IA3XMs=o5b~NcR8tNF$Y>Xx?=1D-)67wVI zKtKZ#qZtRUAcQUiFB8zn!3Gg%pD+4|Ygav9Yj{A|+rpj={{7w|nqGM4DW}8P=xxi( zvlSI)cePE_rqHgh=VZ+&U>}gzdICDHjLspe6lb#`FtCbbIxvh*0HbHl=t*fEWSmM(R>r-Ho)Ep)z=R?WA0p@?)@c;k- diff --git a/recruitment/migrations/0001_initial.py b/recruitment/migrations/0001_initial.py new file mode 100644 index 0000000..d0c43dc --- /dev/null +++ b/recruitment/migrations/0001_initial.py @@ -0,0 +1,478 @@ +# Generated by Django 5.2.7 on 2025-10-21 11:27 + +import django.core.validators +import django.db.models.deletion +import django_ckeditor_5.fields +import django_countries.fields +import django_extensions.db.fields +import recruitment.validators +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='BreakTime', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start_time', models.TimeField(verbose_name='Start Time')), + ('end_time', models.TimeField(verbose_name='End Time')), + ], + ), + migrations.CreateModel( + name='FormStage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('name', models.CharField(help_text='Name of the stage', max_length=200)), + ('order', models.PositiveIntegerField(default=0, help_text='Order of the stage in the form')), + ('is_predefined', models.BooleanField(default=False, help_text='Whether this is a default resume stage')), + ], + options={ + 'verbose_name': 'Form Stage', + 'verbose_name_plural': 'Form Stages', + 'ordering': ['order'], + }, + ), + migrations.CreateModel( + name='HiringAgency', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('name', models.CharField(max_length=200, unique=True, verbose_name='Agency Name')), + ('contact_person', models.CharField(blank=True, max_length=150, verbose_name='Contact Person')), + ('email', models.EmailField(blank=True, max_length=254)), + ('phone', models.CharField(blank=True, max_length=20)), + ('website', models.URLField(blank=True)), + ('notes', models.TextField(blank=True, help_text='Internal notes about the agency')), + ('country', django_countries.fields.CountryField(blank=True, max_length=2, null=True)), + ('address', models.TextField(blank=True, null=True)), + ], + options={ + 'verbose_name': 'Hiring Agency', + 'verbose_name_plural': 'Hiring Agencies', + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='Source', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('name', models.CharField(help_text='e.g., ATS, ERP ', max_length=100, unique=True, verbose_name='Source Name')), + ('source_type', models.CharField(help_text='e.g., ATS, ERP ', max_length=100, verbose_name='Source Type')), + ('description', models.TextField(blank=True, help_text='A description of the source', verbose_name='Description')), + ('ip_address', models.GenericIPAddressField(blank=True, help_text='The IP address of the source', null=True, verbose_name='IP Address')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('api_key', models.CharField(blank=True, help_text='API key for authentication (will be encrypted)', max_length=255, null=True, verbose_name='API Key')), + ('api_secret', models.CharField(blank=True, help_text='API secret for authentication (will be encrypted)', max_length=255, null=True, verbose_name='API Secret')), + ('trusted_ips', models.TextField(blank=True, help_text='Comma-separated list of trusted IP addresses', null=True, verbose_name='Trusted IP Addresses')), + ('is_active', models.BooleanField(default=True, help_text='Whether this source is active for integration', verbose_name='Active')), + ('integration_version', models.CharField(blank=True, help_text='Version of the integration protocol', max_length=50, verbose_name='Integration Version')), + ('last_sync_at', models.DateTimeField(blank=True, help_text='Timestamp of the last successful synchronization', null=True, verbose_name='Last Sync At')), + ('sync_status', models.CharField(blank=True, choices=[('IDLE', 'Idle'), ('SYNCING', 'Syncing'), ('ERROR', 'Error'), ('DISABLED', 'Disabled')], default='IDLE', max_length=20, verbose_name='Sync Status')), + ], + options={ + 'verbose_name': 'Source', + 'verbose_name_plural': 'Sources', + 'ordering': ['name'], + }, + ), + migrations.CreateModel( + name='ZoomMeeting', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('topic', models.CharField(max_length=255, verbose_name='Topic')), + ('meeting_id', models.CharField(db_index=True, max_length=20, unique=True, verbose_name='Meeting ID')), + ('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')), + ('duration', models.PositiveIntegerField(verbose_name='Duration')), + ('timezone', models.CharField(max_length=50, verbose_name='Timezone')), + ('join_url', models.URLField(verbose_name='Join URL')), + ('participant_video', models.BooleanField(default=True, verbose_name='Participant Video')), + ('password', models.CharField(blank=True, max_length=20, null=True, verbose_name='Password')), + ('join_before_host', models.BooleanField(default=False, verbose_name='Join Before Host')), + ('mute_upon_entry', models.BooleanField(default=False, verbose_name='Mute Upon Entry')), + ('waiting_room', models.BooleanField(default=False, verbose_name='Waiting Room')), + ('zoom_gateway_response', models.JSONField(blank=True, null=True, verbose_name='Zoom Gateway Response')), + ('status', models.CharField(blank=True, db_index=True, default='waiting', max_length=20, null=True, verbose_name='Status')), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='FormField', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('label', models.CharField(help_text='Label for the field', max_length=200)), + ('field_type', models.CharField(choices=[('text', 'Text Input'), ('email', 'Email'), ('phone', 'Phone'), ('textarea', 'Text Area'), ('file', 'File Upload'), ('date', 'Date Picker'), ('select', 'Dropdown'), ('radio', 'Radio Buttons'), ('checkbox', 'Checkboxes')], help_text='Type of the field', max_length=20)), + ('placeholder', models.CharField(blank=True, help_text='Placeholder text', max_length=200)), + ('required', models.BooleanField(default=False, help_text='Whether the field is required')), + ('order', models.PositiveIntegerField(default=0, help_text='Order of the field in the stage')), + ('is_predefined', models.BooleanField(default=False, help_text='Whether this is a default field')), + ('options', models.JSONField(blank=True, default=list, help_text='Options for selection fields (stored as JSON array)')), + ('file_types', models.CharField(blank=True, help_text="Allowed file types (comma-separated, e.g., '.pdf,.doc,.docx')", max_length=200)), + ('max_file_size', models.PositiveIntegerField(default=5, help_text='Maximum file size in MB (default: 5MB)')), + ('multiple_files', models.BooleanField(default=False, help_text='Allow multiple files to be uploaded')), + ('max_files', models.PositiveIntegerField(default=1, help_text='Maximum number of files allowed (when multiple_files is True)')), + ('stage', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='recruitment.formstage')), + ], + options={ + 'verbose_name': 'Form Field', + 'verbose_name_plural': 'Form Fields', + 'ordering': ['order'], + }, + ), + migrations.CreateModel( + name='FormTemplate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('name', models.CharField(help_text='Name of the form template', max_length=200)), + ('description', models.TextField(blank=True, help_text='Description of the form template')), + ('is_active', models.BooleanField(default=False, help_text='Whether this template is active')), + ('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='form_templates', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Form Template', + 'verbose_name_plural': 'Form Templates', + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='FormSubmission', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('submitted_at', models.DateTimeField(auto_now_add=True, db_index=True)), + ('applicant_name', models.CharField(blank=True, help_text='Name of the applicant', max_length=200)), + ('applicant_email', models.EmailField(blank=True, db_index=True, help_text='Email of the applicant', max_length=254)), + ('submitted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='form_submissions', to=settings.AUTH_USER_MODEL)), + ('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='recruitment.formtemplate')), + ], + options={ + 'verbose_name': 'Form Submission', + 'verbose_name_plural': 'Form Submissions', + 'ordering': ['-submitted_at'], + }, + ), + migrations.AddField( + model_name='formstage', + name='template', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stages', to='recruitment.formtemplate'), + ), + migrations.CreateModel( + name='Candidate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('first_name', models.CharField(max_length=255, verbose_name='First Name')), + ('last_name', models.CharField(max_length=255, verbose_name='Last Name')), + ('email', models.EmailField(db_index=True, max_length=254, verbose_name='Email')), + ('phone', models.CharField(max_length=20, verbose_name='Phone')), + ('address', models.TextField(max_length=200, verbose_name='Address')), + ('resume', models.FileField(upload_to='resumes/', verbose_name='Resume')), + ('is_resume_parsed', models.BooleanField(default=False, verbose_name='Resume Parsed')), + ('is_potential_candidate', models.BooleanField(default=False, verbose_name='Potential Candidate')), + ('parsed_summary', models.TextField(blank=True, verbose_name='Parsed Summary')), + ('applied', models.BooleanField(default=False, verbose_name='Applied')), + ('stage', models.CharField(choices=[('Applied', 'Applied'), ('Exam', 'Exam'), ('Interview', 'Interview'), ('Offer', 'Offer')], db_index=True, default='Applied', max_length=100, verbose_name='Stage')), + ('applicant_status', models.CharField(blank=True, choices=[('Applicant', 'Applicant'), ('Candidate', 'Candidate')], default='Applicant', max_length=100, null=True, verbose_name='Applicant Status')), + ('exam_date', models.DateTimeField(blank=True, null=True, verbose_name='Exam Date')), + ('exam_status', models.CharField(blank=True, choices=[('Passed', 'Passed'), ('Failed', 'Failed')], max_length=100, null=True, verbose_name='Exam Status')), + ('interview_date', models.DateTimeField(blank=True, null=True, verbose_name='Interview Date')), + ('interview_status', models.CharField(blank=True, choices=[('Passed', 'Passed'), ('Failed', 'Failed')], max_length=100, null=True, verbose_name='Interview Status')), + ('offer_date', models.DateField(blank=True, null=True, verbose_name='Offer Date')), + ('offer_status', models.CharField(blank=True, choices=[('Accepted', 'Accepted'), ('Rejected', 'Rejected')], max_length=100, null=True, verbose_name='Offer Status')), + ('join_date', models.DateField(blank=True, null=True, verbose_name='Join Date')), + ('ai_analysis_data', models.JSONField(default=dict, help_text='Full JSON output from the resume scoring model.', verbose_name='AI Analysis Data')), + ('submitted_by_agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='submitted_candidates', to='recruitment.hiringagency', verbose_name='Submitted by Agency')), + ], + options={ + 'verbose_name': 'Candidate', + 'verbose_name_plural': 'Candidates', + }, + ), + migrations.CreateModel( + name='JobPosting', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('title', models.CharField(max_length=200)), + ('department', models.CharField(blank=True, max_length=100)), + ('job_type', models.CharField(choices=[('FULL_TIME', 'Full-time'), ('PART_TIME', 'Part-time'), ('CONTRACT', 'Contract'), ('INTERNSHIP', 'Internship'), ('FACULTY', 'Faculty'), ('TEMPORARY', 'Temporary')], default='FULL_TIME', max_length=20)), + ('workplace_type', models.CharField(choices=[('ON_SITE', 'On-site'), ('REMOTE', 'Remote'), ('HYBRID', 'Hybrid')], default='ON_SITE', max_length=20)), + ('location_city', models.CharField(blank=True, max_length=100)), + ('location_state', models.CharField(blank=True, max_length=100)), + ('location_country', models.CharField(default='Saudia Arabia', max_length=100)), + ('description', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Description')), + ('qualifications', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)), + ('salary_range', models.CharField(blank=True, help_text='e.g., $60,000 - $80,000', max_length=200)), + ('benefits', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)), + ('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])), + ('application_start_date', models.DateField(blank=True, null=True)), + ('application_deadline', models.DateField(blank=True, db_index=True, null=True)), + ('application_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)), + ('internal_job_id', models.CharField(editable=False, max_length=50, primary_key=True, serialize=False)), + ('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)), + ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], db_index=True, default='DRAFT', max_length=20)), + ('hash_tags', models.CharField(blank=True, help_text='Comma-separated hashtags for linkedin post like #hiring,#jobopening', max_length=200, validators=[recruitment.validators.validate_hash_tags])), + ('linkedin_post_id', models.CharField(blank=True, help_text='LinkedIn post ID after posting', max_length=200)), + ('linkedin_post_url', models.URLField(blank=True, help_text='Direct URL to LinkedIn post')), + ('posted_to_linkedin', models.BooleanField(default=False)), + ('linkedin_post_status', models.CharField(blank=True, help_text='Status of LinkedIn posting', max_length=50)), + ('linkedin_posted_at', models.DateTimeField(blank=True, null=True)), + ('published_at', models.DateTimeField(blank=True, db_index=True, null=True)), + ('position_number', models.CharField(blank=True, help_text='University position number', max_length=50)), + ('reporting_to', models.CharField(blank=True, help_text='Who this position reports to', max_length=100)), + ('joining_date', models.DateField(blank=True, help_text='Desired start date', null=True)), + ('open_positions', models.PositiveIntegerField(default=1, help_text='Number of open positions for this job')), + ('max_applications', models.PositiveIntegerField(blank=True, default=1000, help_text='Maximum number of applications allowed', null=True)), + ('cancel_reason', models.TextField(blank=True, help_text='Reason for canceling the job posting', verbose_name='Cancel Reason')), + ('cancelled_by', models.CharField(blank=True, help_text='Name of person who cancelled this job', max_length=100, verbose_name='Cancelled By')), + ('cancelled_at', models.DateTimeField(blank=True, null=True)), + ('hiring_agency', models.ManyToManyField(blank=True, help_text='External agency responsible for sourcing candidates for this role', related_name='jobs', to='recruitment.hiringagency', verbose_name='Hiring Agency')), + ('source', models.ForeignKey(blank=True, help_text='The system or channel from which this job posting originated or was first published.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='job_postings', to='recruitment.source')), + ], + options={ + 'verbose_name': 'Job Posting', + 'verbose_name_plural': 'Job Postings', + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='InterviewSchedule', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('start_date', models.DateField(db_index=True, verbose_name='Start Date')), + ('end_date', models.DateField(db_index=True, verbose_name='End Date')), + ('working_days', models.JSONField(verbose_name='Working Days')), + ('start_time', models.TimeField(verbose_name='Start Time')), + ('end_time', models.TimeField(verbose_name='End Time')), + ('break_start_time', models.TimeField(blank=True, null=True, verbose_name='Break Start Time')), + ('break_end_time', models.TimeField(blank=True, null=True, verbose_name='Break End Time')), + ('interview_duration', models.PositiveIntegerField(verbose_name='Interview Duration (minutes)')), + ('buffer_time', models.PositiveIntegerField(default=0, verbose_name='Buffer Time (minutes)')), + ('candidates', models.ManyToManyField(blank=True, null=True, related_name='interview_schedules', to='recruitment.candidate')), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interview_schedules', to='recruitment.jobposting')), + ], + ), + migrations.AddField( + model_name='formtemplate', + name='job', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='form_template', to='recruitment.jobposting'), + ), + migrations.AddField( + model_name='candidate', + name='job', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='candidates', to='recruitment.jobposting', verbose_name='Job'), + ), + migrations.CreateModel( + name='JobPostingImage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('post_image', models.ImageField(upload_to='post/', validators=[recruitment.validators.validate_image_size])), + ('job', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='post_images', to='recruitment.jobposting')), + ], + ), + migrations.CreateModel( + name='Profile', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size])), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='SharedFormTemplate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('is_public', models.BooleanField(default=False, help_text='Whether this template is publicly available')), + ('shared_with', models.ManyToManyField(blank=True, related_name='shared_templates', to=settings.AUTH_USER_MODEL)), + ('template', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='recruitment.formtemplate')), + ], + options={ + 'verbose_name': 'Shared Form Template', + 'verbose_name_plural': 'Shared Form Templates', + }, + ), + migrations.CreateModel( + name='IntegrationLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('action', models.CharField(choices=[('REQUEST', 'Request'), ('RESPONSE', 'Response'), ('ERROR', 'Error'), ('SYNC', 'Sync'), ('CREATE_JOB', 'Create Job'), ('UPDATE_JOB', 'Update Job')], max_length=20, verbose_name='Action')), + ('endpoint', models.CharField(blank=True, max_length=255, verbose_name='Endpoint')), + ('method', models.CharField(blank=True, max_length=10, verbose_name='HTTP Method')), + ('request_data', models.JSONField(blank=True, null=True, verbose_name='Request Data')), + ('response_data', models.JSONField(blank=True, null=True, verbose_name='Response Data')), + ('status_code', models.CharField(blank=True, max_length=10, verbose_name='Status Code')), + ('error_message', models.TextField(blank=True, verbose_name='Error Message')), + ('ip_address', models.GenericIPAddressField(verbose_name='IP Address')), + ('user_agent', models.CharField(blank=True, max_length=255, verbose_name='User Agent')), + ('processing_time', models.FloatField(blank=True, null=True, verbose_name='Processing Time (seconds)')), + ('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='integration_logs', to='recruitment.source', verbose_name='Source')), + ], + options={ + 'verbose_name': 'Integration Log', + 'verbose_name_plural': 'Integration Logs', + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='TrainingMaterial', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('title', models.CharField(max_length=255, verbose_name='Title')), + ('content', django_ckeditor_5.fields.CKEditor5Field(blank=True, verbose_name='Content')), + ('video_link', models.URLField(blank=True, verbose_name='Video Link')), + ('file', models.FileField(blank=True, upload_to='training_materials/', verbose_name='File')), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Created by')), + ], + options={ + 'verbose_name': 'Training Material', + 'verbose_name_plural': 'Training Materials', + }, + ), + migrations.CreateModel( + name='ScheduledInterview', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('interview_date', models.DateField(db_index=True, verbose_name='Interview Date')), + ('interview_time', models.TimeField(verbose_name='Interview Time')), + ('status', models.CharField(choices=[('scheduled', 'Scheduled'), ('confirmed', 'Confirmed'), ('cancelled', 'Cancelled'), ('completed', 'Completed')], db_index=True, default='scheduled', max_length=20)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('candidate', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_interviews', to='recruitment.candidate')), + ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_interviews', to='recruitment.jobposting')), + ('schedule', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='interviews', to='recruitment.interviewschedule')), + ('zoom_meeting', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='interview', to='recruitment.zoommeeting')), + ], + ), + migrations.CreateModel( + name='MeetingComment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('content', django_ckeditor_5.fields.CKEditor5Field(verbose_name='Content')), + ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meeting_comments', to=settings.AUTH_USER_MODEL, verbose_name='Author')), + ('meeting', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='recruitment.zoommeeting', verbose_name='Meeting')), + ], + options={ + 'verbose_name': 'Meeting Comment', + 'verbose_name_plural': 'Meeting Comments', + 'ordering': ['-created_at'], + }, + ), + migrations.CreateModel( + name='FieldResponse', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), + ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), + ('value', models.JSONField(blank=True, help_text='Response value (stored as JSON)', null=True)), + ('uploaded_file', models.FileField(blank=True, null=True, upload_to='form_uploads/')), + ('field', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='recruitment.formfield')), + ('submission', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='responses', to='recruitment.formsubmission')), + ], + options={ + 'verbose_name': 'Field Response', + 'verbose_name_plural': 'Field Responses', + 'indexes': [models.Index(fields=['submission'], name='recruitment_submiss_474130_idx'), models.Index(fields=['field'], name='recruitment_field_i_097e5b_idx')], + }, + ), + migrations.AddIndex( + model_name='formsubmission', + index=models.Index(fields=['submitted_at'], name='recruitment_submitt_7946c8_idx'), + ), + migrations.AddIndex( + model_name='interviewschedule', + index=models.Index(fields=['start_date'], name='recruitment_start_d_15d55e_idx'), + ), + migrations.AddIndex( + model_name='interviewschedule', + index=models.Index(fields=['end_date'], name='recruitment_end_dat_aeb00e_idx'), + ), + migrations.AddIndex( + model_name='interviewschedule', + index=models.Index(fields=['created_by'], name='recruitment_created_d0bdcc_idx'), + ), + migrations.AddIndex( + model_name='formtemplate', + index=models.Index(fields=['created_at'], name='recruitment_created_c21775_idx'), + ), + migrations.AddIndex( + model_name='formtemplate', + index=models.Index(fields=['is_active'], name='recruitment_is_acti_ae5efb_idx'), + ), + migrations.AddIndex( + model_name='candidate', + index=models.Index(fields=['stage'], name='recruitment_stage_f1c6eb_idx'), + ), + migrations.AddIndex( + model_name='candidate', + index=models.Index(fields=['created_at'], name='recruitment_created_73590f_idx'), + ), + migrations.AddIndex( + model_name='jobposting', + index=models.Index(fields=['status', 'created_at', 'title'], name='recruitment_status_8b77aa_idx'), + ), + migrations.AddIndex( + model_name='jobposting', + index=models.Index(fields=['slug'], name='recruitment_slug_004045_idx'), + ), + migrations.AddIndex( + model_name='scheduledinterview', + index=models.Index(fields=['job', 'status'], name='recruitment_job_id_f09e22_idx'), + ), + migrations.AddIndex( + model_name='scheduledinterview', + index=models.Index(fields=['interview_date', 'interview_time'], name='recruitment_intervi_7f5877_idx'), + ), + migrations.AddIndex( + model_name='scheduledinterview', + index=models.Index(fields=['candidate', 'job'], name='recruitment_candida_43d5b0_idx'), + ), + ] diff --git a/templates/includes/easy_logs.html b/templates/includes/easy_logs.html index 094b933..307dad7 100644 --- a/templates/includes/easy_logs.html +++ b/templates/includes/easy_logs.html @@ -275,7 +275,7 @@ {% elif active_tab == 'request' %} {{ log.datetime|date:"Y-m-d H:i:s" }} - {{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }} + {{ log.user.email|default:"Anonymous" }} {{ log.method }}