add login to candidate and agency

This commit is contained in:
ismail 2025-11-05 18:27:43 +03:00
parent 9830b1173f
commit caa7ed88aa
22 changed files with 4048 additions and 2375 deletions

View File

@ -9,6 +9,7 @@ https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""
import os
from pathlib import Path
from django.templatetags.static import static
@ -20,7 +21,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-_!ew&)1&r--3h17knd27^x8(xu(&-f4q3%x543lv5vx2!784s*'
SECRET_KEY = "django-insecure-_!ew&)1&r--3h17knd27^x8(xu(&-f4q3%x543lv5vx2!784s*"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
@ -30,116 +31,114 @@ ALLOWED_HOSTS = ["*"]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.humanize',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'recruitment.apps.RecruitmentConfig',
'corsheaders',
'django.contrib.sites',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.linkedin_oauth2',
'channels',
'django_filters',
'crispy_forms',
"django.contrib.admin",
"django.contrib.humanize",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"recruitment.apps.RecruitmentConfig",
"corsheaders",
"django.contrib.sites",
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.linkedin_oauth2",
"channels",
"django_filters",
"crispy_forms",
# 'django_summernote',
# 'ckeditor',
'django_ckeditor_5',
'crispy_bootstrap5',
'django_extensions',
'template_partials',
'django_countries',
'django_celery_results',
'django_q',
'widget_tweaks',
'easyaudit'
"django_ckeditor_5",
"crispy_bootstrap5",
"django_extensions",
"template_partials",
"django_countries",
"django_celery_results",
"django_q",
"widget_tweaks",
"easyaudit",
]
SITE_ID = 1
LOGIN_REDIRECT_URL = 'dashboard'
LOGIN_REDIRECT_URL = "dashboard"
ACCOUNT_LOGOUT_REDIRECT_URL = '/'
ACCOUNT_LOGOUT_REDIRECT_URL = "/"
ACCOUNT_SIGNUP_REDIRECT_URL = '/'
ACCOUNT_SIGNUP_REDIRECT_URL = "/"
LOGIN_URL = '/accounts/login/'
LOGIN_URL = "/accounts/login/"
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'allauth.account.auth_backends.AuthenticationBackend',
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'allauth.account.middleware.AccountMiddleware',
'easyaudit.middleware.easyaudit.EasyAuditMiddleware',
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"allauth.account.middleware.AccountMiddleware",
"easyaudit.middleware.easyaudit.EasyAuditMiddleware",
]
ROOT_URLCONF = 'NorahUniversity.urls'
ROOT_URLCONF = "NorahUniversity.urls"
CORS_ALLOW_ALL_ORIGINS = True
ASGI_APPLICATION = 'hospital_recruitment.asgi.application'
ASGI_APPLICATION = "hospital_recruitment.asgi.application"
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
'hosts': [('127.0.0.1', 6379)],
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("127.0.0.1", 6379)],
},
},
}
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = 'NorahUniversity.wsgi.application'
WSGI_APPLICATION = "NorahUniversity.wsgi.application"
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'haikal_db',
'USER': 'faheed',
'PASSWORD': 'Faheed@215',
'HOST': '127.0.0.1',
'PORT': '5432',
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "norahuniversity",
"USER": "norahuniversity",
"PASSWORD": "norahuniversity",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}
@ -154,7 +153,6 @@ DATABASES = {
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
# AUTH_PASSWORD_VALIDATORS = [
# {
# 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
@ -174,13 +172,13 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
]
ACCOUNT_LOGIN_METHODS = ['email']
ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*']
ACCOUNT_LOGIN_METHODS = ["email"]
ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"]
ACCOUNT_UNIQUE_EMAIL = True
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
@ -188,10 +186,10 @@ ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
ACCOUNT_FORMS = {'signup': 'recruitment.forms.StaffSignupForm'}
ACCOUNT_FORMS = {"signup": "recruitment.forms.StaffSignupForm"}
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# Crispy Forms Configuration
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
@ -199,29 +197,29 @@ CRISPY_TEMPLATE_PACK = "bootstrapconsole5"
# Bootstrap 5 Configuration
CRISPY_BS5 = {
'include_placeholder_text': True,
'use_css_helpers': True,
"include_placeholder_text": True,
"use_css_helpers": True,
}
ACCOUNT_RATE_LIMITS = {
'send_email_confirmation': None, # Disables the limit
"send_email_confirmation": None, # Disables the limit
}
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGES = [
('en', 'English'),
('ar', 'Arabic'),
("en", "English"),
("ar", "Arabic"),
]
LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = "en-us"
LOCALE_PATHS = [
BASE_DIR / 'locale',
BASE_DIR / "locale",
]
TIME_ZONE = 'Asia/Riyadh'
TIME_ZONE = "Asia/Riyadh"
USE_I18N = True
@ -230,36 +228,35 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
STATICFILES_DIRS = [
BASE_DIR / 'static'
]
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
STATIC_URL = "/static/"
MEDIA_URL = "/media/"
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles")
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# LinkedIn OAuth Config
SOCIALACCOUNT_PROVIDERS = {
'linkedin_oauth2': {
'SCOPE': [
'r_liteprofile', 'r_emailaddress', 'w_member_social',
'rw_organization_admin', 'w_organization_social'
"linkedin_oauth2": {
"SCOPE": [
"r_liteprofile",
"r_emailaddress",
"w_member_social",
"rw_organization_admin",
"w_organization_social",
],
'PROFILE_FIELDS': [
'id', 'first-name', 'last-name', 'email-address'
]
"PROFILE_FIELDS": ["id", "first-name", "last-name", "email-address"],
}
}
ZOOM_ACCOUNT_ID = 'HoGikHXsQB2GNDC5Rvyw9A'
ZOOM_CLIENT_ID = 'brC39920R8C8azfudUaQgA'
ZOOM_CLIENT_SECRET = 'rvfhjlbID4ychXPOvZ2lYsoAC0B0Ny2L'
SECRET_TOKEN = '6KdTGyF0SSCSL_V4Xa34aw'
ZOOM_ACCOUNT_ID = "HoGikHXsQB2GNDC5Rvyw9A"
ZOOM_CLIENT_ID = "brC39920R8C8azfudUaQgA"
ZOOM_CLIENT_SECRET = "rvfhjlbID4ychXPOvZ2lYsoAC0B0Ny2L"
SECRET_TOKEN = "6KdTGyF0SSCSL_V4Xa34aw"
ZOOM_WEBHOOK_API_KEY = "2GNDC5Rvyw9AHoGikHXsQB"
# Maximum file upload size (in bytes)
@ -268,142 +265,200 @@ FILE_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 10MB
CORS_ALLOW_CREDENTIALS = True
CELERY_BROKER_URL = 'redis://localhost:6379/0' # Or your message broker URL
CELERY_RESULT_BACKEND = 'django-db' # If using django-celery-results
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'UTC'
CELERY_BROKER_URL = "redis://localhost:6379/0" # Or your message broker URL
CELERY_RESULT_BACKEND = "django-db" # If using django-celery-results
CELERY_ACCEPT_CONTENT = ["application/json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
CELERY_TIMEZONE = "UTC"
LINKEDIN_CLIENT_ID = '867jwsiyem1504'
LINKEDIN_CLIENT_SECRET = 'WPL_AP1.QNH5lYnfRSQpp0Qp.GO8Srw=='
LINKEDIN_REDIRECT_URI = 'http://127.0.0.1:8000/jobs/linkedin/callback/'
LINKEDIN_CLIENT_ID = "867jwsiyem1504"
LINKEDIN_CLIENT_SECRET = "WPL_AP1.QNH5lYnfRSQpp0Qp.GO8Srw=="
LINKEDIN_REDIRECT_URI = "http://127.0.0.1:8000/jobs/linkedin/callback/"
Q_CLUSTER = {
'name': 'KAAUH_CLUSTER',
'workers': 8,
'recycle': 500,
'timeout': 60,
'max_attempts': 1,
'compress': True,
'save_limit': 250,
'queue_limit': 500,
'cpu_affinity': 1,
'label': 'Django Q2',
'redis': {
'host': '127.0.0.1',
'port': 6379,
'db': 3, },
'ALT_CLUSTERS': {
'long': {
'timeout': 3000,
'retry': 3600,
'max_attempts': 2,
"name": "KAAUH_CLUSTER",
"workers": 8,
"recycle": 500,
"timeout": 60,
"max_attempts": 1,
"compress": True,
"save_limit": 250,
"queue_limit": 500,
"cpu_affinity": 1,
"label": "Django Q2",
"redis": {
"host": "127.0.0.1",
"port": 6379,
"db": 3,
},
"ALT_CLUSTERS": {
"long": {
"timeout": 3000,
"retry": 3600,
"max_attempts": 2,
},
'short': {
'timeout': 10,
'max_attempts': 1,
"short": {
"timeout": 10,
"max_attempts": 1,
},
}
},
}
customColorPalette = [
{
'color': 'hsl(4, 90%, 58%)',
'label': 'Red'
},
{
'color': 'hsl(340, 82%, 52%)',
'label': 'Pink'
},
{
'color': 'hsl(291, 64%, 42%)',
'label': 'Purple'
},
{
'color': 'hsl(262, 52%, 47%)',
'label': 'Deep Purple'
},
{
'color': 'hsl(231, 48%, 48%)',
'label': 'Indigo'
},
{
'color': 'hsl(207, 90%, 54%)',
'label': 'Blue'
},
]
{"color": "hsl(4, 90%, 58%)", "label": "Red"},
{"color": "hsl(340, 82%, 52%)", "label": "Pink"},
{"color": "hsl(291, 64%, 42%)", "label": "Purple"},
{"color": "hsl(262, 52%, 47%)", "label": "Deep Purple"},
{"color": "hsl(231, 48%, 48%)", "label": "Indigo"},
{"color": "hsl(207, 90%, 54%)", "label": "Blue"},
]
# CKEDITOR_5_CUSTOM_CSS = 'path_to.css' # optional
# CKEDITOR_5_FILE_STORAGE = "path_to_storage.CustomStorage" # optional
CKEDITOR_5_CONFIGS = {
'default': {
'toolbar': {
'items': ['heading', '|', 'bold', 'italic', 'link',
'bulletedList', 'numberedList', 'blockQuote', 'imageUpload', ],
}
"default": {
"toolbar": {
"items": [
"heading",
"|",
"bold",
"italic",
"link",
"bulletedList",
"numberedList",
"blockQuote",
"imageUpload",
],
}
},
'extends': {
'blockToolbar': [
'paragraph', 'heading1', 'heading2', 'heading3',
'|',
'bulletedList', 'numberedList',
'|',
'blockQuote',
"extends": {
"blockToolbar": [
"paragraph",
"heading1",
"heading2",
"heading3",
"|",
"bulletedList",
"numberedList",
"|",
"blockQuote",
],
'toolbar': {
'items': ['heading', '|', 'outdent', 'indent', '|', 'bold', 'italic', 'link', 'underline', 'strikethrough',
'code','subscript', 'superscript', 'highlight', '|', 'codeBlock', 'sourceEditing', 'insertImage',
'bulletedList', 'numberedList', 'todoList', '|', 'blockQuote', 'imageUpload', '|',
'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', 'mediaEmbed', 'removeFormat',
'insertTable',
],
'shouldNotGroupWhenFull': 'true'
"toolbar": {
"items": [
"heading",
"|",
"outdent",
"indent",
"|",
"bold",
"italic",
"link",
"underline",
"strikethrough",
"code",
"subscript",
"superscript",
"highlight",
"|",
"codeBlock",
"sourceEditing",
"insertImage",
"bulletedList",
"numberedList",
"todoList",
"|",
"blockQuote",
"imageUpload",
"|",
"fontSize",
"fontFamily",
"fontColor",
"fontBackgroundColor",
"mediaEmbed",
"removeFormat",
"insertTable",
],
"shouldNotGroupWhenFull": "true",
},
'image': {
'toolbar': ['imageTextAlternative', '|', 'imageStyle:alignLeft',
'imageStyle:alignRight', 'imageStyle:alignCenter', 'imageStyle:side', '|'],
'styles': [
'full',
'side',
'alignLeft',
'alignRight',
'alignCenter',
]
"image": {
"toolbar": [
"imageTextAlternative",
"|",
"imageStyle:alignLeft",
"imageStyle:alignRight",
"imageStyle:alignCenter",
"imageStyle:side",
"|",
],
"styles": [
"full",
"side",
"alignLeft",
"alignRight",
"alignCenter",
],
},
'table': {
'contentToolbar': [ 'tableColumn', 'tableRow', 'mergeTableCells',
'tableProperties', 'tableCellProperties' ],
'tableProperties': {
'borderColors': customColorPalette,
'backgroundColors': customColorPalette
"table": {
"contentToolbar": [
"tableColumn",
"tableRow",
"mergeTableCells",
"tableProperties",
"tableCellProperties",
],
"tableProperties": {
"borderColors": customColorPalette,
"backgroundColors": customColorPalette,
},
"tableCellProperties": {
"borderColors": customColorPalette,
"backgroundColors": customColorPalette,
},
'tableCellProperties': {
'borderColors': customColorPalette,
'backgroundColors': customColorPalette
}
},
'heading' : {
'options': [
{ 'model': 'paragraph', 'title': 'Paragraph', 'class': 'ck-heading_paragraph' },
{ 'model': 'heading1', 'view': 'h1', 'title': 'Heading 1', 'class': 'ck-heading_heading1' },
{ 'model': 'heading2', 'view': 'h2', 'title': 'Heading 2', 'class': 'ck-heading_heading2' },
{ 'model': 'heading3', 'view': 'h3', 'title': 'Heading 3', 'class': 'ck-heading_heading3' }
"heading": {
"options": [
{
"model": "paragraph",
"title": "Paragraph",
"class": "ck-heading_paragraph",
},
{
"model": "heading1",
"view": "h1",
"title": "Heading 1",
"class": "ck-heading_heading1",
},
{
"model": "heading2",
"view": "h2",
"title": "Heading 2",
"class": "ck-heading_heading2",
},
{
"model": "heading3",
"view": "h3",
"title": "Heading 3",
"class": "ck-heading_heading3",
},
]
},
},
"list": {
"properties": {
"styles": "true",
"startIndex": "true",
"reversed": "true",
}
},
'list': {
'properties': {
'styles': 'true',
'startIndex': 'true',
'reversed': 'true',
}
}
}
# Define a constant in settings.py to specify file upload permissions
CKEDITOR_5_FILE_UPLOAD_PERMISSION = "staff" # Possible values: "staff", "authenticated", "any"
CKEDITOR_5_FILE_UPLOAD_PERMISSION = (
"staff" # Possible values: "staff", "authenticated", "any"
)
# Custom User Model
AUTH_USER_MODEL = "recruitment.CustomUser"

View File

@ -8,7 +8,9 @@ from .models import (
SharedFormTemplate, Source, HiringAgency, IntegrationLog,InterviewSchedule,Profile,JobPostingImage,MeetingComment,
AgencyAccessLink, AgencyJobAssignment
)
from django.contrib.auth import get_user_model
User = get_user_model()
class FormFieldInline(admin.TabularInline):
model = FormField
extra = 1
@ -82,17 +84,10 @@ class HiringAgencyAdmin(admin.ModelAdmin):
readonly_fields = ['slug', 'created_at', 'updated_at']
fieldsets = (
('Basic Information', {
'fields': ('name', 'slug', 'contact_person', 'email', 'phone', 'website')
}),
('Location Details', {
'fields': ('country', 'city', 'address')
}),
('Additional Information', {
'fields': ('description', 'created_at', 'updated_at')
'fields': ('name','contact_person', 'email', 'phone', 'website','user')
}),
)
save_on_top = True
prepopulated_fields = {'slug': ('name',)}
@admin.register(JobPosting)
@ -151,7 +146,7 @@ class CandidateAdmin(admin.ModelAdmin):
readonly_fields = ['slug', 'created_at', 'updated_at']
fieldsets = (
('Personal Information', {
'fields': ('first_name', 'last_name', 'email', 'phone', 'resume')
'fields': ('first_name', 'last_name', 'email', 'phone', 'resume','user')
}),
('Application Details', {
'fields': ('job', 'applied', 'stage','is_resume_parsed')
@ -163,7 +158,7 @@ class CandidateAdmin(admin.ModelAdmin):
'fields': ('ai_analysis_data',)
}),
('Additional Information', {
'fields': ('submitted_by_agency', 'created_at', 'updated_at')
'fields': ('created_at', 'updated_at')
}),
)
save_on_top = True
@ -290,3 +285,4 @@ admin.site.register(AgencyJobAssignment)
admin.site.register(JobPostingImage)
admin.site.register(User)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
# Generated migration for adding user relationships
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
("recruitment", "0002_alter_jobposting_job_type_and_more"),
]
operations = [
migrations.AddField(
model_name="candidate",
name="user",
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="candidate_profile",
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
migrations.AddField(
model_name="hiringagency",
name="user",
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="agency_profile",
to=settings.AUTH_USER_MODEL,
verbose_name="User",
),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.6 on 2025-11-05 13:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0003_auto_20251105_1616'),
]
operations = [
migrations.AlterField(
model_name='candidate',
name='ai_analysis_data',
field=models.JSONField(blank=True, default=dict, help_text='Full JSON output from the resume scoring model.', null=True, verbose_name='AI Analysis Data'),
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 5.2.6 on 2025-11-05 13:37
import django.contrib.auth.models
import django.contrib.auth.validators
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('recruitment', '0004_alter_candidate_ai_analysis_data'),
]
operations = [
migrations.CreateModel(
name='CustomUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('user_type', models.CharField(choices=[('staff', 'Staff'), ('agency', 'Agency'), ('candidate', 'Candidate')], default='staff', max_length=20, verbose_name='User Type')),
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Phone')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'User',
'verbose_name_plural': 'Users',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

File diff suppressed because it is too large Load Diff

View File

@ -5,98 +5,264 @@ from . import views_integration
from . import views_source
urlpatterns = [
path('dashboard/', views_frontend.dashboard_view, name='dashboard'),
path("", views_frontend.dashboard_view, name="dashboard"),
# Job URLs (using JobPosting model)
path('jobs/', views_frontend.JobListView.as_view(), name='job_list'),
path('jobs/create/', views.create_job, name='job_create'),
path('job/<slug:slug>/upload_image_simple/', views.job_image_upload, name='job_image_upload'),
path('jobs/<slug:slug>/update/', views.edit_job, name='job_update'),
path("jobs/", views_frontend.JobListView.as_view(), name="job_list"),
path("jobs/create/", views.create_job, name="job_create"),
path(
"job/<slug:slug>/upload_image_simple/",
views.job_image_upload,
name="job_image_upload",
),
path("jobs/<slug:slug>/update/", views.edit_job, name="job_update"),
# path('jobs/<slug:slug>/delete/', views., name='job_delete'),
path('jobs/<slug:slug>/', views.job_detail, name='job_detail'),
path('careers/',views.kaauh_career,name='kaauh_career'),
path("jobs/<slug:slug>/", views.job_detail, name="job_detail"),
path("careers/", views.kaauh_career, name="kaauh_career"),
# LinkedIn Integration URLs
path('jobs/<slug:slug>/post-to-linkedin/', views.post_to_linkedin, name='post_to_linkedin'),
path('jobs/linkedin/login/', views.linkedin_login, name='linkedin_login'),
path('jobs/linkedin/callback/', views.linkedin_callback, name='linkedin_callback'),
path('jobs/<slug:slug>/schedule-interviews/', views.schedule_interviews_view, name='schedule_interviews'),
path('jobs/<slug:slug>/confirm-schedule-interviews/', views.confirm_schedule_interviews_view, name='confirm_schedule_interviews_view'),
path(
"jobs/<slug:slug>/post-to-linkedin/",
views.post_to_linkedin,
name="post_to_linkedin",
),
path("jobs/linkedin/login/", views.linkedin_login, name="linkedin_login"),
path("jobs/linkedin/callback/", views.linkedin_callback, name="linkedin_callback"),
path(
"jobs/<slug:slug>/schedule-interviews/",
views.schedule_interviews_view,
name="schedule_interviews",
),
path(
"jobs/<slug:slug>/confirm-schedule-interviews/",
views.confirm_schedule_interviews_view,
name="confirm_schedule_interviews_view",
),
# Candidate URLs
path('candidates/', views_frontend.CandidateListView.as_view(), name='candidate_list'),
path('candidates/create/', views_frontend.CandidateCreateView.as_view(), name='candidate_create'),
path('candidates/create/<slug:slug>/', views_frontend.CandidateCreateView.as_view(), name='candidate_create_for_job'),
path('jobs/<slug:slug>/candidates/', views_frontend.JobCandidatesListView.as_view(), name='job_candidates_list'),
path('candidates/<slug:slug>/update/', views_frontend.CandidateUpdateView.as_view(), name='candidate_update'),
path('candidates/<slug:slug>/delete/', views_frontend.CandidateDeleteView.as_view(), name='candidate_delete'),
path('candidate/<slug:slug>/view/', views_frontend.candidate_detail, name='candidate_detail'),
path('candidate/<slug:slug>/resume-template/', views_frontend.candidate_resume_template_view, name='candidate_resume_template'),
path('candidate/<slug:slug>/update-stage/', views_frontend.candidate_update_stage, name='candidate_update_stage'),
path('candidate/<slug:slug>/retry-scoring/', views_frontend.retry_scoring_view, name='candidate_retry_scoring'),
path(
"candidates/", views_frontend.CandidateListView.as_view(), name="candidate_list"
),
path(
"candidates/create/",
views_frontend.CandidateCreateView.as_view(),
name="candidate_create",
),
path(
"candidates/create/<slug:slug>/",
views_frontend.CandidateCreateView.as_view(),
name="candidate_create_for_job",
),
path(
"jobs/<slug:slug>/candidates/",
views_frontend.JobCandidatesListView.as_view(),
name="job_candidates_list",
),
path(
"candidates/<slug:slug>/update/",
views_frontend.CandidateUpdateView.as_view(),
name="candidate_update",
),
path(
"candidates/<slug:slug>/delete/",
views_frontend.CandidateDeleteView.as_view(),
name="candidate_delete",
),
path(
"candidate/<slug:slug>/view/",
views_frontend.candidate_detail,
name="candidate_detail",
),
path(
"candidate/<slug:slug>/resume-template/",
views_frontend.candidate_resume_template_view,
name="candidate_resume_template",
),
path(
"candidate/<slug:slug>/update-stage/",
views_frontend.candidate_update_stage,
name="candidate_update_stage",
),
path(
"candidate/<slug:slug>/retry-scoring/",
views_frontend.retry_scoring_view,
name="candidate_retry_scoring",
),
# Training URLs
path('training/', views_frontend.TrainingListView.as_view(), name='training_list'),
path('training/create/', views_frontend.TrainingCreateView.as_view(), name='training_create'),
path('training/<slug:slug>/', views_frontend.TrainingDetailView.as_view(), name='training_detail'),
path('training/<slug:slug>/update/', views_frontend.TrainingUpdateView.as_view(), name='training_update'),
path('training/<slug:slug>/delete/', views_frontend.TrainingDeleteView.as_view(), name='training_delete'),
path("training/", views_frontend.TrainingListView.as_view(), name="training_list"),
path(
"training/create/",
views_frontend.TrainingCreateView.as_view(),
name="training_create",
),
path(
"training/<slug:slug>/",
views_frontend.TrainingDetailView.as_view(),
name="training_detail",
),
path(
"training/<slug:slug>/update/",
views_frontend.TrainingUpdateView.as_view(),
name="training_update",
),
path(
"training/<slug:slug>/delete/",
views_frontend.TrainingDeleteView.as_view(),
name="training_delete",
),
# Meeting URLs
path('meetings/', views.ZoomMeetingListView.as_view(), name='list_meetings'),
path('meetings/create-meeting/', views.ZoomMeetingCreateView.as_view(), name='create_meeting'),
path('meetings/meeting-details/<slug:slug>/', views.ZoomMeetingDetailsView.as_view(), name='meeting_details'),
path('meetings/update-meeting/<slug:slug>/', views.ZoomMeetingUpdateView.as_view(), name='update_meeting'),
path('meetings/delete-meeting/<slug:slug>/', views.ZoomMeetingDeleteView, name='delete_meeting'),
path("meetings/", views.ZoomMeetingListView.as_view(), name="list_meetings"),
path(
"meetings/create-meeting/",
views.ZoomMeetingCreateView.as_view(),
name="create_meeting",
),
path(
"meetings/meeting-details/<slug:slug>/",
views.ZoomMeetingDetailsView.as_view(),
name="meeting_details",
),
path(
"meetings/update-meeting/<slug:slug>/",
views.ZoomMeetingUpdateView.as_view(),
name="update_meeting",
),
path(
"meetings/delete-meeting/<slug:slug>/",
views.ZoomMeetingDeleteView,
name="delete_meeting",
),
# JobPosting functional views URLs (keeping for compatibility)
path('api/create/', views.create_job, name='create_job_api'),
path('api/<slug:slug>/edit/', views.edit_job, name='edit_job_api'),
path("api/create/", views.create_job, name="create_job_api"),
path("api/<slug:slug>/edit/", views.edit_job, name="edit_job_api"),
# ERP Integration URLs
path('integration/erp/', views_integration.ERPIntegrationView.as_view(), name='erp_integration'),
path('integration/erp/create-job/', views_integration.erp_create_job_view, name='erp_create_job'),
path('integration/erp/update-job/', views_integration.erp_update_job_view, name='erp_update_job'),
path('integration/erp/health/', views_integration.erp_integration_health, name='erp_integration_health'),
path(
"integration/erp/",
views_integration.ERPIntegrationView.as_view(),
name="erp_integration",
),
path(
"integration/erp/create-job/",
views_integration.erp_create_job_view,
name="erp_create_job",
),
path(
"integration/erp/update-job/",
views_integration.erp_update_job_view,
name="erp_update_job",
),
path(
"integration/erp/health/",
views_integration.erp_integration_health,
name="erp_integration_health",
),
# Form Preview URLs
# path('forms/', views.form_list, name='form_list'),
path('forms/builder/', views.form_builder, name='form_builder'),
path('forms/builder/<slug:template_slug>/', views.form_builder, name='form_builder'),
path('forms/', views.form_templates_list, name='form_templates_list'),
path('forms/create-template/', views.create_form_template, name='create_form_template'),
path('jobs/<slug:slug>/edit_linkedin_post_content/',views.edit_linkedin_post_content,name='edit_linkedin_post_content'),
path('jobs/<slug:slug>/candidate_screening_view/', views.candidate_screening_view, name='candidate_screening_view'),
path('jobs/<slug:slug>/candidate_exam_view/', views.candidate_exam_view, name='candidate_exam_view'),
path('jobs/<slug:slug>/candidate_interview_view/', views.candidate_interview_view, name='candidate_interview_view'),
path('jobs/<slug:slug>/candidate_offer_view/', views_frontend.candidate_offer_view, name='candidate_offer_view'),
path('jobs/<slug:slug>/candidate_hired_view/', views_frontend.candidate_hired_view, name='candidate_hired_view'),
path('jobs/<slug:job_slug>/export/<str:stage>/csv/', views_frontend.export_candidates_csv, name='export_candidates_csv'),
path('jobs/<slug:job_slug>/candidates/<slug:candidate_slug>/update_status/<str:stage_type>/<str:status>/', views_frontend.update_candidate_status, name='update_candidate_status'),
path("forms/builder/", views.form_builder, name="form_builder"),
path(
"forms/builder/<slug:template_slug>/", views.form_builder, name="form_builder"
),
path("forms/", views.form_templates_list, name="form_templates_list"),
path(
"forms/create-template/",
views.create_form_template,
name="create_form_template",
),
path(
"jobs/<slug:slug>/edit_linkedin_post_content/",
views.edit_linkedin_post_content,
name="edit_linkedin_post_content",
),
path(
"jobs/<slug:slug>/candidate_screening_view/",
views.candidate_screening_view,
name="candidate_screening_view",
),
path(
"jobs/<slug:slug>/candidate_exam_view/",
views.candidate_exam_view,
name="candidate_exam_view",
),
path(
"jobs/<slug:slug>/candidate_interview_view/",
views.candidate_interview_view,
name="candidate_interview_view",
),
path(
"jobs/<slug:slug>/candidate_offer_view/",
views_frontend.candidate_offer_view,
name="candidate_offer_view",
),
path(
"jobs/<slug:slug>/candidate_hired_view/",
views_frontend.candidate_hired_view,
name="candidate_hired_view",
),
path(
"jobs/<slug:job_slug>/export/<str:stage>/csv/",
views_frontend.export_candidates_csv,
name="export_candidates_csv",
),
path(
"jobs/<slug:job_slug>/candidates/<slug:candidate_slug>/update_status/<str:stage_type>/<str:status>/",
views_frontend.update_candidate_status,
name="update_candidate_status",
),
# Sync URLs
path('jobs/<slug:job_slug>/sync-hired-candidates/', views_frontend.sync_hired_candidates, name='sync_hired_candidates'),
path('sources/<int:source_id>/test-connection/', views_frontend.test_source_connection, name='test_source_connection'),
path('jobs/<slug:slug>/<int:candidate_id>/reschedule_meeting_for_candidate/<int:meeting_id>/', views.reschedule_meeting_for_candidate, name='reschedule_meeting_for_candidate'),
path('jobs/<slug:slug>/update_candidate_exam_status/', views.update_candidate_exam_status, name='update_candidate_exam_status'),
path('jobs/<slug:slug>/bulk_update_candidate_exam_status/', views.bulk_update_candidate_exam_status, name='bulk_update_candidate_exam_status'),
path('htmx/<int:pk>/candidate_criteria_view/', views.candidate_criteria_view_htmx, name='candidate_criteria_view_htmx'),
path('htmx/<slug:slug>/candidate_set_exam_date/', views.candidate_set_exam_date, name='candidate_set_exam_date'),
path('htmx/<slug:slug>/candidate_update_status/', views.candidate_update_status, name='candidate_update_status'),
path(
"jobs/<slug:job_slug>/sync-hired-candidates/",
views_frontend.sync_hired_candidates,
name="sync_hired_candidates",
),
path(
"sources/<int:source_id>/test-connection/",
views_frontend.test_source_connection,
name="test_source_connection",
),
path(
"jobs/<slug:slug>/<int:candidate_id>/reschedule_meeting_for_candidate/<int:meeting_id>/",
views.reschedule_meeting_for_candidate,
name="reschedule_meeting_for_candidate",
),
path(
"jobs/<slug:slug>/update_candidate_exam_status/",
views.update_candidate_exam_status,
name="update_candidate_exam_status",
),
path(
"jobs/<slug:slug>/bulk_update_candidate_exam_status/",
views.bulk_update_candidate_exam_status,
name="bulk_update_candidate_exam_status",
),
path(
"htmx/<int:pk>/candidate_criteria_view/",
views.candidate_criteria_view_htmx,
name="candidate_criteria_view_htmx",
),
path(
"htmx/<slug:slug>/candidate_set_exam_date/",
views.candidate_set_exam_date,
name="candidate_set_exam_date",
),
path(
"htmx/<slug:slug>/candidate_update_status/",
views.candidate_update_status,
name="candidate_update_status",
),
# path('forms/form/<slug:template_slug>/submit/', views.submit_form, name='submit_form'),
# path('forms/form/<slug:template_slug>/', views.form_wizard_view, name='form_wizard'),
path('forms/<int:template_id>/submissions/<slug:slug>/', views.form_submission_details, name='form_submission_details'),
path('forms/template/<slug:slug>/submissions/', views.form_template_submissions_list, name='form_template_submissions_list'),
path('forms/template/<int:template_id>/all-submissions/', views.form_template_all_submissions, name='form_template_all_submissions'),
path(
"forms/<int:template_id>/submissions/<slug:slug>/",
views.form_submission_details,
name="form_submission_details",
),
path(
"forms/template/<slug:slug>/submissions/",
views.form_template_submissions_list,
name="form_template_submissions_list",
),
path(
"forms/template/<int:template_id>/all-submissions/",
views.form_template_all_submissions,
name="form_template_all_submissions",
),
# path('forms/<int:form_id>/', views.form_preview, name='form_preview'),
# path('forms/<int:form_id>/submit/', views.form_submit, name='form_submit'),
# path('forms/<int:form_id>/embed/', views.form_embed, name='form_embed'),
@ -109,74 +275,188 @@ urlpatterns = [
# path('api/templates/save/', views.save_form_template, name='save_form_template'),
# path('api/templates/<slug:template_slug>/', views.load_form_template, name='load_form_template'),
# path('api/templates/<slug:template_slug>/delete/', views.delete_form_template, name='delete_form_template'),
path('jobs/<slug:slug>/calendar/', views.interview_calendar_view, name='interview_calendar'),
path('jobs/<slug:slug>/calendar/interview/<int:interview_id>/', views.interview_detail_view, name='interview_detail'),
path(
"jobs/<slug:slug>/calendar/",
views.interview_calendar_view,
name="interview_calendar",
),
path(
"jobs/<slug:slug>/calendar/interview/<int:interview_id>/",
views.interview_detail_view,
name="interview_detail",
),
# Candidate Meeting Scheduling/Rescheduling URLs
path('jobs/<slug:job_slug>/candidates/<int:candidate_pk>/schedule-meeting/', views.schedule_candidate_meeting, name='schedule_candidate_meeting'),
path('api/jobs/<slug:job_slug>/candidates/<int:candidate_pk>/schedule-meeting/', views.api_schedule_candidate_meeting, name='api_schedule_candidate_meeting'),
path('jobs/<slug:job_slug>/candidates/<int:candidate_pk>/reschedule-meeting/<int:interview_pk>/', views.reschedule_candidate_meeting, name='reschedule_candidate_meeting'),
path('api/jobs/<slug:job_slug>/candidates/<int:candidate_pk>/reschedule-meeting/<int:interview_pk>/', views.api_reschedule_candidate_meeting, name='api_reschedule_candidate_meeting'),
path(
"jobs/<slug:job_slug>/candidates/<int:candidate_pk>/schedule-meeting/",
views.schedule_candidate_meeting,
name="schedule_candidate_meeting",
),
path(
"api/jobs/<slug:job_slug>/candidates/<int:candidate_pk>/schedule-meeting/",
views.api_schedule_candidate_meeting,
name="api_schedule_candidate_meeting",
),
path(
"jobs/<slug:job_slug>/candidates/<int:candidate_pk>/reschedule-meeting/<int:interview_pk>/",
views.reschedule_candidate_meeting,
name="reschedule_candidate_meeting",
),
path(
"api/jobs/<slug:job_slug>/candidates/<int:candidate_pk>/reschedule-meeting/<int:interview_pk>/",
views.api_reschedule_candidate_meeting,
name="api_reschedule_candidate_meeting",
),
# New URL for simple page-based meeting scheduling
path('jobs/<slug:slug>/candidates/<int:candidate_pk>/schedule-meeting-page/', views.schedule_meeting_for_candidate, name='schedule_meeting_for_candidate'),
path('jobs/<slug:slug>/candidates/<int:candidate_pk>/delete_meeting_for_candidate/<int:meeting_id>/', views.delete_meeting_for_candidate, name='delete_meeting_for_candidate'),
path(
"jobs/<slug:slug>/candidates/<int:candidate_pk>/schedule-meeting-page/",
views.schedule_meeting_for_candidate,
name="schedule_meeting_for_candidate",
),
path(
"jobs/<slug:slug>/candidates/<int:candidate_pk>/delete_meeting_for_candidate/<int:meeting_id>/",
views.delete_meeting_for_candidate,
name="delete_meeting_for_candidate",
),
# users urls
path('user/<int:pk>',views.user_detail,name='user_detail'),
path('user/user_profile_image_update/<int:pk>',views.user_profile_image_update,name='user_profile_image_update'),
path('easy_logs/',views.easy_logs,name='easy_logs'),
path('settings/',views.admin_settings,name='admin_settings'),
path('staff/create',views.create_staff_user,name='create_staff_user'),
path('set_staff_password/<int:pk>/',views.set_staff_password,name='set_staff_password'),
path('account_toggle_status/<int:pk>',views.account_toggle_status,name='account_toggle_status'),
path("user/<int:pk>", views.user_detail, name="user_detail"),
path(
"user/user_profile_image_update/<int:pk>",
views.user_profile_image_update,
name="user_profile_image_update",
),
path("easy_logs/", views.easy_logs, name="easy_logs"),
path("settings/", views.admin_settings, name="admin_settings"),
path("staff/create", views.create_staff_user, name="create_staff_user"),
path(
"set_staff_password/<int:pk>/",
views.set_staff_password,
name="set_staff_password",
),
path(
"account_toggle_status/<int:pk>",
views.account_toggle_status,
name="account_toggle_status",
),
# Source URLs
path('sources/', views_source.SourceListView.as_view(), name='source_list'),
path('sources/create/', views_source.SourceCreateView.as_view(), name='source_create'),
path('sources/<int:pk>/', views_source.SourceDetailView.as_view(), name='source_detail'),
path('sources/<int:pk>/update/', views_source.SourceUpdateView.as_view(), name='source_update'),
path('sources/<int:pk>/delete/', views_source.SourceDeleteView.as_view(), name='source_delete'),
path('sources/<int:pk>/generate-keys/', views_source.generate_api_keys_view, name='generate_api_keys'),
path('sources/<int:pk>/toggle-status/', views_source.toggle_source_status_view, name='toggle_source_status'),
path('sources/api/copy-to-clipboard/', views_source.copy_to_clipboard_view, name='copy_to_clipboard'),
path("sources/", views_source.SourceListView.as_view(), name="source_list"),
path(
"sources/create/", views_source.SourceCreateView.as_view(), name="source_create"
),
path(
"sources/<int:pk>/",
views_source.SourceDetailView.as_view(),
name="source_detail",
),
path(
"sources/<int:pk>/update/",
views_source.SourceUpdateView.as_view(),
name="source_update",
),
path(
"sources/<int:pk>/delete/",
views_source.SourceDeleteView.as_view(),
name="source_delete",
),
path(
"sources/<int:pk>/generate-keys/",
views_source.generate_api_keys_view,
name="generate_api_keys",
),
path(
"sources/<int:pk>/toggle-status/",
views_source.toggle_source_status_view,
name="toggle_source_status",
),
path(
"sources/api/copy-to-clipboard/",
views_source.copy_to_clipboard_view,
name="copy_to_clipboard",
),
# Meeting Comments URLs
path('meetings/<slug:slug>/comments/add/', views.add_meeting_comment, name='add_meeting_comment'),
path('meetings/<slug:slug>/comments/<int:comment_id>/edit/', views.edit_meeting_comment, name='edit_meeting_comment'),
path('meetings/<slug:slug>/comments/<int:comment_id>/delete/', views.delete_meeting_comment, name='delete_meeting_comment'),
path('meetings/<slug:slug>/set_meeting_candidate/', views.set_meeting_candidate, name='set_meeting_candidate'),
path(
"meetings/<slug:slug>/comments/add/",
views.add_meeting_comment,
name="add_meeting_comment",
),
path(
"meetings/<slug:slug>/comments/<int:comment_id>/edit/",
views.edit_meeting_comment,
name="edit_meeting_comment",
),
path(
"meetings/<slug:slug>/comments/<int:comment_id>/delete/",
views.delete_meeting_comment,
name="delete_meeting_comment",
),
path(
"meetings/<slug:slug>/set_meeting_candidate/",
views.set_meeting_candidate,
name="set_meeting_candidate",
),
# Hiring Agency URLs
path('agencies/', views.agency_list, name='agency_list'),
path('agencies/create/', views.agency_create, name='agency_create'),
path('agencies/<slug:slug>/', views.agency_detail, name='agency_detail'),
path('agencies/<slug:slug>/update/', views.agency_update, name='agency_update'),
path('agencies/<slug:slug>/delete/', views.agency_delete, name='agency_delete'),
path('agencies/<slug:slug>/candidates/', views.agency_candidates, name='agency_candidates'),
path("agencies/", views.agency_list, name="agency_list"),
path("agencies/create/", views.agency_create, name="agency_create"),
path("agencies/<slug:slug>/", views.agency_detail, name="agency_detail"),
path("agencies/<slug:slug>/update/", views.agency_update, name="agency_update"),
path("agencies/<slug:slug>/delete/", views.agency_delete, name="agency_delete"),
path(
"agencies/<slug:slug>/candidates/",
views.agency_candidates,
name="agency_candidates",
),
# path('agencies/<slug:slug>/send-message/', views.agency_detail_send_message, name='agency_detail_send_message'),
# Agency Assignment Management URLs
path('agency-assignments/', views.agency_assignment_list, name='agency_assignment_list'),
path('agency-assignments/create/', views.agency_assignment_create, name='agency_assignment_create'),
path('agency-assignments/<slug:slug>/create/', views.agency_assignment_create, name='agency_assignment_create'),
path('agency-assignments/<slug:slug>/', views.agency_assignment_detail, name='agency_assignment_detail'),
path('agency-assignments/<slug:slug>/update/', views.agency_assignment_update, name='agency_assignment_update'),
path('agency-assignments/<slug:slug>/extend-deadline/', views.agency_assignment_extend_deadline, name='agency_assignment_extend_deadline'),
path(
"agency-assignments/",
views.agency_assignment_list,
name="agency_assignment_list",
),
path(
"agency-assignments/create/",
views.agency_assignment_create,
name="agency_assignment_create",
),
path(
"agency-assignments/<slug:slug>/create/",
views.agency_assignment_create,
name="agency_assignment_create",
),
path(
"agency-assignments/<slug:slug>/",
views.agency_assignment_detail,
name="agency_assignment_detail",
),
path(
"agency-assignments/<slug:slug>/update/",
views.agency_assignment_update,
name="agency_assignment_update",
),
path(
"agency-assignments/<slug:slug>/extend-deadline/",
views.agency_assignment_extend_deadline,
name="agency_assignment_extend_deadline",
),
# Agency Access Link URLs
path('agency-access-links/create/', views.agency_access_link_create, name='agency_access_link_create'),
path('agency-access-links/<slug:slug>/', views.agency_access_link_detail, name='agency_access_link_detail'),
path('agency-access-links/<slug:slug>/deactivate/', views.agency_access_link_deactivate, name='agency_access_link_deactivate'),
path('agency-access-links/<slug:slug>/reactivate/', views.agency_access_link_reactivate, name='agency_access_link_reactivate'),
path(
"agency-access-links/create/",
views.agency_access_link_create,
name="agency_access_link_create",
),
path(
"agency-access-links/<slug:slug>/",
views.agency_access_link_detail,
name="agency_access_link_detail",
),
path(
"agency-access-links/<slug:slug>/deactivate/",
views.agency_access_link_deactivate,
name="agency_access_link_deactivate",
),
path(
"agency-access-links/<slug:slug>/reactivate/",
views.agency_access_link_reactivate,
name="agency_access_link_reactivate",
),
# Admin Message Center URLs (messaging functionality removed)
# path('admin/messages/', views.admin_message_center, name='admin_message_center'),
# path('admin/messages/compose/', views.admin_compose_message, name='admin_compose_message'),
@ -184,35 +464,62 @@ urlpatterns = [
# path('admin/messages/<int:message_id>/reply/', views.admin_message_reply, name='admin_message_reply'),
# path('admin/messages/<int:message_id>/mark-read/', views.admin_mark_message_read, name='admin_mark_message_read'),
# path('admin/messages/<int:message_id>/delete/', views.admin_delete_message, name='admin_delete_message'),
# Agency Portal URLs (for external agencies)
path('portal/login/', views.agency_portal_login, name='agency_portal_login'),
path('portal/dashboard/', views.agency_portal_dashboard, name='agency_portal_dashboard'),
path('portal/assignment/<slug:slug>/', views.agency_portal_assignment_detail, name='agency_portal_assignment_detail'),
path('portal/assignment/<slug:slug>/submit-candidate/', views.agency_portal_submit_candidate_page, name='agency_portal_submit_candidate_page'),
path('portal/submit-candidate/', views.agency_portal_submit_candidate, name='agency_portal_submit_candidate'),
path('portal/logout/', views.agency_portal_logout, name='agency_portal_logout'),
path("portal/login/", views.agency_portal_login, name="agency_portal_login"),
path(
"portal/dashboard/",
views.agency_portal_dashboard,
name="agency_portal_dashboard",
),
# Unified Portal URLs
path("login/", views.portal_login, name="portal_login"),
path(
"candidate/dashboard/",
views.candidate_portal_dashboard,
name="candidate_portal_dashboard",
),
path(
"portal/assignment/<slug:slug>/",
views.agency_portal_assignment_detail,
name="agency_portal_assignment_detail",
),
path(
"portal/assignment/<slug:slug>/submit-candidate/",
views.agency_portal_submit_candidate_page,
name="agency_portal_submit_candidate_page",
),
path(
"portal/submit-candidate/",
views.agency_portal_submit_candidate,
name="agency_portal_submit_candidate",
),
path("portal/logout/", views.portal_logout, name="portal_logout"),
# Agency Portal Candidate Management URLs
path('portal/candidates/<int:candidate_id>/edit/', views.agency_portal_edit_candidate, name='agency_portal_edit_candidate'),
path('portal/candidates/<int:candidate_id>/delete/', views.agency_portal_delete_candidate, name='agency_portal_delete_candidate'),
path(
"portal/candidates/<int:candidate_id>/edit/",
views.agency_portal_edit_candidate,
name="agency_portal_edit_candidate",
),
path(
"portal/candidates/<int:candidate_id>/delete/",
views.agency_portal_delete_candidate,
name="agency_portal_delete_candidate",
),
# API URLs for messaging (removed)
# path('api/agency/messages/<int:message_id>/', views.api_agency_message_detail, name='api_agency_message_detail'),
# path('api/agency/messages/<int:message_id>/mark-read/', views.api_agency_mark_message_read, name='api_agency_mark_message_read'),
# API URLs for candidate management
path('api/candidate/<int:candidate_id>/', views.api_candidate_detail, name='api_candidate_detail'),
path(
"api/candidate/<int:candidate_id>/",
views.api_candidate_detail,
name="api_candidate_detail",
),
# # Admin Notification API
# path('api/admin/notification-count/', views.api_notification_count, name='admin_notification_count'),
# # Agency Notification API
# path('api/agency/notification-count/', views.api_notification_count, name='api_agency_notification_count'),
# # SSE Notification Stream
# path('api/notifications/stream/', views.notification_stream, name='notification_stream'),
# # Notification URLs
# path('notifications/', views.notification_list, name='notification_list'),
# path('notifications/<int:notification_id>/', views.notification_detail, name='notification_detail'),
@ -221,15 +528,36 @@ urlpatterns = [
# path('notifications/<int:notification_id>/delete/', views.notification_delete, name='notification_delete'),
# path('notifications/mark-all-read/', views.notification_mark_all_read, name='notification_mark_all_read'),
# path('api/notification-count/', views.api_notification_count, name='api_notification_count'),
#participants urls
path('participants/', views_frontend.ParticipantsListView.as_view(), name='participants_list'),
path('participants/create/', views_frontend.ParticipantsCreateView.as_view(), name='participants_create'),
path('participants/<slug:slug>/', views_frontend.ParticipantsDetailView.as_view(), name='participants_detail'),
path('participants/<slug:slug>/update/', views_frontend.ParticipantsUpdateView.as_view(), name='participants_update'),
path('participants/<slug:slug>/delete/', views_frontend.ParticipantsDeleteView.as_view(), name='participants_delete'),
# participants urls
path(
"participants/",
views_frontend.ParticipantsListView.as_view(),
name="participants_list",
),
path(
"participants/create/",
views_frontend.ParticipantsCreateView.as_view(),
name="participants_create",
),
path(
"participants/<slug:slug>/",
views_frontend.ParticipantsDetailView.as_view(),
name="participants_detail",
),
path(
"participants/<slug:slug>/update/",
views_frontend.ParticipantsUpdateView.as_view(),
name="participants_update",
),
path(
"participants/<slug:slug>/delete/",
views_frontend.ParticipantsDeleteView.as_view(),
name="participants_delete",
),
# Email composition URLs
path('jobs/<slug:job_slug>/candidates/<slug:candidate_slug>/compose-email/', views.compose_candidate_email, name='compose_candidate_email'),
path(
"jobs/<slug:job_slug>/candidates/<slug:candidate_slug>/compose-email/",
views.compose_candidate_email,
name="compose_candidate_email",
),
]

File diff suppressed because it is too large Load Diff

View File

@ -54,11 +54,18 @@
<div style="background-color: #00636e;">
<nav class="navbar navbar-expand-lg navbar-dark sticky-top">
<div class="container-fluid" style="max-width: 1600px;">
{% if request.user.user_type == 'candidate' %}
<a class="navbar-brand text-white" href="{% url 'candidate_portal_dashboard' %}" aria-label="Applicant Dashboard">
<img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" style="width: 40px; height: 40px;">
<span class="ms-3 d-none d-md-inline fw-semibold">{% trans "Applicant Portal" %}</span>
</a>
{% elif request.user.user_type == 'agency' %}
<a class="navbar-brand text-white" href="{% url 'agency_portal_dashboard' %}" aria-label="Agency Dashboard">
<img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" style="width: 40px; height: 40px;">
<span class="ms-3 d-none d-md-inline fw-semibold">{% trans "Agency Portal" %}</span>
</a>
{% endif %}
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#agencyNavbar"
aria-controls="agencyNavbar" aria-expanded="false" aria-label="{% trans 'Toggle navigation' %}">
@ -97,7 +104,7 @@
</li>
<li class="nav-item ms-3">
<form method="post" action="{% url 'agency_portal_logout' %}" class="d-inline">
<form method="post" action="{% url 'portal_logout' %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-outline-light btn-sm">
<i class="fas fa-sign-out-alt me-1"></i> {% trans "Logout" %}
@ -134,7 +141,11 @@
{% trans "All rights reserved." %}
</p>
<p class="mb-0 text-white-50">
{% trans "Agency Portal" %}
{% if request.user.user_type == 'candidate' %}
{% trans "Candidate Portal" %}
{% elif request.user.user_type == 'agency' %}
{% trans "Agency Portal" %}
{% endif %}
</p>
</div>
</div>

View File

@ -1,4 +1,4 @@
{% extends 'agency_base.html' %}
{% extends 'portal_base.html' %}
{% load static i18n %}
{% block title %}{% trans "Access Link Details" %} - ATS{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'agency_base.html' %}
{% extends 'portal_base.html' %}
{% load static i18n %}
{% block title %}{{ assignment.job.title }} - {{ assignment.agency.name }} - Agency Portal{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'agency_base.html' %}
{% extends 'portal_base.html' %}
{% load static i18n %}
{% block title %}{% trans "Agency Dashboard" %} - ATS{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'agency_base.html' %}
{% extends 'portal_base.html' %}
{% load static i18n %}
{% block title %}{% trans "Agency Portal Login" %} - ATS{% endblock %}

View File

@ -1,4 +1,4 @@
{% extends 'agency_base.html' %}
{% extends 'portal_base.html' %}
{% load static i18n %}
{% block title %}{% trans "Submit Candidate" %} - {{ assignment.job.title }} - Agency Portal{% endblock %}

View File

@ -0,0 +1,165 @@
{% extends 'portal_base.html' %}
{% load static i18n %}
{% block title %}{% trans "Candidate Dashboard" %} - ATS{% endblock %}
{% block content %}
<div class="container-fluid">
<!-- Dashboard Header -->
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-6">
<h4 class="mb-1">
<i class="fas fa-user-tie me-2 text-primary"></i>
{% trans "Welcome" %} {{ candidate.first_name }}
</h4>
<p class="text-muted mb-0">
{% trans "Manage your applications and profile" %}
</p>
</div>
{% comment %} <div class="col-md-6 text-md-end">
<span class="badge bg-success fs-6">
<i class="fas fa-circle me-1"></i>
{% trans "Active" %}
</span>
</div> {% endcomment %}
</div>
</div>
</div>
</div>
</div>
<!-- Quick Stats -->
<div class="row mb-4">
<div class="col-md-4 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="mb-3">
<i class="fas fa-briefcase fa-2x text-primary"></i>
</div>
<h5 class="card-title">{{ candidate.job.title|default:"No Job" }}</h5>
<p class="text-muted small mb-0">
{% trans "Applied Position" %}
</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="mb-3">
<i class="fas fa-tasks fa-2x text-info"></i>
</div>
<h5 class="card-title">{{ candidate.stage|default:"Applied" }}</h5>
<p class="text-muted small mb-0">
{% trans "Current Stage" %}
</p>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body text-center">
<div class="mb-3">
<i class="fas fa-calendar fa-2x text-success"></i>
</div>
<h5 class="card-title">{{ candidate.created_at|date:"M d, Y" }}</h5>
<p class="text-muted small mb-0">
{% trans "Application Date" %}
</p>
</div>
</div>
</div>
</div>
<!-- Profile Information -->
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">
<i class="fas fa-user me-2"></i>
{% trans "Profile Information" %}
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">
{% trans "Full Name" %}
</label>
<p class="form-control-plaintext">
{{ candidate.first_name }} {{ candidate.last_name }}
</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">
{% trans "Email Address" %}
</label>
<p class="form-control-plaintext">
{{ candidate.email }}
</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">
{% trans "Phone Number" %}
</label>
<p class="form-control-plaintext">
{{ candidate.phone|default:"Not provided" }}
</p>
</div>
<div class="col-md-6 mb-3">
<label class="form-label fw-bold">
{% trans "Resume" %}
</label>
<p class="form-control-plaintext">
{% if candidate.resume %}
<a href="{{ candidate.resume.url }}" class="btn btn-sm btn-outline-primary" target="_blank">
<i class="fas fa-download me-1"></i>
{% trans "Download Resume" %}
</a>
{% else %}
<span class="text-muted">{% trans "No resume uploaded" %}</span>
{% endif %}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Actions -->
<div class="row">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="row text-center">
<div class="col-md-4 mb-3">
<button class="btn btn-outline-primary w-100">
<i class="fas fa-edit me-2"></i>
{% trans "Edit Profile" %}
</button>
</div>
<div class="col-md-4 mb-3">
<button class="btn btn-outline-success w-100">
<i class="fas fa-file-upload me-2"></i>
{% trans "Update Resume" %}
</button>
</div>
<div class="col-md-4 mb-3">
<button class="btn btn-outline-info w-100">
<i class="fas fa-eye me-2"></i>
{% trans "View Application" %}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,295 @@
{% extends 'portal_base.html' %}
{% load static i18n %}
{% block title %}{% trans "Portal Login" %} - ATS{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
body {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
min-height: 100vh;
}
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 0;
}
.login-card {
background: white;
border-radius: 1rem;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
border: none;
max-width: 500px;
width: 100%;
margin: 0 1rem;
}
.login-header {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
color: white;
padding: 2rem;
border-radius: 1rem 1rem 0 0;
text-align: center;
}
.login-body {
padding: 2.5rem;
}
.form-control:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
}
.btn-login {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
border: none;
color: white;
font-weight: 600;
padding: 0.75rem 2rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 99, 110, 0.3);
}
.input-group-text {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.user-type-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1.5rem;
}
.user-type-card {
border: 2px solid #e9ecef;
border-radius: 0.5rem;
padding: 1rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.user-type-card:hover {
border-color: var(--kaauh-teal);
background-color: #f8f9fa;
}
.user-type-card.selected {
border-color: var(--kaauh-teal);
background-color: rgba(0, 99, 110, 0.1);
}
.user-type-icon {
font-size: 2rem;
color: var(--kaauh-teal);
margin-bottom: 0.5rem;
}
.alert {
border-radius: 0.5rem;
border: none;
}
</style>
{% endblock %}
{% block content %}
<div class="login-container">
<div class="login-card">
<!-- Login Header -->
<div class="login-header">
<div class="mb-3">
<i class="fas fa-users fa-3x"></i>
</div>
<h3 class="mb-2">{% trans "Portal Login" %}</h3>
<p class="mb-0 opacity-75">
{% trans "Access your personalized dashboard" %}
</p>
</div>
<!-- Login Body -->
<div class="login-body">
<!-- Login Form -->
<form method="post" novalidate>
{% csrf_token %}
<!-- Email Field -->
<div class="mb-3">
<label for="{{ form.email.id_for_label }}" class="form-label fw-bold">
<i class="fas fa-envelope me-2"></i>
{% trans "Email Address" %}
</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-envelope"></i>
</span>
{{ form.email }}
</div>
{% if form.email.errors %}
<div class="text-danger small mt-1">
{% for error in form.email.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<!-- Password Field -->
<div class="mb-4">
<label for="{{ form.password.id_for_label }}" class="form-label fw-bold">
<i class="fas fa-lock me-2"></i>
{% trans "Password" %}
</label>
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-key"></i>
</span>
{{ form.password }}
</div>
{% if form.password.errors %}
<div class="text-danger small mt-1">
{% for error in form.password.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<!-- User Type Selection -->
<div class="mb-4">
<label for="{{ form.user_type.id_for_label }}" class="form-label fw-bold">
<i class="fas fa-user-tag me-2"></i>
{% trans "Select User Type" %}
</label>
{{ form.user_type }}
{% if form.user_type.errors %}
<div class="text-danger small mt-1">
{% for error in form.user_type.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
<!-- Submit Button -->
<div class="d-grid">
<button type="submit" class="btn btn-login btn-lg">
<i class="fas fa-sign-in-alt me-2"></i>
{% trans "Login" %}
</button>
</div>
</form>
<!-- Help Links -->
<div class="text-center mt-4">
<small class="text-muted">
{% trans "Need help?" %}
<a href="#" class="text-decoration-none">
{% trans "Contact Support" %}
</a>
</small>
</div>
</div>
</div>
</div>
{% endblock %}
{% block customJS %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Focus on user type field
const userTypeField = document.getElementById('{{ form.user_type.id_for_label }}');
if (userTypeField) {
userTypeField.focus();
}
// Form validation
const form = document.querySelector('form');
const emailField = document.getElementById('{{ form.email.id_for_label }}');
const passwordField = document.getElementById('{{ form.password.id_for_label }}');
if (form) {
form.addEventListener('submit', function(e) {
const userType = userTypeField.value;
const email = emailField.value.trim();
const password = passwordField.value.trim();
if (!userType) {
e.preventDefault();
showError('{% trans "Please select a user type." %}');
userTypeField.focus();
return;
}
if (!email) {
e.preventDefault();
showError('{% trans "Please enter your email address." %}');
emailField.focus();
return;
}
if (!password) {
e.preventDefault();
showError('{% trans "Please enter your password." %}');
passwordField.focus();
return;
}
// Basic email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
e.preventDefault();
showError('{% trans "Please enter a valid email address." %}');
emailField.focus();
return;
}
});
}
function showError(message) {
// Remove existing alerts
const existingAlerts = document.querySelectorAll('.alert-danger');
existingAlerts.forEach(alert => alert.remove());
// Create new alert
const alertDiv = document.createElement('div');
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// Insert at the top of login body
const loginBody = document.querySelector('.login-body');
loginBody.insertBefore(alertDiv, loginBody.firstChild);
// Auto-dismiss after 5 seconds
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.remove();
}
}, 5000);
}
});
</script>
{% endblock %}