Merge branch 'update1'
This commit is contained in:
commit
9497bf102e
Binary file not shown.
@ -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
|
||||
@ -22,7 +23,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
|
||||
@ -32,104 +33,102 @@ 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 = '/'
|
||||
|
||||
|
||||
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
|
||||
@ -157,6 +156,23 @@ DATABASES = {
|
||||
|
||||
|
||||
|
||||
# AUTH_PASSWORD_VALIDATORS = [
|
||||
# {
|
||||
# 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
# },
|
||||
# {
|
||||
# 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
# },
|
||||
# {
|
||||
# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
# },
|
||||
# {
|
||||
# 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
# },
|
||||
# ]
|
||||
|
||||
# settings.py
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
@ -173,21 +189,20 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
]
|
||||
|
||||
|
||||
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_EMAIL_VERIFICATION = 'none'
|
||||
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
||||
|
||||
|
||||
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"
|
||||
@ -195,29 +210,29 @@ CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
|
||||
# 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
|
||||
|
||||
@ -226,36 +241,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, 'static/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)
|
||||
@ -264,145 +278,199 @@ 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"
|
||||
|
||||
|
||||
|
||||
@ -411,3 +479,7 @@ from django.contrib.messages import constants as messages
|
||||
MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger',
|
||||
}
|
||||
)
|
||||
|
||||
# Custom User Model
|
||||
AUTH_USER_MODEL = "recruitment.CustomUser"
|
||||
|
||||
@ -26,6 +26,7 @@ urlpatterns = [
|
||||
path('application/<slug:template_slug>/', views.application_submit_form, name='application_submit_form'),
|
||||
path('application/<slug:template_slug>/submit/', views.application_submit, name='application_submit'),
|
||||
path('application/<slug:slug>/apply/', views.application_detail, name='application_detail'),
|
||||
path('application/<slug:slug>/signup/', views.candidate_signup, name='candidate_signup'),
|
||||
path('application/<slug:slug>/success/', views.application_success, name='application_success'),
|
||||
path('application/applicant/profile', views.applicant_profile, name='applicant_profile'),
|
||||
|
||||
|
||||
119
apply_all_translations.py
Normal file
119
apply_all_translations.py
Normal file
@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to apply all batch translations to the main django.po file
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
def apply_all_translations():
|
||||
"""
|
||||
Apply all translations to the main django.po file
|
||||
"""
|
||||
# All translations from batches 02-35
|
||||
translations = {
|
||||
"The date and time this notification is scheduled to be sent.": "التاريخ والوقت المحدد لإرسال هذا الإشعار.",
|
||||
"Send Attempts": "محاولات الإرسال",
|
||||
"Failed to start the job posting process. Please try again.": "فشل في بدء عملية نشر الوظيفة. يرجى المحاولة مرة أخرى.",
|
||||
"You don't have permission to view this page.": "ليس لديك إذن لعرض هذه الصفحة.",
|
||||
"Account Inactive": "الحساب غير نشط",
|
||||
"Princess Nourah bint Abdulrahman University": "جامعة الأميرة نورة بنت عبدالرحمن",
|
||||
"Manage your personal details and security.": "إدارة تفاصيلك الشخصية والأمان.",
|
||||
"Primary": "أساسي",
|
||||
"Verified": "موثق",
|
||||
"Unverified": "غير موثق",
|
||||
"Make Primary": "جعل أساسي",
|
||||
"Remove": "إزالة",
|
||||
"Add Email Address": "إضافة عنوان بريد إلكتروني",
|
||||
"Hello,": "مرحباً،",
|
||||
"Confirm My KAAUH ATS Email": "تأكيد بريدي الإلكتروني في نظام توظيف جامعة نورة",
|
||||
"Alternatively, copy and paste this link into your browser:": "بدلاً من ذلك، انسخ والصق هذا الرابط في متصفحك:",
|
||||
"Password Reset Request": "طلب إعادة تعيين كلمة المرور",
|
||||
"Click Here to Reset Your Password": "اضغط هنا لإعادة تعيين كلمة المرور",
|
||||
"This link is only valid for a limited time.": "هذا الرابط صالح لفترة محدودة فقط.",
|
||||
"Thank you,": "شكراً لك،",
|
||||
"KAAUH ATS Team": "فريق نظام توظيف جامعة نورة",
|
||||
"Confirm Email Address": "تأكيد عنوان البريد الإلكتروني",
|
||||
"Account Verification": "التحقق من الحساب",
|
||||
"Verify your email to secure your account and unlock full features.": "تحقق من بريدك الإلكتروني لتأمين حسابك وإلغاء قفل جميع الميزات.",
|
||||
"Confirm Your Email Address": "تأكيد عنوان بريدك الإلكتروني",
|
||||
"Verification Failed": "فشل التحقق",
|
||||
"The email confirmation link is expired or invalid.": "رابط تأكيد البريد الإلكتروني منتهي الصلاحية أو غير صالح.",
|
||||
"Keep me signed in": "ابق مسجلاً للدخول",
|
||||
"Return to Profile": "العودة إلى الملف الشخصي",
|
||||
"Enter your e-mail address to reset your password.": "أدخل عنوان بريدك الإلكتروني لإعادة تعيين كلمة المرور.",
|
||||
"Remember your password?": "تتذكر كلمة المرور؟",
|
||||
"Log In": "تسجيل الدخول",
|
||||
"Password Reset Sent": "تم إرسال إعادة تعيين كلمة المرور",
|
||||
"Return to Login": "العودة إلى تسجيل الدخول",
|
||||
"Please enter your new password below.": "يرجى إدخال كلمة المرور الجديدة أدناه.",
|
||||
"Logout": "تسجيل الخروج",
|
||||
"Yes": "نعم",
|
||||
"No": "لا",
|
||||
"Success": "نجح",
|
||||
"Login": "تسجيل الدخول",
|
||||
"Link": "ربط",
|
||||
"Clear": "مسح",
|
||||
"All": "الكل",
|
||||
"Comments": "التعليقات",
|
||||
"Save": "حفظ",
|
||||
"Notes": "ملاحظات",
|
||||
"New": "جديد",
|
||||
"Users": "المستخدمون",
|
||||
"Filter": "تصفية",
|
||||
"Home": "الرئيسية",
|
||||
"Username": "اسم المستخدم",
|
||||
"Modified": "تم التعديل",
|
||||
"Unlink": "فك الربط",
|
||||
"Group": "تجميع",
|
||||
"Export": "تصدير",
|
||||
"Import": "استيراد",
|
||||
"None": "لا شيء",
|
||||
"Add": "إضافة",
|
||||
"True": "صحيح",
|
||||
"False": "خطأ",
|
||||
}
|
||||
|
||||
main_po_file = "locale/ar/LC_MESSAGES/django.po"
|
||||
|
||||
# Read the main django.po file
|
||||
with open(main_po_file, 'r', encoding='utf-8') as f:
|
||||
main_content = f.read()
|
||||
|
||||
# Apply translations to main file
|
||||
updated_content = main_content
|
||||
applied_count = 0
|
||||
|
||||
for english, arabic in translations.items():
|
||||
# Pattern to find msgid followed by empty msgstr
|
||||
pattern = rf'(msgid "{re.escape(english)}"\s*\nmsgstr) ""'
|
||||
replacement = rf'\1 "{arabic}"'
|
||||
|
||||
if re.search(pattern, updated_content):
|
||||
updated_content = re.sub(pattern, replacement, updated_content)
|
||||
applied_count += 1
|
||||
print(f"✓ Applied: '{english}' -> '{arabic}'")
|
||||
else:
|
||||
print(f"✗ Not found: '{english}'")
|
||||
|
||||
# Write updated content back to main file
|
||||
with open(main_po_file, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
|
||||
print(f"\nApplied {applied_count} translations to {main_po_file}")
|
||||
return applied_count
|
||||
|
||||
def main():
|
||||
"""Main function to apply all translations"""
|
||||
print("Applying all batch translations to main django.po file...")
|
||||
applied_count = apply_all_translations()
|
||||
|
||||
if applied_count > 0:
|
||||
print(f"\n✅ Successfully applied {applied_count} translations!")
|
||||
print("Next steps:")
|
||||
print("1. Run: python manage.py compilemessages")
|
||||
print("2. Test the translations in the application")
|
||||
else:
|
||||
print("\n❌ No translations were applied.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
85
apply_batch_translations.py
Normal file
85
apply_batch_translations.py
Normal file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to apply completed batch translations back to the main django.po file
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
def apply_batch_to_main_po(batch_file_path, main_po_path):
|
||||
"""
|
||||
Apply translations from a completed batch file to the main django.po file
|
||||
"""
|
||||
# Read the completed batch file
|
||||
with open(batch_file_path, 'r', encoding='utf-8') as f:
|
||||
batch_content = f.read()
|
||||
|
||||
# Extract translations from batch file
|
||||
translations = {}
|
||||
current_msgid = None
|
||||
|
||||
lines = batch_content.split('\n')
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if line.startswith('msgid: "'):
|
||||
current_msgid = line[7:-1] # Extract msgid content
|
||||
# Look for the Arabic translation line (next non-empty line after "Arabic Translation:")
|
||||
j = i + 1
|
||||
while j < len(lines) and not lines[j].strip().startswith('msgstr: "'):
|
||||
j += 1
|
||||
if j < len(lines) and lines[j].strip().startswith('msgstr: "'):
|
||||
translation = lines[j].strip()[8:-1] # Extract msgstr content
|
||||
if translation and current_msgid and translation != '""': # Only add non-empty translations
|
||||
translations[current_msgid] = translation
|
||||
print(f"Extracted: '{current_msgid}' -> '{translation}'")
|
||||
current_msgid = None
|
||||
i += 1
|
||||
|
||||
print(f"Found {len(translations)} translations to apply")
|
||||
|
||||
# Read the main django.po file
|
||||
with open(main_po_path, 'r', encoding='utf-8') as f:
|
||||
main_content = f.read()
|
||||
|
||||
# Apply translations to main file
|
||||
updated_content = main_content
|
||||
applied_count = 0
|
||||
|
||||
for english, arabic in translations.items():
|
||||
# Pattern to find msgid followed by empty msgstr
|
||||
pattern = rf'(msgid "{re.escape(english)}"\s*\nmsgstr) ""'
|
||||
replacement = rf'\1 "{arabic}"'
|
||||
|
||||
if re.search(pattern, updated_content):
|
||||
updated_content = re.sub(pattern, replacement, updated_content)
|
||||
applied_count += 1
|
||||
print(f"✓ Applied: '{english}' -> '{arabic}'")
|
||||
else:
|
||||
print(f"✗ Not found: '{english}'")
|
||||
|
||||
# Write updated content back to main file
|
||||
with open(main_po_path, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
|
||||
print(f"\nApplied {applied_count} translations to {main_po_path}")
|
||||
return applied_count
|
||||
|
||||
def main():
|
||||
"""Main function to apply batch 01 translations"""
|
||||
batch_file = "translation_batch_01_completed.txt"
|
||||
main_po_file = "locale/ar/LC_MESSAGES/django.po"
|
||||
|
||||
print("Applying Batch 01 translations to main django.po file...")
|
||||
applied_count = apply_batch_to_main_po(batch_file, main_po_file)
|
||||
|
||||
if applied_count > 0:
|
||||
print(f"\n✅ Successfully applied {applied_count} translations!")
|
||||
print("Next steps:")
|
||||
print("1. Run: python manage.py compilemessages")
|
||||
print("2. Test the translations in the application")
|
||||
print("3. Continue with the next batch")
|
||||
else:
|
||||
print("\n❌ No translations were applied. Please check the batch file format.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
84
apply_translations_direct.py
Normal file
84
apply_translations_direct.py
Normal file
@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to directly apply Arabic translations to the main django.po file
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
def apply_translations_direct():
|
||||
"""
|
||||
Apply translations directly to the main django.po file
|
||||
"""
|
||||
# Arabic translations for batch 01
|
||||
translations = {
|
||||
"Website": "الموقع الإلكتروني",
|
||||
"Admin Notes": "ملاحظات المسؤول",
|
||||
"Save Assignment": "حفظ التكليف",
|
||||
"Assignment": "التكليف",
|
||||
"Expires At": "ينتهي في",
|
||||
"Access Token": "رمز الوصول",
|
||||
"Subject": "الموضوع",
|
||||
"Recipients": "المستلمون",
|
||||
"Internal staff involved in the recruitment process for this job": "الموظفون الداخليون المشاركون في عملية التوظيف لهذه الوظيفة",
|
||||
"External Participant": "مشارك خارجي",
|
||||
"External participants involved in the recruitment process for this job": "المشاركون الخارجيون المشاركون في عملية التوظيف لهذه الوظيفة",
|
||||
"Reason for canceling the job posting": "سبب إلغاء نشر الوظيفة",
|
||||
"Name of person who cancelled this job": "اسم الشخص الذي ألغى هذه الوظيفة",
|
||||
"Hired": "تم التوظيف",
|
||||
"Author": "المؤلف",
|
||||
"Endpoint URL for sending candidate data (for outbound sync)": "عنوان URL لنقطة النهاية لإرسال بيانات المرشح (للمزامنة الصادرة)",
|
||||
"HTTP method for outbound sync requests": "طريقة HTTP لطلبات المزامنة الصادرة",
|
||||
"HTTP method for connection testing": "طريقة HTTP لاختبار الاتصال",
|
||||
"Custom Headers": "رؤوس مخصصة",
|
||||
"JSON object with custom HTTP headers for sync requests": "كائن JSON يحتوي على رؤوس HTTP مخصصة لطلبات المزامنة",
|
||||
"Supports Outbound Sync": "يدعم المزامنة الصادرة",
|
||||
"Whether this source supports receiving candidate data from ATS": "ما إذا كان هذا المصدر يدعم استقبال بيانات المرشح من نظام تتبع المتقدمين",
|
||||
"Expired": "منتهي الصلاحية",
|
||||
"Maximum candidates agency can submit for this job": "الحد الأقصى للمرشحين الذين يمكن للوكالة تقديمهم لهذه الوظيفة"
|
||||
}
|
||||
|
||||
main_po_file = "locale/ar/LC_MESSAGES/django.po"
|
||||
|
||||
# Read the main django.po file
|
||||
with open(main_po_file, 'r', encoding='utf-8') as f:
|
||||
main_content = f.read()
|
||||
|
||||
# Apply translations to main file
|
||||
updated_content = main_content
|
||||
applied_count = 0
|
||||
|
||||
for english, arabic in translations.items():
|
||||
# Pattern to find msgid followed by empty msgstr
|
||||
pattern = rf'(msgid "{re.escape(english)}"\s*\nmsgstr) ""'
|
||||
replacement = rf'\1 "{arabic}"'
|
||||
|
||||
if re.search(pattern, updated_content):
|
||||
updated_content = re.sub(pattern, replacement, updated_content)
|
||||
applied_count += 1
|
||||
print(f"✓ Applied: '{english}' -> '{arabic}'")
|
||||
else:
|
||||
print(f"✗ Not found: '{english}'")
|
||||
|
||||
# Write updated content back to main file
|
||||
with open(main_po_file, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
|
||||
print(f"\nApplied {applied_count} translations to {main_po_file}")
|
||||
return applied_count
|
||||
|
||||
def main():
|
||||
"""Main function to apply batch 01 translations"""
|
||||
print("Applying Batch 01 translations directly to main django.po file...")
|
||||
applied_count = apply_translations_direct()
|
||||
|
||||
if applied_count > 0:
|
||||
print(f"\n✅ Successfully applied {applied_count} translations!")
|
||||
print("Next steps:")
|
||||
print("1. Run: python manage.py compilemessages")
|
||||
print("2. Test the translations in the application")
|
||||
print("3. Continue with the next batch")
|
||||
else:
|
||||
print("\n❌ No translations were applied.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
212
comprehensive_translation_merger.py
Normal file
212
comprehensive_translation_merger.py
Normal file
@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive Translation Merger
|
||||
Merges all 35 translation batch files into the main django.po file
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
def parse_batch_file(filename):
|
||||
"""Parse a batch file and extract English-Arabic translation pairs"""
|
||||
translations = {}
|
||||
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Pattern to match the format in completed batch files:
|
||||
# msgid: "English text"
|
||||
# msgstr: ""
|
||||
# Arabic Translation:
|
||||
# msgstr: "Arabic text"
|
||||
pattern = r'msgid:\s*"([^"]*?)"\s*\nmsgstr:\s*""\s*\nArabic Translation:\s*\nmsgstr:\s*"([^"]*?)"'
|
||||
|
||||
matches = re.findall(pattern, content, re.MULTILINE | re.DOTALL)
|
||||
|
||||
for english, arabic in matches:
|
||||
english = english.strip()
|
||||
arabic = arabic.strip()
|
||||
|
||||
# Skip empty or invalid entries
|
||||
if english and arabic and len(english) > 1 and len(arabic) > 1:
|
||||
translations[english] = arabic
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error parsing {filename}: {e}")
|
||||
|
||||
return translations
|
||||
|
||||
def parse_current_django_po():
|
||||
"""Parse the current django.po file and extract existing translations"""
|
||||
po_file = 'locale/ar/LC_MESSAGES/django.po'
|
||||
|
||||
if not os.path.exists(po_file):
|
||||
return {}, []
|
||||
|
||||
with open(po_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Extract msgid/msgstr pairs
|
||||
pattern = r'msgid\s+"([^"]*?)"\s*\nmsgstr\s+"([^"]*?)"'
|
||||
matches = re.findall(pattern, content)
|
||||
|
||||
existing_translations = {}
|
||||
for msgid, msgstr in matches:
|
||||
existing_translations[msgid] = msgstr
|
||||
|
||||
# Extract the header and footer
|
||||
parts = re.split(r'(msgid\s+"[^"]*?"\s*\nmsgstr\s+"[^"]*?")', content)
|
||||
|
||||
return existing_translations, parts
|
||||
|
||||
def create_comprehensive_translation_dict():
|
||||
"""Create a comprehensive translation dictionary from all batch files"""
|
||||
all_translations = {}
|
||||
|
||||
# Get all batch files
|
||||
batch_files = glob.glob('translation_batch_*.txt')
|
||||
batch_files.sort() # Process in order
|
||||
|
||||
print(f"Found {len(batch_files)} batch files")
|
||||
|
||||
for batch_file in batch_files:
|
||||
print(f"Processing {batch_file}...")
|
||||
batch_translations = parse_batch_file(batch_file)
|
||||
|
||||
for english, arabic in batch_translations.items():
|
||||
if english not in all_translations:
|
||||
all_translations[english] = arabic
|
||||
else:
|
||||
# Keep the first translation found, but note duplicates
|
||||
print(f" Duplicate found: '{english}' -> '{arabic}' (existing: '{all_translations[english]}')")
|
||||
|
||||
print(f"Total unique translations: {len(all_translations)}")
|
||||
return all_translations
|
||||
|
||||
def update_django_po(translations):
|
||||
"""Update the django.po file with new translations"""
|
||||
po_file = 'locale/ar/LC_MESSAGES/django.po'
|
||||
|
||||
# Read current file
|
||||
with open(po_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
lines = content.split('\n')
|
||||
new_lines = []
|
||||
i = 0
|
||||
updated_count = 0
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
if line.startswith('msgid '):
|
||||
# Extract the msgid content
|
||||
msgid_match = re.match(r'msgid\s+"([^"]*)"', line)
|
||||
if msgid_match:
|
||||
msgid = msgid_match.group(1)
|
||||
|
||||
# Look for the corresponding msgstr
|
||||
if i + 1 < len(lines) and lines[i + 1].startswith('msgstr '):
|
||||
msgstr_match = re.match(r'msgstr\s+"([^"]*)"', lines[i + 1])
|
||||
current_msgstr = msgstr_match.group(1) if msgstr_match else ""
|
||||
|
||||
# Check if we have a translation for this msgid
|
||||
if msgid in translations and (not current_msgstr or current_msgstr == ""):
|
||||
# Update the translation
|
||||
new_translation = translations[msgid]
|
||||
new_lines.append(line) # Keep msgid line
|
||||
new_lines.append(f'msgstr "{new_translation}"') # Update msgstr
|
||||
updated_count += 1
|
||||
print(f" Updated: '{msgid}' -> '{new_translation}'")
|
||||
else:
|
||||
# Keep existing translation
|
||||
new_lines.append(line)
|
||||
new_lines.append(lines[i + 1])
|
||||
|
||||
i += 2 # Skip both msgid and msgstr lines
|
||||
continue
|
||||
|
||||
new_lines.append(line)
|
||||
i += 1
|
||||
|
||||
# Write updated content
|
||||
new_content = '\n'.join(new_lines)
|
||||
|
||||
# Create backup
|
||||
backup_file = po_file + '.backup'
|
||||
with open(backup_file, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
print(f"Created backup: {backup_file}")
|
||||
|
||||
# Write updated file
|
||||
with open(po_file, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print(f"Updated {updated_count} translations in {po_file}")
|
||||
return updated_count
|
||||
|
||||
def add_missing_translations(translations):
|
||||
"""Add completely missing translations to django.po"""
|
||||
po_file = 'locale/ar/LC_MESSAGES/django.po'
|
||||
|
||||
with open(po_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
existing_translations, _ = parse_current_django_po()
|
||||
|
||||
# Find translations that don't exist in the .po file at all
|
||||
missing_translations = {}
|
||||
for english, arabic in translations.items():
|
||||
if english not in existing_translations:
|
||||
missing_translations[english] = arabic
|
||||
|
||||
if missing_translations:
|
||||
print(f"Found {len(missing_translations)} completely missing translations")
|
||||
|
||||
# Add missing translations to the end of the file
|
||||
with open(po_file, 'a', encoding='utf-8') as f:
|
||||
f.write('\n\n# Auto-added missing translations\n')
|
||||
for english, arabic in missing_translations.items():
|
||||
f.write(f'\nmsgid "{english}"\n')
|
||||
f.write(f'msgstr "{arabic}"\n')
|
||||
|
||||
print(f"Added {len(missing_translations)} missing translations")
|
||||
else:
|
||||
print("No missing translations found")
|
||||
|
||||
return len(missing_translations)
|
||||
|
||||
def main():
|
||||
"""Main function to merge all translations"""
|
||||
print("🚀 Starting Comprehensive Translation Merger")
|
||||
print("=" * 50)
|
||||
|
||||
# Step 1: Create comprehensive translation dictionary
|
||||
print("\n📚 Step 1: Building comprehensive translation dictionary...")
|
||||
translations = create_comprehensive_translation_dict()
|
||||
|
||||
# Step 2: Update existing translations in django.po
|
||||
print("\n🔄 Step 2: Updating existing translations in django.po...")
|
||||
updated_count = update_django_po(translations)
|
||||
|
||||
# Step 3: Add completely missing translations
|
||||
print("\n➕ Step 3: Adding missing translations...")
|
||||
added_count = add_missing_translations(translations)
|
||||
|
||||
# Step 4: Summary
|
||||
print("\n📊 Summary:")
|
||||
print(f" Total translations available: {len(translations)}")
|
||||
print(f" Updated existing translations: {updated_count}")
|
||||
print(f" Added missing translations: {added_count}")
|
||||
print(f" Total translations processed: {updated_count + added_count}")
|
||||
|
||||
print("\n✅ Translation merge completed!")
|
||||
print("\n📝 Next steps:")
|
||||
print(" 1. Run: python manage.py compilemessages")
|
||||
print(" 2. Test Arabic translations in the browser")
|
||||
print(" 3. Verify language switching functionality")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
48
empty_translations_summary.txt
Normal file
48
empty_translations_summary.txt
Normal file
@ -0,0 +1,48 @@
|
||||
EMPTY TRANSLATIONS SUMMARY REPORT
|
||||
==================================================
|
||||
|
||||
Total empty translations: 843
|
||||
|
||||
UI Elements (Buttons, Links): 20
|
||||
Form Fields & Inputs: 55
|
||||
Messages (Error/Success/Warning): 27
|
||||
Navigation & Pages: 7
|
||||
Other: 734
|
||||
|
||||
SAMPLE ENTRIES:
|
||||
------------------------------
|
||||
|
||||
UI Elements (showing first 5):
|
||||
Line 1491: "Click Here to Reset Your Password"
|
||||
Line 2685: "Email will be sent to all selected recipients"
|
||||
Line 2743: "Click here to join meeting"
|
||||
Line 2813: "Candidates to Schedule (Hold Ctrl/Cmd to select multiple)"
|
||||
Line 4057: "Select the agency job assignment"
|
||||
|
||||
Form Fields (showing first 5):
|
||||
Line 1658: "Enter your e-mail address to reset your password."
|
||||
Line 1712: "Please enter your new password below."
|
||||
Line 2077: "Form:"
|
||||
Line 2099: "Field Property"
|
||||
Line 2133: "Field Required"
|
||||
|
||||
Messages (showing first 5):
|
||||
Line 1214: "Notification Message"
|
||||
Line 2569: "Success"
|
||||
Line 2776: "An unknown error occurred."
|
||||
Line 2780: "An error occurred while processing your request."
|
||||
Line 2872: "Your application has been submitted successfully"
|
||||
|
||||
Navigation (showing first 5):
|
||||
Line 1295: "You don't have permission to view this page."
|
||||
Line 2232: "Page"
|
||||
Line 6253: "Admin Settings Dashboard"
|
||||
Line 6716: "That page number is not an integer"
|
||||
Line 6720: "That page number is less than 1"
|
||||
|
||||
Other (showing first 5):
|
||||
Line 7: ""
|
||||
Line 1041: "Number of candidates submitted so far"
|
||||
Line 1052: "Deadline for agency to submit candidates"
|
||||
Line 1068: "Original deadline before extensions"
|
||||
Line 1078: "Agency Job Assignment"
|
||||
173
find_empty_translations.py
Normal file
173
find_empty_translations.py
Normal file
@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to find empty msgstr entries in django.po file and organize them into batches
|
||||
for systematic Arabic translation work.
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
from typing import List, Tuple
|
||||
|
||||
def find_empty_translations(po_file_path: str) -> List[Tuple[int, str, str]]:
|
||||
"""
|
||||
Find all entries with empty msgstr in the django.po file.
|
||||
|
||||
Returns:
|
||||
List of tuples: (line_number, msgid, context_before)
|
||||
"""
|
||||
empty_translations = []
|
||||
|
||||
with open(po_file_path, 'r', encoding='utf-8') as file:
|
||||
lines = file.readlines()
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
|
||||
# Look for msgid
|
||||
if line.startswith('msgid '):
|
||||
msgid = line[7:-1] # Remove 'msgid "' and ending '"'
|
||||
|
||||
# Check next few lines for msgstr
|
||||
j = i + 1
|
||||
msgstr_found = False
|
||||
msgstr_empty = False
|
||||
|
||||
while j < len(lines) and j < i + 5: # Look ahead max 5 lines
|
||||
next_line = lines[j].strip()
|
||||
|
||||
if next_line.startswith('msgstr '):
|
||||
msgstr_found = True
|
||||
if next_line == 'msgstr ""':
|
||||
msgstr_empty = True
|
||||
break
|
||||
elif next_line.startswith('msgid ') or next_line.startswith('#'):
|
||||
# Found next entry or comment, no msgstr for current msgid
|
||||
break
|
||||
j += 1
|
||||
|
||||
if msgstr_found and msgstr_empty:
|
||||
# Get context (previous 2-3 lines)
|
||||
context_start = max(0, i - 3)
|
||||
context = ''.join(lines[context_start:i])
|
||||
empty_translations.append((i + 1, msgid, context))
|
||||
|
||||
i = j # Skip to after msgstr
|
||||
else:
|
||||
i += 1
|
||||
|
||||
return empty_translations
|
||||
|
||||
def create_batch_files(empty_translations: List[Tuple[int, str, str]], batch_size: int = 25):
|
||||
"""
|
||||
Create batch files with empty translations for systematic work.
|
||||
"""
|
||||
total_batches = (len(empty_translations) + batch_size - 1) // batch_size
|
||||
|
||||
print(f"Found {len(empty_translations)} empty translations")
|
||||
print(f"Creating {total_batches} batches of ~{batch_size} translations each")
|
||||
|
||||
for batch_num in range(total_batches):
|
||||
start_idx = batch_num * batch_size
|
||||
end_idx = min(start_idx + batch_size, len(empty_translations))
|
||||
batch_translations = empty_translations[start_idx:end_idx]
|
||||
|
||||
batch_filename = f"translation_batch_{batch_num + 1:02d}.txt"
|
||||
|
||||
with open(batch_filename, 'w', encoding='utf-8') as batch_file:
|
||||
batch_file.write(f"=== TRANSLATION BATCH {batch_num + 1:02d} ===\n")
|
||||
batch_file.write(f"Translations {start_idx + 1}-{end_idx} of {len(empty_translations)}\n")
|
||||
batch_file.write("=" * 60 + "\n\n")
|
||||
|
||||
for line_num, msgid, context in batch_translations:
|
||||
batch_file.write(f"Line {line_num}:\n")
|
||||
batch_file.write(f"msgid: \"{msgid}\"\n")
|
||||
batch_file.write(f"msgstr: \"\"\n")
|
||||
batch_file.write(f"\nArabic Translation: \n")
|
||||
batch_file.write(f"msgstr: \"\"\n")
|
||||
batch_file.write("-" * 40 + "\n\n")
|
||||
|
||||
print(f"Created {batch_filename} with {len(batch_translations)} translations")
|
||||
|
||||
def create_summary_report(empty_translations: List[Tuple[int, str, str]]):
|
||||
"""
|
||||
Create a summary report of all empty translations.
|
||||
"""
|
||||
with open("empty_translations_summary.txt", 'w', encoding='utf-8') as report:
|
||||
report.write("EMPTY TRANSLATIONS SUMMARY REPORT\n")
|
||||
report.write("=" * 50 + "\n\n")
|
||||
report.write(f"Total empty translations: {len(empty_translations)}\n\n")
|
||||
|
||||
# Group by type/pattern for better organization
|
||||
ui_elements = []
|
||||
form_fields = []
|
||||
messages = []
|
||||
navigation = []
|
||||
other = []
|
||||
|
||||
for line_num, msgid, context in empty_translations:
|
||||
msgid_lower = msgid.lower()
|
||||
|
||||
if any(word in msgid_lower for word in ['button', 'btn', 'click', 'select']):
|
||||
ui_elements.append((line_num, msgid))
|
||||
elif any(word in msgid_lower for word in ['field', 'form', 'input', 'enter']):
|
||||
form_fields.append((line_num, msgid))
|
||||
elif any(word in msgid_lower for word in ['error', 'success', 'warning', 'message']):
|
||||
messages.append((line_num, msgid))
|
||||
elif any(word in msgid_lower for word in ['menu', 'nav', 'page', 'dashboard']):
|
||||
navigation.append((line_num, msgid))
|
||||
else:
|
||||
other.append((line_num, msgid))
|
||||
|
||||
report.write(f"UI Elements (Buttons, Links): {len(ui_elements)}\n")
|
||||
report.write(f"Form Fields & Inputs: {len(form_fields)}\n")
|
||||
report.write(f"Messages (Error/Success/Warning): {len(messages)}\n")
|
||||
report.write(f"Navigation & Pages: {len(navigation)}\n")
|
||||
report.write(f"Other: {len(other)}\n\n")
|
||||
|
||||
report.write("SAMPLE ENTRIES:\n")
|
||||
report.write("-" * 30 + "\n")
|
||||
|
||||
for category, name, sample_count in [
|
||||
(ui_elements, "UI Elements", 5),
|
||||
(form_fields, "Form Fields", 5),
|
||||
(messages, "Messages", 5),
|
||||
(navigation, "Navigation", 5),
|
||||
(other, "Other", 5)
|
||||
]:
|
||||
if category:
|
||||
report.write(f"\n{name} (showing first {min(len(category), sample_count)}):\n")
|
||||
for line_num, msgid in category[:sample_count]:
|
||||
report.write(f" Line {line_num}: \"{msgid}\"\n")
|
||||
|
||||
def main():
|
||||
"""Main function to process the django.po file."""
|
||||
po_file_path = "locale/ar/LC_MESSAGES/django.po"
|
||||
|
||||
if not os.path.exists(po_file_path):
|
||||
print(f"Error: {po_file_path} not found!")
|
||||
return
|
||||
|
||||
print("Scanning for empty translations...")
|
||||
empty_translations = find_empty_translations(po_file_path)
|
||||
|
||||
if not empty_translations:
|
||||
print("No empty translations found! All msgstr entries have translations.")
|
||||
return
|
||||
|
||||
# Create batch files
|
||||
create_batch_files(empty_translations, batch_size=25)
|
||||
|
||||
# Create summary report
|
||||
create_summary_report(empty_translations)
|
||||
|
||||
print(f"\nProcess completed!")
|
||||
print(f"Check the generated batch files: translation_batch_*.txt")
|
||||
print(f"Summary report: empty_translations_summary.txt")
|
||||
print(f"\nNext steps:")
|
||||
print(f"1. Work through each batch file systematically")
|
||||
print(f"2. Add Arabic translations to each empty msgstr")
|
||||
print(f"3. Update the main django.po file with completed translations")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
91
fix_po_duplicates.py
Normal file
91
fix_po_duplicates.py
Normal file
@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fix duplicate message definitions in .po files
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
def fix_po_duplicates(po_file):
|
||||
"""Remove duplicate message definitions from a .po file"""
|
||||
|
||||
print(f"Processing {po_file}...")
|
||||
|
||||
with open(po_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Split into entries
|
||||
entries = []
|
||||
current_entry = []
|
||||
lines = content.split('\n')
|
||||
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
if line.startswith('msgid '):
|
||||
# Start of new entry
|
||||
if current_entry:
|
||||
entries.append('\n'.join(current_entry))
|
||||
current_entry = [line]
|
||||
elif line.startswith('msgstr ') and current_entry:
|
||||
# End of entry
|
||||
current_entry.append(line)
|
||||
entries.append('\n'.join(current_entry))
|
||||
current_entry = []
|
||||
else:
|
||||
if current_entry:
|
||||
current_entry.append(line)
|
||||
|
||||
i += 1
|
||||
|
||||
# Add the last entry if it exists
|
||||
if current_entry:
|
||||
entries.append('\n'.join(current_entry))
|
||||
|
||||
# Remove duplicates, keeping the first occurrence
|
||||
seen_msgids = set()
|
||||
unique_entries = []
|
||||
|
||||
for entry in entries:
|
||||
# Extract msgid
|
||||
msgid_match = re.search(r'msgid\s+"([^"]*)"', entry)
|
||||
if msgid_match:
|
||||
msgid = msgid_match.group(1)
|
||||
if msgid not in seen_msgids:
|
||||
seen_msgids.add(msgid)
|
||||
unique_entries.append(entry)
|
||||
else:
|
||||
print(f" Removing duplicate: {msgid}")
|
||||
else:
|
||||
# Header or other entry, keep it
|
||||
unique_entries.append(entry)
|
||||
|
||||
# Rebuild content
|
||||
new_content = '\n\n'.join(unique_entries)
|
||||
|
||||
# Write back to file
|
||||
with open(po_file, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print(f" Fixed {po_file} - removed {len(entries) - len(unique_entries)} duplicates")
|
||||
|
||||
def main():
|
||||
"""Fix duplicates in both English and Arabic .po files"""
|
||||
|
||||
po_files = [
|
||||
'locale/ar/LC_MESSAGES/django.po',
|
||||
'locale/en/LC_MESSAGES/django.po'
|
||||
]
|
||||
|
||||
for po_file in po_files:
|
||||
if os.path.exists(po_file):
|
||||
fix_po_duplicates(po_file)
|
||||
else:
|
||||
print(f"File not found: {po_file}")
|
||||
|
||||
print("\n✅ Duplicate fixing completed!")
|
||||
print("Now run: python manage.py compilemessages")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
9035
locale/ar/LC_MESSAGES/django.po.backup
Normal file
9035
locale/ar/LC_MESSAGES/django.po.backup
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -3,12 +3,14 @@ from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from .models import (
|
||||
JobPosting, Candidate, TrainingMaterial, ZoomMeeting,
|
||||
JobPosting, Application, TrainingMaterial, ZoomMeeting,
|
||||
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
|
||||
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)
|
||||
@ -143,43 +138,6 @@ class JobPostingAdmin(admin.ModelAdmin):
|
||||
mark_as_closed.short_description = 'Mark selected jobs as closed'
|
||||
|
||||
|
||||
@admin.register(Candidate)
|
||||
class CandidateAdmin(admin.ModelAdmin):
|
||||
list_display = ['full_name', 'job', 'email', 'phone', 'stage', 'applied','is_resume_parsed', 'created_at']
|
||||
list_filter = ['stage', 'applied', 'created_at', 'job__department']
|
||||
search_fields = ['first_name', 'last_name', 'email', 'phone']
|
||||
readonly_fields = ['slug', 'created_at', 'updated_at']
|
||||
fieldsets = (
|
||||
('Personal Information', {
|
||||
'fields': ('first_name', 'last_name', 'email', 'phone', 'resume')
|
||||
}),
|
||||
('Application Details', {
|
||||
'fields': ('job', 'applied', 'stage','is_resume_parsed')
|
||||
}),
|
||||
('Interview Process', {
|
||||
'fields': ('exam_date', 'exam_status', 'interview_date', 'interview_status', 'offer_date', 'offer_status', 'join_date')
|
||||
}),
|
||||
('Scoring', {
|
||||
'fields': ('ai_analysis_data',)
|
||||
}),
|
||||
('Additional Information', {
|
||||
'fields': ('submitted_by_agency', 'created_at', 'updated_at')
|
||||
}),
|
||||
)
|
||||
save_on_top = True
|
||||
actions = ['mark_as_applied', 'mark_as_not_applied']
|
||||
|
||||
def mark_as_applied(self, request, queryset):
|
||||
updated = queryset.update(applied=True)
|
||||
self.message_user(request, f'{updated} candidates marked as applied.')
|
||||
mark_as_applied.short_description = 'Mark selected candidates as applied'
|
||||
|
||||
def mark_as_not_applied(self, request, queryset):
|
||||
updated = queryset.update(applied=False)
|
||||
self.message_user(request, f'{updated} candidates marked as not applied.')
|
||||
mark_as_not_applied.short_description = 'Mark selected candidates as not applied'
|
||||
|
||||
|
||||
@admin.register(TrainingMaterial)
|
||||
class TrainingMaterialAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'created_by', 'created_at']
|
||||
@ -280,6 +238,7 @@ class FormSubmissionAdmin(admin.ModelAdmin):
|
||||
|
||||
# Register other models
|
||||
admin.site.register(FormStage)
|
||||
admin.site.register(Application)
|
||||
admin.site.register(FormField)
|
||||
admin.site.register(FieldResponse)
|
||||
admin.site.register(InterviewSchedule)
|
||||
@ -290,3 +249,4 @@ admin.site.register(AgencyJobAssignment)
|
||||
|
||||
|
||||
admin.site.register(JobPostingImage)
|
||||
admin.site.register(User)
|
||||
|
||||
@ -1,17 +1,163 @@
|
||||
from functools import wraps
|
||||
from datetime import date
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.http import HttpResponseNotFound, HttpResponseForbidden
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import AccessMixin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.contrib import messages
|
||||
|
||||
def job_not_expired(view_func):
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, job_id, *args, **kwargs):
|
||||
|
||||
|
||||
from .models import JobPosting
|
||||
job = get_object_or_404(JobPosting, pk=job_id)
|
||||
|
||||
if job.expiration_date and job.application_deadline< date.today():
|
||||
return redirect('expired_job_page')
|
||||
|
||||
|
||||
return view_func(request, job_id, *args, **kwargs)
|
||||
return _wrapped_view
|
||||
return _wrapped_view
|
||||
|
||||
|
||||
def user_type_required(allowed_types=None, login_url=None):
|
||||
"""
|
||||
Decorator to restrict view access based on user type.
|
||||
|
||||
Args:
|
||||
allowed_types (list): List of allowed user types ['staff', 'agency', 'candidate']
|
||||
login_url (str): URL to redirect to if user is not authenticated
|
||||
"""
|
||||
if allowed_types is None:
|
||||
allowed_types = ['staff']
|
||||
|
||||
def decorator(view_func):
|
||||
@wraps(view_func)
|
||||
@login_required(login_url=login_url)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
user = request.user
|
||||
|
||||
# Check if user has user_type attribute
|
||||
if not hasattr(user, 'user_type') or not user.user_type:
|
||||
messages.error(request, "User type not specified. Please contact administrator.")
|
||||
return redirect('portal_login')
|
||||
|
||||
# Check if user type is allowed
|
||||
if user.user_type not in allowed_types:
|
||||
# Log unauthorized access attempt
|
||||
messages.error(
|
||||
request,
|
||||
f"Access denied. This page is restricted to {', '.join(allowed_types)} users."
|
||||
)
|
||||
|
||||
# Redirect based on user type
|
||||
if user.user_type == 'agency':
|
||||
return redirect('agency_portal_dashboard')
|
||||
elif user.user_type == 'candidate':
|
||||
return redirect('candidate_portal_dashboard')
|
||||
else:
|
||||
return redirect('dashboard')
|
||||
|
||||
return view_func(request, *args, **kwargs)
|
||||
return _wrapped_view
|
||||
return decorator
|
||||
|
||||
|
||||
class UserTypeRequiredMixin(AccessMixin):
|
||||
"""
|
||||
Mixin for class-based views to restrict access based on user type.
|
||||
"""
|
||||
allowed_user_types = ['staff'] # Default to staff only
|
||||
login_url = '/login/'
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
# Check if user has user_type attribute
|
||||
if not hasattr(request.user, 'user_type') or not request.user.user_type:
|
||||
messages.error(request, "User type not specified. Please contact administrator.")
|
||||
return redirect('portal_login')
|
||||
|
||||
# Check if user type is allowed
|
||||
if request.user.user_type not in self.allowed_user_types:
|
||||
# Log unauthorized access attempt
|
||||
messages.error(
|
||||
request,
|
||||
f"Access denied. This page is restricted to {', '.join(self.allowed_user_types)} users."
|
||||
)
|
||||
|
||||
# Redirect based on user type
|
||||
if request.user.user_type == 'agency':
|
||||
return redirect('agency_portal_dashboard')
|
||||
elif request.user.user_type == 'candidate':
|
||||
return redirect('candidate_portal_dashboard')
|
||||
else:
|
||||
return redirect('dashboard')
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def handle_no_permission(self):
|
||||
if self.request.user.is_authenticated:
|
||||
# User is authenticated but doesn't have permission
|
||||
messages.error(
|
||||
self.request,
|
||||
f"Access denied. This page is restricted to {', '.join(self.allowed_user_types)} users."
|
||||
)
|
||||
return redirect('dashboard')
|
||||
else:
|
||||
# User is not authenticated
|
||||
return super().handle_no_permission()
|
||||
|
||||
|
||||
class StaffRequiredMixin(UserTypeRequiredMixin):
|
||||
"""Mixin to restrict access to staff users only."""
|
||||
allowed_user_types = ['staff']
|
||||
|
||||
|
||||
class AgencyRequiredMixin(UserTypeRequiredMixin):
|
||||
"""Mixin to restrict access to agency users only."""
|
||||
allowed_user_types = ['agency']
|
||||
login_url = '/portal/login/'
|
||||
|
||||
|
||||
class CandidateRequiredMixin(UserTypeRequiredMixin):
|
||||
"""Mixin to restrict access to candidate users only."""
|
||||
allowed_user_types = ['candidate']
|
||||
login_url = '/portal/login/'
|
||||
|
||||
|
||||
class StaffOrAgencyRequiredMixin(UserTypeRequiredMixin):
|
||||
"""Mixin to restrict access to staff and agency users."""
|
||||
allowed_user_types = ['staff', 'agency']
|
||||
|
||||
|
||||
class StaffOrCandidateRequiredMixin(UserTypeRequiredMixin):
|
||||
"""Mixin to restrict access to staff and candidate users."""
|
||||
allowed_user_types = ['staff', 'candidate']
|
||||
|
||||
|
||||
def agency_user_required(view_func):
|
||||
"""Decorator to restrict view to agency users only."""
|
||||
return user_type_required(['agency'], login_url='/portal/login/')(view_func)
|
||||
|
||||
|
||||
def candidate_user_required(view_func):
|
||||
"""Decorator to restrict view to candidate users only."""
|
||||
return user_type_required(['candidate'], login_url='/portal/login/')(view_func)
|
||||
|
||||
|
||||
def staff_user_required(view_func):
|
||||
"""Decorator to restrict view to staff users only."""
|
||||
return user_type_required(['staff'])(view_func)
|
||||
|
||||
|
||||
def staff_or_agency_required(view_func):
|
||||
"""Decorator to restrict view to staff and agency users."""
|
||||
return user_type_required(['staff', 'agency'], login_url='/portal/login/')(view_func)
|
||||
|
||||
|
||||
def staff_or_candidate_required(view_func):
|
||||
"""Decorator to restrict view to staff and candidate users."""
|
||||
return user_type_required(['staff', 'candidate'], login_url='/portal/login/')(view_func)
|
||||
|
||||
1674
recruitment/forms.py
1674
recruitment/forms.py
File diff suppressed because it is too large
Load Diff
71
recruitment/management/commands/translate_po.py
Normal file
71
recruitment/management/commands/translate_po.py
Normal file
@ -0,0 +1,71 @@
|
||||
import os
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.conf import settings
|
||||
from gpt_po_translator.main import translate_po_files
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Translates PO files using gpt-po-translator configured with OpenRouter.'
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--folder',
|
||||
type=str,
|
||||
default=getattr(settings, 'LOCALE_PATHS', ['locale'])[0],
|
||||
help='Path to the folder containing .po files (default is the first LOCALE_PATHS entry).',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--lang',
|
||||
type=str,
|
||||
help='Comma-separated target language codes (e.g., de,fr,es).',
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--model',
|
||||
type=str,
|
||||
default='mistralai/mistral-nemo', # Example OpenRouter model
|
||||
help='The OpenRouter model to use (e.g., openai/gpt-4o, mistralai/mistral-nemo).',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--bulk',
|
||||
action='store_true',
|
||||
help='Enable bulk translation mode for efficiency.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--bulksize',
|
||||
type=int,
|
||||
default=50,
|
||||
help='Entries per batch in bulk mode (default: 50).',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# --- OpenRouter Configuration ---
|
||||
# 1. Get API Key from environment variable
|
||||
api_key = os.environ.get('OPENROUTER_API_KEY')
|
||||
if not api_key:
|
||||
raise CommandError("The OPENROUTER_API_KEY environment variable is not set.")
|
||||
|
||||
# 2. Set the base URL for OpenRouter
|
||||
openrouter_base_url = "https://openrouter.ai/api/v1"
|
||||
|
||||
# 3. Call the core translation function, passing OpenRouter specific config
|
||||
try:
|
||||
self.stdout.write(self.style.NOTICE(f"Starting translation with model: {options['model']} via OpenRouter..."))
|
||||
|
||||
translate_po_files(
|
||||
folder=options['folder'],
|
||||
lang_codes=options['lang'].split(','),
|
||||
provider='openai', # gpt-po-translator uses 'openai' provider for OpenAI-compatible APIs
|
||||
api_key=api_key,
|
||||
model_name=options['model'],
|
||||
bulk=options['bulk'],
|
||||
bulk_size=options['bulksize'],
|
||||
# Set the base_url for the OpenAI client to point to OpenRouter
|
||||
base_url=openrouter_base_url,
|
||||
# OpenRouter often requires a referrer for API usage
|
||||
extra_headers={"HTTP-Referer": "http://your-django-app.com"},
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f"Successfully translated PO files for languages: {options['lang']}"))
|
||||
|
||||
except Exception as e:
|
||||
raise CommandError(f"An error occurred during translation: {e}")
|
||||
@ -1,7 +1,10 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-05 13:05
|
||||
# Generated by Django 5.2.6 on 2025-11-10 14:13
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import django_ckeditor_5.fields
|
||||
import django_countries.fields
|
||||
import django_extensions.db.fields
|
||||
@ -15,7 +18,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@ -44,28 +47,6 @@ class Migration(migrations.Migration):
|
||||
'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='Participants',
|
||||
fields=[
|
||||
@ -137,6 +118,78 @@ class Migration(migrations.Migration):
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
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()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Application',
|
||||
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')),
|
||||
('resume', models.FileField(upload_to='resumes/', verbose_name='Resume')),
|
||||
('cover_letter', models.FileField(blank=True, null=True, upload_to='cover_letters/', verbose_name='Cover Letter')),
|
||||
('is_resume_parsed', models.BooleanField(default=False, verbose_name='Resume Parsed')),
|
||||
('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'), ('Hired', 'Hired'), ('Rejected', 'Rejected')], db_index=True, default='Applied', max_length=20, verbose_name='Stage')),
|
||||
('applicant_status', models.CharField(blank=True, choices=[('Applicant', 'Applicant'), ('Candidate', 'Candidate')], default='Applicant', max_length=20, 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=20, 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=20, 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'), ('Pending', 'Pending')], max_length=20, null=True, verbose_name='Offer Status')),
|
||||
('hired_date', models.DateField(blank=True, null=True, verbose_name='Hired Date')),
|
||||
('join_date', models.DateField(blank=True, null=True, verbose_name='Join Date')),
|
||||
('ai_analysis_data', models.JSONField(blank=True, default=dict, help_text='Full JSON output from the resume scoring model.', null=True, verbose_name='AI Analysis Data')),
|
||||
('retry', models.SmallIntegerField(default=3, verbose_name='Resume Parsing Retry')),
|
||||
('hiring_source', models.CharField(blank=True, choices=[('Public', 'Public'), ('Internal', 'Internal'), ('Agency', 'Agency')], default='Public', max_length=255, null=True, verbose_name='Hiring Source')),
|
||||
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='application_profile', to=settings.AUTH_USER_MODEL, verbose_name='User Account')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Application',
|
||||
'verbose_name_plural': 'Applications',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Candidate',
|
||||
fields=[
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Candidate (Legacy)',
|
||||
'verbose_name_plural': 'Candidates (Legacy)',
|
||||
'proxy': True,
|
||||
'indexes': [],
|
||||
'constraints': [],
|
||||
},
|
||||
bases=('recruitment.application',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FormField',
|
||||
fields=[
|
||||
@ -206,42 +259,33 @@ class Migration(migrations.Migration):
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stages', to='recruitment.formtemplate'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Candidate',
|
||||
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')),
|
||||
('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'), ('Hired', 'Hired')], 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')),
|
||||
('hired_date', models.DateField(blank=True, null=True, verbose_name='Hired Date')),
|
||||
('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')),
|
||||
('retry', models.SmallIntegerField(default=3, verbose_name='Resume Parsing Retry')),
|
||||
('hiring_source', models.CharField(blank=True, choices=[('Public', 'Public'), ('Internal', 'Internal'), ('Agency', 'Agency')], default='Public', max_length=255, null=True, verbose_name='Hiring Source')),
|
||||
('hiring_agency', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='candidates', to='recruitment.hiringagency', verbose_name='Hiring Agency')),
|
||||
('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)),
|
||||
('user', 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')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Candidate',
|
||||
'verbose_name_plural': 'Candidates',
|
||||
'verbose_name': 'Hiring Agency',
|
||||
'verbose_name_plural': 'Hiring Agencies',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='application',
|
||||
name='hiring_agency',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='applications', to='recruitment.hiringagency', verbose_name='Hiring Agency'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='JobPosting',
|
||||
fields=[
|
||||
@ -251,8 +295,8 @@ class Migration(migrations.Migration):
|
||||
('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)),
|
||||
('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)),
|
||||
@ -281,6 +325,7 @@ class Migration(migrations.Migration):
|
||||
('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)),
|
||||
('assigned_to', models.ForeignKey(blank=True, help_text='The user who has been assigned to this job', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assigned_jobs', to=settings.AUTH_USER_MODEL, verbose_name='Assigned To')),
|
||||
('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')),
|
||||
('users', models.ManyToManyField(blank=True, help_text='Internal staff involved in the recruitment process for this job', related_name='jobs_assigned', to=settings.AUTH_USER_MODEL, verbose_name='Internal Participant')),
|
||||
('participants', models.ManyToManyField(blank=True, help_text='External participants involved in the recruitment process for this job', related_name='jobs_participating', to='recruitment.participants', verbose_name='External Participant')),
|
||||
@ -308,7 +353,7 @@ class Migration(migrations.Migration):
|
||||
('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')),
|
||||
('candidates', models.ManyToManyField(blank=True, null=True, related_name='interview_schedules', to='recruitment.application')),
|
||||
('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')),
|
||||
],
|
||||
@ -319,9 +364,9 @@ class Migration(migrations.Migration):
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='form_template', to='recruitment.jobposting'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='candidate',
|
||||
model_name='application',
|
||||
name='job',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='candidates', to='recruitment.jobposting', verbose_name='Job'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='applications', to='recruitment.jobposting', verbose_name='Job'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AgencyJobAssignment',
|
||||
@ -356,6 +401,77 @@ class Migration(migrations.Migration):
|
||||
('job', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='post_images', to='recruitment.jobposting')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Message',
|
||||
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')),
|
||||
('subject', models.CharField(max_length=200, verbose_name='Subject')),
|
||||
('content', models.TextField(verbose_name='Message Content')),
|
||||
('message_type', models.CharField(choices=[('direct', 'Direct Message'), ('job_related', 'Job Related'), ('system', 'System Notification')], default='direct', max_length=20, verbose_name='Message Type')),
|
||||
('is_read', models.BooleanField(default=False, verbose_name='Is Read')),
|
||||
('read_at', models.DateTimeField(blank=True, null=True, verbose_name='Read At')),
|
||||
('job', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='recruitment.jobposting', verbose_name='Related Job')),
|
||||
('recipient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL, verbose_name='Recipient')),
|
||||
('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL, verbose_name='Sender')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Message',
|
||||
'verbose_name_plural': 'Messages',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Person',
|
||||
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')),
|
||||
('middle_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Middle Name')),
|
||||
('email', models.EmailField(db_index=True, help_text='Unique email address for the person', max_length=254, unique=True, verbose_name='Email')),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Phone')),
|
||||
('date_of_birth', models.DateField(blank=True, null=True, verbose_name='Date of Birth')),
|
||||
('gender', models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female'), ('O', 'Other'), ('P', 'Prefer not to say')], max_length=1, null=True, verbose_name='Gender')),
|
||||
('nationality', django_countries.fields.CountryField(blank=True, max_length=2, null=True, verbose_name='Nationality')),
|
||||
('address', models.TextField(blank=True, null=True, verbose_name='Address')),
|
||||
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size], verbose_name='Profile Image')),
|
||||
('linkedin_profile', models.URLField(blank=True, null=True, verbose_name='LinkedIn Profile URL')),
|
||||
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='person_profile', to=settings.AUTH_USER_MODEL, verbose_name='User Account')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Person',
|
||||
'verbose_name_plural': 'People',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Document',
|
||||
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')),
|
||||
('file', models.FileField(upload_to='candidate_documents/%Y/%m/', validators=[recruitment.validators.validate_image_size], verbose_name='Document File')),
|
||||
('document_type', models.CharField(choices=[('resume', 'Resume'), ('cover_letter', 'Cover Letter'), ('certificate', 'Certificate'), ('id_document', 'ID Document'), ('passport', 'Passport'), ('education', 'Education Document'), ('experience', 'Experience Letter'), ('other', 'Other')], default='other', max_length=20, verbose_name='Document Type')),
|
||||
('description', models.CharField(blank=True, max_length=200, verbose_name='Description')),
|
||||
('uploaded_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='Uploaded By')),
|
||||
('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='recruitment.person', verbose_name='Person')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Document',
|
||||
'verbose_name_plural': 'Documents',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='application',
|
||||
name='person',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='applications', to='recruitment.person', verbose_name='Person'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Profile',
|
||||
fields=[
|
||||
@ -435,7 +551,7 @@ class Migration(migrations.Migration):
|
||||
('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')),
|
||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_interviews', to='recruitment.application')),
|
||||
('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')),
|
||||
@ -543,14 +659,6 @@ class Migration(migrations.Migration):
|
||||
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='agencyjobassignment',
|
||||
index=models.Index(fields=['agency', 'status'], name='recruitment_agency__491a54_idx'),
|
||||
@ -571,6 +679,58 @@ class Migration(migrations.Migration):
|
||||
name='agencyjobassignment',
|
||||
unique_together={('agency', 'job')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='message',
|
||||
index=models.Index(fields=['sender', 'created_at'], name='recruitment_sender__49d984_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='message',
|
||||
index=models.Index(fields=['recipient', 'is_read', 'created_at'], name='recruitment_recipie_af0e6d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='message',
|
||||
index=models.Index(fields=['job', 'created_at'], name='recruitment_job_id_18f813_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='message',
|
||||
index=models.Index(fields=['message_type', 'created_at'], name='recruitment_message_f25659_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='person',
|
||||
index=models.Index(fields=['email'], name='recruitment_email_0b1ab1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='person',
|
||||
index=models.Index(fields=['first_name', 'last_name'], name='recruitment_first_n_739de5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='person',
|
||||
index=models.Index(fields=['created_at'], name='recruitment_created_33495a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='document',
|
||||
index=models.Index(fields=['person', 'document_type', 'created_at'], name='recruitment_person__0a6844_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='application',
|
||||
index=models.Index(fields=['person', 'job'], name='recruitment_person__34355c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='application',
|
||||
index=models.Index(fields=['stage'], name='recruitment_stage_52c2d1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='application',
|
||||
index=models.Index(fields=['created_at'], name='recruitment_created_80633f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='application',
|
||||
index=models.Index(fields=['person', 'stage', 'created_at'], name='recruitment_person__8715ec_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='application',
|
||||
unique_together={('person', 'job')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='jobposting',
|
||||
index=models.Index(fields=['status', 'created_at', 'title'], name='recruitment_status_8b77aa_idx'),
|
||||
@ -589,7 +749,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledinterview',
|
||||
index=models.Index(fields=['candidate', 'job'], name='recruitment_candida_43d5b0_idx'),
|
||||
index=models.Index(fields=['application', 'job'], name='recruitment_applica_927561_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='notification',
|
||||
|
||||
25
recruitment/migrations/0002_delete_candidate_and_more.py
Normal file
25
recruitment/migrations/0002_delete_candidate_and_more.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.6 on 2025-11-11 10:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Candidate',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='interviewschedule',
|
||||
old_name='candidates',
|
||||
new_name='applications',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='application',
|
||||
name='user',
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,45 @@
|
||||
# Generated by Django 5.2.6 on 2025-11-11 12:13
|
||||
|
||||
import django.db.models.deletion
|
||||
import recruitment.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
('recruitment', '0002_delete_candidate_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveIndex(
|
||||
model_name='document',
|
||||
name='recruitment_person__0a6844_idx',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='document',
|
||||
name='person',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='content_type',
|
||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype', verbose_name='Content Type'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='document',
|
||||
name='object_id',
|
||||
field=models.PositiveIntegerField(default=1, verbose_name='Object ID'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='document',
|
||||
name='file',
|
||||
field=models.FileField(upload_to='documents/%Y/%m/', validators=[recruitment.validators.validate_image_size], verbose_name='Document File'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='document',
|
||||
index=models.Index(fields=['content_type', 'object_id', 'document_type', 'created_at'], name='recruitment_content_547650_idx'),
|
||||
),
|
||||
]
|
||||
19
recruitment/migrations/0004_person_agency.py
Normal file
19
recruitment/migrations/0004_person_agency.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.6 on 2025-11-12 20:22
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0003_convert_document_to_generic_fk'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='person',
|
||||
name='agency',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='recruitment.hiringagency', verbose_name='Hiring Agency'),
|
||||
),
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,14 @@
|
||||
from rest_framework import serializers
|
||||
from .models import JobPosting, Candidate
|
||||
from .models import JobPosting, Application
|
||||
|
||||
class JobPostingSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = JobPosting
|
||||
fields = '__all__'
|
||||
|
||||
class CandidateSerializer(serializers.ModelSerializer):
|
||||
class ApplicationSerializer(serializers.ModelSerializer):
|
||||
job_title = serializers.CharField(source='job.title', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Candidate
|
||||
model = Application
|
||||
fields = '__all__'
|
||||
|
||||
@ -1,18 +1,20 @@
|
||||
import logging
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from django.db import transaction
|
||||
from django_q.models import Schedule
|
||||
from django_q.tasks import schedule
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django_q.tasks import async_task
|
||||
from django.db.models.signals import post_save
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
from .models import FormField,FormStage,FormTemplate,Candidate,JobPosting,Notification,AgencyJobAssignment,AgencyAccessLink
|
||||
from .models import FormField,FormStage,FormTemplate,Application,JobPosting,Notification,HiringAgency,Person
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
User = get_user_model()
|
||||
@receiver(post_save, sender=JobPosting)
|
||||
def format_job(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
@ -57,9 +59,9 @@ def format_job(sender, instance, created, **kwargs):
|
||||
# instance.form_template.is_active = False
|
||||
# instance.save()
|
||||
|
||||
@receiver(post_save, sender=Candidate)
|
||||
@receiver(post_save, sender=Application)
|
||||
def score_candidate_resume(sender, instance, created, **kwargs):
|
||||
if not instance.is_resume_parsed:
|
||||
if instance.resume and not instance.is_resume_parsed:
|
||||
logger.info(f"Scoring resume for candidate {instance.pk}")
|
||||
async_task(
|
||||
'recruitment.tasks.handle_reume_parsing_and_scoring',
|
||||
@ -399,11 +401,33 @@ def notification_created(sender, instance, created, **kwargs):
|
||||
|
||||
logger.info(f"Notification cached for SSE: {notification_data}")
|
||||
|
||||
@receiver(post_save,sender=AgencyJobAssignment)
|
||||
def create_access_link(sender,instance,created,**kwargs):
|
||||
def generate_random_password():
|
||||
import string
|
||||
return ''.join(random.choices(string.ascii_letters + string.digits, k=12))
|
||||
@receiver(post_save, sender=HiringAgency)
|
||||
def hiring_agency_created(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
link=AgencyAccessLink(assignment=instance)
|
||||
link.access_password = link.generate_password()
|
||||
link.unique_token = link.generate_token()
|
||||
link.expires_at = datetime.now() + timedelta(days=4)
|
||||
link.save()
|
||||
logger.info(f"New hiring agency created: {instance.pk} - {instance.name}")
|
||||
user = User.objects.create_user(
|
||||
username=instance.name,
|
||||
email=instance.email,
|
||||
user_type="agency"
|
||||
)
|
||||
user.set_password(generate_random_password())
|
||||
user.save()
|
||||
instance.user = user
|
||||
instance.save()
|
||||
@receiver(post_save, sender=Person)
|
||||
def person_created(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
logger.info(f"New Person created: {instance.pk} - {instance.email}")
|
||||
user = User.objects.create_user(
|
||||
username=instance.slug,
|
||||
first_name=instance.first_name,
|
||||
last_name=instance.last_name,
|
||||
email=instance.email,
|
||||
phone=instance.phone,
|
||||
user_type="candidate"
|
||||
)
|
||||
instance.user = user
|
||||
instance.save()
|
||||
@ -7,7 +7,7 @@ from PyPDF2 import PdfReader
|
||||
from datetime import datetime
|
||||
from django.db import transaction
|
||||
from .utils import create_zoom_meeting
|
||||
from recruitment.models import Candidate
|
||||
from recruitment.models import Application
|
||||
from . linkedin_service import LinkedInService
|
||||
from django.shortcuts import get_object_or_404
|
||||
from . models import JobPosting
|
||||
@ -244,8 +244,8 @@ def handle_reume_parsing_and_scoring(pk):
|
||||
|
||||
# --- 1. Robust Object Retrieval (Prevents looping on DoesNotExist) ---
|
||||
try:
|
||||
instance = Candidate.objects.get(pk=pk)
|
||||
except Candidate.DoesNotExist:
|
||||
instance = Application.objects.get(pk=pk)
|
||||
except Application.DoesNotExist:
|
||||
# Exit gracefully if the candidate was deleted after the task was queued
|
||||
logger.warning(f"Candidate matching query does not exist for pk={pk}. Exiting task.")
|
||||
print(f"Candidate matching query does not exist for pk={pk}. Exiting task.")
|
||||
@ -453,7 +453,7 @@ def create_interview_and_meeting(
|
||||
Synchronous task for a single interview slot, dispatched by django-q.
|
||||
"""
|
||||
try:
|
||||
candidate = Candidate.objects.get(pk=candidate_id)
|
||||
candidate = Application.objects.get(pk=candidate_id)
|
||||
job = JobPosting.objects.get(pk=job_id)
|
||||
schedule = InterviewSchedule.objects.get(pk=schedule_id)
|
||||
|
||||
@ -477,7 +477,7 @@ def create_interview_and_meeting(
|
||||
password=result["meeting_details"]["password"]
|
||||
)
|
||||
ScheduledInterview.objects.create(
|
||||
candidate=candidate,
|
||||
application=Application,
|
||||
job=job,
|
||||
zoom_meeting=zoom_meeting,
|
||||
schedule=schedule,
|
||||
@ -485,11 +485,11 @@ def create_interview_and_meeting(
|
||||
interview_time=slot_time
|
||||
)
|
||||
# Log success or use Django-Q result system for monitoring
|
||||
logger.info(f"Successfully scheduled interview for {candidate.name}")
|
||||
logger.info(f"Successfully scheduled interview for {Application.name}")
|
||||
return True # Task succeeded
|
||||
else:
|
||||
# Handle Zoom API failure (e.g., log it or notify administrator)
|
||||
logger.error(f"Zoom API failed for {candidate.name}: {result['message']}")
|
||||
logger.error(f"Zoom API failed for {Application.name}: {result['message']}")
|
||||
return False # Task failed
|
||||
|
||||
except Exception as e:
|
||||
@ -704,14 +704,14 @@ def sync_candidate_to_source_task(candidate_id, source_id):
|
||||
|
||||
try:
|
||||
# Get the candidate and source
|
||||
candidate = Candidate.objects.get(pk=candidate_id)
|
||||
application = Application.objects.get(pk=candidate_id)
|
||||
source = Source.objects.get(pk=source_id)
|
||||
|
||||
# Initialize sync service
|
||||
sync_service = CandidateSyncService()
|
||||
|
||||
# Perform the sync operation
|
||||
result = sync_service.sync_candidate_to_source(candidate, source)
|
||||
result = sync_service.sync_candidate_to_source(application, source)
|
||||
|
||||
# Log the operation
|
||||
IntegrationLog.objects.create(
|
||||
@ -719,7 +719,7 @@ def sync_candidate_to_source_task(candidate_id, source_id):
|
||||
action=IntegrationLog.ActionChoices.SYNC,
|
||||
endpoint=source.sync_endpoint or "unknown",
|
||||
method=source.sync_method or "POST",
|
||||
request_data={"candidate_id": candidate_id, "candidate_name": candidate.name},
|
||||
request_data={"candidate_id": candidate_id, "application_name": application.name},
|
||||
response_data=result,
|
||||
status_code="SUCCESS" if result.get('success') else "ERROR",
|
||||
error_message=result.get('error') if not result.get('success') else None,
|
||||
@ -731,8 +731,8 @@ def sync_candidate_to_source_task(candidate_id, source_id):
|
||||
logger.info(f"Sync completed for candidate {candidate_id} to source {source_id}: {result}")
|
||||
return result
|
||||
|
||||
except Candidate.DoesNotExist:
|
||||
error_msg = f"Candidate not found: {candidate_id}"
|
||||
except Application.DoesNotExist:
|
||||
error_msg = f"Application not found: {candidate_id}"
|
||||
logger.error(error_msg)
|
||||
return {"success": False, "error": error_msg}
|
||||
|
||||
|
||||
27
recruitment/templatetags/file_filters.py
Normal file
27
recruitment/templatetags/file_filters.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def filename(value):
|
||||
"""
|
||||
Extract just the filename from a file path.
|
||||
Example: 'documents/resume.pdf' -> 'resume.pdf'
|
||||
"""
|
||||
if not value:
|
||||
return ''
|
||||
|
||||
# Convert to string and split by path separators
|
||||
import os
|
||||
return os.path.basename(str(value))
|
||||
|
||||
@register.filter
|
||||
def split(value, delimiter):
|
||||
"""
|
||||
Split a string by a delimiter and return a list.
|
||||
This is a custom implementation of the split functionality.
|
||||
"""
|
||||
if not value:
|
||||
return []
|
||||
|
||||
return str(value).split(delimiter)
|
||||
@ -1,5 +1,5 @@
|
||||
from django.test import TestCase, Client
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
@ -7,6 +7,8 @@ from datetime import datetime, time, timedelta
|
||||
import json
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
from .models import (
|
||||
JobPosting, Candidate, ZoomMeeting, FormTemplate, FormStage, FormField,
|
||||
FormSubmission, FieldResponse, InterviewSchedule, ScheduledInterview,
|
||||
@ -14,11 +16,11 @@ from .models import (
|
||||
)
|
||||
from .forms import (
|
||||
JobPostingForm, CandidateForm, ZoomMeetingForm, MeetingCommentForm,
|
||||
CandidateStageForm, InterviewScheduleForm
|
||||
CandidateStageForm, InterviewScheduleForm, CandidateSignupForm
|
||||
)
|
||||
from .views import (
|
||||
ZoomMeetingListView, ZoomMeetingCreateView, job_detail, candidate_screening_view,
|
||||
candidate_exam_view, candidate_interview_view, submit_form, api_schedule_candidate_meeting
|
||||
candidate_exam_view, candidate_interview_view, api_schedule_candidate_meeting
|
||||
)
|
||||
from .views_frontend import CandidateListView, JobListView
|
||||
from .utils import create_zoom_meeting, get_candidates_from_request
|
||||
@ -46,14 +48,21 @@ class BaseTestCase(TestCase):
|
||||
location_country='Saudi Arabia',
|
||||
description='Job description',
|
||||
qualifications='Job qualifications',
|
||||
application_deadline=timezone.now() + timedelta(days=30),
|
||||
created_by=self.user
|
||||
)
|
||||
|
||||
self.candidate = Candidate.objects.create(
|
||||
# Create a person first
|
||||
from .models import Person
|
||||
person = Person.objects.create(
|
||||
first_name='John',
|
||||
last_name='Doe',
|
||||
email='john@example.com',
|
||||
phone='1234567890',
|
||||
phone='1234567890'
|
||||
)
|
||||
|
||||
self.candidate = Candidate.objects.create(
|
||||
person=person,
|
||||
resume=SimpleUploadedFile('resume.pdf', b'file_content', content_type='application/pdf'),
|
||||
job=self.job,
|
||||
stage='Applied'
|
||||
@ -231,28 +240,6 @@ class ViewTests(BaseTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, 'success')
|
||||
|
||||
def test_submit_form(self):
|
||||
"""Test submit_form view"""
|
||||
# Create a form template first
|
||||
template = FormTemplate.objects.create(
|
||||
job=self.job,
|
||||
name='Test Template',
|
||||
created_by=self.user,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
data = {
|
||||
'field_1': 'John', # Assuming field ID 1 corresponds to First Name
|
||||
'field_2': 'Doe', # Assuming field ID 2 corresponds to Last Name
|
||||
'field_3': 'john@example.com', # Email
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
reverse('application_submit', kwargs={'template_id': template.id}),
|
||||
data
|
||||
)
|
||||
# After successful submission, should redirect to success page
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
|
||||
class FormTests(BaseTestCase):
|
||||
@ -268,13 +255,13 @@ class FormTests(BaseTestCase):
|
||||
'location_city': 'Riyadh',
|
||||
'location_state': 'Riyadh',
|
||||
'location_country': 'Saudi Arabia',
|
||||
'description': 'Job description',
|
||||
'description': 'Job description with at least 20 characters to meet validation requirements',
|
||||
'qualifications': 'Job qualifications',
|
||||
'salary_range': '5000-7000',
|
||||
'application_deadline': '2025-12-31',
|
||||
'max_applications': '100',
|
||||
'open_positions': '2',
|
||||
'hash_tags': '#hiring, #jobopening'
|
||||
'hash_tags': '#hiring,#jobopening'
|
||||
}
|
||||
form = JobPostingForm(data=form_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
@ -315,24 +302,51 @@ class FormTests(BaseTestCase):
|
||||
form_data = {
|
||||
'stage': 'Exam'
|
||||
}
|
||||
form = CandidateStageForm(data=form_data, candidate=self.candidate)
|
||||
form = CandidateStageForm(data=form_data, instance=self.candidate)
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_interview_schedule_form(self):
|
||||
"""Test InterviewScheduleForm"""
|
||||
# Update candidate to Interview stage first
|
||||
self.candidate.stage = 'Interview'
|
||||
self.candidate.save()
|
||||
|
||||
form_data = {
|
||||
'candidates': [self.candidate.id],
|
||||
'start_date': (timezone.now() + timedelta(days=1)).date(),
|
||||
'end_date': (timezone.now() + timedelta(days=7)).date(),
|
||||
'working_days': [0, 1, 2, 3, 4], # Monday to Friday
|
||||
'start_time': '09:00',
|
||||
'end_time': '17:00',
|
||||
'interview_duration': 60,
|
||||
'buffer_time': 15
|
||||
}
|
||||
form = InterviewScheduleForm(slug=self.job.slug, data=form_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_candidate_signup_form_valid(self):
|
||||
"""Test CandidateSignupForm with valid data"""
|
||||
form_data = {
|
||||
'first_name': 'John',
|
||||
'last_name': 'Doe',
|
||||
'email': 'john.doe@example.com',
|
||||
'phone': '+1234567890',
|
||||
'password': 'SecurePass123',
|
||||
'confirm_password': 'SecurePass123'
|
||||
}
|
||||
form = CandidateSignupForm(data=form_data)
|
||||
self.assertTrue(form.is_valid())
|
||||
|
||||
def test_candidate_signup_form_password_mismatch(self):
|
||||
"""Test CandidateSignupForm with password mismatch"""
|
||||
form_data = {
|
||||
'first_name': 'John',
|
||||
'last_name': 'Doe',
|
||||
'email': 'john.doe@example.com',
|
||||
'phone': '+1234567890',
|
||||
'password': 'SecurePass123',
|
||||
'confirm_password': 'DifferentPass123'
|
||||
}
|
||||
form = CandidateSignupForm(data=form_data)
|
||||
self.assertFalse(form.is_valid())
|
||||
self.assertIn('Passwords do not match', str(form.errors))
|
||||
|
||||
|
||||
class IntegrationTests(BaseTestCase):
|
||||
"""Integration tests for multiple components"""
|
||||
@ -340,11 +354,14 @@ class IntegrationTests(BaseTestCase):
|
||||
def test_candidate_journey(self):
|
||||
"""Test the complete candidate journey from application to interview"""
|
||||
# 1. Create candidate
|
||||
candidate = Candidate.objects.create(
|
||||
person = Person.objects.create(
|
||||
first_name='Jane',
|
||||
last_name='Smith',
|
||||
email='jane@example.com',
|
||||
phone='9876543210',
|
||||
phone='9876543210'
|
||||
)
|
||||
candidate = Candidate.objects.create(
|
||||
person=person,
|
||||
resume=SimpleUploadedFile('resume.pdf', b'file_content', content_type='application/pdf'),
|
||||
job=self.job,
|
||||
stage='Applied'
|
||||
@ -449,11 +466,15 @@ class PerformanceTests(BaseTestCase):
|
||||
"""Test pagination with large datasets"""
|
||||
# Create many candidates
|
||||
for i in range(100):
|
||||
Candidate.objects.create(
|
||||
person = Person.objects.create(
|
||||
first_name=f'Candidate{i}',
|
||||
last_name=f'Test{i}',
|
||||
email=f'candidate{i}@example.com',
|
||||
phone=f'123456789{i}',
|
||||
phone=f'123456789{i}'
|
||||
)
|
||||
Candidate.objects.create(
|
||||
person=person,
|
||||
resume=SimpleUploadedFile(f'resume{i}.pdf', b'file_content', content_type='application/pdf'),
|
||||
job=self.job,
|
||||
stage='Applied'
|
||||
)
|
||||
@ -594,13 +615,17 @@ class TestFactories:
|
||||
@staticmethod
|
||||
def create_candidate(**kwargs):
|
||||
job = TestFactories.create_job_posting()
|
||||
person = Person.objects.create(
|
||||
first_name='Test',
|
||||
last_name='Candidate',
|
||||
email='test@example.com',
|
||||
phone='1234567890'
|
||||
)
|
||||
defaults = {
|
||||
'first_name': 'Test',
|
||||
'last_name': 'Candidate',
|
||||
'email': 'test@example.com',
|
||||
'phone': '1234567890',
|
||||
'person': person,
|
||||
'job': job,
|
||||
'stage': 'Applied'
|
||||
'stage': 'Applied',
|
||||
'resume': SimpleUploadedFile('resume.pdf', b'file_content', content_type='application/pdf')
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return Candidate.objects.create(**defaults)
|
||||
|
||||
@ -5,13 +5,22 @@ from . import views_integration
|
||||
from . import views_source
|
||||
|
||||
urlpatterns = [
|
||||
path('', 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("persons/", views.PersonListView.as_view(), name="person_list"),
|
||||
path("persons/create/", views.PersonCreateView.as_view(), name="person_create"),
|
||||
path("persons/<slug:slug>/", views.PersonDetailView.as_view(), name="person_detail"),
|
||||
path("persons/<slug:slug>/update/", views.PersonUpdateView.as_view(), name="person_update"),
|
||||
path("persons/<slug:slug>/delete/", views.PersonDeleteView.as_view(), name="person_delete"),
|
||||
|
||||
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('jobs/<slug:slug>/download/cvs/', views.job_cvs_download, name='job_cvs_download'),
|
||||
@ -19,85 +28,250 @@ urlpatterns = [
|
||||
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.ApplicationListView.as_view(), name="candidate_list"
|
||||
),
|
||||
path(
|
||||
"candidates/create/",
|
||||
views_frontend.ApplicationCreateView.as_view(),
|
||||
name="candidate_create",
|
||||
),
|
||||
path(
|
||||
"candidates/create/<slug:slug>/",
|
||||
views_frontend.ApplicationCreateView.as_view(),
|
||||
name="candidate_create_for_job",
|
||||
),
|
||||
path(
|
||||
"jobs/<slug:slug>/candidates/",
|
||||
views_frontend.JobApplicationListView.as_view(),
|
||||
name="job_candidates_list",
|
||||
),
|
||||
path(
|
||||
"candidates/<slug:slug>/update/",
|
||||
views_frontend.ApplicationUpdateView.as_view(),
|
||||
name="candidate_update",
|
||||
),
|
||||
path(
|
||||
"candidates/<slug:slug>/delete/",
|
||||
views_frontend.ApplicationDeleteView.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'),
|
||||
@ -110,74 +284,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'),
|
||||
@ -185,35 +473,72 @@ 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/dashboard/",
|
||||
views.agency_portal_dashboard,
|
||||
name="agency_portal_dashboard",
|
||||
),
|
||||
path(
|
||||
"portal/persons/",
|
||||
views.agency_portal_persons_list,
|
||||
name="agency_portal_persons_list",
|
||||
),
|
||||
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'),
|
||||
@ -222,27 +547,63 @@ 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",
|
||||
),
|
||||
# Message URLs
|
||||
path("messages/", views.message_list, name="message_list"),
|
||||
path("messages/create/", views.message_create, name="message_create"),
|
||||
path("messages/<int:message_id>/", views.message_detail, name="message_detail"),
|
||||
path("messages/<int:message_id>/reply/", views.message_reply, name="message_reply"),
|
||||
path("messages/<int:message_id>/mark-read/", views.message_mark_read, name="message_mark_read"),
|
||||
path("messages/<int:message_id>/mark-unread/", views.message_mark_unread, name="message_mark_unread"),
|
||||
path("messages/<int:message_id>/delete/", views.message_delete, name="message_delete"),
|
||||
path("api/unread-count/", views.api_unread_count, name="api_unread_count"),
|
||||
|
||||
# Documents
|
||||
path("documents/upload/<int:application_id>/", views.document_upload, name="document_upload"),
|
||||
path("documents/<int:document_id>/delete/", views.document_delete, name="document_delete"),
|
||||
path("documents/<int:document_id>/download/", views.document_download, name="document_download"),
|
||||
path('jobs/<slug:job_slug>/candidates/compose_email/', views.compose_candidate_email, name='compose_candidate_email'),
|
||||
path('interview/partcipants/<slug:slug>/',views.create_interview_participants,name='create_interview_participants'),
|
||||
path('interview/email/<slug:slug>/',views.send_interview_email,name='send_interview_email'),
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# # --- SCHEDULED INTERVIEW URLS (New Centralized Management) ---
|
||||
# path('interview/list/', views.InterviewListView.as_view(), name='interview_list'),
|
||||
# path('interviews/<slug:slug>/', views.ScheduledInterviewDetailView.as_view(), name='scheduled_interview_detail'),
|
||||
# path('interviews/<slug:slug>/update/', views.ScheduledInterviewUpdateView.as_view(), name='update_scheduled_interview'),
|
||||
# path('interviews/<slug:slug>/delete/', views.ScheduledInterviewDeleteView.as_view(), name='delete_scheduled_interview'),
|
||||
|
||||
|
||||
|
||||
]
|
||||
|
||||
3513
recruitment/views.py
3513
recruitment/views.py
File diff suppressed because it is too large
Load Diff
@ -30,6 +30,9 @@ from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
import json
|
||||
|
||||
# Add imports for user type restrictions
|
||||
from recruitment.decorators import StaffRequiredMixin, staff_user_required
|
||||
|
||||
|
||||
from datastar_py.django import (
|
||||
DatastarResponse,
|
||||
@ -39,7 +42,7 @@ from datastar_py.django import (
|
||||
# from rich import print
|
||||
from rich.markdown import CodeBlock
|
||||
|
||||
class JobListView(LoginRequiredMixin, ListView):
|
||||
class JobListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
||||
model = models.JobPosting
|
||||
template_name = 'jobs/job_list.html'
|
||||
context_object_name = 'jobs'
|
||||
@ -47,7 +50,6 @@ class JobListView(LoginRequiredMixin, ListView):
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = super().get_queryset().order_by('-created_at')
|
||||
|
||||
# Handle search
|
||||
search_query = self.request.GET.get('search', '')
|
||||
if search_query:
|
||||
@ -58,24 +60,23 @@ class JobListView(LoginRequiredMixin, ListView):
|
||||
)
|
||||
|
||||
# Filter for non-staff users
|
||||
if not self.request.user.is_staff:
|
||||
queryset = queryset.filter(status='Published')
|
||||
# if not self.request.user.is_staff:
|
||||
# queryset = queryset.filter(status='Published')
|
||||
|
||||
status=self.request.GET.get('status')
|
||||
status = self.request.GET.get('status')
|
||||
if status:
|
||||
queryset=queryset.filter(status=status)
|
||||
queryset = queryset.filter(status=status)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['search_query'] = self.request.GET.get('search', '')
|
||||
context['lang'] = get_language()
|
||||
return context
|
||||
|
||||
|
||||
class JobCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
class JobCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = models.JobPosting
|
||||
form_class = forms.JobPostingForm
|
||||
template_name = 'jobs/create_job.html'
|
||||
@ -83,7 +84,7 @@ class JobCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
success_message = 'Job created successfully.'
|
||||
|
||||
|
||||
class JobUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
class JobUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = models.JobPosting
|
||||
form_class = forms.JobPostingForm
|
||||
template_name = 'jobs/edit_job.html'
|
||||
@ -92,27 +93,25 @@ class JobUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
|
||||
class JobDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
class JobDeleteView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
model = models.JobPosting
|
||||
template_name = 'jobs/partials/delete_modal.html'
|
||||
success_url = reverse_lazy('job_list')
|
||||
success_message = 'Job deleted successfully.'
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
class JobCandidatesListView(LoginRequiredMixin, ListView):
|
||||
model = models.Candidate
|
||||
class JobApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
||||
model = models.Application
|
||||
template_name = 'jobs/job_candidates_list.html'
|
||||
context_object_name = 'candidates'
|
||||
context_object_name = 'applications'
|
||||
paginate_by = 10
|
||||
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
# Get the job by slug
|
||||
self.job = get_object_or_404(models.JobPosting, slug=self.kwargs['slug'])
|
||||
|
||||
# Filter candidates for this specific job
|
||||
queryset = models.Candidate.objects.filter(job=self.job)
|
||||
queryset = models.Application.objects.filter(job=self.job)
|
||||
|
||||
if self.request.GET.get('stage'):
|
||||
stage=self.request.GET.get('stage')
|
||||
@ -132,7 +131,7 @@ class JobCandidatesListView(LoginRequiredMixin, ListView):
|
||||
|
||||
# Filter for non-staff users
|
||||
if not self.request.user.is_staff:
|
||||
return models.Candidate.objects.none() # Restrict for non-staff
|
||||
return models.Application.objects.none() # Restrict for non-staff
|
||||
|
||||
return queryset.order_by('-created_at')
|
||||
|
||||
@ -143,10 +142,10 @@ class JobCandidatesListView(LoginRequiredMixin, ListView):
|
||||
return context
|
||||
|
||||
|
||||
class CandidateListView(LoginRequiredMixin, ListView):
|
||||
model = models.Candidate
|
||||
class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
||||
model = models.Application
|
||||
template_name = 'recruitment/candidate_list.html'
|
||||
context_object_name = 'candidates'
|
||||
context_object_name = 'applications'
|
||||
paginate_by = 100
|
||||
|
||||
def get_queryset(self):
|
||||
@ -156,22 +155,22 @@ class CandidateListView(LoginRequiredMixin, ListView):
|
||||
search_query = self.request.GET.get('search', '')
|
||||
job = self.request.GET.get('job', '')
|
||||
stage = self.request.GET.get('stage', '')
|
||||
if search_query:
|
||||
queryset = queryset.filter(
|
||||
Q(first_name__icontains=search_query) |
|
||||
Q(last_name__icontains=search_query) |
|
||||
Q(email__icontains=search_query) |
|
||||
Q(phone__icontains=search_query) |
|
||||
Q(stage__icontains=search_query) |
|
||||
Q(job__title__icontains=search_query)
|
||||
)
|
||||
# if search_query:
|
||||
# queryset = queryset.filter(
|
||||
# Q(first_name__icontains=search_query) |
|
||||
# Q(last_name__icontains=search_query) |
|
||||
# Q(email__icontains=search_query) |
|
||||
# Q(phone__icontains=search_query) |
|
||||
# Q(stage__icontains=search_query) |
|
||||
# Q(job__title__icontains=search_query)
|
||||
# )
|
||||
if job:
|
||||
queryset = queryset.filter(job__slug=job)
|
||||
if stage:
|
||||
queryset = queryset.filter(stage=stage)
|
||||
# Filter for non-staff users
|
||||
if not self.request.user.is_staff:
|
||||
return models.Candidate.objects.none() # Restrict for non-staff
|
||||
# if not self.request.user.is_staff:
|
||||
# return models.Application.objects.none() # Restrict for non-staff
|
||||
|
||||
return queryset.order_by('-created_at')
|
||||
|
||||
@ -184,9 +183,9 @@ class CandidateListView(LoginRequiredMixin, ListView):
|
||||
return context
|
||||
|
||||
|
||||
class CandidateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = models.Candidate
|
||||
form_class = forms.CandidateForm
|
||||
class ApplicationCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = models.Application
|
||||
form_class = forms.ApplicationForm
|
||||
template_name = 'recruitment/candidate_create.html'
|
||||
success_url = reverse_lazy('candidate_list')
|
||||
success_message = 'Candidate created successfully.'
|
||||
@ -204,18 +203,23 @@ class CandidateCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
form.instance.job = job
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
if self.request.method == 'GET':
|
||||
context['person_form'] = forms.PersonForm()
|
||||
return context
|
||||
|
||||
class CandidateUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = models.Candidate
|
||||
form_class = forms.CandidateForm
|
||||
class ApplicationUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = models.Application
|
||||
form_class = forms.ApplicationForm
|
||||
template_name = 'recruitment/candidate_update.html'
|
||||
success_url = reverse_lazy('candidate_list')
|
||||
success_message = 'Candidate updated successfully.'
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
|
||||
class CandidateDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
model = models.Candidate
|
||||
class ApplicationDeleteView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
model = models.Application
|
||||
template_name = 'recruitment/candidate_delete.html'
|
||||
success_url = reverse_lazy('candidate_list')
|
||||
success_message = 'Candidate deleted successfully.'
|
||||
@ -225,28 +229,30 @@ class CandidateDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
def retry_scoring_view(request,slug):
|
||||
from django_q.tasks import async_task
|
||||
|
||||
candidate = get_object_or_404(models.Candidate, slug=slug)
|
||||
|
||||
application = get_object_or_404(models.Application, slug=slug)
|
||||
|
||||
async_task(
|
||||
'recruitment.tasks.handle_reume_parsing_and_scoring',
|
||||
candidate.pk,
|
||||
application.pk,
|
||||
hook='recruitment.hooks.callback_ai_parsing',
|
||||
sync=True,
|
||||
)
|
||||
return redirect('candidate_detail', slug=candidate.slug)
|
||||
)
|
||||
return redirect('candidate_detail', slug=application.slug)
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def training_list(request):
|
||||
materials = models.TrainingMaterial.objects.all().order_by('-created_at')
|
||||
return render(request, 'recruitment/training_list.html', {'materials': materials})
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def candidate_detail(request, slug):
|
||||
from rich.json import JSON
|
||||
candidate = get_object_or_404(models.Candidate, slug=slug)
|
||||
candidate = get_object_or_404(models.Application, slug=slug)
|
||||
try:
|
||||
parsed = ast.literal_eval(candidate.parsed_summary)
|
||||
except:
|
||||
@ -255,9 +261,10 @@ def candidate_detail(request, slug):
|
||||
# Create stage update form for staff users
|
||||
stage_form = None
|
||||
if request.user.is_staff:
|
||||
stage_form = forms.CandidateStageForm()
|
||||
|
||||
|
||||
stage_form = forms.ApplicationStageForm()
|
||||
|
||||
|
||||
|
||||
# parsed = JSON(json.dumps(parsed), indent=2, highlight=True, skip_keys=False, ensure_ascii=False, check_circular=True, allow_nan=True, default=None, sort_keys=False)
|
||||
# parsed = json_to_markdown_table([parsed])
|
||||
return render(request, 'recruitment/candidate_detail.html', {
|
||||
@ -268,31 +275,33 @@ def candidate_detail(request, slug):
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def candidate_resume_template_view(request, slug):
|
||||
"""Display formatted resume template for a candidate"""
|
||||
candidate = get_object_or_404(models.Candidate, slug=slug)
|
||||
application = get_object_or_404(models.Application, slug=slug)
|
||||
|
||||
if not request.user.is_staff:
|
||||
messages.error(request, _("You don't have permission to view this page."))
|
||||
return redirect('candidate_list')
|
||||
|
||||
return render(request, 'recruitment/candidate_resume_template.html', {
|
||||
'candidate': candidate
|
||||
'application': application
|
||||
})
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def candidate_update_stage(request, slug):
|
||||
"""Handle HTMX stage update requests"""
|
||||
candidate = get_object_or_404(models.Candidate, slug=slug)
|
||||
form = forms.CandidateStageForm(request.POST, instance=candidate)
|
||||
application = get_object_or_404(models.Application, slug=slug)
|
||||
form = forms.ApplicationStageForm(request.POST, instance=application)
|
||||
if form.is_valid():
|
||||
stage_value = form.cleaned_data['stage']
|
||||
candidate.stage = stage_value
|
||||
candidate.save(update_fields=['stage'])
|
||||
messages.success(request,"Candidate Stage Updated")
|
||||
return redirect("candidate_detail",slug=candidate.slug)
|
||||
application.stage = stage_value
|
||||
application.save(update_fields=['stage'])
|
||||
messages.success(request,"application Stage Updated")
|
||||
return redirect("candidate_detail",slug=application.slug)
|
||||
|
||||
class TrainingListView(LoginRequiredMixin, ListView):
|
||||
class TrainingListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
||||
model = models.TrainingMaterial
|
||||
template_name = 'recruitment/training_list.html'
|
||||
context_object_name = 'materials'
|
||||
@ -320,7 +329,7 @@ class TrainingListView(LoginRequiredMixin, ListView):
|
||||
return context
|
||||
|
||||
|
||||
class TrainingCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
class TrainingCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = models.TrainingMaterial
|
||||
form_class = forms.TrainingMaterialForm
|
||||
template_name = 'recruitment/training_create.html'
|
||||
@ -332,7 +341,7 @@ class TrainingCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class TrainingUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
class TrainingUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = models.TrainingMaterial
|
||||
form_class = forms.TrainingMaterialForm
|
||||
template_name = 'recruitment/training_update.html'
|
||||
@ -341,13 +350,13 @@ class TrainingUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
|
||||
class TrainingDetailView(LoginRequiredMixin, DetailView):
|
||||
class TrainingDetailView(LoginRequiredMixin, StaffRequiredMixin, DetailView):
|
||||
model = models.TrainingMaterial
|
||||
template_name = 'recruitment/training_detail.html'
|
||||
context_object_name = 'material'
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
class TrainingDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
class TrainingDeleteView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
model = models.TrainingMaterial
|
||||
template_name = 'recruitment/training_delete.html'
|
||||
success_url = reverse_lazy('training_list')
|
||||
@ -355,7 +364,7 @@ class TrainingDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
|
||||
|
||||
# IMPORTANT: Ensure 'models' correctly refers to your Django models file
|
||||
# Example: from . import models
|
||||
# Example: from . import models
|
||||
|
||||
# --- Constants ---
|
||||
SCORE_PATH = 'ai_analysis_data__analysis_data__match_score'
|
||||
@ -365,27 +374,28 @@ TARGET_TIME_TO_HIRE_DAYS = 45 # Used for the template visualization
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def dashboard_view(request):
|
||||
|
||||
|
||||
selected_job_pk = request.GET.get('selected_job_pk')
|
||||
today = timezone.now().date()
|
||||
|
||||
|
||||
# --- 1. BASE QUERYSETS & GLOBAL METRICS (UNFILTERED) ---
|
||||
|
||||
|
||||
all_jobs_queryset = models.JobPosting.objects.all().order_by('-created_at')
|
||||
all_candidates_queryset = models.Candidate.objects.all()
|
||||
all_candidates_queryset = models.Application.objects.all()
|
||||
|
||||
# Global KPI Card Metrics
|
||||
total_jobs_global = all_jobs_queryset.count()
|
||||
total_participants = models.Participants.objects.count()
|
||||
total_jobs_posted_linkedin = all_jobs_queryset.filter(linkedin_post_id__isnull=False).count()
|
||||
|
||||
|
||||
# Data for Job App Count Chart (always for ALL jobs)
|
||||
job_titles = [job.title for job in all_jobs_queryset]
|
||||
job_app_counts = [job.candidates.count() for job in all_jobs_queryset]
|
||||
job_app_counts = [job.applications.count() for job in all_jobs_queryset]
|
||||
|
||||
# --- 2. TIME SERIES: GLOBAL DAILY APPLICANTS ---
|
||||
|
||||
|
||||
# Group ALL candidates by creation date
|
||||
global_daily_applications_qs = all_candidates_queryset.annotate(
|
||||
date=TruncDate('created_at')
|
||||
@ -398,22 +408,22 @@ def dashboard_view(request):
|
||||
|
||||
|
||||
# --- 3. FILTERING LOGIC: Determine the scope for scoped metrics ---
|
||||
|
||||
|
||||
candidate_queryset = all_candidates_queryset
|
||||
job_scope_queryset = all_jobs_queryset
|
||||
interview_queryset = models.ScheduledInterview.objects.all()
|
||||
|
||||
|
||||
current_job = None
|
||||
if selected_job_pk:
|
||||
# Filter all base querysets
|
||||
candidate_queryset = candidate_queryset.filter(job__pk=selected_job_pk)
|
||||
interview_queryset = interview_queryset.filter(job__pk=selected_job_pk)
|
||||
|
||||
|
||||
try:
|
||||
current_job = all_jobs_queryset.get(pk=selected_job_pk)
|
||||
job_scope_queryset = models.JobPosting.objects.filter(pk=selected_job_pk)
|
||||
except models.JobPosting.DoesNotExist:
|
||||
pass
|
||||
pass
|
||||
|
||||
# --- 4. TIME SERIES: SCOPED DAILY APPLICANTS ---
|
||||
|
||||
@ -426,15 +436,15 @@ def dashboard_view(request):
|
||||
).values('date').annotate(
|
||||
count=Count('pk')
|
||||
).order_by('date')
|
||||
|
||||
|
||||
scoped_dates = [item['date'].strftime('%Y-%m-%d') for item in scoped_daily_applications_qs]
|
||||
scoped_counts = [item['count'] for item in scoped_daily_applications_qs]
|
||||
|
||||
|
||||
# --- 5. SCOPED CORE AGGREGATIONS (FILTERED OR ALL) ---
|
||||
|
||||
|
||||
total_candidates = candidate_queryset.count()
|
||||
|
||||
|
||||
candidates_with_score_query = candidate_queryset.filter(
|
||||
is_resume_parsed=True
|
||||
).annotate(
|
||||
@ -448,11 +458,11 @@ def dashboard_view(request):
|
||||
total_active_jobs = job_scope_queryset.filter(status="ACTIVE").count()
|
||||
last_week = timezone.now() - timedelta(days=7)
|
||||
new_candidates_7days = candidate_queryset.filter(created_at__gte=last_week).count()
|
||||
|
||||
|
||||
open_positions_agg = job_scope_queryset.filter(status="ACTIVE").aggregate(total_open=Sum('open_positions'))
|
||||
total_open_positions = open_positions_agg['total_open'] or 0
|
||||
average_applications_result = job_scope_queryset.annotate(
|
||||
candidate_count=Count('candidates', distinct=True)
|
||||
candidate_count=Count('applications', distinct=True)
|
||||
).aggregate(avg_apps=Avg('candidate_count'))['avg_apps']
|
||||
average_applications = round(average_applications_result or 0, 2)
|
||||
|
||||
@ -463,21 +473,24 @@ def dashboard_view(request):
|
||||
)
|
||||
|
||||
lst=[c.time_to_hire_days for c in hired_candidates]
|
||||
|
||||
|
||||
time_to_hire_query = hired_candidates.annotate(
|
||||
time_diff=ExpressionWrapper(
|
||||
F('hired_date') - F('created_at__date'),
|
||||
F('join_date') - F('created_at__date'),
|
||||
output_field=fields.DurationField()
|
||||
)
|
||||
).aggregate(avg_time_to_hire=Avg('time_diff'))
|
||||
|
||||
|
||||
|
||||
print(time_to_hire_query)
|
||||
|
||||
|
||||
|
||||
avg_time_to_hire_days = (
|
||||
time_to_hire_query.get('avg_time_to_hire').days
|
||||
time_to_hire_query.get('avg_time_to_hire').days
|
||||
if time_to_hire_query.get('avg_time_to_hire') else 0
|
||||
)
|
||||
|
||||
print(avg_time_to_hire_days)
|
||||
|
||||
applied_count = candidate_queryset.filter(stage='Applied').count()
|
||||
advanced_count = candidate_queryset.filter(stage__in=['Exam', 'Interview', 'Offer']).count()
|
||||
screening_pass_rate = round( (advanced_count / applied_count) * 100, 1 ) if applied_count > 0 else 0
|
||||
@ -493,8 +506,8 @@ def dashboard_view(request):
|
||||
meetings_scheduled_this_week = interview_queryset.filter(
|
||||
interview_date__week=current_week, interview_date__year=current_year
|
||||
).count()
|
||||
avg_match_score_result = candidates_with_score_query.aggregate(avg_score=Avg('annotated_match_score'))['avg_score']
|
||||
avg_match_score = round(avg_match_score_result or 0, 1)
|
||||
avg_match_score_result = candidates_with_score_query.aggregate(avg_score=Avg('annotated_match_score'))['avg_score']
|
||||
avg_match_score = round(avg_match_score_result or 0, 1)
|
||||
high_potential_count = candidates_with_score_query.filter(annotated_match_score__gte=HIGH_POTENTIAL_THRESHOLD).count()
|
||||
high_potential_ratio = round( (high_potential_count / total_candidates) * 100, 1 ) if total_candidates > 0 else 0
|
||||
total_scored_candidates = candidates_with_score_query.count()
|
||||
@ -506,15 +519,15 @@ def dashboard_view(request):
|
||||
# A. Pipeline Funnel (Scoped)
|
||||
stage_counts = candidate_queryset.values('stage').annotate(count=Count('stage'))
|
||||
stage_map = {item['stage']: item['count'] for item in stage_counts}
|
||||
candidate_stage = ['Applied', 'Exam', 'Interview', 'Offer', 'Hired']
|
||||
candidate_stage = ['Applied', 'Exam', 'Interview', 'Offer', 'Hired']
|
||||
candidates_count = [
|
||||
stage_map.get('Applied', 0), stage_map.get('Exam', 0), stage_map.get('Interview', 0),
|
||||
stage_map.get('Applied', 0), stage_map.get('Exam', 0), stage_map.get('Interview', 0),
|
||||
stage_map.get('Offer', 0), stage_map.get('Hired',0)
|
||||
]
|
||||
|
||||
|
||||
|
||||
# --- 7. GAUGE CHART CALCULATION (Time-to-Hire) ---
|
||||
|
||||
|
||||
current_days = avg_time_to_hire_days
|
||||
rotation_percent = current_days / MAX_TIME_TO_HIRE_DAYS if MAX_TIME_TO_HIRE_DAYS > 0 else 0
|
||||
rotation_degrees = rotation_percent * 180
|
||||
@ -524,20 +537,20 @@ def dashboard_view(request):
|
||||
hiring_source_counts = candidate_queryset.values('hiring_source').annotate(count=Count('stage'))
|
||||
source_map= {item['hiring_source']: item['count'] for item in hiring_source_counts}
|
||||
candidates_count_in_each_source = [
|
||||
source_map.get('Public', 0), source_map.get('Internal', 0), source_map.get('Agency', 0),
|
||||
|
||||
source_map.get('Public', 0), source_map.get('Internal', 0), source_map.get('Agency', 0),
|
||||
|
||||
]
|
||||
all_hiring_sources=["Public", "Internal", "Agency"]
|
||||
all_hiring_sources=["Public", "Internal", "Agency"]
|
||||
|
||||
|
||||
# --- 8. CONTEXT RETURN ---
|
||||
|
||||
|
||||
context = {
|
||||
# Global KPIs
|
||||
'total_jobs_global': total_jobs_global,
|
||||
'total_participants': total_participants,
|
||||
'total_jobs_posted_linkedin': total_jobs_posted_linkedin,
|
||||
|
||||
|
||||
# Scoped KPIs
|
||||
'total_active_jobs': total_active_jobs,
|
||||
'total_candidates': total_candidates,
|
||||
@ -549,16 +562,16 @@ def dashboard_view(request):
|
||||
'offers_accepted_rate': offers_accepted_rate,
|
||||
'vacancy_fill_rate': vacancy_fill_rate,
|
||||
'meetings_scheduled_this_week': meetings_scheduled_this_week,
|
||||
'avg_match_score': avg_match_score,
|
||||
'avg_match_score': avg_match_score,
|
||||
'high_potential_count': high_potential_count,
|
||||
'high_potential_ratio': high_potential_ratio,
|
||||
'scored_ratio': scored_ratio,
|
||||
|
||||
|
||||
# Chart Data
|
||||
'candidate_stage': json.dumps(candidate_stage),
|
||||
'candidates_count': json.dumps(candidates_count),
|
||||
'job_titles': json.dumps(job_titles),
|
||||
'job_app_counts': json.dumps(job_app_counts),
|
||||
'job_titles': json.dumps(job_titles),
|
||||
'job_app_counts': json.dumps(job_app_counts),
|
||||
# 'source_volume_chart_data' is intentionally REMOVED
|
||||
|
||||
# Time Series Data
|
||||
@ -572,7 +585,7 @@ def dashboard_view(request):
|
||||
'gauge_max_days': MAX_TIME_TO_HIRE_DAYS,
|
||||
'gauge_target_days': TARGET_TIME_TO_HIRE_DAYS,
|
||||
'gauge_rotation_degrees': rotation_degrees_final,
|
||||
|
||||
|
||||
# UI Control
|
||||
'jobs': all_jobs_queryset,
|
||||
'current_job_id': selected_job_pk,
|
||||
@ -582,11 +595,12 @@ def dashboard_view(request):
|
||||
'candidates_count_in_each_source': json.dumps(candidates_count_in_each_source),
|
||||
'all_hiring_sources': json.dumps(all_hiring_sources),
|
||||
}
|
||||
|
||||
|
||||
return render(request, 'recruitment/dashboard.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def candidate_offer_view(request, slug):
|
||||
"""View for candidates in the Offer stage"""
|
||||
job = get_object_or_404(models.JobPosting, slug=slug)
|
||||
@ -616,6 +630,7 @@ def candidate_offer_view(request, slug):
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def candidate_hired_view(request, slug):
|
||||
"""View for hired candidates"""
|
||||
job = get_object_or_404(models.JobPosting, slug=slug)
|
||||
@ -645,13 +660,16 @@ def candidate_hired_view(request, slug):
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def update_candidate_status(request, job_slug, candidate_slug, stage_type, status):
|
||||
"""Handle exam/interview/offer status updates"""
|
||||
from django.utils import timezone
|
||||
|
||||
job = get_object_or_404(models.JobPosting, slug=job_slug)
|
||||
candidate = get_object_or_404(models.Candidate, slug=candidate_slug, job=job)
|
||||
|
||||
candidate = get_object_or_404(models.Application, slug=candidate_slug, job=job)
|
||||
print(stage_type)
|
||||
print(status)
|
||||
print(request.method)
|
||||
if request.method == "POST":
|
||||
if stage_type == 'exam':
|
||||
candidate.exam_status = status
|
||||
@ -709,6 +727,7 @@ STAGE_CONFIG = {
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def export_candidates_csv(request, job_slug, stage):
|
||||
"""Export candidates for a specific stage as CSV"""
|
||||
job = get_object_or_404(models.JobPosting, slug=job_slug)
|
||||
@ -722,9 +741,9 @@ def export_candidates_csv(request, job_slug, stage):
|
||||
|
||||
# Filter candidates based on stage
|
||||
if stage == 'hired':
|
||||
candidates = job.candidates.filter(**config['filter'])
|
||||
candidates = job.applications.filter(**config['filter'])
|
||||
else:
|
||||
candidates = job.candidates.filter(**config['filter'])
|
||||
candidates = job.applications.filter(**config['filter'])
|
||||
|
||||
# Handle search if provided
|
||||
search_query = request.GET.get('search', '')
|
||||
@ -848,6 +867,7 @@ def export_candidates_csv(request, job_slug, stage):
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def sync_hired_candidates(request, job_slug):
|
||||
"""Sync hired candidates to external sources using Django-Q"""
|
||||
from django_q.tasks import async_task
|
||||
@ -886,6 +906,7 @@ def sync_hired_candidates(request, job_slug):
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def test_source_connection(request, source_id):
|
||||
"""Test connection to an external source"""
|
||||
from .candidate_sync_service import CandidateSyncService
|
||||
@ -920,6 +941,7 @@ def test_source_connection(request, source_id):
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def sync_task_status(request, task_id):
|
||||
"""Check the status of a sync task"""
|
||||
from django_q.models import Task
|
||||
@ -971,6 +993,7 @@ def sync_task_status(request, task_id):
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def sync_history(request, job_slug=None):
|
||||
"""View sync history and logs"""
|
||||
from .models import IntegrationLog
|
||||
@ -1005,7 +1028,7 @@ def sync_history(request, job_slug=None):
|
||||
|
||||
|
||||
#participants views
|
||||
class ParticipantsListView(LoginRequiredMixin, ListView):
|
||||
class ParticipantsListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
||||
model = models.Participants
|
||||
template_name = 'participants/participants_list.html'
|
||||
context_object_name = 'participants'
|
||||
@ -1034,13 +1057,13 @@ class ParticipantsListView(LoginRequiredMixin, ListView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['search_query'] = self.request.GET.get('search', '')
|
||||
return context
|
||||
class ParticipantsDetailView(LoginRequiredMixin, DetailView):
|
||||
class ParticipantsDetailView(LoginRequiredMixin, StaffRequiredMixin, DetailView):
|
||||
model = models.Participants
|
||||
template_name = 'participants/participants_detail.html'
|
||||
context_object_name = 'participant'
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
class ParticipantsCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
class ParticipantsCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = models.Participants
|
||||
form_class = forms.ParticipantsForm
|
||||
template_name = 'participants/participants_create.html'
|
||||
@ -1054,9 +1077,9 @@ class ParticipantsCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateVie
|
||||
# initial['jobs'] = [job]
|
||||
# return initial
|
||||
|
||||
|
||||
|
||||
class ParticipantsUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
|
||||
|
||||
class ParticipantsUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = models.Participants
|
||||
form_class = forms.ParticipantsForm
|
||||
template_name = 'participants/participants_create.html'
|
||||
@ -1064,9 +1087,9 @@ class ParticipantsUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateVie
|
||||
success_message = 'Participant updated successfully.'
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
class ParticipantsDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
class ParticipantsDeleteView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
model = models.Participants
|
||||
|
||||
|
||||
success_url = reverse_lazy('participants_list') # Redirect to the participants list after success
|
||||
success_message = 'Participant deleted successfully.'
|
||||
slug_url_kwarg = 'slug'
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
@ -122,6 +122,11 @@
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %} {% endcomment %}
|
||||
<li class="nav-item me-2">
|
||||
<a class="nav-link" href="{% url 'message_list' %}">
|
||||
<i class="fas fa-envelope"></i>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<button
|
||||
@ -237,7 +242,15 @@
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'candidate_list' %}active{% endif %}" href="{% url 'candidate_list' %}">
|
||||
<span class="d-flex align-items-center gap-2">
|
||||
{% include "icons/users.html" %}
|
||||
{% trans "Applicants" %}
|
||||
{% trans "Applications" %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item me-lg-4">
|
||||
<a class="nav-link {% if request.resolver_match.url_name == 'person_list' %}active{% endif %}" href="{% url 'person_list' %}">
|
||||
<span class="d-flex align-items-center gap-2">
|
||||
{% include "icons/users.html" %}
|
||||
{% trans "Person" %}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
@ -340,6 +353,7 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.6/bundles/datastar.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Navbar collapse auto-close on link click (Standard Mobile UX)
|
||||
@ -404,6 +418,23 @@
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Message Count JavaScript -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Update unread message count on page load
|
||||
fetch('/api/unread-count/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const badge = document.getElementById('unread-messages-badge');
|
||||
if (badge && data.unread_count > 0) {
|
||||
badge.textContent = data.unread_count;
|
||||
badge.style.display = 'inline-block';
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching unread count:', error));
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Notification JavaScript for Admin Users -->
|
||||
{% comment %} {% if request.user.is_authenticated and request.user.is_staff %}
|
||||
<script>
|
||||
|
||||
149
templates/includes/document_list.html
Normal file
149
templates/includes/document_list.html
Normal file
@ -0,0 +1,149 @@
|
||||
{% load static %}
|
||||
{% load file_filters %}
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white border-bottom d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0 text-primary">Documents</h5>
|
||||
<button
|
||||
type="button"
|
||||
class="btn bg-primary-theme text-white btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#documentUploadModal"
|
||||
>
|
||||
<i class="fas fa-plus me-2"></i>Upload Document
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Document Upload Modal -->
|
||||
<div class="modal fade" id="documentUploadModal" tabindex="-1" aria-labelledby="documentUploadModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="documentUploadModalLabel">Upload Document</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<form
|
||||
method="post"
|
||||
enctype="multipart/form-data"
|
||||
hx-post="{% url 'document_upload' candidate.id %}"
|
||||
hx-target="#documents-pane"
|
||||
hx-select="#documents-pane"
|
||||
hx-swap="outerHTML"
|
||||
hx-on::after-request="bootstrap.Modal.getInstance(document.getElementById('documentUploadModal')).hide()"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="documentType" class="form-label">Document Type</label>
|
||||
<select name="document_type" id="documentType" class="form-select">
|
||||
<option value="resume">Resume</option>
|
||||
<option value="cover_letter">Cover Letter</option>
|
||||
<option value="portfolio">Portfolio</option>
|
||||
<option value="certificate">Certificate</option>
|
||||
<option value="id_proof">ID Proof</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="documentFile" class="form-label">File</label>
|
||||
<input
|
||||
type="file"
|
||||
name="file"
|
||||
id="documentFile"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="documentDescription" class="form-label">Description</label>
|
||||
<textarea
|
||||
name="description"
|
||||
id="documentDescription"
|
||||
rows="3"
|
||||
class="form-control"
|
||||
placeholder="Optional description..."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-upload me-2"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Documents List -->
|
||||
<div class="card-body" id="document-list-container">
|
||||
{% if documents %}
|
||||
{% for document in documents %}
|
||||
<div class="d-flex justify-content-between align-items-center p-3 border-bottom hover-bg-light">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-file text-primary me-3"></i>
|
||||
<div>
|
||||
<div class="fw-medium text-dark">{{ document.get_document_type_display }}</div>
|
||||
<div class="small text-muted">{{ document.file.name|filename }}</div>
|
||||
{% if document.description %}
|
||||
<div class="small text-muted">{{ document.description }}</div>
|
||||
{% endif %}
|
||||
<div class="small text-muted">
|
||||
Uploaded by {{ document.uploaded_by.get_full_name|default:document.uploaded_by.username }} on {{ document.created_at|date:"M d, Y" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<a
|
||||
href="{% url 'document_download' document.id %}"
|
||||
class="btn btn-sm btn-outline-primary me-2"
|
||||
title="Download"
|
||||
>
|
||||
<i class="fas fa-download"></i>
|
||||
</a>
|
||||
|
||||
{% if user.is_superuser or candidate.job.assigned_to == user %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
onclick="confirmDelete({{ document.id }}, '{{ document.file.name|filename|default:"Document" }}')"
|
||||
title="Delete"
|
||||
>
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="fas fa-file-alt fa-3x mb-3"></i>
|
||||
<p class="mb-2">No documents uploaded yet.</p>
|
||||
<p class="small">Click "Upload Document" to add files for this candidate.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.hover-bg-light:hover {
|
||||
background-color: #f8f9fa;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function confirmDelete(documentId, fileName) {
|
||||
if (confirm(`Are you sure you want to delete "${fileName}"?`)) {
|
||||
htmx.ajax('POST', `{% url 'document_delete' 0 %}`.replace('0', documentId), {
|
||||
target: '#document-list-container',
|
||||
swap: 'innerHTML'
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -4,24 +4,24 @@
|
||||
|
||||
{# Helper to build the query string while excluding the 'page' parameter #}
|
||||
{% load url_extras %}
|
||||
|
||||
|
||||
{# Build a string of all current filters (e.g., &department=IT&type=FULL_TIME) #}
|
||||
{% add_get_params request.GET as filter_params %}
|
||||
{% with filter_params=filter_params %}
|
||||
|
||||
{% if page_obj.has_previous %}
|
||||
|
||||
|
||||
{# First Page Link #}
|
||||
<li class="page-item">
|
||||
<a class="page-link text-primary-theme"
|
||||
<a class="page-link text-primary-theme"
|
||||
href="?page=1{{ filter_params }}">
|
||||
First
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
{# Previous Page Link #}
|
||||
<li class="page-item">
|
||||
<a class="page-link text-primary-theme"
|
||||
<a class="page-link text-primary-theme"
|
||||
href="?page={{ page_obj.previous_page_number }}{{ filter_params }}">
|
||||
Previous
|
||||
</a>
|
||||
@ -36,26 +36,26 @@
|
||||
</li>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
|
||||
|
||||
{# Next Page Link #}
|
||||
<li class="page-item">
|
||||
<a class="page-link text-primary-theme"
|
||||
<a class="page-link text-primary-theme"
|
||||
href="?page={{ page_obj.next_page_number }}{{ filter_params }}">
|
||||
Next
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
||||
{# Last Page Link #}
|
||||
<li class="page-item">
|
||||
<a class="page-link text-primary-theme"
|
||||
<a class="page-link text-primary-theme"
|
||||
href="?page={{ page_obj.paginator.num_pages }}{{ filter_params }}">
|
||||
Last
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endwith %}
|
||||
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
@ -65,7 +65,7 @@
|
||||
padding: 10px 15px;
|
||||
background-color: var(--kaauh-teal-light);
|
||||
}
|
||||
|
||||
|
||||
/* FullCalendar Customization */
|
||||
#calendar {
|
||||
font-size: 0.9em;
|
||||
@ -87,7 +87,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-5">
|
||||
<h1 class="h3 page-header">
|
||||
<i class="fas fa-calendar-alt me-2 text-primary-theme"></i> Interview Schedule Preview: **{{ job.title }}**
|
||||
@ -98,13 +98,13 @@
|
||||
<div class="card-body p-4 p-lg-5">
|
||||
<h4 class="card-title-border">{% trans "Schedule Parameters" %}</h4>
|
||||
<div class="row g-4">
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
<p class="mb-2"><strong><i class="fas fa-clock me-2 text-primary-theme"></i> Working Hours:</strong> {{ start_time|time:"g:i A" }} to {{ end_time|time:"g:i A" }}</p>
|
||||
<p class="mb-2"><strong><i class="fas fa-hourglass-half me-2 text-primary-theme"></i> Interview Duration:</strong> {{ interview_duration }} minutes</p>
|
||||
<p class="mb-2"><strong><i class="fas fa-shield-alt me-2 text-primary-theme"></i> Buffer Time:</strong> {{ buffer_time }} minutes</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
<p class="mb-2"><strong><i class="fas fa-calendar-day me-2 text-primary-theme"></i> Interview Period:</strong> {{ start_date|date:"F j, Y" }} — {{ end_date|date:"F j, Y" }}</p>
|
||||
<p class="mb-2"><strong><i class="fas fa-list-check me-2 text-primary-theme"></i> Active Days:</strong>
|
||||
@ -122,7 +122,7 @@
|
||||
<p class="mb-2"><strong><i class="fas fa-calendar-day me-2 text-primary-theme"></i> Interview Type:</strong> {{interview_type}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<h5 class="mt-4 pt-3 border-top">{% trans "Daily Break Times" %}</h5>
|
||||
{% if breaks %}
|
||||
<div class="d-flex flex-wrap gap-3 mt-3">
|
||||
@ -162,9 +162,9 @@
|
||||
{% for item in schedule %}
|
||||
<tr>
|
||||
<td>{{ item.date|date:"F j, Y" }}</td>
|
||||
<td class="fw-bold text-primary-theme">{{ item.time|time:"g:i A" }}</td>
|
||||
<td>{{ item.candidate.name }}</td>
|
||||
<td>{{ item.candidate.email }}</td>
|
||||
<td>{{ item.time|time:"g:i A" }}</td>
|
||||
<td>{{ item.applications.name }}</td>
|
||||
<td>{{ item.applications.email }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -204,7 +204,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
start: '{{ item.date|date:"Y-m-d" }}T{{ item.time|time:"H:i:s" }}',
|
||||
url: '#',
|
||||
// Use the theme color for candidate events
|
||||
color: 'var(--kaauh-teal-dark)',
|
||||
color: 'var(--kaauh-teal-dark)',
|
||||
extendedProps: {
|
||||
email: '{{ item.candidate.email }}',
|
||||
time: '{{ item.time|time:"g:i A" }}'
|
||||
@ -214,7 +214,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
{% for break in breaks %}
|
||||
{
|
||||
title: 'Break',
|
||||
// FullCalendar requires a specific date for breaks, using start_date as a placeholder for daily breaks.
|
||||
// FullCalendar requires a specific date for breaks, using start_date as a placeholder for daily breaks.
|
||||
// Note: Breaks displayed on the monthly grid will only show on start_date, but weekly/daily view should reflect it daily if implemented correctly in the backend or using recurring events.
|
||||
start: '{{ start_date|date:"Y-m-d" }}T{{ break.start_time|time:"H:i:s" }}',
|
||||
end: '{{ start_date|date:"Y-m-d" }}T{{ break.end_time|time:"H:i:s" }}',
|
||||
|
||||
@ -130,9 +130,9 @@
|
||||
<label for="{{ form.candidates.id_for_label }}">
|
||||
{% trans "Candidates to Schedule (Hold Ctrl/Cmd to select multiple)" %}
|
||||
</label>
|
||||
{{ form.candidates }}
|
||||
{% if form.candidates.errors %}
|
||||
<div class="text-danger small mt-1">{{ form.candidates.errors }}</div>
|
||||
{{ form.applications }}
|
||||
{% if form.applications.errors %}
|
||||
<div class="text-danger small mt-1">{{ form.applications.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -285,7 +285,7 @@
|
||||
<th style="width: calc(50% / 7);">{% trans "Offer" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
||||
<tbody>
|
||||
{% for job in jobs %}
|
||||
<tr>
|
||||
|
||||
@ -319,7 +319,7 @@
|
||||
<td><strong class="text-primary"><a href="{% url 'meeting_details' meeting.slug %}" class="text-decoration-none text-secondary">{{ meeting.topic }}<a></strong></td>
|
||||
<td>
|
||||
{% if meeting.interview %}
|
||||
<a class="text-primary text-decoration-none" href="{% url 'candidate_detail' meeting.interview.candidate.slug %}">{{ meeting.interview.candidate.name }} <i class="fas fa-link"></i></a>
|
||||
<a class="text-primary text-decoration-none" href="{% url 'candidate_detail' meeting.interview.application.slug %}">{{ meeting.interview.candidate.name }} <i class="fas fa-link"></i></a>
|
||||
{% else %}
|
||||
<button data-bs-toggle="modal"
|
||||
data-bs-target="#meetingModal"
|
||||
|
||||
@ -249,9 +249,9 @@ body {
|
||||
<div class="p-3 bg-white rounded shadow-sm h-100 d-flex flex-column">
|
||||
<h2 class="text-start"><i class="fas fa-briefcase me-2"></i> {% trans "Interview Detail" %}</h2>
|
||||
<div class="detail-row-group flex-grow-1">
|
||||
<div class="detail-row-simple"><div class="detail-label-simple">{% trans "Job Title" %}:</div><div class="detail-value-simple"><a class="text-decoration-none text-dark" href="{% url 'job_detail' meeting.get_job.slug %}">{{ meeting.get_job.title|default:"N/A" }}</a></div></div>
|
||||
<div class="detail-row-simple"><div class="detail-label-simple">{% trans "Candidate Name" %}:</div><div class="detail-value-simple"><a class="text-decoration-none text-dark" href="{% url 'candidate_detail' meeting.get_candidate.slug %}">{{ meeting.get_candidate.name|default:"N/A" }}</a></div></div>
|
||||
<div class="detail-row-simple"><div class="detail-label-simple">{% trans "Candidate Email" %}:</div><div class="detail-value-simple"><a class="text-decoration-none text-dark" href="{% url 'candidate_detail' meeting.get_candidate.slug %}">{{ meeting.get_candidate.email|default:"N/A" }}</a></div></div>
|
||||
<div class="detail-row-simple"><div class="detail-label-simple">{% trans "Job Title" %}:</div><div class="detail-value-simple">{{ meeting.get_job.title|default:"N/A" }}</div></div>
|
||||
<div class="detail-row-simple"><div class="detail-label-simple">{% trans "Candidate Name" %}:</div><div class="detail-value-simple"><a href="">{{ meeting.candidate_full_name|default:"N/A" }}</a></div></div>
|
||||
<div class="detail-row-simple"><div class="detail-label-simple">{% trans "Candidate Email" %}:</div><div class="detail-value-simple"><a href="">{{ meeting.get_candidate.email|default:"N/A" }}</a></div></div>
|
||||
<div class="detail-row-simple"><div class="detail-label-simple">{% trans "Job Type" %}:</div><div class="detail-value-simple">{{ meeting.get_job.job_type|default:"N/A" }}</div></div>
|
||||
{% if meeting.get_candidate.belong_to_agency %}
|
||||
<div class="detail-row-simple"><div class="detail-label-simple">{% trans "Agency" %}:</div><div class="detail-value-simple"><a href="">{{ meeting.get_candidate.hiring_agency.name|default:"N/A" }}</a></div></div>
|
||||
@ -471,15 +471,15 @@ body {
|
||||
<form method="post" action="{% url 'create_interview_participants' meeting.interview.slug %}">
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
|
||||
<div class="modal-body table-responsive">
|
||||
|
||||
{{ meeting.name }}
|
||||
|
||||
<hr>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<table class="table tab table-bordered mt-3">
|
||||
<thead>
|
||||
<th class="col">👥 {% trans "Participants" %}</th>
|
||||
@ -487,7 +487,7 @@ body {
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{{ form.participants.errors }}
|
||||
@ -498,7 +498,7 @@ body {
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
@ -525,7 +525,7 @@ body {
|
||||
<form method="post" action="{% url 'send_interview_email' meeting.interview.slug %}">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ email_form.subject.id_for_label }}" class="form-label fw-bold">Subject</label>
|
||||
{{ email_form.subject | add_class:"form-control" }}
|
||||
@ -551,18 +551,18 @@ body {
|
||||
</ul>
|
||||
|
||||
<div class="tab-content border border-top-0 p-3 bg-light-subtle">
|
||||
|
||||
|
||||
{# --- Candidate/Agency Pane --- #}
|
||||
<div class="tab-pane fade show active" id="candidate-pane" role="tabpanel" aria-labelledby="candidate-tab">
|
||||
<p class="text-muted small">{% trans "This email will be sent to the candidate or their hiring agency." %}</p>
|
||||
|
||||
|
||||
{% if not candidate.belong_to_an_agency %}
|
||||
<div class="form-group">
|
||||
<label for="{{ email_form.message_for_candidate.id_for_label }}" class="form-label d-none">{% trans "Candidate Message" %}</label>
|
||||
{{ email_form.message_for_candidate | add_class:"form-control" }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if candidate.belong_to_an_agency %}
|
||||
<div class="form-group">
|
||||
<label for="{{ email_form.message_for_agency.id_for_label }}" class="form-label d-none">{% trans "Agency Message" %}</label>
|
||||
|
||||
179
templates/messages/message_detail.html
Normal file
179
templates/messages/message_detail.html
Normal file
@ -0,0 +1,179 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ message.subject }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<!-- Message Header -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
{{ message.subject }}
|
||||
{% if message.parent_message %}
|
||||
<span class="badge bg-secondary ms-2">Reply</span>
|
||||
{% endif %}
|
||||
</h5>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'message_reply' message.id %}" class="btn btn-outline-info">
|
||||
<i class="fas fa-reply"></i> Reply
|
||||
</a>
|
||||
{% if message.recipient == request.user %}
|
||||
<a href="{% url 'message_mark_unread' message.id %}"
|
||||
class="btn btn-outline-warning"
|
||||
hx-post="{% url 'message_mark_unread' message.id %}">
|
||||
<i class="fas fa-envelope"></i> Mark Unread
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'message_delete' message.id %}"
|
||||
class="btn btn-outline-danger"
|
||||
hx-get="{% url 'message_delete' message.id %}"
|
||||
hx-confirm="Are you sure you want to delete this message?">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</a>
|
||||
<a href="{% url 'message_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Messages
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<strong>From:</strong>
|
||||
<span class="text-primary">{{ message.sender.get_full_name|default:message.sender.username }}</span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>To:</strong>
|
||||
<span class="text-primary">{{ message.recipient.get_full_name|default:message.recipient.username }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<strong>Type:</strong>
|
||||
<span class="badge bg-{{ message.message_type|lower }}">
|
||||
{{ message.get_message_type_display }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>Status:</strong>
|
||||
{% if message.is_read %}
|
||||
<span class="badge bg-success">Read</span>
|
||||
{% if message.read_at %}
|
||||
<small class="text-muted">({{ message.read_at|date:"M d, Y H:i" }})</small>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge bg-warning">Unread</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<strong>Created:</strong>
|
||||
<span>{{ message.created_at|date:"M d, Y H:i" }}</span>
|
||||
</div>
|
||||
{% if message.job %}
|
||||
<div class="col-md-6">
|
||||
<strong>Related Job:</strong>
|
||||
<a href="{% url 'job_detail' message.job.slug %}" class="text-primary">
|
||||
{{ message.job.title }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if message.parent_message %}
|
||||
<div class="alert alert-info">
|
||||
<strong>In reply to:</strong>
|
||||
<a href="{% url 'message_detail' message.parent_message.id %}">
|
||||
{{ message.parent_message.subject }}
|
||||
</a>
|
||||
<small class="text-muted d-block">
|
||||
From {{ message.parent_message.sender.get_full_name|default:message.parent_message.sender.username }}
|
||||
on {{ message.parent_message.created_at|date:"M d, Y H:i" }}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Content -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Message</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="message-content">
|
||||
{{ message.content|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Thread (if this is a reply and has replies) -->
|
||||
{% if message.replies.all %}
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-comments"></i> Replies ({{ message.replies.count }})
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for reply in message.replies.all %}
|
||||
<div class="border-start ps-3 mb-3">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<div>
|
||||
<strong>{{ reply.sender.get_full_name|default:reply.sender.username }}</strong>
|
||||
<small class="text-muted ms-2">
|
||||
{{ reply.created_at|date:"M d, Y H:i" }}
|
||||
</small>
|
||||
</div>
|
||||
<span class="badge bg-{{ reply.message_type|lower }}">
|
||||
{{ reply.get_message_type_display }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="reply-content">
|
||||
{{ reply.content|linebreaks }}
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<a href="{% url 'message_reply' reply.id %}" class="btn btn-sm btn-outline-info">
|
||||
<i class="fas fa-reply"></i> Reply to this
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.message-content {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.6;
|
||||
padding: 1rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.reply-content {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
line-height: 1.5;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.border-start {
|
||||
border-left: 3px solid #0d6efd;
|
||||
}
|
||||
|
||||
.ps-3 {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
237
templates/messages/message_form.html
Normal file
237
templates/messages/message_form.html
Normal file
@ -0,0 +1,237 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{% if form.instance.pk %}Reply to Message{% else %}Compose Message{% endif %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
{% if form.instance.pk %}
|
||||
<i class="fas fa-reply"></i> Reply to Message
|
||||
{% else %}
|
||||
<i class="fas fa-envelope"></i> Compose Message
|
||||
{% endif %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if form.instance.parent_message %}
|
||||
<div class="alert alert-info mb-4">
|
||||
<strong>Replying to:</strong> {{ form.instance.parent_message.subject }}
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
From {{ form.instance.parent_message.sender.get_full_name|default:form.instance.parent_message.sender.username }}
|
||||
on {{ form.instance.parent_message.created_at|date:"M d, Y H:i" }}
|
||||
</small>
|
||||
<div class="mt-2">
|
||||
<strong>Original message:</strong>
|
||||
<div class="border-start ps-3 mt-2">
|
||||
{{ form.instance.parent_message.content|linebreaks }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" id="messageForm">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.recipient.id_for_label }}" class="form-label">
|
||||
Recipient <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.recipient }}
|
||||
{% if form.recipient.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.recipient.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-text">
|
||||
Select the user who will receive this message
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.message_type.id_for_label }}" class="form-label">
|
||||
Message Type <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.message_type }}
|
||||
{% if form.message_type.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.message_type.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-text">
|
||||
Select the type of message you're sending
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.subject.id_for_label }}" class="form-label">
|
||||
Subject <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.subject }}
|
||||
{% if form.subject.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.subject.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.job.id_for_label }}" class="form-label">
|
||||
Related Job
|
||||
</label>
|
||||
{{ form.job }}
|
||||
{% if form.job.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.job.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-text">
|
||||
Optional: Select a job if this message is related to a specific position
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.content.id_for_label }}" class="form-label">
|
||||
Message <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.content }}
|
||||
{% if form.content.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{{ form.content.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-text">
|
||||
Write your message here. You can use line breaks and basic formatting.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'message_list' %}" class="btn btn-secondary">
|
||||
<i class="fas fa-times"></i> Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
{% if form.instance.pk %}
|
||||
Send Reply
|
||||
{% else %}
|
||||
Send Message
|
||||
{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
#id_content {
|
||||
min-height: 200px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
{% if form.recipient.field.widget.attrs.disabled %}
|
||||
background-color: #f8f9fa;
|
||||
{% endif %}
|
||||
}
|
||||
|
||||
.border-start {
|
||||
border-left: 3px solid #0d6efd;
|
||||
}
|
||||
|
||||
.ps-3 {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Auto-resize textarea based on content
|
||||
const textarea = document.getElementById('id_content');
|
||||
if (textarea) {
|
||||
textarea.addEventListener('input', function() {
|
||||
this.style.height = 'auto';
|
||||
this.style.height = (this.scrollHeight) + 'px';
|
||||
});
|
||||
|
||||
// Set initial height
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = (textarea.scrollHeight) + 'px';
|
||||
}
|
||||
|
||||
// Character counter for subject
|
||||
const subjectField = document.getElementById('id_subject');
|
||||
const maxLength = 200;
|
||||
|
||||
if (subjectField) {
|
||||
// Add character counter display
|
||||
const counter = document.createElement('small');
|
||||
counter.className = 'text-muted';
|
||||
counter.style.float = 'right';
|
||||
subjectField.parentNode.appendChild(counter);
|
||||
|
||||
function updateCounter() {
|
||||
const remaining = maxLength - subjectField.value.length;
|
||||
counter.textContent = `${subjectField.value.length}/${maxLength} characters`;
|
||||
if (remaining < 20) {
|
||||
counter.className = 'text-warning';
|
||||
} else {
|
||||
counter.className = 'text-muted';
|
||||
}
|
||||
}
|
||||
|
||||
subjectField.addEventListener('input', updateCounter);
|
||||
updateCounter();
|
||||
}
|
||||
|
||||
// Form validation before submit
|
||||
const form = document.getElementById('messageForm');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const content = document.getElementById('id_content').value.trim();
|
||||
const subject = document.getElementById('id_subject').value.trim();
|
||||
const recipient = document.getElementById('id_recipient').value;
|
||||
|
||||
if (!recipient) {
|
||||
e.preventDefault();
|
||||
alert('Please select a recipient.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subject) {
|
||||
e.preventDefault();
|
||||
alert('Please enter a subject.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!content) {
|
||||
e.preventDefault();
|
||||
alert('Please enter a message.');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
230
templates/messages/message_list.html
Normal file
230
templates/messages/message_list.html
Normal file
@ -0,0 +1,230 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Messages{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h4 class="mb-0">Messages</h4>
|
||||
<a href="{% url 'message_create' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-plus"></i> Compose Message
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label for="status" class="form-label">Status</label>
|
||||
<select name="status" id="status" class="form-select">
|
||||
<option value="">All Status</option>
|
||||
<option value="read" {% if status_filter == 'read' %}selected{% endif %}>Read</option>
|
||||
<option value="unread" {% if status_filter == 'unread' %}selected{% endif %}>Unread</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="type" class="form-label">Type</label>
|
||||
<select name="type" id="type" class="form-select">
|
||||
<option value="">All Types</option>
|
||||
<option value="GENERAL" {% if type_filter == 'GENERAL' %}selected{% endif %}>General</option>
|
||||
<option value="JOB_RELATED" {% if type_filter == 'JOB_RELATED' %}selected{% endif %}>Job Related</option>
|
||||
<option value="INTERVIEW" {% if type_filter == 'INTERVIEW' %}selected{% endif %}>Interview</option>
|
||||
<option value="OFFER" {% if type_filter == 'OFFER' %}selected{% endif %}>Offer</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="q" class="form-label">Search</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" id="q" class="form-control"
|
||||
value="{{ search_query }}" placeholder="Search messages...">
|
||||
<button class="btn btn-outline-secondary" type="submit">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label"> </label>
|
||||
<button type="submit" class="btn btn-secondary w-100">Filter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Total Messages</h6>
|
||||
<h3 class="text-primary">{{ total_messages }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card bg-light">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">Unread Messages</h6>
|
||||
<h3 class="text-warning">{{ unread_messages }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Messages List -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Subject</th>
|
||||
<th>Sender</th>
|
||||
<th>Recipient</th>
|
||||
<th>Type</th>
|
||||
<th>Status</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for message in page_obj %}
|
||||
<tr class="{% if not message.is_read %}table-warning{% endif %}">
|
||||
<td>
|
||||
<a href="{% url 'message_detail' message.id %}"
|
||||
class="{% if not message.is_read %}fw-bold{% endif %}">
|
||||
{{ message.subject }}
|
||||
</a>
|
||||
{% if message.parent_message %}
|
||||
<span class="badge bg-secondary ms-2">Reply</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ message.sender.get_full_name|default:message.sender.username }}</td>
|
||||
<td>{{ message.recipient.get_full_name|default:message.recipient.username }}</td>
|
||||
<td>
|
||||
<span>
|
||||
{{ message.get_message_type_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if message.is_read %}
|
||||
<span class="badge bg-primary-theme">Read</span>
|
||||
{% else %}
|
||||
<span class="badge bg-warning">Unread</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ message.created_at|date:"M d, Y H:i" }}</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'message_detail' message.id %}"
|
||||
class="btn btn-sm btn-outline-primary" title="View">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
{% if not message.is_read and message.recipient == request.user %}
|
||||
<a href="{% url 'message_mark_read' message.id %}"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
hx-post="{% url 'message_mark_read' message.id %}"
|
||||
title="Mark as Read">
|
||||
<i class="fas fa-check"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'message_reply' message.id %}"
|
||||
class="btn btn-sm btn-outline-primary" title="Reply">
|
||||
<i class="fas fa-reply"></i>
|
||||
</a>
|
||||
<a href="{% url 'message_delete' message.id %}"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
hx-get="{% url 'message_delete' message.id %}"
|
||||
hx-confirm="Are you sure you want to delete this message?"
|
||||
title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted">
|
||||
<i class="fas fa-inbox fa-3x mb-3"></i>
|
||||
<p class="mb-0">No messages found.</p>
|
||||
<p class="small">Try adjusting your filters or compose a new message.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="Message pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ num }}</span>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ num }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">{{ num }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}&status={{ status_filter }}&type={{ type_filter }}&q={{ search_query }}">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="fas fa-inbox fa-3x mb-3"></i>
|
||||
<p class="mb-0">No messages found.</p>
|
||||
<p class="small">Try adjusting your filters or compose a new message.</p>
|
||||
<a href="{% url 'message_create' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-plus"></i> Compose Message
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Auto-refresh unread count every 30 seconds
|
||||
setInterval(() => {
|
||||
fetch('/api/unread-count/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Update unread count in navigation if it exists
|
||||
const unreadBadge = document.querySelector('.unread-messages-count');
|
||||
if (unreadBadge) {
|
||||
unreadBadge.textContent = data.unread_count;
|
||||
unreadBadge.style.display = data.unread_count > 0 ? 'inline-block' : 'none';
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching unread count:', error));
|
||||
}, 30000);
|
||||
</script>
|
||||
{% endblock %}
|
||||
444
templates/people/create_person.html
Normal file
444
templates/people/create_person.html
Normal file
@ -0,0 +1,444 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}Create Person - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* UI Variables for the KAAT-S Theme */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-gray-light: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Form Container Styling */
|
||||
.form-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Card Styling */
|
||||
.card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
/* Main Action Button Style */
|
||||
.btn-main-action {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 1.5rem;
|
||||
}
|
||||
|
||||
.btn-main-action:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* Secondary Button Style */
|
||||
.btn-outline-secondary {
|
||||
color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal);
|
||||
}
|
||||
.btn-outline-secondary:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
color: white;
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
}
|
||||
|
||||
/* Form Field Styling */
|
||||
.form-control:focus {
|
||||
border-color: var(--kaauh-teal);
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
border-color: var(--kaauh-teal);
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||
}
|
||||
|
||||
/* Profile Image Upload Styling */
|
||||
.profile-image-upload {
|
||||
border: 2px dashed var(--kaauh-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile-image-upload:hover {
|
||||
border-color: var(--kaauh-teal);
|
||||
background-color: var(--kaauh-gray-light);
|
||||
}
|
||||
|
||||
.profile-image-preview {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--kaauh-teal);
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
/* Breadcrumb Styling */
|
||||
.breadcrumb {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: ">";
|
||||
color: var(--kaauh-teal);
|
||||
}
|
||||
|
||||
/* Alert Styling */
|
||||
.alert {
|
||||
border-radius: 0.5rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.btn.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.btn.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: auto;
|
||||
border: 2px solid transparent;
|
||||
border-top-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="form-container">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'person_list' %}" class="text-decoration-none">
|
||||
<i class="fas fa-user-friends me-1"></i> {% trans "People" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{% trans "Create Person" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-user-plus me-2"></i> {% trans "Create New Person" %}
|
||||
</h1>
|
||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h5 class="alert-heading">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
||||
</h5>
|
||||
{% for error in form.non_field_errors %}
|
||||
<p class="mb-0">{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data" id="person-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Profile Image Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="profile-image-upload" onclick="document.getElementById('id_profile_image').click()">
|
||||
<div id="image-preview-container">
|
||||
<i class="fas fa-camera fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">{% trans "Upload Profile Photo" %}</h5>
|
||||
<p class="text-muted small">{% trans "Click to browse or drag and drop" %}</p>
|
||||
</div>
|
||||
<input type="file" name="profile_image" id="id_profile_image"
|
||||
class="d-none" accept="image/*">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Personal Information Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fas fa-user me-2"></i> {% trans "Personal Information" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.first_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.middle_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.last_name|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fas fa-envelope me-2"></i> {% trans "Contact Information" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ form.email|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ form.phone|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Information Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fas fa-info-circle me-2"></i> {% trans "Additional Information" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.date_of_birth|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.nationality|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.gender|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fas fa-map-marker-alt me-2"></i> {% trans "Address" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
{{ form.address }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LinkedIn Profile Section -->
|
||||
{% comment %} <div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fab fa-linkedin me-2"></i> {% trans "Professional Profile" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-group mb-3">
|
||||
<label for="id_linkedin_profile" class="form-label">
|
||||
{% trans "LinkedIn Profile URL" %}
|
||||
</label>
|
||||
<input type="url" name="linkedin_profile" id="id_linkedin_profile"
|
||||
class="form-control" placeholder="https://linkedin.com/in/username">
|
||||
<small class="form-text text-muted">
|
||||
{% trans "Optional: Add LinkedIn profile URL" %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||
</a>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="reset" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-undo me-1"></i> {% trans "Reset" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-save me-1"></i> {% trans "Create Person" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Profile Image Preview
|
||||
const profileImageInput = document.getElementById('id_profile_image');
|
||||
const imagePreviewContainer = document.getElementById('image-preview-container');
|
||||
|
||||
profileImageInput.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
imagePreviewContainer.innerHTML = `
|
||||
<img src="${e.target.result}" alt="Profile Preview" class="profile-image-preview">
|
||||
<h5 class="text-muted mt-3">${file.name}</h5>
|
||||
<p class="text-muted small">{% trans "Click to change photo" %}</p>
|
||||
`;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
|
||||
// Form Validation
|
||||
const form = document.getElementById('person-form');
|
||||
form.addEventListener('submit', function(e) {
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// Basic validation
|
||||
const firstName = document.getElementById('id_first_name').value.trim();
|
||||
const lastName = document.getElementById('id_last_name').value.trim();
|
||||
const email = document.getElementById('id_email').value.trim();
|
||||
|
||||
if (!firstName || !lastName) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.disabled = false;
|
||||
alert('{% trans "First name and last name are required." %}');
|
||||
return;
|
||||
}
|
||||
|
||||
if (email && !isValidEmail(email)) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.disabled = false;
|
||||
alert('{% trans "Please enter a valid email address." %}');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Email validation helper
|
||||
function isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
// LinkedIn URL validation
|
||||
const linkedinInput = document.getElementById('id_linkedin_profile');
|
||||
linkedinInput.addEventListener('blur', function() {
|
||||
const value = this.value.trim();
|
||||
if (value && !isValidLinkedInURL(value)) {
|
||||
this.classList.add('is-invalid');
|
||||
if (!this.nextElementSibling || !this.nextElementSibling.classList.contains('invalid-feedback')) {
|
||||
const feedback = document.createElement('div');
|
||||
feedback.className = 'invalid-feedback';
|
||||
feedback.textContent = '{% trans "Please enter a valid LinkedIn URL" %}';
|
||||
this.parentNode.appendChild(feedback);
|
||||
}
|
||||
} else {
|
||||
this.classList.remove('is-invalid');
|
||||
const feedback = this.parentNode.querySelector('.invalid-feedback');
|
||||
if (feedback) feedback.remove();
|
||||
}
|
||||
});
|
||||
|
||||
function isValidLinkedInURL(url) {
|
||||
const linkedinRegex = /^https?:\/\/(www\.)?linkedin\.com\/.+/i;
|
||||
return linkedinRegex.test(url);
|
||||
}
|
||||
|
||||
// Drag and Drop functionality
|
||||
const uploadArea = document.querySelector('.profile-image-upload');
|
||||
|
||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, preventDefaults, false);
|
||||
});
|
||||
|
||||
function preventDefaults(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, highlight, false);
|
||||
});
|
||||
|
||||
['dragleave', 'drop'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, unhighlight, false);
|
||||
});
|
||||
|
||||
function highlight(e) {
|
||||
uploadArea.style.borderColor = 'var(--kaauh-teal)';
|
||||
uploadArea.style.backgroundColor = 'var(--kaauh-gray-light)';
|
||||
}
|
||||
|
||||
function unhighlight(e) {
|
||||
uploadArea.style.borderColor = 'var(--kaauh-border)';
|
||||
uploadArea.style.backgroundColor = 'transparent';
|
||||
}
|
||||
|
||||
uploadArea.addEventListener('drop', handleDrop, false);
|
||||
|
||||
function handleDrop(e) {
|
||||
const dt = e.dataTransfer;
|
||||
const files = dt.files;
|
||||
|
||||
if (files.length > 0) {
|
||||
profileImageInput.files = files;
|
||||
const event = new Event('change', { bubbles: true });
|
||||
profileImageInput.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" />
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('.select2').select2();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
607
templates/people/person_detail.html
Normal file
607
templates/people/person_detail.html
Normal file
@ -0,0 +1,607 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{{ person.get_full_name }} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* UI Variables for the KAAT-S Theme */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-gray-light: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Profile Header Styling */
|
||||
.profile-header {
|
||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
||||
color: white;
|
||||
border-radius: 0.75rem;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.profile-image-large {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
border: 4px solid white;
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Card Styling */
|
||||
.card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Main Action Button Style */
|
||||
.btn-main-action {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.btn-main-action:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* Secondary Button Style */
|
||||
.btn-outline-secondary {
|
||||
color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal);
|
||||
}
|
||||
.btn-outline-secondary:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
color: white;
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
}
|
||||
|
||||
/* Info Section Styling */
|
||||
.info-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.info-section h5 {
|
||||
color: var(--kaauh-teal-dark);
|
||||
border-bottom: 2px solid var(--kaauh-teal);
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.info-item:hover {
|
||||
background-color: var(--kaauh-gray-light);
|
||||
}
|
||||
|
||||
.info-item i {
|
||||
color: var(--kaauh-teal);
|
||||
width: 20px;
|
||||
margin-right: 1rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: var(--kaauh-teal-dark);
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #495057;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* Badge Styling */
|
||||
.badge {
|
||||
font-weight: 600;
|
||||
padding: 0.4em 0.7em;
|
||||
border-radius: 0.3rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Related Items Styling */
|
||||
.related-item {
|
||||
border-left: 3px solid var(--kaauh-teal);
|
||||
padding-left: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.related-item:hover {
|
||||
border-left-color: var(--kaauh-teal-dark);
|
||||
background-color: var(--kaauh-gray-light);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
/* Breadcrumb Styling */
|
||||
.breadcrumb {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: ">";
|
||||
color: var(--kaauh-teal);
|
||||
}
|
||||
|
||||
/* Empty State Styling */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 3rem;
|
||||
color: var(--kaauh-teal);
|
||||
opacity: 0.5;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Status Indicator */
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.profile-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profile-image-large {
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
min-width: auto;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'person_list' %}" class="text-decoration-none">
|
||||
<i class="fas fa-user-friends me-1"></i> {% trans "People" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ person.get_full_name }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Profile Header -->
|
||||
<div class="profile-header">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-3 text-center">
|
||||
{% if person.profile_image %}
|
||||
<img src="{{ person.profile_image.url }}" alt="{{ person.get_full_name }}"
|
||||
class="profile-image-large">
|
||||
{% else %}
|
||||
<div class="profile-image-large d-flex align-items-center justify-content-center bg-white">
|
||||
<i class="fas fa-user text-muted fa-3x"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<h1 class="display-5 fw-bold mb-2">{{ person.get_full_name }}</h1>
|
||||
{% if person.email %}
|
||||
<p class="lead mb-3">
|
||||
<i class="fas fa-envelope me-2"></i>{{ person.email }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
{% if person.nationality %}
|
||||
<span class="badge bg-light text-dark">
|
||||
<i class="fas fa-globe me-1"></i>{{ person.nationality }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if person.gender %}
|
||||
<span class="badge bg-info">
|
||||
{% if person.gender == 'M' %}{% trans "Male" %}{% else %}{% trans "Female" %}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if person.user %}
|
||||
<span class="badge bg-success">
|
||||
<i class="fas fa-user-check me-1"></i>{% trans "User Account" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if user.is_staff %}
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'person_update' person.slug %}" class="btn btn-light">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Person" %}
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-light"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-delete-url="{% url 'person_delete' person.slug %}"
|
||||
data-item-name="{{ person.get_full_name }}">
|
||||
<i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Personal Information Column -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="info-section">
|
||||
<h5><i class="fas fa-user me-2"></i>{% trans "Personal Information" %}</h5>
|
||||
|
||||
<div class="info-item">
|
||||
<i class="fas fa-signature"></i>
|
||||
<span class="info-label">{% trans "Full Name" %}:</span>
|
||||
<span class="info-value">{{ person.get_full_name }}</span>
|
||||
</div>
|
||||
|
||||
{% if person.first_name %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-user"></i>
|
||||
<span class="info-label">{% trans "First Name" %}:</span>
|
||||
<span class="info-value">{{ person.first_name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if person.middle_name %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-user"></i>
|
||||
<span class="info-label">{% trans "Middle Name" %}:</span>
|
||||
<span class="info-value">{{ person.middle_name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if person.last_name %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-user"></i>
|
||||
<span class="info-label">{% trans "Last Name" %}:</span>
|
||||
<span class="info-value">{{ person.last_name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if person.date_of_birth %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-birthday-cake"></i>
|
||||
<span class="info-label">{% trans "Date of Birth" %}:</span>
|
||||
<span class="info-value">{{ person.date_of_birth }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if person.gender %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-venus-mars"></i>
|
||||
<span class="info-label">{% trans "Gender" %}:</span>
|
||||
<span class="info-value">
|
||||
{% if person.gender == 'M' %}{% trans "Male" %}{% else %}{% trans "Female" %}{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if person.nationality %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-globe"></i>
|
||||
<span class="info-label">{% trans "Nationality" %}:</span>
|
||||
<span class="info-value">{{ person.nationality }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information Column -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="info-section">
|
||||
<h5><i class="fas fa-address-book me-2"></i>{% trans "Contact Information" %}</h5>
|
||||
|
||||
{% if person.email %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<span class="info-label">{% trans "Email" %}:</span>
|
||||
<span class="info-value">
|
||||
<a href="mailto:{{ person.email }}" class="text-decoration-none">
|
||||
{{ person.email }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if person.phone %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-phone"></i>
|
||||
<span class="info-label">{% trans "Phone" %}:</span>
|
||||
<span class="info-value">
|
||||
<a href="tel:{{ person.phone }}" class="text-decoration-none">
|
||||
{{ person.phone }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if person.address %}
|
||||
<div class="info-item">
|
||||
<i class="fas fa-map-marker-alt"></i>
|
||||
<span class="info-label">{% trans "Address" %}:</span>
|
||||
<span class="info-value">{{ person.address|linebreaksbr }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if person.linkedin_profile %}
|
||||
<div class="info-item">
|
||||
<i class="fab fa-linkedin"></i>
|
||||
<span class="info-label">{% trans "LinkedIn" %}:</span>
|
||||
<span class="info-value">
|
||||
<a href="{{ person.linkedin_profile }}" target="_blank"
|
||||
class="text-decoration-none">
|
||||
{% trans "View Profile" %}
|
||||
<i class="fas fa-external-link-alt ms-1"></i>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Related Information -->
|
||||
<div class="row">
|
||||
<!-- Applications -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-briefcase me-2"></i>{% trans "Applications" %}
|
||||
<span class="badge bg-primary ms-2">{{ person.applications.count }}</span>
|
||||
</h5>
|
||||
|
||||
{% if person.applications %}
|
||||
{% for application in person.applications.all %}
|
||||
<div class="related-item">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="mb-1">
|
||||
<a href="{% url 'candidate_detail' application.slug %}"
|
||||
class="text-decoration-none">
|
||||
{{ application.job.title }}
|
||||
</a>
|
||||
</h6>
|
||||
<small class="text-muted">
|
||||
{% trans "Applied" %}: {{ application.created_at|date:"d M Y" }}
|
||||
</small>
|
||||
</div>
|
||||
<span class="badge bg-primary">{{ application.stage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-briefcase"></i>
|
||||
<p>{% trans "No applications found" %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Documents -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-file-alt me-2"></i>{% trans "Documents" %}
|
||||
<span class="badge bg-primary ms-2">{{ person.documents.count }}</span>
|
||||
</h5>
|
||||
|
||||
{% if person.documents %}
|
||||
{% for document in person.documents %}
|
||||
<div class="related-item">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h6 class="mb-1">
|
||||
<a href="{{ document.file.url }}" target="_blank"
|
||||
class="text-decoration-none">
|
||||
{{ document.filename }}
|
||||
</a>
|
||||
</h6>
|
||||
<small class="text-muted">
|
||||
{{ document.file_size|filesizeformat }} •
|
||||
{{ document.uploaded_at|date:"d M Y" }}
|
||||
</small>
|
||||
</div>
|
||||
<a href="{{ document.file.url }}" download="{{ document.filename }}"
|
||||
class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-download"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
<p>{% trans "No documents found" %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Information -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-info-circle me-2"></i>{% trans "System Information" %}
|
||||
</h5>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="info-item">
|
||||
<i class="fas fa-calendar-plus"></i>
|
||||
<span class="info-label">{% trans "Created" %}:</span>
|
||||
<span class="info-value">{{ person.created_at|date:"d M Y H:i" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="info-item">
|
||||
<i class="fas fa-calendar-edit"></i>
|
||||
<span class="info-label">{% trans "Last Updated" %}:</span>
|
||||
<span class="info-value">{{ person.updated_at|date:"d M Y H:i" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% if person.user %}
|
||||
<div class="col-md-6">
|
||||
<div class="info-item">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
<span class="info-label">{% trans "User Account" %}:</span>
|
||||
<span class="info-value">
|
||||
<a href="{% url 'user_detail' person.user.pk %}"
|
||||
class="text-decoration-none">
|
||||
{{ person.user.username }}
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to People" %}
|
||||
</a>
|
||||
{% if user.is_staff %}
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'person_update' person.slug %}" class="btn btn-main-action">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Person" %}
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-delete-url="{% url 'person_delete' person.slug %}"
|
||||
data-item-name="{{ person.get_full_name }}">
|
||||
<i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Add smooth scrolling for internal links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
target.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add copy to clipboard functionality for email and phone
|
||||
const copyElements = document.querySelectorAll('[data-copy]');
|
||||
copyElements.forEach(element => {
|
||||
element.addEventListener('click', function() {
|
||||
const textToCopy = this.getAttribute('data-copy');
|
||||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||
// Show temporary feedback
|
||||
const originalText = this.innerHTML;
|
||||
this.innerHTML = '<i class="fas fa-check me-1"></i>Copied!';
|
||||
this.classList.add('text-success');
|
||||
|
||||
setTimeout(() => {
|
||||
this.innerHTML = originalText;
|
||||
this.classList.remove('text-success');
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Add hover effects for cards
|
||||
const cards = document.querySelectorAll('.card');
|
||||
cards.forEach(card => {
|
||||
card.addEventListener('mouseenter', function() {
|
||||
this.style.transform = 'translateY(-2px)';
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', function() {
|
||||
this.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
411
templates/people/person_list.html
Normal file
411
templates/people/person_list.html
Normal file
@ -0,0 +1,411 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}People - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* UI Variables for the KAAT-S Theme (Consistent with Reference) */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-primary-text: #343a40;
|
||||
--kaauh-gray-light: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Primary Color Overrides */
|
||||
.text-primary-theme { color: var(--kaauh-teal) !important; }
|
||||
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
|
||||
.text-success { color: var(--kaauh-success) !important; }
|
||||
.text-danger { color: var(--kaauh-danger) !important; }
|
||||
.text-info { color: #17a2b8 !important; }
|
||||
|
||||
/* Enhanced Card Styling */
|
||||
.card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
background-color: white;
|
||||
}
|
||||
.card:not(.no-hover):hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
|
||||
}
|
||||
.card.no-hover:hover {
|
||||
transform: none;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
/* Main Action Button Style */
|
||||
.btn-main-action {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.btn-main-action:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* Secondary Button Style */
|
||||
.btn-outline-secondary {
|
||||
color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal);
|
||||
}
|
||||
.btn-outline-secondary:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
color: white;
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
}
|
||||
|
||||
/* Person Card Specifics */
|
||||
.person-card .card-title {
|
||||
color: var(--kaauh-teal-dark);
|
||||
font-weight: 600;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
.person-card .card-text i {
|
||||
color: var(--kaauh-teal);
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
/* Badge Styling */
|
||||
.badge {
|
||||
font-weight: 600;
|
||||
padding: 0.4em 0.7em;
|
||||
border-radius: 0.3rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
/* Table Styling */
|
||||
.table-view .table thead th {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border-color: var(--kaauh-border);
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 1rem;
|
||||
}
|
||||
.table-view .table tbody td {
|
||||
vertical-align: middle;
|
||||
padding: 1rem;
|
||||
border-color: var(--kaauh-border);
|
||||
}
|
||||
.table-view .table tbody tr:hover {
|
||||
background-color: var(--kaauh-gray-light);
|
||||
}
|
||||
|
||||
/* Pagination Link Styling */
|
||||
.pagination .page-item .page-link {
|
||||
color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-border);
|
||||
}
|
||||
.pagination .page-item.active .page-link {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
}
|
||||
.pagination .page-item:hover .page-link:not(.active) {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
/* Profile Image Styling */
|
||||
.profile-image-small {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--kaauh-border);
|
||||
}
|
||||
|
||||
.profile-image-medium {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--kaauh-teal);
|
||||
}
|
||||
|
||||
/* Filter & Search Layout */
|
||||
.filter-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-user-friends me-2"></i> {% trans "People Directory" %}
|
||||
</h1>
|
||||
<a href="{% url 'person_create' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add New Person" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filters -->
|
||||
<div class="card mb-4 shadow-sm no-hover">
|
||||
<div class="card-body">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<label for="search" class="form-label small text-muted">{% trans "Search by Name or Email" %}</label>
|
||||
<form method="get" action="" class="w-100">
|
||||
<div class="input-group input-group-lg">
|
||||
<input type="text" name="q" class="form-control" id="search"
|
||||
placeholder="{% trans 'Search people...' %}"
|
||||
value="{{ request.GET.q }}">
|
||||
<button class="btn btn-main-action" type="submit">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<form method="GET" class="row g-3 align-items-end h-100">
|
||||
{% if request.GET.q %}<input type="hidden" name="q" value="{{ request.GET.q }}">{% endif %}
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="nationality_filter" class="form-label small text-muted">{% trans "Filter by Nationality" %}</label>
|
||||
<select name="nationality" id="nationality_filter" class="form-select form-select-sm">
|
||||
<option value="">{% trans "All Nationalities" %}</option>
|
||||
{% for nationality in nationalities %}
|
||||
<option value="{{ nationality }}" {% if request.GET.nationality == nationality %}selected{% endif %}>{{ nationality }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="gender_filter" class="form-label small text-muted">{% trans "Filter by Gender" %}</label>
|
||||
<select name="gender" id="gender_filter" class="form-select form-select-sm">
|
||||
<option value="">{% trans "All Genders" %}</option>
|
||||
<option value="M" {% if request.GET.gender == 'M' %}selected{% endif %}>{% trans "Male" %}</option>
|
||||
<option value="F" {% if request.GET.gender == 'F' %}selected{% endif %}>{% trans "Female" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 d-flex justify-content-end align-self-end">
|
||||
<div class="filter-buttons">
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Apply" %}
|
||||
</button>
|
||||
{% if request.GET.q or request.GET.nationality or request.GET.gender %}
|
||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if people_list %}
|
||||
<div id="person-list">
|
||||
<!-- View Switcher -->
|
||||
{% include "includes/_list_view_switcher.html" with list_id="person-list" %}
|
||||
|
||||
<!-- Table View (Default) -->
|
||||
<div class="table-view">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Photo" %}</th>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Email" %}</th>
|
||||
<th scope="col">{% trans "Phone" %}</th>
|
||||
<th scope="col">{% trans "Nationality" %}</th>
|
||||
<th scope="col">{% trans "Gender" %}</th>
|
||||
<th scope="col">{% trans "Agency" %}</th>
|
||||
<th scope="col">{% trans "Created" %}</th>
|
||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for person in people_list %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if person.profile_image %}
|
||||
<img src="{{ person.profile_image.url }}" alt="{{ person.get_full_name }}"
|
||||
class="profile-image-small">
|
||||
{% else %}
|
||||
<div class="profile-image-small d-flex align-items-center justify-content-center bg-light">
|
||||
<i class="fas fa-user text-muted"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="fw-medium">
|
||||
<a href="{% url 'person_detail' person.slug %}"
|
||||
class="text-decoration-none link-secondary">
|
||||
{{ person.full_name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ person.email|default:"N/A" }}</td>
|
||||
<td>{{ person.phone|default:"N/A" }}</td>
|
||||
<td>
|
||||
{% if person.nationality %}
|
||||
<span class="badge bg-primary">{{ person.nationality }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if person.gender %}
|
||||
<span class="badge bg-info">
|
||||
{% if person.gender == 'M' %}{% trans "Male" %}{% else %}{% trans "Female" %}{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><span class="badge bg-secondary">{{ person.agency.name|default:"N/A" }}</span></td>
|
||||
<td>{{ person.created_at|date:"d-m-Y" }}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{% url 'person_detail' person.slug %}"
|
||||
class="btn btn-outline-primary" title="{% trans 'View' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url 'person_update' person.slug %}"
|
||||
class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
title="{% trans 'Delete' %}"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-delete-url="{% url 'person_delete' person.slug %}"
|
||||
data-item-name="{{ person.get_full_name }}">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card View -->
|
||||
<div class="card-view row">
|
||||
{% for person in people_list %}
|
||||
<div class="col-md-6 col-lg-4 mb-4">
|
||||
<div class="card person-card h-100 shadow-sm">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="d-flex align-items-start mb-3">
|
||||
<div class="me-3">
|
||||
{% if person.profile_image %}
|
||||
<img src="{{ person.profile_image.url }}" alt="{{ person.get_full_name }}"
|
||||
class="profile-image-medium">
|
||||
{% else %}
|
||||
<div class="profile-image-medium d-flex align-items-center justify-content-center bg-light">
|
||||
<i class="fas fa-user text-muted fa-2x"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="card-title mb-1">
|
||||
<a href="{% url 'person_detail' person.slug %}"
|
||||
class="text-decoration-none text-primary-theme">
|
||||
{{ person.get_full_name }}
|
||||
</a>
|
||||
</h5>
|
||||
<p class="text-muted small mb-2">{{ person.email|default:"N/A" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-text text-muted small">
|
||||
{% if person.phone %}
|
||||
<div class="mb-2">
|
||||
<i class="fas fa-phone me-2"></i>{{ person.phone }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if person.nationality %}
|
||||
<div class="mb-2">
|
||||
<i class="fas fa-globe me-2"></i>
|
||||
<span class="badge bg-primary">{{ person.nationality }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if person.gender %}
|
||||
<div class="mb-2">
|
||||
<i class="fas fa-venus-mars me-2"></i>
|
||||
<span class="badge bg-info">
|
||||
{% if person.gender == 'M' %}{% trans "Male" %}{% else %}{% trans "Female" %}{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if person.date_of_birth %}
|
||||
<div class="mb-2">
|
||||
<i class="fas fa-birthday-cake me-2"></i>{{ person.date_of_birth }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mt-auto pt-3 border-top">
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'person_detail' person.slug %}"
|
||||
class="btn btn-sm btn-main-action">
|
||||
<i class="fas fa-eye"></i> {% trans "View" %}
|
||||
</a>
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url 'person_update' person.slug %}"
|
||||
class="btn btn-sm btn-outline-secondary">
|
||||
<i class="fas fa-edit"></i> {% trans "Edit" %}
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm"
|
||||
title="{% trans 'Delete' %}"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-delete-url="{% url 'person_delete' person.slug %}"
|
||||
data-item-name="{{ person.get_full_name }}">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% include "includes/paginator.html" %}
|
||||
{% else %}
|
||||
<!-- Empty State -->
|
||||
<div class="text-center py-5 card shadow-sm">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-user-friends fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
|
||||
<h3>{% trans "No people found" %}</h3>
|
||||
<p class="text-muted">{% trans "Create your first person record." %}</p>
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url 'person_create' %}" class="btn btn-main-action mt-3">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add Person" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
572
templates/people/update_person.html
Normal file
572
templates/people/update_person.html
Normal file
@ -0,0 +1,572 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}Update {{ person.get_full_name }} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* UI Variables for the KAAT-S Theme */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-gray-light: #f8f9fa;
|
||||
}
|
||||
|
||||
/* Form Container Styling */
|
||||
.form-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Card Styling */
|
||||
.card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
/* Main Action Button Style */
|
||||
.btn-main-action {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.5rem 1.5rem;
|
||||
}
|
||||
|
||||
.btn-main-action:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
/* Secondary Button Style */
|
||||
.btn-outline-secondary {
|
||||
color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal);
|
||||
}
|
||||
.btn-outline-secondary:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
color: white;
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
}
|
||||
|
||||
/* Form Field Styling */
|
||||
.form-control:focus {
|
||||
border-color: var(--kaauh-teal);
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||
}
|
||||
|
||||
.form-select:focus {
|
||||
border-color: var(--kaauh-teal);
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||
}
|
||||
|
||||
/* Profile Image Upload Styling */
|
||||
.profile-image-upload {
|
||||
border: 2px dashed var(--kaauh-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.profile-image-upload:hover {
|
||||
border-color: var(--kaauh-teal);
|
||||
background-color: var(--kaauh-gray-light);
|
||||
}
|
||||
|
||||
.profile-image-preview {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--kaauh-teal);
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
.current-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
border: 2px solid var(--kaauh-teal);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
/* Breadcrumb Styling */
|
||||
.breadcrumb {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
content: ">";
|
||||
color: var(--kaauh-teal);
|
||||
}
|
||||
|
||||
/* Alert Styling */
|
||||
.alert {
|
||||
border-radius: 0.5rem;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Loading State */
|
||||
.btn.loading {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.btn.loading::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: auto;
|
||||
border: 2px solid transparent;
|
||||
border-top-color: #ffffff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Current Profile Section */
|
||||
.current-profile {
|
||||
background-color: var(--kaauh-gray-light);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.current-profile h6 {
|
||||
color: var(--kaauh-teal-dark);
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="form-container">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'person_list' %}" class="text-decoration-none">
|
||||
<i class="fas fa-user-friends me-1"></i> {% trans "People" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'person_detail' person.slug %}" class="text-decoration-none">
|
||||
{{ person.get_full_name }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{% trans "Update" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-user-edit me-2"></i> {% trans "Update Person" %}
|
||||
</h1>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'person_detail' person.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||
</a>
|
||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Profile Info -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<div class="current-profile">
|
||||
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if person.profile_image %}
|
||||
<img src="{{ person.profile_image.url }}" alt="{{ person.get_full_name }}"
|
||||
class="current-image">
|
||||
{% else %}
|
||||
<div class="current-image d-flex align-items-center justify-content-center bg-light">
|
||||
<i class="fas fa-user text-muted"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h5 class="mb-1">{{ person.get_full_name }}</h5>
|
||||
{% if person.email %}
|
||||
<p class="text-muted mb-0">{{ person.email }}</p>
|
||||
{% endif %}
|
||||
<small class="text-muted">
|
||||
{% trans "Created" %}: {{ person.created_at|date:"d M Y" }} •
|
||||
{% trans "Last Updated" %}: {{ person.updated_at|date:"d M Y" }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h5 class="alert-heading">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
||||
</h5>
|
||||
{% for error in form.non_field_errors %}
|
||||
<p class="mb-0">{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data" id="person-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Profile Image Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="profile-image-upload" onclick="document.getElementById('id_profile_image').click()">
|
||||
<div id="image-preview-container">
|
||||
{% if person.profile_image %}
|
||||
<img src="{{ person.profile_image.url }}" alt="Current Profile"
|
||||
class="profile-image-preview">
|
||||
<h5 class="text-muted mt-3">{% trans "Click to change photo" %}</h5>
|
||||
<p class="text-muted small">{% trans "Current photo will be replaced" %}</p>
|
||||
{% else %}
|
||||
<i class="fas fa-camera fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">{% trans "Upload Profile Photo" %}</h5>
|
||||
<p class="text-muted small">{% trans "Click to browse or drag and drop" %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<input type="file" name="profile_image" id="id_profile_image"
|
||||
class="d-none" accept="image/*">
|
||||
</div>
|
||||
{% if person.profile_image %}
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">
|
||||
{% trans "Leave empty to keep current photo" %}
|
||||
</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Personal Information Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fas fa-user me-2"></i> {% trans "Personal Information" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.first_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.middle_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.last_name|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Information Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fas fa-envelope me-2"></i> {% trans "Contact Information" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ form.email|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ form.phone|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Information Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fas fa-info-circle me-2"></i> {% trans "Additional Information" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.date_of_birth|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.nationality|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ form.gender|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fas fa-map-marker-alt me-2"></i> {% trans "Address Information" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
{{ form.address|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LinkedIn Profile Section -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); border-bottom: 2px solid var(--kaauh-teal); padding-bottom: 0.5rem;">
|
||||
<i class="fab fa-linkedin me-2"></i> {% trans "Professional Profile" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="form-group mb-3">
|
||||
<label for="id_linkedin_profile" class="form-label">
|
||||
{% trans "LinkedIn Profile URL" %}
|
||||
</label>
|
||||
<input type="url" name="linkedin_profile" id="id_linkedin_profile"
|
||||
class="form-control" placeholder="https://linkedin.com/in/username"
|
||||
value="{{ person.linkedin_profile|default:'' }}">
|
||||
<small class="form-text text-muted">
|
||||
{% trans "Optional: Add LinkedIn profile URL" %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'person_detail' person.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||
</a>
|
||||
<a href="{% url 'person_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-list me-1"></i> {% trans "Back to List" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="reset" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-undo me-1"></i> {% trans "Reset Changes" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-save me-1"></i> {% trans "Update Person" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Profile Image Preview
|
||||
const profileImageInput = document.getElementById('id_profile_image');
|
||||
const imagePreviewContainer = document.getElementById('image-preview-container');
|
||||
const originalImage = imagePreviewContainer.innerHTML;
|
||||
|
||||
profileImageInput.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
imagePreviewContainer.innerHTML = `
|
||||
<img src="${e.target.result}" alt="Profile Preview" class="profile-image-preview">
|
||||
<h5 class="text-muted mt-3">${file.name}</h5>
|
||||
<p class="text-muted small">{% trans "New photo selected" %}</p>
|
||||
`;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else if (!file) {
|
||||
// Reset to original if no file selected
|
||||
imagePreviewContainer.innerHTML = originalImage;
|
||||
}
|
||||
});
|
||||
|
||||
// Form Validation
|
||||
const form = document.getElementById('person-form');
|
||||
form.addEventListener('submit', function(e) {
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.disabled = true;
|
||||
|
||||
// Basic validation
|
||||
const firstName = document.getElementById('id_first_name').value.trim();
|
||||
const lastName = document.getElementById('id_last_name').value.trim();
|
||||
const email = document.getElementById('id_email').value.trim();
|
||||
|
||||
if (!firstName || !lastName) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.disabled = false;
|
||||
alert('{% trans "First name and last name are required." %}');
|
||||
return;
|
||||
}
|
||||
|
||||
if (email && !isValidEmail(email)) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.disabled = false;
|
||||
alert('{% trans "Please enter a valid email address." %}');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// Email validation helper
|
||||
function isValidEmail(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
|
||||
// LinkedIn URL validation
|
||||
const linkedinInput = document.getElementById('id_linkedin_profile');
|
||||
linkedinInput.addEventListener('blur', function() {
|
||||
const value = this.value.trim();
|
||||
if (value && !isValidLinkedInURL(value)) {
|
||||
this.classList.add('is-invalid');
|
||||
if (!this.nextElementSibling || !this.nextElementSibling.classList.contains('invalid-feedback')) {
|
||||
const feedback = document.createElement('div');
|
||||
feedback.className = 'invalid-feedback';
|
||||
feedback.textContent = '{% trans "Please enter a valid LinkedIn URL" %}';
|
||||
this.parentNode.appendChild(feedback);
|
||||
}
|
||||
} else {
|
||||
this.classList.remove('is-invalid');
|
||||
const feedback = this.parentNode.querySelector('.invalid-feedback');
|
||||
if (feedback) feedback.remove();
|
||||
}
|
||||
});
|
||||
|
||||
function isValidLinkedInURL(url) {
|
||||
const linkedinRegex = /^https?:\/\/(www\.)?linkedin\.com\/.+/i;
|
||||
return linkedinRegex.test(url);
|
||||
}
|
||||
|
||||
// Drag and Drop functionality
|
||||
const uploadArea = document.querySelector('.profile-image-upload');
|
||||
|
||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, preventDefaults, false);
|
||||
});
|
||||
|
||||
function preventDefaults(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, highlight, false);
|
||||
});
|
||||
|
||||
['dragleave', 'drop'].forEach(eventName => {
|
||||
uploadArea.addEventListener(eventName, unhighlight, false);
|
||||
});
|
||||
|
||||
function highlight(e) {
|
||||
uploadArea.style.borderColor = 'var(--kaauh-teal)';
|
||||
uploadArea.style.backgroundColor = 'var(--kaauh-gray-light)';
|
||||
}
|
||||
|
||||
function unhighlight(e) {
|
||||
uploadArea.style.borderColor = 'var(--kaauh-border)';
|
||||
uploadArea.style.backgroundColor = 'transparent';
|
||||
}
|
||||
|
||||
uploadArea.addEventListener('drop', handleDrop, false);
|
||||
|
||||
function handleDrop(e) {
|
||||
const dt = e.dataTransfer;
|
||||
const files = dt.files;
|
||||
|
||||
if (files.length > 0) {
|
||||
profileImageInput.files = files;
|
||||
const event = new Event('change', { bubbles: true });
|
||||
profileImageInput.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset button functionality
|
||||
const resetBtn = form.querySelector('button[type="reset"]');
|
||||
resetBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Reset form fields
|
||||
form.reset();
|
||||
|
||||
// Reset image preview
|
||||
imagePreviewContainer.innerHTML = originalImage;
|
||||
|
||||
// Clear any validation states
|
||||
form.querySelectorAll('.is-invalid').forEach(element => {
|
||||
element.classList.remove('is-invalid');
|
||||
});
|
||||
|
||||
// Remove any invalid feedback messages
|
||||
form.querySelectorAll('.invalid-feedback').forEach(element => {
|
||||
element.remove();
|
||||
});
|
||||
});
|
||||
|
||||
// Warn before leaving if changes are made
|
||||
let formChanged = false;
|
||||
const formInputs = form.querySelectorAll('input, select, textarea');
|
||||
|
||||
formInputs.forEach(input => {
|
||||
input.addEventListener('change', function() {
|
||||
formChanged = true;
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('beforeunload', function(e) {
|
||||
if (formChanged) {
|
||||
e.preventDefault();
|
||||
e.returnValue = '{% trans "You have unsaved changes. Are you sure you want to leave?" %}';
|
||||
return e.returnValue;
|
||||
}
|
||||
});
|
||||
|
||||
form.addEventListener('submit', function() {
|
||||
formChanged = false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -49,16 +49,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{# Using inline style for nav background color - replace with a dedicated CSS class (e.g., .bg-kaauh-nav) if defined in main.css #}
|
||||
<div style="background-color: #00636e;">
|
||||
<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' %}">
|
||||
@ -67,8 +74,26 @@
|
||||
|
||||
<div class="collapse navbar-collapse" id="agencyNavbar">
|
||||
<div class="navbar-nav ms-auto">
|
||||
|
||||
|
||||
{# NAVIGATION LINKS (Add your portal links here if needed) #}
|
||||
{% if request.user.user_type == 'agency' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white" href="{% url 'agency_portal_dashboard' %}">
|
||||
<i class="fas fa-tachometer-alt me-1"></i> {% trans "Dashboard" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white" href="{% url 'agency_portal_persons_list' %}">
|
||||
<i class="fas fa-users me-1"></i> {% trans "Persons" %}
|
||||
</a>
|
||||
</li>
|
||||
{% elif request.user.user_type == 'candidate' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-white" href="{% url 'candidate_portal_dashboard' %}">
|
||||
<i class="fas fa-tachometer-alt me-1"></i> {% trans "Dashboard" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown"
|
||||
@ -97,7 +122,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 +159,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>
|
||||
@ -146,7 +175,7 @@
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.6/bundles/datastar.js"></script>
|
||||
|
||||
|
||||
{# JavaScript (Left unchanged as it was mostly correct) #}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@ -206,4 +235,4 @@
|
||||
|
||||
{% block customJS %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends 'agency_base.html' %}
|
||||
{% extends 'portal_base.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Access Link Details" %} - ATS{% endblock %}
|
||||
|
||||
@ -162,7 +162,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="kaauh-card shadow-sm mb-4">
|
||||
@ -210,10 +210,12 @@
|
||||
{% trans "Share these credentials securely with the agency. They can use this information to log in and submit candidates." %}
|
||||
</div>
|
||||
|
||||
<a href="{% url 'agency_access_link_detail' access_link.slug %}"
|
||||
class="btn btn-outline-info btn-sm mx-2">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Access Links Details" %}
|
||||
</a>
|
||||
{% if access_link %}
|
||||
<a href="{% url 'agency_access_link_detail' access_link.slug %}"
|
||||
class="btn btn-outline-info btn-sm mx-2">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Access Links Details" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -331,7 +333,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Actions Card -->
|
||||
<div class="kaauh-card p-4">
|
||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
||||
@ -488,14 +490,14 @@ function copyToClipboard(elementId) {
|
||||
function confirmDeactivate() {
|
||||
if (confirm('{% trans "Are you sure you want to deactivate this access link? Agencies will no longer be able to use it." %}')) {
|
||||
// Submit form to deactivate
|
||||
window.location.href = '{% url "agency_access_link_deactivate" access_link.slug %}';
|
||||
window.location.href = '';
|
||||
}
|
||||
}
|
||||
|
||||
function confirmReactivate() {
|
||||
if (confirm('{% trans "Are you sure you want to reactivate this access link?" %}')) {
|
||||
// Submit form to reactivate
|
||||
window.location.href = '{% url "agency_access_link_reactivate" access_link.slug %}';
|
||||
window.location.href = '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends 'agency_base.html' %}
|
||||
{% extends 'portal_base.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Agency Dashboard" %} - ATS{% endblock %}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends 'agency_base.html' %}
|
||||
{% extends 'portal_base.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Agency Portal Login" %} - ATS{% endblock %}
|
||||
@ -132,7 +132,7 @@
|
||||
<!-- Login Body -->
|
||||
<div class="login-body">
|
||||
<!-- Messages -->
|
||||
|
||||
|
||||
|
||||
<!-- Login Form -->
|
||||
<form method="post" novalidate>
|
||||
|
||||
390
templates/recruitment/agency_portal_persons_list.html
Normal file
390
templates/recruitment/agency_portal_persons_list.html
Normal file
@ -0,0 +1,390 @@
|
||||
{% extends 'portal_base.html' %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}{% trans "Persons List" %} - ATS{% endblock %}
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
: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;
|
||||
}
|
||||
|
||||
.kaauh-card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.btn-main-action {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-main-action:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.person-row:hover {
|
||||
background-color: #f8f9fa;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.stage-badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.375rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
background-color: white;
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock%}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4 persons-list">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="px-2 py-2">
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-users me-2"></i>
|
||||
{% trans "All Persons" %}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
{% trans "All persons who come through" %} {{ agency.name }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Add Person Button -->
|
||||
<button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#personModal">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add New Person" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Section -->
|
||||
<div class="kaauh-card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" class="search-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="search" class="form-label fw-semibold">
|
||||
<i class="fas fa-search me-1"></i>{% trans "Search" %}
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="search"
|
||||
name="q"
|
||||
value="{{ search_query }}"
|
||||
placeholder="{% trans 'Search by name, email, phone, or job title...' %}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="stage" class="form-label fw-semibold">
|
||||
<i class="fas fa-filter me-1"></i>{% trans "Stage" %}
|
||||
</label>
|
||||
<select class="form-select" id="stage" name="stage">
|
||||
<option value="">{% trans "All Stages" %}</option>
|
||||
{% for stage_value, stage_label in stage_choices %}
|
||||
<option value="{{ stage_value }}"
|
||||
{% if stage_filter == stage_value %}selected{% endif %}>
|
||||
{{ stage_label }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-main-action w-100">
|
||||
<i class="fas fa-search me-1"></i> {% trans "Search" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Summary -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="kaauh-card shadow-sm h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-info mb-2">
|
||||
<i class="fas fa-users fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="card-title">{{ total_persons }}</h4>
|
||||
<p class="card-text text-muted">{% trans "Total Persons" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="kaauh-card shadow-sm h-100">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-success mb-2">
|
||||
<i class="fas fa-check-circle fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="card-title">{{ page_obj|length }}</h4>
|
||||
<p class="card-text text-muted">{% trans "Showing on this page" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Persons Table -->
|
||||
<div class="kaauh-card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Email" %}</th>
|
||||
<th scope="col">{% trans "Phone" %}</th>
|
||||
<th scope="col">{% trans "Job" %}</th>
|
||||
<th scope="col">{% trans "Stage" %}</th>
|
||||
<th scope="col">{% trans "Applied Date" %}</th>
|
||||
<th scope="col" class="text-center">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for person in page_obj %}
|
||||
<tr class="person-row">
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center me-2"
|
||||
style="width: 32px; height: 32px; font-size: 14px; font-weight: 600;">
|
||||
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-semibold">{{ person.first_name }} {{ person.last_name }}</div>
|
||||
{% if person.address %}
|
||||
<small class="text-muted">{{ person.address|truncatechars:50 }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="mailto:{{ person.email }}" class="text-decoration-none">
|
||||
{{ person.email }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ person.phone|default:"-" }}</td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark">
|
||||
{{ person.job.title|truncatechars:30 }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% with stage_class=person.stage|lower %}
|
||||
<span class="stage-badge
|
||||
{% if stage_class == 'applied' %}bg-secondary{% endif %}
|
||||
{% if stage_class == 'exam' %}bg-info{% endif %}
|
||||
{% if stage_class == 'interview' %}bg-warning{% endif %}
|
||||
{% if stage_class == 'offer' %}bg-success{% endif %}
|
||||
{% if stage_class == 'hired' %}bg-primary{% endif %}
|
||||
{% if stage_class == 'rejected' %}bg-danger{% endif %}
|
||||
text-white">
|
||||
{{ person.get_stage_display }}
|
||||
</span>
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>{{ person.created_at|date:"Y-m-d" }}</td>
|
||||
<td class="text-center">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'candidate_detail' person.slug %}"
|
||||
class="btn btn-sm btn-outline-primary"
|
||||
title="{% trans 'View Details' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
title="{% trans 'Edit Person' %}"
|
||||
onclick="editPerson({{ person.id }})">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-users fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">{% trans "No persons found" %}</h5>
|
||||
<p class="text-muted">
|
||||
{% if search_query or stage_filter %}
|
||||
{% trans "Try adjusting your search or filter criteria." %}
|
||||
{% else %}
|
||||
{% trans "No persons have been added yet." %}
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if not search_query and not stage_filter and agency.assignments.exists %}
|
||||
<a href="{% url 'agency_portal_submit_candidate_page' agency.assignments.first.slug %}"
|
||||
class="btn btn-main-action">
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Add First Person" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="{% trans 'Persons pagination' %}" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
||||
<i class="fas fa-angle-double-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
||||
<i class="fas fa-angle-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ num }}</span>
|
||||
</li>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">{{ num }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
||||
<i class="fas fa-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
||||
<i class="fas fa-angle-double-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- Person Modal -->
|
||||
<div class="modal fade modal-lg" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="personModalLabel">
|
||||
<i class="fas fa-users me-2"></i>
|
||||
{% trans "Person Details" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="personModalBody">
|
||||
<form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"portal","agency":"{{ agency.slug }}"}' hx-select=".persons-list" hx-target=".persons-list" hx-swap="outerHTML"
|
||||
hx-on:afterRequest="$('#personModal').modal('hide')">
|
||||
{% csrf_token %}
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
{{ person_form.first_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ person_form.middle_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ person_form.last_name|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.email|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.phone|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.date_of_birth|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.nationality|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
{{ person_form.address|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-main-action" type="submit" form="person_form">{% trans "Save" %}</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openPersonModal(personId, personName) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('personModal'));
|
||||
document.getElementById('person-modal-text').innerHTML = `<strong>${personName}</strong> (ID: ${personId})`;
|
||||
modal.show();
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
function editPerson(personId) {
|
||||
// Placeholder for edit functionality
|
||||
// This would typically open a modal or navigate to edit page
|
||||
console.log('Edit person:', personId);
|
||||
// For now, you can redirect to a placeholder edit URL
|
||||
// window.location.href = `/portal/candidates/${personId}/edit/`;
|
||||
}
|
||||
|
||||
// Auto-submit form on filter change
|
||||
document.getElementById('stage').addEventListener('change', function() {
|
||||
this.form.submit();
|
||||
});
|
||||
|
||||
// Add row click functionality
|
||||
document.querySelectorAll('.person-row').forEach(row => {
|
||||
row.addEventListener('click', function(e) {
|
||||
// Don't trigger if clicking on buttons or links
|
||||
if (e.target.closest('a, button')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the view details button and click it
|
||||
const viewBtn = this.querySelector('a[title*="View"]');
|
||||
if (viewBtn) {
|
||||
viewBtn.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@ -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 %}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}Create Candidate - {{ block.super }}{% endblock %}
|
||||
{% block title %}Create Application - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
@ -36,7 +36,7 @@
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
|
||||
/* Outlined Button Styles */
|
||||
.btn-secondary, .btn-outline-secondary {
|
||||
background-color: #f8f9fa;
|
||||
@ -58,7 +58,7 @@
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
/* Colored Header Card */
|
||||
.candidate-header-card {
|
||||
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
|
||||
@ -84,18 +84,22 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="candidate-header-card">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap">
|
||||
<div class="flex-grow-1">
|
||||
<h1 class="h3 mb-1">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
{% trans "Create New Candidate" %}
|
||||
<i class="fas fa-user-plus"></i>
|
||||
{% trans "Create New Application" %}
|
||||
</h1>
|
||||
<p class="text-white opacity-75 mb-0">{% trans "Enter details to create a new candidate record." %}</p>
|
||||
<p class="text-white opacity-75 mb-0">{% trans "Enter details to create a new application record." %}</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 mt-1">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#personModal">
|
||||
<i class="fas fa-user-plus me-1"></i>
|
||||
<span class="d-none d-sm-inline">{% trans "Create New Person" %}</span>
|
||||
</button>
|
||||
<a href="{% url 'candidate_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
|
||||
@ -109,13 +113,13 @@
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h2 class="h5 mb-0 text-primary">
|
||||
<i class="fas fa-file-alt me-1"></i>
|
||||
{% trans "Candidate Information" %}
|
||||
{% trans "Application Information" %}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
|
||||
{# Split form into two columns for better horizontal use #}
|
||||
<div class="row g-4">
|
||||
{% for field in form %}
|
||||
@ -124,14 +128,69 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
<hr class="mt-4 mb-4">
|
||||
<button class="btn btn-main-action" type="submit">
|
||||
<i class="fas fa-save me-1"></i>
|
||||
{% trans "Create Candidate" %}
|
||||
{% trans "Create Application" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade modal-lg" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="personModalLabel">
|
||||
<i class="fas fa-question-circle me-2"></i>{% trans "Help" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"job"}' hx-target="#div_id_person" hx-select="#div_id_person" hx-swap="outerHTML">
|
||||
{% csrf_token %}
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
{{ person_form.first_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ person_form.middle_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ person_form.last_name|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.email|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.phone|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.date_of_birth|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.nationality|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
{{ person_form.address|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-main-action" data-bs-dismiss="modal" form="person_form">{% trans "Save" %}</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -321,6 +321,12 @@
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="documents-tab" data-bs-toggle="tab" data-bs-target="#documents-pane" type="button" role="tab" aria-controls="documents-pane" aria-selected="false">
|
||||
<i class="fas fa-file-alt me-1"></i> {% trans "Documents" %}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="card-body">
|
||||
@ -417,7 +423,7 @@
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if candidate.get_interview_date %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-comments"></i></div>
|
||||
@ -440,13 +446,13 @@
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.offer_date|date:"M d, Y" }}
|
||||
|
||||
|
||||
</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if candidate.hired_date %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-handshake"></i></div>
|
||||
@ -454,19 +460,25 @@
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.hired_date|date:"M d, Y" }}
|
||||
|
||||
|
||||
</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# TAB 4 CONTENT: PARSED SUMMARY #}
|
||||
{# TAB 4 CONTENT: DOCUMENTS #}
|
||||
<div class="tab-pane fade" id="documents-pane" role="tabpanel" aria-labelledby="documents-tab">
|
||||
{% with documents=candidate.documents %}
|
||||
{% include 'includes/document_list.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
{# TAB 5 CONTENT: PARSED SUMMARY #}
|
||||
{% if candidate.parsed_summary %}
|
||||
<div class="tab-pane fade" id="summary-pane" role="tabpanel" aria-labelledby="summary-tab">
|
||||
<h5 class="text-primary mb-4">{% trans "AI Generated Summary" %}</h5>
|
||||
@ -666,7 +678,7 @@
|
||||
|
||||
<div class="card shadow-sm mb-4 p-2">
|
||||
<h5 class="text-muted mb-3"><i class="fas fa-clock me-2"></i>{% trans "Time to Hire:" %}
|
||||
|
||||
|
||||
{% with days=candidate.time_to_hire_days %}
|
||||
{% if days > 0 %}
|
||||
{{ days }} day{{ days|pluralize }}
|
||||
@ -712,4 +724,4 @@
|
||||
{% if user.is_staff %}
|
||||
{% include "recruitment/partials/stage_update_modal.html" with candidate=candidate form=stage_form %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -196,7 +196,7 @@
|
||||
<h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);">
|
||||
{% trans "Candidate List" %}
|
||||
<span class="badge bg-primary-theme ms-2">{{ candidates|length }} / {{ total_candidates }} Total</span>
|
||||
<small class="text-muted fw-normal ms-2">(Sorted by AI Score)</small>
|
||||
<small class="text-muted fw-normal ms-2">({% trans "Sorted by AI Score" %})</small>
|
||||
</h2>
|
||||
|
||||
<div class="kaauh-card shadow-sm p-3">
|
||||
@ -210,7 +210,7 @@
|
||||
|
||||
{# Select Input Group #}
|
||||
<div>
|
||||
|
||||
|
||||
<select name="mark_as" id="update_status" class="form-select form-select-sm" style="min-width: 150px;">
|
||||
<option selected>
|
||||
----------
|
||||
@ -259,7 +259,7 @@
|
||||
{% endif %}
|
||||
</th>
|
||||
<th style="width: 15%;">{% trans "Name" %}</th>
|
||||
<th style="width: 15%;">{% trans "Contact" %}</th>
|
||||
<th style="width: 15%;">{% trans "Contact Info" %}</th>
|
||||
<th style="width: 10%;" class="text-center">{% trans "AI Score" %}</th>
|
||||
<th style="width: 15%;">{% trans "Exam Date" %}</th>
|
||||
<th style="width: 10%;" class="text-center">{% trans "Exam Results" %}</th>
|
||||
@ -313,7 +313,7 @@
|
||||
hx-get="{% url 'update_candidate_status' job.slug candidate.slug 'exam' 'passed' %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="Pass Exam">
|
||||
{{ candidate.interview_status }}
|
||||
{{ candidate.exam_status }}
|
||||
</button>
|
||||
{% else %}
|
||||
--
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}Candidates - {{ block.super }}{% endblock %}
|
||||
{% block title %}Applications - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
@ -190,13 +190,11 @@
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-users me-2"></i> {% trans "Candidate Profiles" %}
|
||||
<i class="fas fa-users me-2"></i> {% trans "Applications List" %}
|
||||
</h1>
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url 'candidate_create' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add New Candidate" %}
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add New Application" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card mb-4 shadow-sm no-hover">
|
||||
@ -255,7 +253,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if candidates %}
|
||||
{% if applications %}
|
||||
<div id="candidate-list">
|
||||
{# View Switcher - list_id must match the container ID #}
|
||||
{% include "includes/_list_view_switcher.html" with list_id="candidate-list" %}
|
||||
@ -278,7 +276,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for candidate in candidates %}
|
||||
{% for candidate in applications %}
|
||||
<tr>
|
||||
<td class="fw-medium"><a href="{% url 'candidate_detail' candidate.slug %}" class="text-decoration-none link-secondary">{{ candidate.name }}<a></td>
|
||||
<td>{{ candidate.email }}</td>
|
||||
@ -307,14 +305,14 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if candidate.hiring_agency %}
|
||||
{% if candidate.hiring_agency and candidate.hiring_source == 'Agency' %}
|
||||
<a href="{% url 'agency_detail' candidate.hiring_agency.slug %}" class="text-decoration-none">
|
||||
<span class="badge bg-info">
|
||||
<span class="badge bg-primary">
|
||||
<i class="fas fa-building"></i> {{ candidate.hiring_agency.name }}
|
||||
</span>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
<span class="badge bg-primary">{{ candidate.hiring_source }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ candidate.created_at|date:"d-m-Y" }}</td>
|
||||
@ -396,11 +394,11 @@
|
||||
<div class="text-center py-5 card shadow-sm">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-users fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
|
||||
<h3>{% trans "No candidate profiles found" %}</h3>
|
||||
<p class="text-muted">{% trans "Create your first candidate profile or adjust your filters." %}</p>
|
||||
<h3>{% trans "No application found" %}</h3>
|
||||
<p class="text-muted">{% trans "Create your first application." %}</p>
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url 'candidate_create' %}" class="btn btn-main-action mt-3">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add Candidate" %}
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add Application" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
165
templates/recruitment/candidate_portal_dashboard.html
Normal file
165
templates/recruitment/candidate_portal_dashboard.html
Normal 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 %}
|
||||
@ -162,6 +162,13 @@
|
||||
font-size: 0.8rem !important; /* Slightly smaller font */
|
||||
}
|
||||
|
||||
.kaats-spinner {
|
||||
animation: kaats-spinner-rotate 1.5s linear infinite; /* Faster rotation */
|
||||
width: 40px; /* Standard size */
|
||||
height: 40px;
|
||||
display: inline-block; /* Useful for table cells */
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.kaats-spinner .path {
|
||||
stroke: var(--kaauh-teal, #00636e); /* Use Teal color, fallback to dark teal */
|
||||
@ -265,16 +272,16 @@
|
||||
<select name="screening_rating" id="screening_rating" class="form-select form-select-sm" style="width: 120px;">
|
||||
<option value="">{% trans "Any Rating" %}</option>
|
||||
<option value="Highly Qualified" {% if screening_rating == "Highly Qualified" %}selected{% endif %}>
|
||||
Highly Qualified
|
||||
{% trans "Highly Qualified" %}
|
||||
</option>
|
||||
<option value="Qualified" {% if screening_rating == "Qualified" %}selected{% endif %}>
|
||||
Qualified
|
||||
{% trans "Qualified" %}
|
||||
</option>
|
||||
<option value="Partially Qualified" {% if screening_rating == "Partially Qualified" %}selected{% endif %}>
|
||||
Partially Qualified
|
||||
{% trans "Partially Qualified" %}
|
||||
</option>
|
||||
<option value="Not Qualified" {% if screening_rating == "Not Qualified" %}selected{% endif %}>
|
||||
Not Qualified
|
||||
{% trans "Not Qualified" %}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -312,7 +319,7 @@
|
||||
|
||||
{# Select Input Group #}
|
||||
<div>
|
||||
|
||||
|
||||
<select name="mark_as" id="update_status" class="form-select form-select-sm" style="min-width: 150px;">
|
||||
<option selected>
|
||||
----------
|
||||
@ -415,7 +422,7 @@
|
||||
<div class="text-nowrap">
|
||||
<svg class="kaats-spinner" viewBox="0 0 50 50" style="width: 25px; height: 25px;">
|
||||
<circle cx="25" cy="25" r="20"></circle>
|
||||
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"
|
||||
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"
|
||||
style="animation: kaats-spinner-dash 1.5s ease-in-out infinite;"></circle>
|
||||
</svg>
|
||||
<span class="text-teal-primary">{% trans 'AI scoring..' %}</span>
|
||||
@ -511,7 +518,7 @@
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
|
||||
{% trans "Loading email form..." %}
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
148
templates/recruitment/candidate_signup.html
Normal file
148
templates/recruitment/candidate_signup.html
Normal file
@ -0,0 +1,148 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Candidate Signup" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8 col-lg-6">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h4 class="mb-0">
|
||||
<i class="fas fa-user-plus me-2"></i>
|
||||
{% trans "Candidate Signup" %}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.first_name.id_for_label }}" class="form-label">
|
||||
{% trans "First Name" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.first_name }}
|
||||
{% if form.first_name.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.first_name.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.last_name.id_for_label }}" class="form-label">
|
||||
{% trans "Last Name" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.last_name }}
|
||||
{% if form.last_name.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.last_name.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.middle_name.id_for_label }}" class="form-label">
|
||||
{% trans "Middle Name" %}
|
||||
</label>
|
||||
{{ form.middle_name }}
|
||||
{% if form.middle_name.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.middle_name.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
||||
{% trans "Phone Number" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.phone }}
|
||||
{% if form.phone.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.phone.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.email.id_for_label }}" class="form-label">
|
||||
{% trans "Email Address" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.email }}
|
||||
{% if form.email.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.email.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.password.id_for_label }}" class="form-label">
|
||||
{% trans "Password" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.password }}
|
||||
{% if form.password.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.password.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.confirm_password.id_for_label }}" class="form-label">
|
||||
{% trans "Confirm Password" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.confirm_password }}
|
||||
{% if form.confirm_password.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.confirm_password.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
{% for error in form.non_field_errors %}
|
||||
{{ error }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-user-plus me-2"></i>
|
||||
{% trans "Sign Up" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<small class="text-muted">
|
||||
{% trans "Already have an account?" %}
|
||||
<a href="{% url 'portal_login' %}" class="text-decoration-none">
|
||||
{% trans "Login here" %}
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,14 +1,4 @@
|
||||
<td class="text-center" id="status-result-{{ candidate.pk}}">
|
||||
{% if not candidate.interview_status %}
|
||||
<button type="button" class="btn btn-warning btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'update_candidate_status' job.slug candidate.slug 'exam' 'passed' %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="Pass Exam">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
{% if candidate.exam_status %}
|
||||
<button type="button" class="btn btn-{% if candidate.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
@ -21,5 +11,4 @@
|
||||
{% else %}
|
||||
--
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
@ -1,22 +1,22 @@
|
||||
<td class="text-center" id="status-result-{{ candidate.pk}}">
|
||||
<td class="text-center" id="interview-result-{{ candidate.pk}}">
|
||||
{% if not candidate.interview_status %}
|
||||
<button type="button" class="btn btn-warning btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'update_candidate_status' job.slug candidate.slug 'offer' 'passed' %}"
|
||||
hx-get="{% url 'update_candidate_status' job.slug candidate.slug 'interview' 'passed' %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="Pass Exam">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
{% if candidate.offer_status %}
|
||||
<button type="button" class="btn btn-{% if candidate.offer_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
|
||||
{% if candidate.interview_status %}
|
||||
<button type="button" class="btn btn-{% if candidate.interview_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'update_candidate_status' job.slug candidate.slug 'offer' 'passed' %}"
|
||||
hx-get="{% url 'update_candidate_status' job.slug candidate.slug 'interview' 'passed' %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="Pass Exam">
|
||||
{{ candidate.offer_status }}
|
||||
{{ candidate.interview_status }}
|
||||
</button>
|
||||
{% else %}
|
||||
--
|
||||
|
||||
295
templates/recruitment/portal_login.html
Normal file
295
templates/recruitment/portal_login.html
Normal 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 %}
|
||||
444
translate_all_batches.py
Normal file
444
translate_all_batches.py
Normal file
@ -0,0 +1,444 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive script to translate all remaining batch files with Arabic translations
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
|
||||
# Comprehensive Arabic translation dictionary
|
||||
TRANSLATIONS = {
|
||||
# Email and Authentication
|
||||
"The date and time this notification is scheduled to be sent.": "التاريخ والوقت المحدد لإرسال هذا الإشعار.",
|
||||
"Send Attempts": "محاولات الإرسال",
|
||||
"Failed to start the job posting process. Please try again.": "فشل في بدء عملية نشر الوظيفة. يرجى المحاولة مرة أخرى.",
|
||||
"You don't have permission to view this page.": "ليس لديك إذن لعرض هذه الصفحة.",
|
||||
"Account Inactive": "الحساب غير نشط",
|
||||
"Princess Nourah bint Abdulrahman University": "جامعة الأميرة نورة بنت عبدالرحمن",
|
||||
"Manage your personal details and security.": "إدارة تفاصيلك الشخصية والأمان.",
|
||||
"Primary": "أساسي",
|
||||
"Verified": "موثق",
|
||||
"Unverified": "غير موثق",
|
||||
"Make Primary": "جعل أساسي",
|
||||
"Remove": "إزالة",
|
||||
"Add Email Address": "إضافة عنوان بريد إلكتروني",
|
||||
"Hello,": "مرحباً،",
|
||||
"Confirm My KAAUH ATS Email": "تأكيد بريدي الإلكتروني في نظام توظيف جامعة نورة",
|
||||
"Alternatively, copy and paste this link into your browser:": "بدلاً من ذلك، انسخ والصق هذا الرابط في متصفحك:",
|
||||
"Password Reset Request": "طلب إعادة تعيين كلمة المرور",
|
||||
"Click Here to Reset Your Password": "اضغط هنا لإعادة تعيين كلمة المرور",
|
||||
"This link is only valid for a limited time.": "هذا الرابط صالح لفترة محدودة فقط.",
|
||||
"Thank you,": "شكراً لك،",
|
||||
"KAAUH ATS Team": "فريق نظام توظيف جامعة نورة",
|
||||
"Confirm Email Address": "تأكيد عنوان البريد الإلكتروني",
|
||||
"Account Verification": "التحقق من الحساب",
|
||||
"Verify your email to secure your account and unlock full features.": "تحقق من بريدك الإلكتروني لتأمين حسابك وإلغاء قفل جميع الميزات.",
|
||||
"Confirm Your Email Address": "تأكيد عنوان بريدك الإلكتروني",
|
||||
"Verification Failed": "فشل التحقق",
|
||||
"The email confirmation link is expired or invalid.": "رابط تأكيد البريد الإلكتروني منتهي الصلاحية أو غير صالح.",
|
||||
"Keep me signed in": "ابق مسجلاً للدخول",
|
||||
"Return to Profile": "العودة إلى الملف الشخصي",
|
||||
"Enter your e-mail address to reset your password.": "أدخل عنوان بريدك الإلكتروني لإعادة تعيين كلمة المرور.",
|
||||
"Remember your password?": "تتذكر كلمة المرور؟",
|
||||
"Log In": "تسجيل الدخول",
|
||||
"Password Reset Sent": "تم إرسال إعادة تعيين كلمة المرور",
|
||||
"Return to Login": "العودة إلى تسجيل الدخول",
|
||||
"Please enter your new password below.": "يرجى إدخال كلمة المرور الجديدة أدناه.",
|
||||
|
||||
# Common UI Elements
|
||||
"Save": "حفظ",
|
||||
"Cancel": "إلغاء",
|
||||
"Delete": "حذف",
|
||||
"Edit": "تحرير",
|
||||
"View": "عرض",
|
||||
"Create": "إنشاء",
|
||||
"Update": "تحديث",
|
||||
"Submit": "إرسال",
|
||||
"Search": "بحث",
|
||||
"Filter": "تصفية",
|
||||
"Sort": "ترتيب",
|
||||
"Export": "تصدير",
|
||||
"Import": "استيراد",
|
||||
"Download": "تنزيل",
|
||||
"Upload": "رفع",
|
||||
"Close": "إغلاق",
|
||||
"Back": "رجوع",
|
||||
"Next": "التالي",
|
||||
"Previous": "السابق",
|
||||
"First": "الأول",
|
||||
"Last": "الأخير",
|
||||
"Home": "الرئيسية",
|
||||
"Dashboard": "لوحة التحكم",
|
||||
"Profile": "الملف الشخصي",
|
||||
"Settings": "الإعدادات",
|
||||
"Help": "المساعدة",
|
||||
"About": "حول",
|
||||
"Contact": "اتصال",
|
||||
"Logout": "تسجيل الخروج",
|
||||
"Login": "تسجيل الدخول",
|
||||
"Register": "التسجيل",
|
||||
"Sign Up": "إنشاء حساب",
|
||||
"Sign In": "تسجيل الدخول",
|
||||
|
||||
# Status Messages
|
||||
"Active": "نشط",
|
||||
"Inactive": "غير نشط",
|
||||
"Pending": "في الانتظار",
|
||||
"Completed": "مكتمل",
|
||||
"Failed": "فشل",
|
||||
"Success": "نجح",
|
||||
"Error": "خطأ",
|
||||
"Warning": "تحذير",
|
||||
"Info": "معلومات",
|
||||
"Loading": "جاري التحميل",
|
||||
"Processing": "جاري المعالجة",
|
||||
"Ready": "جاهز",
|
||||
"Not Ready": "غير جاهز",
|
||||
"Available": "متاح",
|
||||
"Unavailable": "غير متاح",
|
||||
"Online": "متصل",
|
||||
"Offline": "غير متصل",
|
||||
"Connected": "متصل",
|
||||
"Disconnected": "منقطع",
|
||||
"Enabled": "مفعل",
|
||||
"Disabled": "معطل",
|
||||
"Required": "مطلوب",
|
||||
"Optional": "اختياري",
|
||||
"Yes": "نعم",
|
||||
"No": "لا",
|
||||
"True": "صحيح",
|
||||
"False": "خطأ",
|
||||
"On": "مفعل",
|
||||
"Off": "معطل",
|
||||
"Open": "مفتوح",
|
||||
"Closed": "مغلق",
|
||||
"Locked": "مقفل",
|
||||
"Unlocked": "غير مقفل",
|
||||
|
||||
# Form Fields
|
||||
"Name": "الاسم",
|
||||
"Email": "البريد الإلكتروني",
|
||||
"Phone": "الهاتف",
|
||||
"Address": "العنوان",
|
||||
"City": "المدينة",
|
||||
"Country": "البلد",
|
||||
"State": "الولاية",
|
||||
"Zip Code": "الرمز البريدي",
|
||||
"Password": "كلمة المرور",
|
||||
"Confirm Password": "تأكيد كلمة المرور",
|
||||
"Username": "اسم المستخدم",
|
||||
"First Name": "الاسم الأول",
|
||||
"Last Name": "اسم العائلة",
|
||||
"Full Name": "الاسم الكامل",
|
||||
"Company": "الشركة",
|
||||
"Position": "المنصب",
|
||||
"Department": "القسم",
|
||||
"Title": "العنوان",
|
||||
"Description": "الوصف",
|
||||
"Comments": "التعليقات",
|
||||
"Notes": "ملاحظات",
|
||||
"Date": "التاريخ",
|
||||
"Time": "الوقت",
|
||||
"Start Date": "تاريخ البدء",
|
||||
"End Date": "تاريخ الانتهاء",
|
||||
"Created": "تم الإنشاء",
|
||||
"Modified": "تم التعديل",
|
||||
"Updated": "تم التحديث",
|
||||
|
||||
# Messages
|
||||
"Please select an option": "يرجى اختيار خيار",
|
||||
"This field is required": "هذا الحقل مطلوب",
|
||||
"Invalid email address": "عنوان بريد إلكتروني غير صالح",
|
||||
"Password must be at least 8 characters": "يجب أن تكون كلمة المرور 8 أحرف على الأقل",
|
||||
"Passwords do not match": "كلمات المرور غير متطابقة",
|
||||
"Email already exists": "البريد الإلكتروني موجود بالفعل",
|
||||
"User not found": "المستخدم غير موجود",
|
||||
"Invalid credentials": "بيانات الاعتماد غير صالحة",
|
||||
"Access denied": "الوصول مرفوض",
|
||||
"Permission denied": "الإذن مرفوض",
|
||||
"Operation successful": "تمت العملية بنجاح",
|
||||
"Operation failed": "فشلت العملية",
|
||||
"Data saved successfully": "تم حفظ البيانات بنجاح",
|
||||
"Data deleted successfully": "تم حذف البيانات بنجاح",
|
||||
"Are you sure you want to delete this item?": "هل أنت متأكد من أنك تريد حذف هذا العنصر؟",
|
||||
"This action cannot be undone": "لا يمكن التراجع عن هذا الإجراء",
|
||||
|
||||
# Navigation
|
||||
"Menu": "القائمة",
|
||||
"Home": "الرئيسية",
|
||||
"Dashboard": "لوحة التحكم",
|
||||
"Profile": "الملف الشخصي",
|
||||
"Settings": "الإعدادات",
|
||||
"Admin": "المسؤول",
|
||||
"Users": "المستخدمون",
|
||||
"Reports": "التقارير",
|
||||
"Analytics": "التحليلات",
|
||||
"Messages": "الرسائل",
|
||||
"Notifications": "الإشعارات",
|
||||
"Tasks": "المهام",
|
||||
"Calendar": "التقويم",
|
||||
"Documents": "المستندات",
|
||||
"Files": "الملفات",
|
||||
"Media": "الوسائط",
|
||||
"Help": "المساعدة",
|
||||
"Support": "الدعم",
|
||||
"FAQ": "الأسئلة الشائعة",
|
||||
"Terms": "الشروط",
|
||||
"Privacy": "الخصوصية",
|
||||
"Legal": "قانوني",
|
||||
|
||||
# Common Actions
|
||||
"Add": "إضافة",
|
||||
"Remove": "إزالة",
|
||||
"Edit": "تحرير",
|
||||
"Update": "تحديث",
|
||||
"Delete": "حذف",
|
||||
"View": "عرض",
|
||||
"Show": "إظهار",
|
||||
"Hide": "إخفاء",
|
||||
"Enable": "تفعيل",
|
||||
"Disable": "تعطيل",
|
||||
"Activate": "تنشيط",
|
||||
"Deactivate": "إلغاء تنشيط",
|
||||
"Approve": "موافقة",
|
||||
"Reject": "رفض",
|
||||
"Accept": "قبول",
|
||||
"Decline": "رفض",
|
||||
"Send": "إرسال",
|
||||
"Receive": "استلام",
|
||||
"Download": "تنزيل",
|
||||
"Upload": "رفع",
|
||||
"Import": "استيراد",
|
||||
"Export": "تصدير",
|
||||
"Print": "طباعة",
|
||||
"Copy": "نسخ",
|
||||
"Move": "نقل",
|
||||
"Rename": "إعادة تسمية",
|
||||
"Share": "مشاركة",
|
||||
"Subscribe": "اشتراك",
|
||||
"Unsubscribe": "إلغاء الاشتراك",
|
||||
"Follow": "متابعة",
|
||||
"Unfollow": "إلغاء المتابعة",
|
||||
"Like": "إعجاب",
|
||||
"Unlike": "إلغاء الإعجاب",
|
||||
"Comment": "تعليق",
|
||||
"Rate": "تقييم",
|
||||
"Review": "مراجعة",
|
||||
"Bookmark": "إشارة مرجعية",
|
||||
"Favorite": "مفضل",
|
||||
"Archive": "أرشفة",
|
||||
"Restore": "استعادة",
|
||||
"Backup": "نسخ احتياطي",
|
||||
"Recover": "استرداد",
|
||||
"Reset": "إعادة تعيين",
|
||||
"Refresh": "تحديث",
|
||||
"Reload": "إعادة تحميل",
|
||||
"Sync": "مزامنة",
|
||||
"Connect": "اتصال",
|
||||
"Disconnect": "قطع الاتصال",
|
||||
"Link": "ربط",
|
||||
"Unlink": "فك الربط",
|
||||
"Attach": "إرفاق",
|
||||
"Detach": "فصل",
|
||||
"Merge": "دمج",
|
||||
"Split": "تقسيم",
|
||||
"Combine": "دمج",
|
||||
"Separate": "فصل",
|
||||
"Group": "تجميع",
|
||||
"Ungroup": "فك التجميع",
|
||||
"Sort": "ترتيب",
|
||||
"Filter": "تصفية",
|
||||
"Search": "بحث",
|
||||
"Find": "بحث",
|
||||
"Replace": "استبدال",
|
||||
"Clear": "مسح",
|
||||
"Clean": "تنظيف",
|
||||
"Empty": "فارغ",
|
||||
"Full": "ممتلئ",
|
||||
"All": "الكل",
|
||||
"None": "لا شيء",
|
||||
"Some": "بعض",
|
||||
"Any": "أي",
|
||||
"Other": "آخر",
|
||||
"More": "المزيد",
|
||||
"Less": "أقل",
|
||||
"New": "جديد",
|
||||
"Old": "قديم",
|
||||
"Recent": "الحديث",
|
||||
"Latest": "الأحدث",
|
||||
"Previous": "السابق",
|
||||
"Next": "التالي",
|
||||
"First": "الأول",
|
||||
"Last": "الأخير",
|
||||
"Current": "الحالي",
|
||||
"Today": "اليوم",
|
||||
"Yesterday": "أمس",
|
||||
"Tomorrow": "غداً",
|
||||
"This week": "هذا الأسبوع",
|
||||
"Last week": "الأسبوع الماضي",
|
||||
"Next week": "الأسبوع القادم",
|
||||
"This month": "هذا الشهر",
|
||||
"Last month": "الشهر الماضي",
|
||||
"Next month": "الشهر القادم",
|
||||
"This year": "هذا العام",
|
||||
"Last year": "العام الماضي",
|
||||
"Next year": "العام القادم",
|
||||
}
|
||||
|
||||
def translate_batch_file(batch_file_path):
|
||||
"""
|
||||
Translate a single batch file and return the translations
|
||||
"""
|
||||
translations = {}
|
||||
|
||||
with open(batch_file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
lines = content.split('\n')
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i].strip()
|
||||
if line.startswith('msgid: "'):
|
||||
# Extract msgid content, removing the extra quote at the beginning
|
||||
msgid = line[8:-1] # Extract msgid content (skip the extra quote)
|
||||
|
||||
# Skip empty msgid or already Arabic text
|
||||
if not msgid or msgid.strip() == "" or is_arabic_text(msgid):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Find translation
|
||||
translation = TRANSLATIONS.get(msgid, "")
|
||||
if translation:
|
||||
translations[msgid] = translation
|
||||
print(f"✓ Found translation: '{msgid}' -> '{translation}'")
|
||||
else:
|
||||
print(f"✗ No translation found: '{msgid}'")
|
||||
i += 1
|
||||
|
||||
return translations
|
||||
|
||||
def is_arabic_text(text):
|
||||
"""Check if text contains Arabic characters"""
|
||||
arabic_chars = set('ابتثجحخدذرزسشصضطظعغفقكلمنهويءآأؤإئابةة')
|
||||
return any(char in arabic_chars for char in text)
|
||||
|
||||
def process_all_batches():
|
||||
"""
|
||||
Process all batch files and create a comprehensive translation file
|
||||
"""
|
||||
all_translations = {}
|
||||
|
||||
# Process batches 02-35 (batch 01 already done)
|
||||
for batch_num in range(2, 36):
|
||||
batch_file = f"translation_batch_{batch_num:02d}.txt"
|
||||
if os.path.exists(batch_file):
|
||||
print(f"\n=== Processing {batch_file} ===")
|
||||
batch_translations = translate_batch_file(batch_file)
|
||||
all_translations.update(batch_translations)
|
||||
print(f"Found {len(batch_translations)} translations in {batch_file}")
|
||||
else:
|
||||
print(f"⚠️ {batch_file} not found")
|
||||
|
||||
return all_translations
|
||||
|
||||
def create_translation_script(all_translations):
|
||||
"""
|
||||
Create a script to apply all translations to the main django.po file
|
||||
"""
|
||||
script_content = '''#!/usr/bin/env python3
|
||||
"""
|
||||
Script to apply all batch translations to the main django.po file
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
def apply_all_translations():
|
||||
"""
|
||||
Apply all translations to the main django.po file
|
||||
"""
|
||||
# All translations from batches 02-35
|
||||
translations = {
|
||||
'''
|
||||
|
||||
for english, arabic in all_translations.items():
|
||||
script_content += f' "{english}": "{arabic}",\n'
|
||||
|
||||
script_content += ''' }
|
||||
|
||||
main_po_file = "locale/ar/LC_MESSAGES/django.po"
|
||||
|
||||
# Read the main django.po file
|
||||
with open(main_po_file, 'r', encoding='utf-8') as f:
|
||||
main_content = f.read()
|
||||
|
||||
# Apply translations to main file
|
||||
updated_content = main_content
|
||||
applied_count = 0
|
||||
|
||||
for english, arabic in translations.items():
|
||||
# Pattern to find msgid followed by empty msgstr
|
||||
pattern = rf'(msgid "{re.escape(english)}"\\s*\\nmsgstr) ""'
|
||||
replacement = rf'\\1 "{arabic}"'
|
||||
|
||||
if re.search(pattern, updated_content):
|
||||
updated_content = re.sub(pattern, replacement, updated_content)
|
||||
applied_count += 1
|
||||
print(f"✓ Applied: '{english}' -> '{arabic}'")
|
||||
else:
|
||||
print(f"✗ Not found: '{english}'")
|
||||
|
||||
# Write updated content back to main file
|
||||
with open(main_po_file, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
|
||||
print(f"\\nApplied {applied_count} translations to {main_po_file}")
|
||||
return applied_count
|
||||
|
||||
def main():
|
||||
"""Main function to apply all translations"""
|
||||
print("Applying all batch translations to main django.po file...")
|
||||
applied_count = apply_all_translations()
|
||||
|
||||
if applied_count > 0:
|
||||
print(f"\\n✅ Successfully applied {applied_count} translations!")
|
||||
print("Next steps:")
|
||||
print("1. Run: python manage.py compilemessages")
|
||||
print("2. Test the translations in the application")
|
||||
else:
|
||||
print("\\n❌ No translations were applied.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
with open("apply_all_translations.py", 'w', encoding='utf-8') as f:
|
||||
f.write(script_content)
|
||||
|
||||
print("Created apply_all_translations.py script")
|
||||
|
||||
def main():
|
||||
"""Main function to process all batches"""
|
||||
print("🚀 Starting comprehensive translation process...")
|
||||
|
||||
# Process all batch files
|
||||
all_translations = process_all_batches()
|
||||
|
||||
print(f"\n📊 Summary:")
|
||||
print(f"Total translations found: {len(all_translations)}")
|
||||
|
||||
if all_translations:
|
||||
# Create the application script
|
||||
create_translation_script(all_translations)
|
||||
|
||||
print(f"\n✅ Translation processing complete!")
|
||||
print(f"📝 Created apply_all_translations.py with {len(all_translations)} translations")
|
||||
print(f"\n🎯 Next steps:")
|
||||
print(f"1. Run: python apply_all_translations.py")
|
||||
print(f"2. Run: python manage.py compilemessages")
|
||||
print(f"3. Test the translations in the application")
|
||||
else:
|
||||
print("\n❌ No translations found to process.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
58
translate_batch_01.py
Normal file
58
translate_batch_01.py
Normal file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to add Arabic translations for batch 01
|
||||
"""
|
||||
|
||||
# Arabic translations for batch 01
|
||||
translations = {
|
||||
"": "", # Line 7 - empty string, keep as is
|
||||
"Website": "الموقع الإلكتروني",
|
||||
"Admin Notes": "ملاحظات المسؤول",
|
||||
"Save Assignment": "حفظ التكليف",
|
||||
"Assignment": "التكليف",
|
||||
"Expires At": "ينتهي في",
|
||||
"Access Token": "رمز الوصول",
|
||||
"Subject": "الموضوع",
|
||||
"Recipients": "المستلمون",
|
||||
"Internal staff involved in the recruitment process for this job": "الموظفون الداخليون المشاركون في عملية التوظيف لهذه الوظيفة",
|
||||
"External Participant": "مشارك خارجي",
|
||||
"External participants involved in the recruitment process for this job": "المشاركون الخارجيون المشاركون في عملية التوظيف لهذه الوظيفة",
|
||||
"Reason for canceling the job posting": "سبب إلغاء نشر الوظيفة",
|
||||
"Name of person who cancelled this job": "اسم الشخص الذي ألغى هذه الوظيفة",
|
||||
"Hired": "تم التوظيف",
|
||||
"Author": "المؤلف",
|
||||
"Endpoint URL for sending candidate data (for outbound sync)": "عنوان URL لنقطة النهاية لإرسال بيانات المرشح (للمزامنة الصادرة)",
|
||||
"HTTP method for outbound sync requests": "طريقة HTTP لطلبات المزامنة الصادرة",
|
||||
"HTTP method for connection testing": "طريقة HTTP لاختبار الاتصال",
|
||||
"Custom Headers": "رؤوس مخصصة",
|
||||
"JSON object with custom HTTP headers for sync requests": "كائن JSON يحتوي على رؤوس HTTP مخصصة لطلبات المزامنة",
|
||||
"Supports Outbound Sync": "يدعم المزامنة الصادرة",
|
||||
"Whether this source supports receiving candidate data from ATS": "ما إذا كان هذا المصدر يدعم استقبال بيانات المرشح من نظام تتبع المتقدمين",
|
||||
"Expired": "منتهي الصلاحية",
|
||||
"Maximum candidates agency can submit for this job": "الحد الأقصى للمرشحين الذين يمكن للوكالة تقديمهم لهذه الوظيفة"
|
||||
}
|
||||
|
||||
def update_batch_file():
|
||||
"""Update the batch file with Arabic translations"""
|
||||
input_file = "translation_batch_01.txt"
|
||||
output_file = "translation_batch_01_completed.txt"
|
||||
|
||||
with open(input_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace empty msgstr with translations
|
||||
for english, arabic in translations.items():
|
||||
if english: # Skip empty string
|
||||
# Find the pattern and replace
|
||||
old_pattern = f'msgid: "{english}"\nmsgstr: ""\n\nArabic Translation: \nmsgstr: ""'
|
||||
new_pattern = f'msgid: "{english}"\nmsgstr: ""\n\nArabic Translation: \nmsgstr: "{arabic}"'
|
||||
content = content.replace(old_pattern, new_pattern)
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"Updated batch file saved as: {output_file}")
|
||||
print("Arabic translations added for batch 01")
|
||||
|
||||
if __name__ == "__main__":
|
||||
update_batch_file()
|
||||
204
translation_batch_01.txt
Normal file
204
translation_batch_01.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 01 ===
|
||||
Translations 1-25 of 843
|
||||
============================================================
|
||||
|
||||
Line 7:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1041:
|
||||
msgid: "Number of candidates submitted so far"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1052:
|
||||
msgid: "Deadline for agency to submit candidates"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1068:
|
||||
msgid: "Original deadline before extensions"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1078:
|
||||
msgid: "Agency Job Assignment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1082:
|
||||
msgid: "Agency Job Assignments"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1086:
|
||||
msgid: "Deadline date must be in the future"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1090:
|
||||
msgid: "Maximum candidates must be greater than 0"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1094:
|
||||
msgid: "Candidates submitted cannot exceed maximum candidates"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1098:
|
||||
msgid: "Unique Token"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1108:
|
||||
msgid: "Password for agency access"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1119:
|
||||
msgid: "When this access link expires"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1124:
|
||||
msgid: "Last Accessed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1128:
|
||||
msgid: "Access Count"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1132:
|
||||
msgid: "Agency Access Link"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1136:
|
||||
msgid: "Agency Access Links"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1140:
|
||||
msgid: "Expiration date must be in the future"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1190:
|
||||
msgid: "In-App"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1194:
|
||||
msgid: "Pending"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1198:
|
||||
msgid: "Sent"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1202:
|
||||
msgid: "Read"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1206:
|
||||
msgid: "Retrying"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1210:
|
||||
msgid: "Recipient"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1214:
|
||||
msgid: "Notification Message"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1218:
|
||||
msgid: "Notification Type"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_01_completed.txt
Normal file
204
translation_batch_01_completed.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 01 ===
|
||||
Translations 1-25 of 867
|
||||
============================================================
|
||||
|
||||
Line 7:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 320:
|
||||
msgid: "Website"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "الموقع الإلكتروني"
|
||||
----------------------------------------
|
||||
|
||||
Line 406:
|
||||
msgid: "Admin Notes"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "ملاحظات المسؤول"
|
||||
----------------------------------------
|
||||
|
||||
Line 410:
|
||||
msgid: "Save Assignment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "حفظ التكليف"
|
||||
----------------------------------------
|
||||
|
||||
Line 416:
|
||||
msgid: "Assignment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "التكليف"
|
||||
----------------------------------------
|
||||
|
||||
Line 422:
|
||||
msgid: "Expires At"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "ينتهي في"
|
||||
----------------------------------------
|
||||
|
||||
Line 449:
|
||||
msgid: "Access Token"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "رمز الوصول"
|
||||
----------------------------------------
|
||||
|
||||
Line 474:
|
||||
msgid: "Subject"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "الموضوع"
|
||||
----------------------------------------
|
||||
|
||||
Line 485:
|
||||
msgid: "Recipients"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "المستلمون"
|
||||
----------------------------------------
|
||||
|
||||
Line 525:
|
||||
msgid: "Internal staff involved in the recruitment process for this job"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "الموظفون الداخليون المشاركون في عملية التوظيف لهذه الوظيفة"
|
||||
----------------------------------------
|
||||
|
||||
Line 529:
|
||||
msgid: "External Participant"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "مشارك خارجي"
|
||||
----------------------------------------
|
||||
|
||||
Line 533:
|
||||
msgid: "External participants involved in the recruitment process for this job"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "المشاركون الخارجيون المشاركون في عملية التوظيف لهذه الوظيفة"
|
||||
----------------------------------------
|
||||
|
||||
Line 541:
|
||||
msgid: "Reason for canceling the job posting"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "سبب إلغاء نشر الوظيفة"
|
||||
----------------------------------------
|
||||
|
||||
Line 551:
|
||||
msgid: "Name of person who cancelled this job"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "اسم الشخص الذي ألغى هذه الوظيفة"
|
||||
----------------------------------------
|
||||
|
||||
Line 595:
|
||||
msgid: "Hired"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "تم التوظيف"
|
||||
----------------------------------------
|
||||
|
||||
Line 782:
|
||||
msgid: "Author"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "المؤلف"
|
||||
----------------------------------------
|
||||
|
||||
Line 877:
|
||||
msgid: "Endpoint URL for sending candidate data (for outbound sync)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "عنوان URL لنقطة النهاية لإرسال بيانات المرشح (للمزامنة الصادرة)"
|
||||
----------------------------------------
|
||||
|
||||
Line 887:
|
||||
msgid: "HTTP method for outbound sync requests"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "طريقة HTTP لطلبات المزامنة الصادرة"
|
||||
----------------------------------------
|
||||
|
||||
Line 897:
|
||||
msgid: "HTTP method for connection testing"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "طريقة HTTP لاختبار الاتصال"
|
||||
----------------------------------------
|
||||
|
||||
Line 901:
|
||||
msgid: "Custom Headers"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "رؤوس مخصصة"
|
||||
----------------------------------------
|
||||
|
||||
Line 905:
|
||||
msgid: "JSON object with custom HTTP headers for sync requests"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "كائن JSON يحتوي على رؤوس HTTP مخصصة لطلبات المزامنة"
|
||||
----------------------------------------
|
||||
|
||||
Line 909:
|
||||
msgid: "Supports Outbound Sync"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "يدعم المزامنة الصادرة"
|
||||
----------------------------------------
|
||||
|
||||
Line 913:
|
||||
msgid: "Whether this source supports receiving candidate data from ATS"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "ما إذا كان هذا المصدر يدعم استقبال بيانات المرشح من نظام تتبع المتقدمين"
|
||||
----------------------------------------
|
||||
|
||||
Line 1026:
|
||||
msgid: "Expired"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "منتهي الصلاحية"
|
||||
----------------------------------------
|
||||
|
||||
Line 1030:
|
||||
msgid: "Maximum candidates agency can submit for this job"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: "الحد الأقصى للمرشحين الذين يمكن للوكالة تقديمهم لهذه الوظيفة"
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_02.txt
Normal file
204
translation_batch_02.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 02 ===
|
||||
Translations 26-50 of 843
|
||||
============================================================
|
||||
|
||||
Line 1234:
|
||||
msgid: "The date and time this notification is scheduled to be sent."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1238:
|
||||
msgid: "Send Attempts"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1275:
|
||||
msgid: "Failed to start the job posting process. Please try again."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1291:
|
||||
msgid: "Model Changes (CRUD)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1295:
|
||||
msgid: "You don't have permission to view this page."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1300:
|
||||
msgid: "Account Inactive"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1307:
|
||||
msgid: "جامعة الأميرة نورة بنت عبدالرحمن الأكاديمية"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1314:
|
||||
msgid: "ومستشفى الملك عبدالله بن عبدالعزيز التخصصي"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1321:
|
||||
msgid: "Princess Nourah bint Abdulrahman University"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1334:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1339:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1367:
|
||||
msgid: "Manage your personal details and security."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1399:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1405:
|
||||
msgid: "Primary"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1409:
|
||||
msgid: "Verified"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1413:
|
||||
msgid: "Unverified"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1417:
|
||||
msgid: "Make Primary"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1428:
|
||||
msgid: "Remove"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1438:
|
||||
msgid: "Add Email Address"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1451:
|
||||
msgid: "Hello,"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1456:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1463:
|
||||
msgid: "Confirm My KAAUH ATS Email"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1468:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1474:
|
||||
msgid: "Alternatively, copy and paste this link into your browser:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1479:
|
||||
msgid: "Password Reset Request"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_03.txt
Normal file
204
translation_batch_03.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 03 ===
|
||||
Translations 51-75 of 843
|
||||
============================================================
|
||||
|
||||
Line 1484:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1491:
|
||||
msgid: "Click Here to Reset Your Password"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1496:
|
||||
msgid: "This link is only valid for a limited time."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1501:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1508:
|
||||
msgid: "Thank you,"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1513:
|
||||
msgid: "KAAUH ATS Team"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1518:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1524:
|
||||
msgid: "Confirm Email Address"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1528:
|
||||
msgid: "Account Verification"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1532:
|
||||
msgid: "Verify your email to secure your account and unlock full features."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1536:
|
||||
msgid: "Confirm Your Email Address"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1541:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1553:
|
||||
msgid: "Verification Failed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1557:
|
||||
msgid: "The email confirmation link is expired or invalid."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1561:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1603:
|
||||
msgid: "Keep me signed in"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1649:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1654:
|
||||
msgid: "Return to Profile"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1658:
|
||||
msgid: "Enter your e-mail address to reset your password."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1674:
|
||||
msgid: "Remember your password?"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1678:
|
||||
msgid: "Log In"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1683:
|
||||
msgid: "Password Reset Sent"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1695:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1701:
|
||||
msgid: "Return to Login"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1712:
|
||||
msgid: "Please enter your new password below."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_04.txt
Normal file
204
translation_batch_04.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 04 ===
|
||||
Translations 76-100 of 843
|
||||
============================================================
|
||||
|
||||
Line 1716:
|
||||
msgid: "You can then log in."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1720:
|
||||
msgid: "Password Reset Failed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1724:
|
||||
msgid: "The password reset link is invalid or has expired."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1728:
|
||||
msgid: "Request New Reset Link"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1744:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1756:
|
||||
msgid: "Verify Your Email Address"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1768:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1774:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1780:
|
||||
msgid: "Change or Resend Email"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1789:
|
||||
msgid: "Django site admin"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1799:
|
||||
msgid: "KAAUH Agency Portal"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1807:
|
||||
msgid: "kaauh logo green bg"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1827:
|
||||
msgid: "Logout"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1941:
|
||||
msgid: "Ready to Apply?"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1945:
|
||||
msgid: "Review the job details, then apply below."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1950:
|
||||
msgid: "Apply for this Position"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1969:
|
||||
msgid: "Not specified"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 1992:
|
||||
msgid: "JOB ID:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2062:
|
||||
msgid: "Submission Metadata"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2066:
|
||||
msgid: "Submission ID:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2077:
|
||||
msgid: "Form:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2099:
|
||||
msgid: "Field Property"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2133:
|
||||
msgid: "Field Required"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2141:
|
||||
msgid: "Yes"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2149:
|
||||
msgid: "No"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_05.txt
Normal file
204
translation_batch_05.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 05 ===
|
||||
Translations 101-125 of 843
|
||||
============================================================
|
||||
|
||||
Line 2153:
|
||||
msgid: "No response fields were found for this submission."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2157:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2180:
|
||||
msgid: "Submissions"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2184:
|
||||
msgid: "All Submissions Table"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2188:
|
||||
msgid: "All Submissions for"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2193:
|
||||
msgid: "Submission ID"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2232:
|
||||
msgid: "Page"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2238:
|
||||
msgid: "of"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2250:
|
||||
msgid: "There are no submissions for this form template yet."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2254:
|
||||
msgid: "Submissions for"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2460:
|
||||
msgid: "Careers"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2473:
|
||||
msgid: "AI Score"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2484:
|
||||
msgid: "Top Keywords"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2488:
|
||||
msgid: "Experience"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2492:
|
||||
msgid: "years"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2496:
|
||||
msgid: "Recent Role:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2500:
|
||||
msgid: "Skills"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2504:
|
||||
msgid: "Soft Skills:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2508:
|
||||
msgid: "Industry Match:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2513:
|
||||
msgid: "Recommendation"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2518:
|
||||
msgid: "Strengths"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2523:
|
||||
msgid: "Weaknesses"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2528:
|
||||
msgid: "Criteria Assessment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2541:
|
||||
msgid: "Met"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2547:
|
||||
msgid: "Not Met"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_06.txt
Normal file
204
translation_batch_06.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 06 ===
|
||||
Translations 126-150 of 843
|
||||
============================================================
|
||||
|
||||
Line 2558:
|
||||
msgid: "Screening Rating"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2563:
|
||||
msgid: "Language Fluency"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2569:
|
||||
msgid: "Success"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2574:
|
||||
msgid: "Copied \"%(text)s\" to clipboard!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2584:
|
||||
msgid: "System Audit Logs"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2588:
|
||||
msgid: "Viewing Logs"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2592:
|
||||
msgid: "Displaying"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2596:
|
||||
msgid: "total records."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2614:
|
||||
msgid: "User"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2618:
|
||||
msgid: "Model"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2622:
|
||||
msgid: "Object PK"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2648:
|
||||
msgid: "Path"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2652:
|
||||
msgid: "CREATE"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2656:
|
||||
msgid: "UPDATE"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2660:
|
||||
msgid: "DELETE"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2664:
|
||||
msgid: "Login"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2674:
|
||||
msgid: "No logs found for this section or the database is empty."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2685:
|
||||
msgid: "Email will be sent to all selected recipients"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2700:
|
||||
msgid: "Loading..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2704:
|
||||
msgid: "Sending email..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2708:
|
||||
msgid: "Sending..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2727:
|
||||
msgid: "Meeting Details (will appear after scheduling):"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2743:
|
||||
msgid: "Click here to join meeting"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2772:
|
||||
msgid: "Processing..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2776:
|
||||
msgid: "An unknown error occurred."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_07.txt
Normal file
204
translation_batch_07.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 07 ===
|
||||
Translations 151-175 of 843
|
||||
============================================================
|
||||
|
||||
Line 2780:
|
||||
msgid: "An error occurred while processing your request."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2788:
|
||||
msgid: "Bulk Interview Scheduling"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2792:
|
||||
msgid: "Configure time slots for:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2813:
|
||||
msgid: "Candidates to Schedule (Hold Ctrl/Cmd to select multiple)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2868:
|
||||
msgid: "Thank You!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2872:
|
||||
msgid: "Your application has been submitted successfully"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2876:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2883:
|
||||
msgid: "Return to Job Listings"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2887:
|
||||
msgid: "Job ID#"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 2919:
|
||||
msgid: "Link"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3005:
|
||||
msgid: "Hashtags (For Promotion/Search on Linkedin)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3065:
|
||||
msgid: "Search by name, email, phone, or stage..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3069:
|
||||
msgid: "Filter Results"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3074:
|
||||
msgid: "Clear Filters"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3167:
|
||||
msgid: "JOB ID: "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3171:
|
||||
msgid: "Share Public Link"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3177:
|
||||
msgid: "Copied!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3215:
|
||||
msgid: "Tracking"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3249:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3269:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3330:
|
||||
msgid: "Candidate Categories & Scores"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3334:
|
||||
msgid: "Key Performance Indicators"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3338:
|
||||
msgid: "Avg. AI Score"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3343:
|
||||
msgid: "High Potential"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3353:
|
||||
msgid: "Avg. Exam Review"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_08.txt
Normal file
204
translation_batch_08.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 08 ===
|
||||
Translations 176-200 of 843
|
||||
============================================================
|
||||
|
||||
Line 3357:
|
||||
msgid: "Vacancy Fill Rate"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3373:
|
||||
msgid: "Status form not available. Please check your view."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3380:
|
||||
msgid: "Save Changes"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3396:
|
||||
msgid: "Search by Title or Department"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3425:
|
||||
msgid: "Archived"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3440:
|
||||
msgid: "Clear"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3450:
|
||||
msgid: "Max Apps"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3481:
|
||||
msgid: "All"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3486:
|
||||
msgid: "Screened"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3496:
|
||||
msgid: "Form"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3500:
|
||||
msgid: "N/A"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3520:
|
||||
msgid: "Create your first job posting to get started or adjust your filters."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3554:
|
||||
msgid: "Search by Topic"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3596:
|
||||
msgid: "Create your first meeting or adjust your filters."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3641:
|
||||
msgid: "minutes"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3655:
|
||||
msgid: "Assigned Participants"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3665:
|
||||
msgid: "External Participants"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3669:
|
||||
msgid: "System User"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3673:
|
||||
msgid: "Comments"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3696:
|
||||
msgid: "No comments yet. Be the first to comment!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3712:
|
||||
msgid: "You must be logged in to add a comment."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3730:
|
||||
msgid: "You are updating the existing meeting schedule."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3748:
|
||||
msgid: "Candidate has upcoming interviews. Updating existing schedule."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3752:
|
||||
msgid: "e.g., Technical Screening, HR Interview"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3762:
|
||||
msgid: "Save"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_09.txt
Normal file
204
translation_batch_09.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 09 ===
|
||||
Translations 201-225 of 843
|
||||
============================================================
|
||||
|
||||
Line 3858:
|
||||
msgid: "This participant is not currently assigned to any job."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3862:
|
||||
msgid: "Metadata"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3873:
|
||||
msgid: "at"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3877:
|
||||
msgid: "Total Assigned Jobs"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3896:
|
||||
msgid: "This action cannot be undone."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3913:
|
||||
msgid: "Search by Name or Email"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3917:
|
||||
msgid: "Filter by Assigned Job"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3935:
|
||||
msgid: "Create your first participant record or adjust your filters."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3952:
|
||||
msgid: "Secure access link for agency candidate submissions"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3984:
|
||||
msgid: "Access Credentials"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 3996:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4002:
|
||||
msgid: "Usage Statistics"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4006:
|
||||
msgid: "Total Accesses"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4010:
|
||||
msgid: "Never"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4014:
|
||||
msgid: "View Assignment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4032:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4045:
|
||||
msgid: "Generate a secure access link for agency to submit candidates"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4057:
|
||||
msgid: "Select the agency job assignment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4061:
|
||||
msgid: "When will this access link expire?"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4065:
|
||||
msgid: "Max Submissions"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4069:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4074:
|
||||
msgid: "Whether this access link is currently active"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4078:
|
||||
msgid: "Notes"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4088:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4094:
|
||||
msgid: "Assignment Details and Management"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_10.txt
Normal file
204
translation_batch_10.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 10 ===
|
||||
Translations 226-250 of 843
|
||||
============================================================
|
||||
|
||||
Line 4099:
|
||||
msgid: "Edit Assignment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4166:
|
||||
msgid: "Submission Progress"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4194:
|
||||
msgid: "Recent Messages"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4201:
|
||||
msgid: "From"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4207:
|
||||
msgid: "New"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4218:
|
||||
msgid: "Extend Assignment Deadline"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4234:
|
||||
msgid: "Token copied to clipboard!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4238:
|
||||
msgid: "Assign a job to an external hiring agency"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4242:
|
||||
msgid: "Maximum number of candidates the agency can submit"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4246:
|
||||
msgid: "Date and time when submission period ends"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4263:
|
||||
msgid: "Total Assignments:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4267:
|
||||
msgid: "New Assignment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4271:
|
||||
msgid: "Search by agency or job title..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4281:
|
||||
msgid: "Assignments pagination"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4291:
|
||||
msgid: "Create your first agency assignment to get started."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4295:
|
||||
msgid: "Create Assignment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4306:
|
||||
msgid: "You are about to delete a hiring agency. This action cannot be undone."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4316:
|
||||
msgid: "Warning: This action cannot be undone!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4320:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4326:
|
||||
msgid: "Agency to be Deleted"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4336:
|
||||
msgid: "candidate(s) are associated with this agency."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4340:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4346:
|
||||
msgid: "What will happen when you delete this agency?"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4350:
|
||||
msgid: "The agency profile and all its information will be permanently deleted"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4360:
|
||||
msgid: "Associated candidates will lose their agency reference"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_11.txt
Normal file
204
translation_batch_11.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 11 ===
|
||||
Translations 251-275 of 843
|
||||
============================================================
|
||||
|
||||
Line 4364:
|
||||
msgid: "Historical data linking candidates to this agency will be lost"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4368:
|
||||
msgid: "This action cannot be undone under any circumstances"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4372:
|
||||
msgid: "Type the agency name to confirm deletion:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4376:
|
||||
msgid: "This is required to prevent accidental deletions."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4380:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4386:
|
||||
msgid: "Delete Agency Permanently"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4404:
|
||||
msgid: "Hiring Agency Details and Candidate Management"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4408:
|
||||
msgid: "Assign job"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4467:
|
||||
msgid: "This agency hasn't submitted any candidates yet."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4477:
|
||||
msgid: "Total"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4487:
|
||||
msgid: "Visit Website"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4509:
|
||||
msgid: "Update the hiring agency information below."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4513:
|
||||
msgid: "Fill in the details to add a new hiring agency."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4517:
|
||||
msgid: "Please correct the errors below:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4531:
|
||||
msgid: "Quick Tips"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4535:
|
||||
msgid: "Provide accurate contact information for better communication"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4539:
|
||||
msgid: "Include a valid website URL if available"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4543:
|
||||
msgid: "Add a detailed description to help identify the agency"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4547:
|
||||
msgid: "All fields marked with * are required"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4575:
|
||||
msgid: "Search by name, contact person, email, or country..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4579:
|
||||
msgid: "Agency pagination"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4589:
|
||||
msgid: "No hiring agencies have been added yet."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4593:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4644:
|
||||
msgid: "Submit candidates using the form above to get started."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4662:
|
||||
msgid: "Assignment Info"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_12.txt
Normal file
204
translation_batch_12.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 12 ===
|
||||
Translations 276-300 of 843
|
||||
============================================================
|
||||
|
||||
Line 4666:
|
||||
msgid: "Days Remaining"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4671:
|
||||
msgid: "days"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4675:
|
||||
msgid: "Submission Rate"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4679:
|
||||
msgid: "Send Message to Admin"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4683:
|
||||
msgid: "Priority"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4687:
|
||||
msgid: "Low"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4691:
|
||||
msgid: "Medium"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4695:
|
||||
msgid: "High"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4718:
|
||||
msgid: "Error loading candidate data. Please try again."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4723:
|
||||
msgid: "Error updating candidate. Please try again."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4728:
|
||||
msgid: "Error removing candidate. Please try again."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4739:
|
||||
msgid: "Welcome back"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4743:
|
||||
msgid: "Total Assignments"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4747:
|
||||
msgid: "Active Assignments"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4764:
|
||||
msgid: "Your Job Assignments"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4768:
|
||||
msgid: "assignments"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4772:
|
||||
msgid: "days left"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4776:
|
||||
msgid: "days overdue"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4780:
|
||||
msgid: "Submissions Closed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4784:
|
||||
msgid: "No Job Assignments Found"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4788:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4794:
|
||||
msgid: "Agency Portal Login"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4804:
|
||||
msgid: "Enter the access token provided by the hiring organization"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4808:
|
||||
msgid: "Enter the password for this access token"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4812:
|
||||
msgid: "Access Portal"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_13.txt
Normal file
204
translation_batch_13.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 13 ===
|
||||
Translations 301-325 of 843
|
||||
============================================================
|
||||
|
||||
Line 4816:
|
||||
msgid: "Need Help?"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4826:
|
||||
msgid: "Reach out to your hiring contact"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4836:
|
||||
msgid: "View user guides and tutorials"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4840:
|
||||
msgid: "Security Notice"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4844:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4850:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4856:
|
||||
msgid: "Please enter your access token."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4860:
|
||||
msgid: "Please enter your password."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4876:
|
||||
msgid: "Days Remaining:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4927:
|
||||
msgid: "Click to upload or drag and drop"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4931:
|
||||
msgid: "Accepted formats: PDF, DOC, DOCX (Maximum 5MB)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4935:
|
||||
msgid: "Remove File"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4939:
|
||||
msgid: "Additional Notes"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4943:
|
||||
msgid: "Notes (Optional)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4947:
|
||||
msgid: "Any additional information about the candidate"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4951:
|
||||
msgid: "Submitted candidates will be reviewed by the hiring team."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4961:
|
||||
msgid: "This assignment has expired. Submissions are no longer accepted."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4965:
|
||||
msgid: "Maximum candidate limit reached for this assignment."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4969:
|
||||
msgid: "This assignment is not currently active."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4973:
|
||||
msgid: "Submitting candidate..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4977:
|
||||
msgid: "Please wait while we process your submission."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4981:
|
||||
msgid: "Please upload a PDF, DOC, or DOCX file."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 4985:
|
||||
msgid: "File size must be less than 5MB."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5001:
|
||||
msgid: "Error submitting candidate. Please try again."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5005:
|
||||
msgid: "Network error. Please check your connection and try again."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_14.txt
Normal file
204
translation_batch_14.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 14 ===
|
||||
Translations 326-350 of 843
|
||||
============================================================
|
||||
|
||||
Line 5041:
|
||||
msgid: "Journey Timeline"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5087:
|
||||
msgid: "AI Analysis Report"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5091:
|
||||
msgid: "Match Score"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5095:
|
||||
msgid: "Category"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5099:
|
||||
msgid: "Job Fit Narrative"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5109:
|
||||
msgid: "Years of Experience:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5113:
|
||||
msgid: "Most Recent Job Title:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5117:
|
||||
msgid: "Experience Industry Match:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5121:
|
||||
msgid: "Soft Skills Score:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5131:
|
||||
msgid: "Minimum Requirements Met:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5135:
|
||||
msgid: "Screening Stage Rating:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5139:
|
||||
msgid: "Resume is being parsed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5143:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5175:
|
||||
msgid: "View Resume AI Overview"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5179:
|
||||
msgid: "Time to Hire: "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5183:
|
||||
msgid: "Unable to Parse Resume , click to retry"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5193:
|
||||
msgid: "Candidates in Exam Stage:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5197:
|
||||
msgid: "Export exam candidates to CSV"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5205:
|
||||
msgid: "Export CSV"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5222:
|
||||
msgid: "Screening Stage"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5232:
|
||||
msgid: "No candidates are currently in the Exam stage for this job."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5236:
|
||||
msgid: "Candidate Details & Exam Update"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5253:
|
||||
msgid: "Successfully Hired:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5257:
|
||||
msgid: "Sync hired candidates to external sources"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5268:
|
||||
msgid: "Export hired candidates to CSV"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_15.txt
Normal file
204
translation_batch_15.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 15 ===
|
||||
Translations 351-375 of 843
|
||||
============================================================
|
||||
|
||||
Line 5272:
|
||||
msgid: "Congratulations!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5276:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5303:
|
||||
msgid: "Loading content..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5313:
|
||||
msgid: "Syncing candidates..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5317:
|
||||
msgid: "Syncing hired candidates..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5321:
|
||||
msgid: "Please wait while we sync candidates to external sources."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5325:
|
||||
msgid: "Syncing..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5329:
|
||||
msgid: "An unexpected error occurred during sync."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5345:
|
||||
msgid: "Successful:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5387:
|
||||
msgid: "Sync task failed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5391:
|
||||
msgid: "Failed to check sync status"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5395:
|
||||
msgid: "Sync timed out after 5 minutes"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5399:
|
||||
msgid: "Sync in progress..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5415:
|
||||
msgid: "Candidates in Interview Stage:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5419:
|
||||
msgid: "Export interview candidates to CSV"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5465:
|
||||
msgid: "Minutes"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5469:
|
||||
msgid: "No candidates are currently in the Interview stage for this job."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5475:
|
||||
msgid: "Candidate Details / Bulk Action Form"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5479:
|
||||
msgid: "Manage all participants"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5484:
|
||||
msgid: "Users"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5488:
|
||||
msgid: "Loading email form..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5500:
|
||||
msgid: "Filter by Job"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5504:
|
||||
msgid: "Major"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5538:
|
||||
msgid: "Candidates in Offer Stage:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5542:
|
||||
msgid: "Export offer candidates to CSV"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_16.txt
Normal file
204
translation_batch_16.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 16 ===
|
||||
Translations 376-400 of 843
|
||||
============================================================
|
||||
|
||||
Line 5546:
|
||||
msgid: "To Hired"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5556:
|
||||
msgid: "No candidates are currently in the Offer stage for this job."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5566:
|
||||
msgid: "Job:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5570:
|
||||
msgid: "Export screening candidates to CSV"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5574:
|
||||
msgid: "AI Scoring & Top Candidate Filter"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5578:
|
||||
msgid: "Min AI Score"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5582:
|
||||
msgid: "Min Years Exp"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5586:
|
||||
msgid: "Any Rating"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5614:
|
||||
msgid: "Is Qualified?"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5618:
|
||||
msgid: "Professional Category"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5622:
|
||||
msgid: "Top 3 Skills"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5626:
|
||||
msgid: "AI scoring.."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5630:
|
||||
msgid: "No candidates match the current stage and filter criteria."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5666:
|
||||
msgid: "Recruitment Analytics"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5670:
|
||||
msgid: "Data Scope: "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5674:
|
||||
msgid: "Data Scope: All Jobs"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5684:
|
||||
msgid: "All Jobs (Default View)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5688:
|
||||
msgid: "Daily Candidate Applications Trend"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5698:
|
||||
msgid: "Pipeline Funnel: "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5702:
|
||||
msgid: "Total Pipeline Funnel (All Jobs)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5706:
|
||||
msgid: "Time-to-Hire Target Check"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5716:
|
||||
msgid: "Top 5 Most Applied Jobs"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5738:
|
||||
msgid: "Daily Applications (Last 30 Days)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5754:
|
||||
msgid: "Mark All as Read"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5758:
|
||||
msgid: "What this will do"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_17.txt
Normal file
204
translation_batch_17.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 17 ===
|
||||
Translations 401-425 of 843
|
||||
============================================================
|
||||
|
||||
Line 5781:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5787:
|
||||
msgid: "All caught up!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5791:
|
||||
msgid: "You don't have any unread notifications to mark as read."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5795:
|
||||
msgid: "Yes, Mark All as Read"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5812:
|
||||
msgid: "Notification Preview"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5829:
|
||||
msgid: "View notification details and manage your preferences"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5834:
|
||||
msgid: "Mark as Read"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5839:
|
||||
msgid: "Mark as Unread"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5867:
|
||||
msgid: "Delivery Attempts"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5919:
|
||||
msgid: "Mark All Read"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5930:
|
||||
msgid: "Unread"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5934:
|
||||
msgid: "All Types"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5938:
|
||||
msgid: "Filter"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5942:
|
||||
msgid: "Total Notifications"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5946:
|
||||
msgid: "Email Notifications"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5956:
|
||||
msgid: "Mark as read"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5960:
|
||||
msgid: "Mark as unread"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5970:
|
||||
msgid: "Notifications pagination"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5980:
|
||||
msgid: "Try adjusting your filters to see more notifications."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5990:
|
||||
msgid: "Name / Contact"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 5994:
|
||||
msgid: "View Details and Score Breakdown"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6004:
|
||||
msgid: "Move to Next Stage"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6008:
|
||||
msgid: "Move to"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6024:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6031:
|
||||
msgid: "Days"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_18.txt
Normal file
204
translation_batch_18.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 18 ===
|
||||
Translations 426-450 of 843
|
||||
============================================================
|
||||
|
||||
Line 6035:
|
||||
msgid: "Target:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6039:
|
||||
msgid: "Max Scale:"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6049:
|
||||
msgid: "Home"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6065:
|
||||
msgid: "All Active & Drafted Positions (Global)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6073:
|
||||
msgid: "Currently Open Requisitions (Scoped)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6077:
|
||||
msgid: "Total Profiles in Current Scope"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6081:
|
||||
msgid: "Total Slots to be Filled (Scoped)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6091:
|
||||
msgid: "Total Recruiters/Interviewers (Global)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6101:
|
||||
msgid: "Total Job Posts Sent to LinkedIn (Global)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6105:
|
||||
msgid: "New Apps (7 Days)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6109:
|
||||
msgid: "Incoming applications last week"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6113:
|
||||
msgid: "Avg. Apps per Job"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6117:
|
||||
msgid: "Average Applications per Job (Scoped)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6127:
|
||||
msgid: "Avg. Days (Application to Hired)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6131:
|
||||
msgid: "Avg. Match Score"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6135:
|
||||
msgid: "Average AI Score (Current Scope)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6140:
|
||||
msgid: "Score ≥ 75%% Profiles"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6150:
|
||||
msgid: "Scheduled Interviews (Current Week)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6154:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6166:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6172:
|
||||
msgid: "Please select a date and time for the interview."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6193:
|
||||
msgid: "Search by Title or Creator"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6253:
|
||||
msgid: "Admin Settings Dashboard"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6257:
|
||||
msgid: "Staff User List"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6273:
|
||||
msgid: "Last Login"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_19.txt
Normal file
204
translation_batch_19.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 19 ===
|
||||
Translations 451-475 of 843
|
||||
============================================================
|
||||
|
||||
Line 6277:
|
||||
msgid: "Deactivate User"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6318:
|
||||
msgid: "Manage email addresses"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6322:
|
||||
msgid: "Security"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6338:
|
||||
msgid: "Username"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6342:
|
||||
msgid: "Date Joined"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6347:
|
||||
msgid: "{editor}: Editing failed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6352:
|
||||
msgid: "{editor}: Editing failed: {e}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6358:
|
||||
msgid: "{text} {deprecated_message}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6381:
|
||||
msgid: "DeprecationWarning: The command {name!r} is deprecated.{extra_message}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6386:
|
||||
msgid: "Aborted!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6391:
|
||||
msgid: "Commands"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6395:
|
||||
msgid: "Missing command."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6399:
|
||||
msgid: "No such command {name!r}."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6403:
|
||||
msgid: "Value must be an iterable."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6418:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6425:
|
||||
msgid: "env var: {var}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6432:
|
||||
msgid: "default: {default}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6437:
|
||||
msgid: "(dynamic)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6442:
|
||||
msgid: "%(prog)s, version %(version)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6446:
|
||||
msgid: "Show the version and exit."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6450:
|
||||
msgid: "Show this message and exit."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6462:
|
||||
msgid: "Try '{command} {option}' for help."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6467:
|
||||
msgid: "Invalid value: {message}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6472:
|
||||
msgid: "Invalid value for {param_hint}: {message}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6476:
|
||||
msgid: "Missing argument"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_20.txt
Normal file
204
translation_batch_20.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 20 ===
|
||||
Translations 476-500 of 843
|
||||
============================================================
|
||||
|
||||
Line 6486:
|
||||
msgid: "Missing parameter"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6491:
|
||||
msgid: "Missing {param_type}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6496:
|
||||
msgid: "Missing parameter: {param_name}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6501:
|
||||
msgid: "No such option: {name}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6516:
|
||||
msgid: "unknown error"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6520:
|
||||
msgid: "Could not open file {filename!r}: {message}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6530:
|
||||
msgid: "Argument {name!r} takes {nargs} values."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6534:
|
||||
msgid: "Option {name!r} does not take a value."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6548:
|
||||
msgid: "Shell completion is not supported for Bash versions older than 4.4."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6552:
|
||||
msgid: "Couldn't detect Bash version, shell completion is not supported."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6562:
|
||||
msgid: "Error: The value you entered was invalid."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6572:
|
||||
msgid: "Error: The two entered values do not match."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6576:
|
||||
msgid: "Error: invalid input"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6580:
|
||||
msgid: "Press any key to continue..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6585:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6611:
|
||||
msgid: "{value!r} is not a valid {number_type}."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6616:
|
||||
msgid: "{value} is not in the range {range}."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6620:
|
||||
msgid: "{value!r} is not a valid boolean. Recognized values: {states}"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6624:
|
||||
msgid: "{value!r} is not a valid UUID."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6634:
|
||||
msgid: "directory"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6638:
|
||||
msgid: "path"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6642:
|
||||
msgid: "{name} {filename!r} does not exist."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6646:
|
||||
msgid: "{name} {filename!r} is a file."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6650:
|
||||
msgid: "{name} {filename!r} is a directory."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6654:
|
||||
msgid: "{name} {filename!r} is not readable."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_21.txt
Normal file
204
translation_batch_21.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 21 ===
|
||||
Translations 501-525 of 843
|
||||
============================================================
|
||||
|
||||
Line 6658:
|
||||
msgid: "{name} {filename!r} is not writable."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6662:
|
||||
msgid: "{name} {filename!r} is not executable."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6677:
|
||||
msgid: "RoW"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6682:
|
||||
msgid: "GLO"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6686:
|
||||
msgid: "RoE"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6696:
|
||||
msgid: "Site Maps"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6700:
|
||||
msgid: "Static Files"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6712:
|
||||
msgid: "…"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6716:
|
||||
msgid: "That page number is not an integer"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6720:
|
||||
msgid: "That page number is less than 1"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6724:
|
||||
msgid: "That page contains no results"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6728:
|
||||
msgid: "Enter a valid value."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6739:
|
||||
msgid: "Enter a valid URL."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6743:
|
||||
msgid: "Enter a valid integer."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6747:
|
||||
msgid: "Enter a valid email address."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6752:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6757:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6767:
|
||||
msgid: "Enter a valid %(protocol)s address."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6771:
|
||||
msgid: "IPv4"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6776:
|
||||
msgid: "IPv6"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6780:
|
||||
msgid: "IPv4 or IPv6"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6784:
|
||||
msgid: "Enter only digits separated by commas."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6789:
|
||||
msgid: "Ensure this value is %(limit_value)s (it is %(show_value)s)."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6794:
|
||||
msgid: "Ensure this value is less than or equal to %(limit_value)s."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6799:
|
||||
msgid: "Ensure this value is greater than or equal to %(limit_value)s."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_22.txt
Normal file
204
translation_batch_22.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 22 ===
|
||||
Translations 526-550 of 843
|
||||
============================================================
|
||||
|
||||
Line 6804:
|
||||
msgid: "Ensure this value is a multiple of step size %(limit_value)s."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6809:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6889:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6895:
|
||||
msgid: "Null characters are not allowed."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6901:
|
||||
msgid: "and"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6906:
|
||||
msgid: "%(model_name)s with this %(field_labels)s already exists."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6911:
|
||||
msgid: "Constraint “%(name)s” is violated."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6916:
|
||||
msgid: "Value %(value)r is not a valid choice."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6920:
|
||||
msgid: "This field cannot be null."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6924:
|
||||
msgid: "This field cannot be blank."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6929:
|
||||
msgid: "%(model_name)s with this %(field_label)s already exists."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6936:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6942:
|
||||
msgid: "Field of type: %(field_type)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6947:
|
||||
msgid: "“%(value)s” value must be either True or False."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6952:
|
||||
msgid: "“%(value)s” value must be either True, False, or None."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6956:
|
||||
msgid: "Boolean (Either True or False)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6961:
|
||||
msgid: "String (up to %(max_length)s)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6965:
|
||||
msgid: "String (unlimited)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6976:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6984:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6990:
|
||||
msgid: "Date (without time)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 6995:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7002:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7008:
|
||||
msgid: "Date (with time)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7013:
|
||||
msgid: "“%(value)s” value must be a decimal number."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_23.txt
Normal file
204
translation_batch_23.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 23 ===
|
||||
Translations 551-575 of 843
|
||||
============================================================
|
||||
|
||||
Line 7017:
|
||||
msgid: "Decimal number"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7022:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7041:
|
||||
msgid: "“%(value)s” value must be a float."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7052:
|
||||
msgid: "“%(value)s” value must be an integer."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7062:
|
||||
msgid: "Big (8 byte) integer"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7066:
|
||||
msgid: "Small integer"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7084:
|
||||
msgid: "“%(value)s” value must be either None, True or False."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7088:
|
||||
msgid: "Boolean (Either True, False or None)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7092:
|
||||
msgid: "Positive big integer"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7096:
|
||||
msgid: "Positive integer"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7100:
|
||||
msgid: "Positive small integer"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7105:
|
||||
msgid: "Slug (up to %(max_length)s)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7109:
|
||||
msgid: "Text"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7114:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7121:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7137:
|
||||
msgid: "URL"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7141:
|
||||
msgid: "Raw binary data"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7146:
|
||||
msgid: "“%(value)s” is not a valid UUID."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7150:
|
||||
msgid: "Universally unique identifier"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7154:
|
||||
msgid: "Image"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7158:
|
||||
msgid: "A JSON object"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7162:
|
||||
msgid: "Value must be valid JSON."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7167:
|
||||
msgid: "%(model)s instance with %(field)s %(value)r is not a valid choice."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7171:
|
||||
msgid: "Foreign Key (type determined by related field)"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7175:
|
||||
msgid: "One-to-one relationship"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_24.txt
Normal file
204
translation_batch_24.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 24 ===
|
||||
Translations 576-600 of 843
|
||||
============================================================
|
||||
|
||||
Line 7180:
|
||||
msgid: "%(from)s-%(to)s relationship"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7185:
|
||||
msgid: "%(from)s-%(to)s relationships"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7189:
|
||||
msgid: "Many-to-many relationship"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7195:
|
||||
msgid: ":?.!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7199:
|
||||
msgid: "This field is required."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7223:
|
||||
msgid: "Enter a valid date/time."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7234:
|
||||
msgid: "The number of days must be between {min_days} and {max_days}."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7238:
|
||||
msgid: "No file was submitted. Check the encoding type on the form."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7242:
|
||||
msgid: "No file was submitted."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7246:
|
||||
msgid: "The submitted file is empty."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7262:
|
||||
msgid: "Please either submit a file or check the clear checkbox, not both."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7266:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7275:
|
||||
msgid: "Select a valid choice. %(value)s is not one of the available choices."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7293:
|
||||
msgid: "Enter a valid UUID."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7297:
|
||||
msgid: "Enter a valid JSON."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7302:
|
||||
msgid: ":"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7307:
|
||||
msgid: "(Hidden field %(name)s) %(error)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7312:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7341:
|
||||
msgid: "Order"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7346:
|
||||
msgid: "Please correct the duplicate data for %(field)s."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7351:
|
||||
msgid: "Please correct the duplicate data for %(field)s, which must be unique."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7356:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7362:
|
||||
msgid: "Please correct the duplicate values below."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7366:
|
||||
msgid: "The inline value did not match the parent instance."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7370:
|
||||
msgid: "Select a valid choice. That choice is not one of the available choices."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_25.txt
Normal file
204
translation_batch_25.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 25 ===
|
||||
Translations 601-625 of 843
|
||||
============================================================
|
||||
|
||||
Line 7375:
|
||||
msgid: "“%(pk)s” is not a valid value."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7380:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7386:
|
||||
msgid: "Currently"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7400:
|
||||
msgid: "Unknown"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7405:
|
||||
msgid: "yes,no,maybe"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7422:
|
||||
msgid: "%s KB"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7427:
|
||||
msgid: "%s MB"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7432:
|
||||
msgid: "%s GB"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7437:
|
||||
msgid: "%s TB"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7442:
|
||||
msgid: "%s PB"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7446:
|
||||
msgid: "p.m."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7450:
|
||||
msgid: "a.m."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7454:
|
||||
msgid: "PM"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7458:
|
||||
msgid: "AM"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7462:
|
||||
msgid: "midnight"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7466:
|
||||
msgid: "noon"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7470:
|
||||
msgid: "Monday"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7474:
|
||||
msgid: "Tuesday"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7478:
|
||||
msgid: "Wednesday"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7482:
|
||||
msgid: "Thursday"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7486:
|
||||
msgid: "Friday"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7490:
|
||||
msgid: "Saturday"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7494:
|
||||
msgid: "Sunday"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7498:
|
||||
msgid: "Mon"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7502:
|
||||
msgid: "Tue"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_26.txt
Normal file
204
translation_batch_26.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 26 ===
|
||||
Translations 626-650 of 843
|
||||
============================================================
|
||||
|
||||
Line 7506:
|
||||
msgid: "Wed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7510:
|
||||
msgid: "Thu"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7514:
|
||||
msgid: "Fri"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7524:
|
||||
msgid: "Sun"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7528:
|
||||
msgid: "January"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7532:
|
||||
msgid: "February"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7542:
|
||||
msgid: "April"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7546:
|
||||
msgid: "May"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7550:
|
||||
msgid: "June"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7554:
|
||||
msgid: "July"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7558:
|
||||
msgid: "August"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7562:
|
||||
msgid: "September"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7566:
|
||||
msgid: "October"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7570:
|
||||
msgid: "November"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7574:
|
||||
msgid: "December"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7578:
|
||||
msgid: "jan"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7582:
|
||||
msgid: "feb"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7592:
|
||||
msgid: "apr"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7602:
|
||||
msgid: "jun"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7606:
|
||||
msgid: "jul"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7616:
|
||||
msgid: "sep"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7620:
|
||||
msgid: "oct"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7624:
|
||||
msgid: "nov"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7628:
|
||||
msgid: "dec"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7633:
|
||||
msgid: "Jan."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_27.txt
Normal file
204
translation_batch_27.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 27 ===
|
||||
Translations 651-675 of 843
|
||||
============================================================
|
||||
|
||||
Line 7638:
|
||||
msgid: "Feb."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7650:
|
||||
msgid: "April"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7655:
|
||||
msgid: "May"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7660:
|
||||
msgid: "June"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7665:
|
||||
msgid: "July"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7670:
|
||||
msgid: "Aug."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7675:
|
||||
msgid: "Sept."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7680:
|
||||
msgid: "Oct."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7685:
|
||||
msgid: "Nov."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7690:
|
||||
msgid: "Dec."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7695:
|
||||
msgid: "January"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7700:
|
||||
msgid: "February"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7712:
|
||||
msgid: "April"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7717:
|
||||
msgid: "May"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7722:
|
||||
msgid: "June"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7727:
|
||||
msgid: "July"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7732:
|
||||
msgid: "August"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7737:
|
||||
msgid: "September"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7742:
|
||||
msgid: "October"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7747:
|
||||
msgid: "November"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7752:
|
||||
msgid: "December"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7756:
|
||||
msgid: "This is not a valid IPv6 address."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7762:
|
||||
msgid: "%(truncated_text)s…"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7774:
|
||||
msgid: ", "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7844:
|
||||
msgid: "Forbidden"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_28.txt
Normal file
204
translation_batch_28.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 28 ===
|
||||
Translations 676-700 of 843
|
||||
============================================================
|
||||
|
||||
Line 7848:
|
||||
msgid: "CSRF verification failed. Request aborted."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7860:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7876:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7883:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7889:
|
||||
msgid: "More information is available with DEBUG=True."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7893:
|
||||
msgid: "No year specified"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7899:
|
||||
msgid: "Date out of range"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7903:
|
||||
msgid: "No month specified"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7907:
|
||||
msgid: "No day specified"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7911:
|
||||
msgid: "No week specified"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7917:
|
||||
msgid: "No %(verbose_name_plural)s available"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7922:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7929:
|
||||
msgid: "Invalid date string “%(datestr)s” given format “%(format)s”"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7934:
|
||||
msgid: "No %(verbose_name)s found matching the query"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7938:
|
||||
msgid: "Page is not “last”, nor can it be converted to an int."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7943:
|
||||
msgid: "Invalid page (%(page_number)s): %(message)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7948:
|
||||
msgid: "Empty list and “%(class_name)s.allow_empty” is False."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7952:
|
||||
msgid: "Directory indexes are not allowed here."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7957:
|
||||
msgid: "“%(path)s” does not exist"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7964:
|
||||
msgid: "Index of %(directory)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7969:
|
||||
msgid: "The install worked successfully! Congratulations!"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7974:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7989:
|
||||
msgid: "Django Documentation"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7993:
|
||||
msgid: "Topics, references, & how-to’s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 7997:
|
||||
msgid: "Tutorial: A Polling App"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_29.txt
Normal file
204
translation_batch_29.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 29 ===
|
||||
Translations 701-725 of 843
|
||||
============================================================
|
||||
|
||||
Line 8001:
|
||||
msgid: "Get started with Django"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8005:
|
||||
msgid: "Django Community"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8009:
|
||||
msgid: "Connect, get help, or contribute"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8013:
|
||||
msgid: "You do not have permission to upload files."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8017:
|
||||
msgid: "You must be logged in to upload files."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8022:
|
||||
msgid: "File should be at most %(max_size)s MB."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8026:
|
||||
msgid: "Invalid form data"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8030:
|
||||
msgid: "Check the correct settings.CKEDITOR_5_CONFIGS "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8034:
|
||||
msgid: "Only POST method is allowed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8038:
|
||||
msgid: "Attachment module is disabled"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8042:
|
||||
msgid: "Only authenticated users are allowed"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8046:
|
||||
msgid: "No files were requested"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8050:
|
||||
msgid: "File size exceeds the limit allowed and cannot be saved"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8054:
|
||||
msgid: "Failed to save attachment"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8059:
|
||||
msgid: "Attempting to connect to qpid with SASL mechanism %s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8064:
|
||||
msgid: "Connected to qpid with SASL mechanism %s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8069:
|
||||
msgid: "Unable to connect to qpid with SASL mechanism %s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8074:
|
||||
msgid: "required"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8079:
|
||||
msgid: "Arguments"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8089:
|
||||
msgid: "[default: {}]"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8093:
|
||||
msgid: "[env var: {}]"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8097:
|
||||
msgid: "[required]"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8101:
|
||||
msgid: "Aborted."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8106:
|
||||
msgid: "Try [blue]'{command_path} {help_option}'[/] for help."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8123:
|
||||
msgid: "Collapse"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_30.txt
Normal file
204
translation_batch_30.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 30 ===
|
||||
Translations 726-750 of 843
|
||||
============================================================
|
||||
|
||||
Line 8128:
|
||||
msgid: "Value"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8132:
|
||||
msgid: "Default"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8137:
|
||||
msgid: "Code"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8141:
|
||||
msgid: "Modified"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8145:
|
||||
msgid: "Reset to default"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8166:
|
||||
msgid: " By %(filter_title)s "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8171:
|
||||
msgid: "To"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8175:
|
||||
msgid: "Date from"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8179:
|
||||
msgid: "Date to"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8195:
|
||||
msgid: "Paragraph"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8199:
|
||||
msgid: "Underlined"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8203:
|
||||
msgid: "Bold"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8207:
|
||||
msgid: "Italic"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8211:
|
||||
msgid: "Strike"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8218:
|
||||
msgid: "Heading"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8222:
|
||||
msgid: "Quote"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8226:
|
||||
msgid: "Unordered list"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8230:
|
||||
msgid: "Ordered list"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8234:
|
||||
msgid: "Indent increase"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8238:
|
||||
msgid: "Indent decrease"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8242:
|
||||
msgid: "Undo"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8246:
|
||||
msgid: "Redo"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8256:
|
||||
msgid: "Unlink"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8260:
|
||||
msgid: "Object permissions"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8267:
|
||||
msgid: "Object"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_31.txt
Normal file
204
translation_batch_31.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 31 ===
|
||||
Translations 751-775 of 843
|
||||
============================================================
|
||||
|
||||
Line 8272:
|
||||
msgid: "Group"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8276:
|
||||
msgid: "Group permissions"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8286:
|
||||
msgid: "User permissions"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8297:
|
||||
msgid: "Export"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8301:
|
||||
msgid: "Import"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8322:
|
||||
msgid: "This exporter will export the following fields"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8326:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8344:
|
||||
msgid: "Skipped"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8348:
|
||||
msgid: "Some rows failed to validate"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8352:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8359:
|
||||
msgid: "Row"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8370:
|
||||
msgid: "Non field specific"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8374:
|
||||
msgid: "This exporter will export the following fields: "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8378:
|
||||
msgid: "This importer will import the following fields: "
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8385:
|
||||
msgid: "%(class_name)s %(instance)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8390:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8396:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8415:
|
||||
msgid: "This object doesn't have a change history."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8419:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8424:
|
||||
msgid: "Press the 'Change History' button below to edit the history."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8431:
|
||||
msgid: "Date/time"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8449:
|
||||
msgid: "None"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8453:
|
||||
msgid: "Revert"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8469:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8483:
|
||||
msgid: "Run the selected action"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_32.txt
Normal file
204
translation_batch_32.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 32 ===
|
||||
Translations 776-800 of 843
|
||||
============================================================
|
||||
|
||||
Line 8487:
|
||||
msgid: "Run"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8491:
|
||||
msgid: "Click here to select the objects across all pages"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8496:
|
||||
msgid: "Select all %(total_count)s %(module_name)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8500:
|
||||
msgid: "Clear selection"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8506:
|
||||
msgid: "Models in the %(name)s application"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8512:
|
||||
msgid: "You don’t have permission to view or edit anything."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8516:
|
||||
msgid: "After you've created a user, you’ll be able to edit more user options."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8521:
|
||||
msgid: "Enter a new password for the user <strong>%(username)s</strong>."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8533:
|
||||
msgid: "History"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8545:
|
||||
msgid: "Filters"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8549:
|
||||
msgid: "Select all rows"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8559:
|
||||
msgid: "Remove from sorting"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8564:
|
||||
msgid: "Sorting priority: %(priority_number)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8569:
|
||||
msgid: "Expand row"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8574:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8582:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8589:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8596:
|
||||
msgid: "Objects"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8601:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8609:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8616:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8626:
|
||||
msgid: "Welcome back to"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8631:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8637:
|
||||
msgid: "Log in"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8641:
|
||||
msgid: "Forgotten your password or username?"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
204
translation_batch_33.txt
Normal file
204
translation_batch_33.txt
Normal file
@ -0,0 +1,204 @@
|
||||
=== TRANSLATION BATCH 33 ===
|
||||
Translations 801-825 of 843
|
||||
============================================================
|
||||
|
||||
Line 8645:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8653:
|
||||
msgid: "Show all"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8658:
|
||||
msgid: "Type to search"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8662:
|
||||
msgid: "Save and continue editing"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8666:
|
||||
msgid: "Save and view"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8670:
|
||||
msgid: "Save and add another"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8674:
|
||||
msgid: "Save as new"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8678:
|
||||
msgid: "You have been successfully logged out from the administration"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8682:
|
||||
msgid: "Thanks for spending some quality time with the web site today."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8686:
|
||||
msgid: "Log in again"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8690:
|
||||
msgid: "Your password was changed."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8694:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8721:
|
||||
msgid: "Add %(name)s"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8731:
|
||||
msgid: "Add"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8735:
|
||||
msgid: "True"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8739:
|
||||
msgid: "False"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8743:
|
||||
msgid: "Hide counts"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8753:
|
||||
msgid: "Clear all filters"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8757:
|
||||
msgid: "Recent searches"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8761:
|
||||
msgid: "No recent searches"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8771:
|
||||
msgid: "Loading more results..."
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8792:
|
||||
msgid: "No, take me back"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8796:
|
||||
msgid: "Yes, I’m sure"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8800:
|
||||
msgid: "Record picture"
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Line 8816:
|
||||
msgid: ""
|
||||
msgstr: ""
|
||||
|
||||
Arabic Translation:
|
||||
msgstr: ""
|
||||
----------------------------------------
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user